From ac576bbc7b64080fb94533811a5f0088ef694da9 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sun, 20 Apr 2025 01:08:17 -0400 Subject: [PATCH 01/34] Runtime startup check of PE version --- .../manager/init/load/PacketEventsInit.java | 43 +++++++++++++++++++ .../mixins/ServerPlayerEntityMixin.java | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java b/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java index 1c586f9511..690227e642 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java @@ -1,5 +1,6 @@ package ac.grim.grimac.manager.init.load; +import ac.grim.grimac.GrimAPI; import ac.grim.grimac.utils.anticheat.LogUtil; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEventsAPI; @@ -10,12 +11,14 @@ import com.github.retrooper.packetevents.protocol.item.type.ItemTypes; import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes; import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; +import com.github.retrooper.packetevents.util.PEVersion; import java.util.concurrent.Executors; public class PacketEventsInit implements LoadableInitable { PacketEventsAPI packetEventsAPI; + PEVersion MINIMUM_REQUIRED_PE_VERSION = new PEVersion(2, 8, 0, true); public PacketEventsInit(PacketEventsAPI packetEventsAPI) { this.packetEventsAPI = packetEventsAPI; @@ -25,6 +28,17 @@ public PacketEventsInit(PacketEventsAPI packetEventsAPI) { public void load() { LogUtil.info("Loading PacketEvents..."); PacketEvents.setAPI(packetEventsAPI); + + if (!checkPacketEventsVersion()) { + LogUtil.error("\n" + + "******************************************************\n" + + "GrimAC requires PacketEvents >= " + MINIMUM_REQUIRED_PE_VERSION + + (MINIMUM_REQUIRED_PE_VERSION.snapshot() ? "-SNAPSHOT" : "") + "\n" + + "Current version: " + PacketEvents.getAPI().getVersion() + "\n" + + "Please update PacketEvents to a compatible version.\n" + + "*****************************************************"); + } + PacketEvents.getAPI().getSettings() .fullStackTrace(true) .kickOnPacketException(true) @@ -43,4 +57,33 @@ public void load() { ParticleTypes.DUST.getName(); }).start(); } + + private boolean checkPacketEventsVersion() { + PEVersion current = PacketEvents.getAPI().getVersion(); + PEVersion required = MINIMUM_REQUIRED_PE_VERSION; + + // If current version is newer, always accept + if (current.isNewerThan(required)) { + return true; + } + + // If current version is exactly equal to required (including snapshot status), accept + if (current.major() == required.major() + && current.minor() == required.minor() + && current.patch() == required.patch() + && current.snapshot() == required.snapshot()) { + return true; + } + + // If required is a snapshot, accept matching release or snapshot + if (required.snapshot() + && current.major() == required.major() + && current.minor() == required.minor() + && current.patch() == required.patch()) { + return true; + } + + // Otherwise, reject + return false; + } } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/mixins/ServerPlayerEntityMixin.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/mixins/ServerPlayerEntityMixin.java index 95c7e43b63..7a9699ea7f 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/mixins/ServerPlayerEntityMixin.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/mixins/ServerPlayerEntityMixin.java @@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import net.minecraft.server.network.ServerPlayerEntity; - +// Works from 1.14 - latest (1.21.5) @Mixin(ServerPlayerEntity.class) abstract class ServerPlayerEntityMixin { From 245d2391b216e2c03c60e8c3115948b8d918c1fe Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sun, 20 Apr 2025 08:39:04 -0400 Subject: [PATCH 02/34] Add /grim history command to see historical alerts - Add basic runtime reloading support. - Will only actually reload if current database is different/has different setting than the one requested in config. - Added [log] statements matching [alert] statements --- .../kotlin/grim.shadow-conventions.gradle.kts | 1 + .../player/BukkitOfflinePlatformPlayer.java | 38 +++++ .../player/BukkitPlatformPlayerFactory.java | 21 +++ common/build.gradle.kts | 1 + .../src/main/java/ac/grim/grimac/GrimAPI.java | 3 + .../java/ac/grim/grimac/GrimExternalAPI.java | 1 + .../grimac/command/commands/GrimHistory.java | 103 ++++++++++++++ .../ac/grim/grimac/manager/InitManager.java | 11 +- .../grimac/manager/PunishmentManager.java | 5 + .../manager/init/load/PacketEventsInit.java | 1 - .../manager/init/start/CommandRegister.java | 16 +-- .../MySQLViolationDatabase.java | 131 ++++++++++++++++++ .../NoOpViolationDatabase.java | 17 +++ .../SQLiteViolationDatabase.java | 130 +++++++++++++++++ .../manager/violationdatabase/Violation.java | 38 +++++ .../violationdatabase/ViolationDatabase.java | 20 +++ .../ViolationDatabaseManager.java | 101 ++++++++++++++ .../platform/api/entity/GrimEntity.java | 6 +- .../player/AbstractPlatformPlayerFactory.java | 27 +++- .../api/player/OfflinePlatformPlayer.java | 10 ++ .../platform/api/player/PlatformPlayer.java | 14 +- .../api/player/PlatformPlayerFactory.java | 6 + common/src/main/resources/config/de.yml | 16 +++ common/src/main/resources/config/en.yml | 16 +++ common/src/main/resources/config/es.yml | 16 +++ common/src/main/resources/config/fr.yml | 16 +++ common/src/main/resources/config/it.yml | 16 +++ common/src/main/resources/config/ja.yml | 16 +++ common/src/main/resources/config/nl.yml | 16 +++ common/src/main/resources/config/pt.yml | 16 +++ common/src/main/resources/config/ru.yml | 16 +++ common/src/main/resources/config/tr.yml | 16 +++ common/src/main/resources/config/zh.yml | 16 +++ common/src/main/resources/messages/de.yml | 7 + common/src/main/resources/messages/en.yml | 7 + common/src/main/resources/messages/es.yml | 7 + common/src/main/resources/messages/fr.yml | 7 + common/src/main/resources/messages/it.yml | 7 + common/src/main/resources/messages/ja.yml | 7 + common/src/main/resources/messages/nl.yml | 7 + common/src/main/resources/messages/pt.yml | 7 + common/src/main/resources/messages/ru.yml | 7 + common/src/main/resources/messages/tr.yml | 8 +- common/src/main/resources/messages/zh.yml | 7 + common/src/main/resources/punishments/de.yml | 9 ++ common/src/main/resources/punishments/en.yml | 9 ++ common/src/main/resources/punishments/es.yml | 9 ++ common/src/main/resources/punishments/fr.yml | 9 ++ common/src/main/resources/punishments/it.yml | 9 ++ common/src/main/resources/punishments/ja.yml | 9 ++ common/src/main/resources/punishments/nl.yml | 9 ++ common/src/main/resources/punishments/pt.yml | 9 ++ common/src/main/resources/punishments/ru.yml | 9 ++ common/src/main/resources/punishments/tr.yml | 9 ++ common/src/main/resources/punishments/zh.yml | 9 ++ .../mc1161/GrimACFabric1161LoaderPlugin.java | 4 +- .../mc1171/Fabric1171PlatformServer.java | 18 +++ .../mc1171/GrimACFabric1170LoaderPlugin.java | 8 +- .../mc1194/Fabric1190PlatformServer.java | 4 +- .../mc1194/GrimACFabric1190LoaderPlugin.java | 4 +- .../fabric/AbstractFabricPlatformServer.java | 7 + .../fabric/GrimACFabricLoaderPlugin.java | 7 +- .../player/FabricOfflinePlatformPlayer.java | 40 ++++++ .../player/FabricPlatformPlayerFactory.java | 48 +++++++ 64 files changed, 1139 insertions(+), 55 deletions(-) create mode 100644 bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitOfflinePlatformPlayer.java create mode 100644 common/src/main/java/ac/grim/grimac/command/commands/GrimHistory.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/MySQLViolationDatabase.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/NoOpViolationDatabase.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/SQLiteViolationDatabase.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/Violation.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabase.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabaseManager.java create mode 100644 common/src/main/java/ac/grim/grimac/platform/api/player/OfflinePlatformPlayer.java create mode 100644 fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/Fabric1171PlatformServer.java create mode 100644 fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricOfflinePlatformPlayer.java diff --git a/buildSrc/src/main/kotlin/grim.shadow-conventions.gradle.kts b/buildSrc/src/main/kotlin/grim.shadow-conventions.gradle.kts index 56bc68db5c..b4bf0fbb05 100644 --- a/buildSrc/src/main/kotlin/grim.shadow-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/grim.shadow-conventions.gradle.kts @@ -38,6 +38,7 @@ tasks.named("shadowJar") { relocate("org.jetbrains", "ac.grim.grimac.shaded.jetbrains") relocate("org.incendo", "ac.grim.grimac.shaded.incendo") relocate("io.leangen.geantyref", "ac.grim.grimac.shaded.geantyref") // Required by cloud + relocate("com.zaxxer", "ac.grim.grimac.shaded.zaxxer") // Database history } mergeServiceFiles() } diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitOfflinePlatformPlayer.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitOfflinePlatformPlayer.java new file mode 100644 index 0000000000..ec85a7440f --- /dev/null +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitOfflinePlatformPlayer.java @@ -0,0 +1,38 @@ +package ac.grim.grimac.platform.bukkit.player; + +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public class BukkitOfflinePlatformPlayer implements OfflinePlatformPlayer { + private final OfflinePlayer offlinePlayer; + + public BukkitOfflinePlatformPlayer(OfflinePlayer offlinePlayer) { + this.offlinePlayer = offlinePlayer; + } + + @Override + public boolean isOnline() { + return offlinePlayer.isOnline(); + } + + @Override + public @NotNull String getName() { + return offlinePlayer.getName(); + } + + @Override + public @NotNull UUID getUniqueId() { + return offlinePlayer.getUniqueId(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof OfflinePlatformPlayer offlinePlatformPlayer) { + return this.getUniqueId().equals(offlinePlatformPlayer.getUniqueId()); + } + return false; + } +} diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitPlatformPlayerFactory.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitPlatformPlayerFactory.java index ecd49b324d..705840af13 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitPlatformPlayerFactory.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitPlatformPlayerFactory.java @@ -1,9 +1,12 @@ package ac.grim.grimac.platform.bukkit.player; import ac.grim.grimac.platform.api.player.AbstractPlatformPlayerFactory; +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; import ac.grim.grimac.platform.api.player.PlatformPlayer; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -11,11 +14,17 @@ public class BukkitPlatformPlayerFactory extends AbstractPlatformPlayerFactory { + @Override protected Player getNativePlayer(@NotNull UUID uuid) { return Bukkit.getPlayer(uuid); } + @Override + protected Player getNativePlayer(@NonNull String name) { + return Bukkit.getPlayer(name); + } + @Override protected PlatformPlayer createPlatformPlayer(@NotNull Player nativePlayer) { return new BukkitPlatformPlayer(nativePlayer); @@ -44,4 +53,16 @@ protected Collection getNativeOnlinePlayers() { // Cast Collection to Collection return (Collection) Bukkit.getOnlinePlayers(); } + + @Override + public OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid) { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); + return new BukkitOfflinePlatformPlayer(offlinePlayer); + } + + @Override + public OfflinePlatformPlayer getOfflineFromName(@NotNull String name) { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(name); + return new BukkitOfflinePlatformPlayer(offlinePlayer); + } } diff --git a/common/build.gradle.kts b/common/build.gradle.kts index f20115c33e..1f63bd8e92 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -45,6 +45,7 @@ dependencies { api(libs.fastutil) api(libs.adventure.text.minimessage) api(libs.jetbrains.annotations) + api("com.zaxxer:HikariCP:4.0.3") api("ac.grim.grimac:GrimAPI:1.1.0.0") diff --git a/common/src/main/java/ac/grim/grimac/GrimAPI.java b/common/src/main/java/ac/grim/grimac/GrimAPI.java index 6c105762ac..026630e335 100644 --- a/common/src/main/java/ac/grim/grimac/GrimAPI.java +++ b/common/src/main/java/ac/grim/grimac/GrimAPI.java @@ -10,6 +10,7 @@ import ac.grim.grimac.manager.TickManager; import ac.grim.grimac.manager.config.BaseConfigManager; import ac.grim.grimac.manager.init.Initable; +import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager; import ac.grim.grimac.platform.api.Platform; import ac.grim.grimac.platform.api.PlatformLoader; import ac.grim.grimac.platform.api.PlatformServer; @@ -45,6 +46,7 @@ public final class GrimAPI { private final TickManager tickManager; private final EventBus eventBus; private final GrimExternalAPI externalAPI; + private ViolationDatabaseManager violationDatabaseManager; private PlatformLoader loader; private InitManager initManager; private boolean initialized = false; @@ -76,6 +78,7 @@ private static Platform detectPlatform() { public void load(PlatformLoader platformLoader, Initable... platformSpecificInitables) { this.loader = platformLoader; + this.violationDatabaseManager = new ViolationDatabaseManager(getGrimPlugin()); this.initManager = new InitManager(loader.getPacketEvents(), loader::getCommandManager, platformSpecificInitables); this.initManager.load(); this.initialized = true; diff --git a/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java b/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java index 3bc391ebce..3c5344a60a 100644 --- a/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java +++ b/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java @@ -181,6 +181,7 @@ public void onReload(ConfigManager newConfig) { GrimAPI.INSTANCE.getAlertManager().reload(configManager); GrimAPI.INSTANCE.getDiscordManager().reload(); GrimAPI.INSTANCE.getSpectateManager().reload(); + GrimAPI.INSTANCE.getViolationDatabaseManager().reload(); // Don't reload players if the plugin hasn't started yet if (!started) return; // Reload checks for all players diff --git a/common/src/main/java/ac/grim/grimac/command/commands/GrimHistory.java b/common/src/main/java/ac/grim/grimac/command/commands/GrimHistory.java new file mode 100644 index 0000000000..42bdc37dd5 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/command/commands/GrimHistory.java @@ -0,0 +1,103 @@ +package ac.grim.grimac.command.commands; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.command.BuildableCommand; +import ac.grim.grimac.manager.violationdatabase.Violation; +import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager; +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; +import ac.grim.grimac.platform.api.sender.Sender; +import ac.grim.grimac.utils.anticheat.MessageUtil; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.parser.standard.StringParser; + +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class GrimHistory implements BuildableCommand { + + @Override + public void register(CommandManager commandManager) { + commandManager.command( + commandManager.commandBuilder("grim", "grimac") + .literal("history", "hist") + .permission("grim.help") + .required("target", StringParser.stringParser()) + .optional("page", IntegerParser.integerParser()) + .permission("grim.history") + .handler(this::handleHistory) + ); + } + + private void handleHistory(CommandContext context) { + Sender sender = context.sender(); + String target = context.get("target"); + Integer page = context.getOrDefault("page", 1); + + if (!GrimAPI.INSTANCE.getViolationDatabaseManager().isEnabled()) { + String msg = GrimAPI.INSTANCE.getConfigManager().getConfig() + .getStringElse("grim-history-disabled", + "%prefix% &cHistory subsystem is disabled!"); + sender.sendMessage(MessageUtil.miniMessage(msg)); + return; + } + + GrimAPI.INSTANCE.getScheduler().getAsyncScheduler().runNow(GrimAPI.INSTANCE.getGrimPlugin(), () -> { + int entriesPerPage = GrimAPI.INSTANCE.getConfigManager().getConfig().getIntElse("history.entries-per-page", 15); + String header = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("grim-history-header", + "%prefix% &bShowing logs for &f%player% (&f%page%&b/&f%maxPages%&f)"); + String logFormat = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("grim-history-entry", + "%prefix% &8[&f%server%&8] &bFailed &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% ago&7)"); + + OfflinePlatformPlayer targetPlayer = GrimAPI.INSTANCE.getPlatformPlayerFactory().getOfflineFromName(target); + + ViolationDatabaseManager violations = GrimAPI.INSTANCE.getViolationDatabaseManager(); + int logCount = violations.getLogCount(targetPlayer.getUniqueId()); + List logs = violations.getViolations(targetPlayer.getUniqueId(), page, entriesPerPage); + int maxPages = (int) Math.ceil((float) logCount / entriesPerPage); + + sender.sendMessage(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(sender, header + .replace("%player%", targetPlayer.getName()) + .replace("%page%", String.valueOf(page)) + .replace("%maxPages%", String.valueOf(maxPages)) + ))); + + for (int i = logs.size() - 1; i >= 0; i--) { + Violation log = logs.get(i); + sender.sendMessage(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(sender, logFormat + .replace("%player%", targetPlayer.getName()) + .replace("%check%", log.getCheckName()) + .replace("%verbose%", log.getVerbose()) + .replace("%vl%", String.valueOf(log.getVl())) + .replace("%timeago%", getTimeAgo(log.getCreatedAt())) + .replace("%server%", log.getServerName()) + ))); + } + }); + } + + private String getTimeAgo(Date date) { + long durationMillis = new Date().getTime() - date.getTime(); + + long days = TimeUnit.MILLISECONDS.toDays(durationMillis); + durationMillis -= TimeUnit.DAYS.toMillis(days); + + long hours = TimeUnit.MILLISECONDS.toHours(durationMillis); + durationMillis -= TimeUnit.HOURS.toMillis(hours); + + long minutes = TimeUnit.MILLISECONDS.toMinutes(durationMillis); + durationMillis -= TimeUnit.MINUTES.toMillis(minutes); + + long seconds = TimeUnit.MILLISECONDS.toSeconds(durationMillis); + + StringBuilder result = new StringBuilder(); + if (days > 0) result.append(days).append("d "); + if (hours > 0) result.append(hours).append("h "); + if (minutes > 0) result.append(minutes).append("m "); + if (seconds > 0) result.append(seconds).append("s"); + + return result.toString().trim(); + } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/InitManager.java b/common/src/main/java/ac/grim/grimac/manager/InitManager.java index 41836c500b..80f3f3f9d3 100644 --- a/common/src/main/java/ac/grim/grimac/manager/InitManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/InitManager.java @@ -4,15 +4,7 @@ import ac.grim.grimac.manager.init.Initable; import ac.grim.grimac.manager.init.load.LoadableInitable; import ac.grim.grimac.manager.init.load.PacketEventsInit; -import ac.grim.grimac.manager.init.start.CommandRegister; -import ac.grim.grimac.manager.init.start.JavaVersion; -import ac.grim.grimac.manager.init.start.PacketLimiter; -import ac.grim.grimac.manager.init.start.PacketManager; -import ac.grim.grimac.manager.init.start.StartableInitable; -import ac.grim.grimac.manager.init.start.TAB; -import ac.grim.grimac.manager.init.start.TickRunner; -import ac.grim.grimac.manager.init.start.ViaBackwardsManager; -import ac.grim.grimac.manager.init.start.ViaVersion; +import ac.grim.grimac.manager.init.start.*; import ac.grim.grimac.manager.init.stop.StoppableInitable; import ac.grim.grimac.manager.init.stop.TerminatePacketEvents; import ac.grim.grimac.platform.api.sender.Sender; @@ -63,6 +55,7 @@ public InitManager(PacketEventsAPI packetEventsAPI, Supplier GrimAPI.INSTANCE.getDiscordManager().sendAlert(player, verbose, check.getDisplayName(), vl); + case "[log]" -> { + int vls = (int) group.violations.values().stream().filter((e) -> e == check).count(); + String verboseWithoutGl = verbose.replaceAll(" /gl .*", ""); + GrimAPI.INSTANCE.getViolationDatabaseManager().logAlert(player, verboseWithoutGl, check.getDisplayName(), vls); + } case "[proxy]" -> ProxyAlertMessenger.sendPluginMessage(cmd); case "[alert]" -> { sentDebug = true; diff --git a/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java b/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java index 690227e642..7ab0690e57 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java @@ -1,6 +1,5 @@ package ac.grim.grimac.manager.init.load; -import ac.grim.grimac.GrimAPI; import ac.grim.grimac.utils.anticheat.LogUtil; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEventsAPI; diff --git a/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java b/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java index 9c7f4a280e..8218e625e1 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java @@ -2,20 +2,7 @@ import ac.grim.grimac.GrimAPI; import ac.grim.grimac.command.SenderRequirement; -import ac.grim.grimac.command.commands.GrimAlerts; -import ac.grim.grimac.command.commands.GrimBrands; -import ac.grim.grimac.command.commands.GrimDebug; -import ac.grim.grimac.command.commands.GrimDump; -import ac.grim.grimac.command.commands.GrimHelp; -import ac.grim.grimac.command.commands.GrimLog; -import ac.grim.grimac.command.commands.GrimPerf; -import ac.grim.grimac.command.commands.GrimProfile; -import ac.grim.grimac.command.commands.GrimReload; -import ac.grim.grimac.command.commands.GrimSendAlert; -import ac.grim.grimac.command.commands.GrimSpectate; -import ac.grim.grimac.command.commands.GrimStopSpectating; -import ac.grim.grimac.command.commands.GrimVerbose; -import ac.grim.grimac.command.commands.GrimVersion; +import ac.grim.grimac.command.commands.*; import ac.grim.grimac.command.handler.GrimCommandFailureHandler; import ac.grim.grimac.platform.api.sender.Sender; import ac.grim.grimac.utils.anticheat.MessageUtil; @@ -60,6 +47,7 @@ public static void registerCommands(CommandManager commandManager) { new GrimProfile().register(commandManager); new GrimSendAlert().register(commandManager); new GrimHelp().register(commandManager); + new GrimHistory().register(commandManager); new GrimReload().register(commandManager); new GrimSpectate().register(commandManager); new GrimStopSpectating().register(commandManager); diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/MySQLViolationDatabase.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/MySQLViolationDatabase.java new file mode 100644 index 0000000000..8434748ce6 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/MySQLViolationDatabase.java @@ -0,0 +1,131 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.api.plugin.GrimPlugin; +import ac.grim.grimac.player.GrimPlayer; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; + +public class MySQLViolationDatabase implements ViolationDatabase { + + private final GrimPlugin plugin; + private HikariDataSource dataSource; + + public MySQLViolationDatabase(GrimPlugin plugin, String url, String database, String username, String password) { + this.plugin = plugin; + setupDataSource(url, database, username, password); + } + + private void setupDataSource(String url, String database, String username, String password) { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl("jdbc:mysql://" + url + "/" + database); + config.setUsername(username); + config.setPassword(password); + config.addDataSourceProperty("cachePrepStmts", "true"); + config.addDataSourceProperty("prepStmtCacheSize", "250"); + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + config.setMaximumPoolSize(10); + config.setAutoCommit(true); + dataSource = new HikariDataSource(config); + } + + @Override + public void connect() { + try (Connection connection = dataSource.getConnection()) { + connection.prepareStatement( + "CREATE TABLE IF NOT EXISTS violations(" + + "id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, " + + "server VARCHAR(255) NOT NULL, " + + "uuid CHAR(36) NOT NULL, " + + "check_name TEXT NOT NULL, " + + "verbose TEXT NOT NULL, " + + "vl INTEGER NOT NULL, " + + "created_at BIGINT NOT NULL" + + ")" + ).execute(); + + connection.prepareStatement( + "CREATE INDEX IF NOT EXISTS idx_violations_uuid ON violations(uuid);" + ).execute(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to generate violations database:", ex); + } + } + + @Override + public synchronized void logAlert(GrimPlayer player, String verbose, String checkName, int vls) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement insertAlert = connection.prepareStatement( + "INSERT INTO violations (server, uuid, check_name, verbose, vl, created_at) VALUES (?, ?, ?, ?, ?, ?)" + ) + ) { + insertAlert.setString(1, GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("history.server-name", "Prison")); + insertAlert.setString(2, player.getUniqueId().toString()); + insertAlert.setString(3, checkName); + insertAlert.setString(4, verbose); + insertAlert.setInt(5, vls); + insertAlert.setLong(6, System.currentTimeMillis()); + insertAlert.execute(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to log alert", ex); + } + } + + @Override + public synchronized int getLogCount(UUID player) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement countLogs = connection.prepareStatement( + "SELECT COUNT(*) FROM violations WHERE uuid = ?" + ) + ) { + countLogs.setString(1, player.toString()); + ResultSet result = countLogs.executeQuery(); + if (result.next()) { + return result.getInt(1); + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to count logs", ex); + } + return 0; + } + + @Override + public synchronized List getViolations(UUID player, int page, int limit) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement fetchLogs = connection.prepareStatement( + "SELECT server, uuid, check_name, verbose, vl, created_at FROM violations" + + " WHERE uuid = ? ORDER BY created_at DESC LIMIT ? OFFSET ?" + ) + ) { + fetchLogs.setString(1, player.toString()); + fetchLogs.setInt(2, limit); + fetchLogs.setInt(3, (page - 1) * limit); + return Violation.fromResultSet(fetchLogs.executeQuery()); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to fetch logs", ex); + return null; + } + } + + @Override + public void disconnect() { + if (dataSource != null && !dataSource.isClosed()) { + dataSource.close(); + } + } + + public boolean sameConfig(String host, String db, String user, String pwd) { + String wantUrl = "jdbc:mysql://" + host + "/" + db; + return wantUrl.equalsIgnoreCase(dataSource.getJdbcUrl()) + && user.equals(dataSource.getUsername()) + && pwd .equals(dataSource.getPassword()); // Hikari stores clear text + } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/NoOpViolationDatabase.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/NoOpViolationDatabase.java new file mode 100644 index 0000000000..5d008d5144 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/NoOpViolationDatabase.java @@ -0,0 +1,17 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.player.GrimPlayer; + +import java.util.List; +import java.util.UUID; + +public final class NoOpViolationDatabase implements ViolationDatabase { + public static final NoOpViolationDatabase INSTANCE = new NoOpViolationDatabase(); + private NoOpViolationDatabase() {} + + @Override public void connect() {} + @Override public void disconnect() {} + @Override public void logAlert(GrimPlayer p, String v, String c, int vl) {} + @Override public int getLogCount(UUID player) { return 0; } + @Override public List getViolations(UUID p, int page, int lim) { return List.of(); } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/SQLiteViolationDatabase.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/SQLiteViolationDatabase.java new file mode 100644 index 0000000000..889a14b143 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/SQLiteViolationDatabase.java @@ -0,0 +1,130 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.api.plugin.GrimPlugin; +import ac.grim.grimac.player.GrimPlayer; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; + +public class SQLiteViolationDatabase implements ViolationDatabase { + + private final GrimPlugin plugin; + + private Connection openConnection; + + public SQLiteViolationDatabase(@NotNull GrimPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void connect() { + try (Connection connection = getConnection()) { + connection.prepareStatement( + "CREATE TABLE IF NOT EXISTS violations(" + + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "server VARCHAR(255) NOT NULL, " + + "uuid CHARACTER(36) NOT NULL, " + + "check_name TEXT NOT NULL, " + + "verbose TEXT NOT NULL, " + + "vl INTEGER NOT NULL, " + + "created_at BIGINT NOT NULL" + + ")" + ).execute(); + + connection.prepareStatement( + "CREATE INDEX IF NOT EXISTS idx_violations_uuid ON violations(uuid)" + ).execute(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to generate violations database:", ex); + } + } + + @Override + public synchronized void logAlert(GrimPlayer player, String verbose, String checkName, int vls) { + try ( + Connection connection = getConnection(); + PreparedStatement insertLog = connection.prepareStatement( + "INSERT INTO violations (server, uuid, check_name, verbose, vl, created_at) VALUES (?, ?, ?, ?, ?, ?)" + ) + ) { + insertLog.setString(1, GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("history.server-name", "Prison")); + insertLog.setString(2, player.getUniqueId().toString()); + insertLog.setString(3, verbose); + insertLog.setString(4, checkName); + insertLog.setInt(5, vls); + insertLog.setLong(6, System.currentTimeMillis()); + + insertLog.executeUpdate(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to insert violation:", ex); + } + } + + public synchronized int getLogCount(UUID player) { + try ( + Connection connection = getConnection(); + PreparedStatement fetchLogs = connection.prepareStatement( + "SELECT COUNT(*) FROM violations WHERE uuid = ?" + ) + ) { + fetchLogs.setString(1, player.toString()); + ResultSet resultSet = fetchLogs.executeQuery(); + if (resultSet.next()) { + return resultSet.getInt(1); + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to fetch number of violations:", ex); + } + return 0; + } + + @Override + public synchronized List getViolations(UUID player, int page, int limit) { + List violations = new ArrayList<>(); + try ( + Connection connection = getConnection(); + PreparedStatement fetchLogs = connection.prepareStatement( + "SELECT server, uuid, check_name, verbose, vl, created_at FROM violations" + + " WHERE uuid = ? ORDER BY created_at DESC LIMIT ? OFFSET ?" + ) + ) { + fetchLogs.setString(1, player.toString()); + fetchLogs.setInt(2, limit); + fetchLogs.setInt(3, (page - 1) * limit); + + return Violation.fromResultSet(fetchLogs.executeQuery()); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to fetch violations:", ex); + } + + return violations; + } + + @Override + public void disconnect() { + try { + if (openConnection != null && !openConnection.isClosed()) { + openConnection.close(); + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to close connection", ex); + } + } + + protected synchronized Connection getConnection() throws SQLException { + if (openConnection == null || openConnection.isClosed()) { + openConnection = openConnection(); + } + return openConnection; + } + + protected Connection openConnection() throws SQLException { + return DriverManager.getConnection("jdbc:sqlite:" + plugin.getDataFolder().getAbsolutePath() + File.separator + "violations.sqlite"); + } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/Violation.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/Violation.java new file mode 100644 index 0000000000..7aae419def --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/Violation.java @@ -0,0 +1,38 @@ +package ac.grim.grimac.manager.violationdatabase; + +import lombok.Data; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +@Data +public class Violation { + + private final String serverName; + private final UUID playerUUID; + private final String checkName; + private final String verbose; + private final int vl; + private final Date createdAt; + + public static List fromResultSet(ResultSet resultSet) throws SQLException { + List violations = new ArrayList<>(); + while(resultSet.next()) { + String server = resultSet.getString("server"); + UUID player = UUID.fromString(resultSet.getString("uuid")); + String checkName = resultSet.getString("check_name"); + String verbose = resultSet.getString("verbose"); + int vl = resultSet.getInt("vl"); + Date createdAt = new Date(resultSet.getLong("created_at")); + + violations.add(new Violation(server, player, checkName, verbose, vl, createdAt)); + } + + return violations; + } + +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabase.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabase.java new file mode 100644 index 0000000000..d749758c84 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabase.java @@ -0,0 +1,20 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.player.GrimPlayer; + +import java.util.List; +import java.util.UUID; + +public interface ViolationDatabase { + + void connect(); + + void logAlert(GrimPlayer player, String verbose, String checkName, int vls); + + int getLogCount(UUID player); + + List getViolations(UUID player, int page, int limit); + + void disconnect(); + +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabaseManager.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabaseManager.java new file mode 100644 index 0000000000..c70b573116 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabaseManager.java @@ -0,0 +1,101 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.api.config.ConfigManager; +import ac.grim.grimac.api.plugin.GrimPlugin; +import ac.grim.grimac.manager.init.ReloadableInitable; +import ac.grim.grimac.manager.init.start.StartableInitable; +import ac.grim.grimac.player.GrimPlayer; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; + +public class ViolationDatabaseManager implements StartableInitable, ReloadableInitable { + + private final GrimPlugin plugin; + + private @NonNull ViolationDatabase database; + + public ViolationDatabaseManager(GrimPlugin plugin) { + this.plugin = plugin; + this.database = NoOpViolationDatabase.INSTANCE; + } + + @Override + public void start() { + load(); + } + + @Override + public void reload() { + load(); + } + + public void load() { + ConfigManager cfg = GrimAPI.INSTANCE.getConfigManager().getConfig(); + boolean enabled = cfg.getBooleanElse("history.enabled", false); + String rawType = enabled ? cfg.getStringElse("history.database.type", "SQLITE").toUpperCase() : "NOOP"; + + switch (rawType) { + case "SQLITE" -> { + if (!(database instanceof SQLiteViolationDatabase)) { + database.disconnect(); + try { + // Init sqlite + Class.forName("org.sqlite.JDBC"); + this.database = new SQLiteViolationDatabase(plugin); + } catch (ClassNotFoundException e) { + plugin.getLogger().log(Level.SEVERE, + """ + Could not load SQLite driver for /grim history database. + Download the minecraft-sqlite-jdbc mod/plugin for SQLite support, or change history.database.type + Alternatively set history.enabled=false if /grim history support is not desired""" + ); + this.database = NoOpViolationDatabase.INSTANCE; + } + database.connect(); + } + } + + case "MYSQL" -> { + String host = cfg.getStringElse("history.database.host", "localhost:3306"); + String db = cfg.getStringElse("history.database.database", "grimac"); + String user = cfg.getStringElse("history.database.username", "root"); + String pwd = cfg.getStringElse("history.database.password", "password"); + + if (database instanceof MySQLViolationDatabase mysql + && mysql.sameConfig(host, db, user, pwd)) { + break; // nothing changed → keep pool + } + database.disconnect(); + database = new MySQLViolationDatabase(plugin, host, db, user, pwd); + database.connect(); + } + + default -> { // NOOP or invalid + if (!(database instanceof NoOpViolationDatabase)) { + database.disconnect(); + database = NoOpViolationDatabase.INSTANCE; + } + } + } + } + + public void logAlert(GrimPlayer player, String verbose, String checkName, int vls) { + GrimAPI.INSTANCE.getScheduler().getAsyncScheduler().runNow(plugin, () -> database.logAlert(player, verbose, checkName, vls)); + } + + public int getLogCount(UUID player) { + return database.getLogCount(player); + } + + public List getViolations(UUID player, int page, int limit) { + return database.getViolations(player, page, limit); + } + + public boolean isEnabled() { + return !(database instanceof NoOpViolationDatabase); + } +} diff --git a/common/src/main/java/ac/grim/grimac/platform/api/entity/GrimEntity.java b/common/src/main/java/ac/grim/grimac/platform/api/entity/GrimEntity.java index 27a49ee292..82236e60c9 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/entity/GrimEntity.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/entity/GrimEntity.java @@ -1,15 +1,13 @@ package ac.grim.grimac.platform.api.entity; +import ac.grim.grimac.api.GrimIdentity; import ac.grim.grimac.platform.api.world.PlatformWorld; import ac.grim.grimac.utils.math.Location; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.UUID; import java.util.concurrent.CompletableFuture; -public interface GrimEntity { - UUID getUniqueId(); - +public interface GrimEntity extends GrimIdentity { /** * Eject any passenger. * diff --git a/common/src/main/java/ac/grim/grimac/platform/api/player/AbstractPlatformPlayerFactory.java b/common/src/main/java/ac/grim/grimac/platform/api/player/AbstractPlatformPlayerFactory.java index ad17f0e56f..61de0c863a 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/player/AbstractPlatformPlayerFactory.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/player/AbstractPlatformPlayerFactory.java @@ -2,6 +2,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; @@ -11,9 +12,8 @@ public abstract class AbstractPlatformPlayerFactory implements PlatformPlayerFactory { protected final PlatformPlayerCache cache = PlatformPlayerCache.getInstance(); - @Override - public @Nullable - final PlatformPlayer getFromUUID(@NonNull UUID uuid) { + @Override @Nullable + public final PlatformPlayer getFromUUID(@NonNull UUID uuid) { // Check cache first PlatformPlayer cachedPlayer = cache.getPlayer(uuid); if (cachedPlayer != null) { @@ -31,6 +31,18 @@ final PlatformPlayer getFromUUID(@NonNull UUID uuid) { return cache.addOrGetPlayer(uuid, platformPlayer); } + @Override @Nullable + public PlatformPlayer getFromName(@NonNull String name) { + T nativePlayer = getNativePlayer(name); + if (nativePlayer == null) { + return null; + } + + // Create new PlatformPlayer and cache it + PlatformPlayer platformPlayer = createPlatformPlayer(nativePlayer); + return cache.addOrGetPlayer(platformPlayer.getUniqueId(), platformPlayer); + } + @Override public final PlatformPlayer getFromNativePlayerType(@NonNull Object playerObject) { if (!isNativePlayerType(playerObject)) { @@ -81,6 +93,8 @@ public void replaceNativePlayer(@NonNull UUID uuid, @NonNull T player) {} */ protected abstract T getNativePlayer(@NonNull UUID uuid); + protected abstract T getNativePlayer(@NonNull String name); + /** * Creates a PlatformPlayer instance from the native player object. * @@ -118,4 +132,11 @@ public void replaceNativePlayer(@NonNull UUID uuid, @NonNull T player) {} * @return a collection of native player objects */ protected abstract Collection getNativeOnlinePlayers(); + + + @Override + public abstract OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid); + + @Override + public abstract OfflinePlatformPlayer getOfflineFromName(@NotNull String name); } diff --git a/common/src/main/java/ac/grim/grimac/platform/api/player/OfflinePlatformPlayer.java b/common/src/main/java/ac/grim/grimac/platform/api/player/OfflinePlatformPlayer.java new file mode 100644 index 0000000000..cc3f6ad596 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/platform/api/player/OfflinePlatformPlayer.java @@ -0,0 +1,10 @@ +package ac.grim.grimac.platform.api.player; + +import ac.grim.grimac.api.GrimIdentity; + +public interface OfflinePlatformPlayer extends GrimIdentity { + + boolean isOnline(); + + String getName(); +} diff --git a/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayer.java b/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayer.java index fea5591c7c..2a1f7d6952 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayer.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayer.java @@ -7,24 +7,20 @@ import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; -public interface PlatformPlayer extends GrimEntity { +public interface PlatformPlayer extends GrimEntity, OfflinePlatformPlayer { void kickPlayer(String textReason); - boolean hasPermission(String s); - - boolean hasPermission(String s, boolean defaultIfUnset); - boolean isSneaking(); void setSneaking(boolean b); - void sendMessage(String message); + boolean hasPermission(String s); - void sendMessage(Component message); + boolean hasPermission(String s, boolean defaultIfUnset); - boolean isOnline(); + void sendMessage(String message); - String getName(); + void sendMessage(Component message); void updateInventory(); diff --git a/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayerFactory.java b/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayerFactory.java index 0326c3a234..742325b433 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayerFactory.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayerFactory.java @@ -4,6 +4,12 @@ import java.util.UUID; public interface PlatformPlayerFactory { + OfflinePlatformPlayer getOfflineFromUUID(UUID uuid); + + OfflinePlatformPlayer getOfflineFromName(String name); + + PlatformPlayer getFromName(String name); + PlatformPlayer getFromUUID(UUID uuid); PlatformPlayer getFromNativePlayerType(Object playerObject); diff --git a/common/src/main/resources/config/de.yml b/common/src/main/resources/config/de.yml index 14d606dbc0..287a8af156 100644 --- a/common/src/main/resources/config/de.yml +++ b/common/src/main/resources/config/de.yml @@ -199,4 +199,20 @@ max-ping-out-of-flying: 1000 # Dies verhindert, dass Spieler mit hoher Latenz einen einzigen Feuerwerks‑Boost mit Elytra unbegrenzt nutzen können. max-ping-firework-boost: 1000 +history: + enabled: true + # Wie viele Einträge sollen pro Seite mit /grim history angezeigt werden + entries-per-page: 15 + # Welcher Servername soll für den History‑Befehl eingetragen werden? Nützlich, wenn dieselbe Datenbank für mehrere Server verwendet wird + server-name: Prison + database: + # SQLITE für lokale Speicherung, MYSQL für eine externe MySQL‑Datenbank. Wird nur nach Serverneustart aktualisiert + type: SQLITE + # MySQL‑Verbindungsdetails + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/en.yml b/common/src/main/resources/config/en.yml index 3f0f307274..c7fa811390 100644 --- a/common/src/main/resources/config/en.yml +++ b/common/src/main/resources/config/en.yml @@ -199,4 +199,20 @@ max-ping-out-of-flying: 1000 # This prevents high latency players from being able to use 1 firework boost with an elytra forever. max-ping-firework-boost: 1000 +history: + enabled: true + # How many entries should be shown for each page with /grim history + entries-per-page: 15 + # What should the inserted server name be for the history command? This is useful if you use the same database for multiple servers + server-name: Prison + database: + # Use SQLITE for local storage, use MYSQL if you have an external MySQL database. This is only updated on server restart + type: SQLITE + # MySQL connection details + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/es.yml b/common/src/main/resources/config/es.yml index 5612b782f7..5442139c00 100644 --- a/common/src/main/resources/config/es.yml +++ b/common/src/main/resources/config/es.yml @@ -201,4 +201,20 @@ max-ping-out-of-flying: 1000 # Esto evita que los jugadores con alta latencia puedan usar un solo impulso con fuegos artificiales y una elytra para siempre. max-ping-firework-boost: 1000 +history: + enabled: true + # Cuántas entradas se mostrarán por página con /grim history + entries-per-page: 15 + # ¿Qué nombre de servidor debe insertarse para el comando history? Útil si compartes la misma base de datos entre varios servidores + server-name: Prison + database: + # Usa SQLITE para almacenamiento local o MYSQL si tienes una base de datos MySQL externa. Solo se actualiza al reiniciar el servidor + type: SQLITE + # Detalles de conexión MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/fr.yml b/common/src/main/resources/config/fr.yml index c67a913afe..5c4251bc24 100644 --- a/common/src/main/resources/config/fr.yml +++ b/common/src/main/resources/config/fr.yml @@ -196,4 +196,20 @@ max-ping-out-of-flying: 1000 # Cela empêche les joueurs à forte latence d’utiliser indéfiniment un seul boost de feu d’artifice avec une élytra. max-ping-firework-boost: 1000 +history: + enabled: true + # Combien d’entrées doivent être affichées par page avec /grim history + entries-per-page: 15 + # Quel nom de serveur doit être inséré pour la commande history ? Utile si vous utilisez la même base de données pour plusieurs serveurs + server-name: Prison + database: + # Utilisez SQLITE pour un stockage local, MYSQL si vous disposez d’une base MySQL externe. Mis à jour seulement au redémarrage du serveur + type: SQLITE + # Détails de connexion MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/it.yml b/common/src/main/resources/config/it.yml index 711db82e1f..636a2a4282 100644 --- a/common/src/main/resources/config/it.yml +++ b/common/src/main/resources/config/it.yml @@ -175,4 +175,20 @@ max-ping-out-of-flying: 1000 # Impedisce ai giocatori con alta latenza di usare all’infinito un solo boost con fuochi d’artificio ed elytra. max-ping-firework-boost: 1000 +history: + enabled: true + # Quante voci devono essere mostrate per pagina con /grim history + entries-per-page: 15 + # Quale nome server deve essere inserito per il comando history? Utile se usi lo stesso database per più server + server-name: Prison + database: + # Usa SQLITE per l’archiviazione locale, MYSQL se hai un database MySQL esterno. Aggiornato solo al riavvio del server + type: SQLITE + # Dettagli di connessione MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/ja.yml b/common/src/main/resources/config/ja.yml index e920642eeb..58b9b3c2c4 100644 --- a/common/src/main/resources/config/ja.yml +++ b/common/src/main/resources/config/ja.yml @@ -208,4 +208,20 @@ max-ping-out-of-flying: 1000 # これにより、レイテンシの高いプレイヤーが1つのロケット花火による加速でエリトラを永久に使用するのを防ぎます。 max-ping-firework-boost: 1000 +history: + enabled: true + # /grim history で 1 ページに表示するエントリー数 + entries-per-page: 15 + # history コマンドで挿入されるサーバー名は? 複数サーバーで同じデータベースを使用する場合に便利 + server-name: Prison + database: + # ローカル保存には SQLITE、外部 MySQL データベースには MYSQL を使用します。サーバー再起動時にのみ反映 + type: SQLITE + # MySQL 接続情報 + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/nl.yml b/common/src/main/resources/config/nl.yml index e69dda24c8..7fcec87bd3 100644 --- a/common/src/main/resources/config/nl.yml +++ b/common/src/main/resources/config/nl.yml @@ -199,4 +199,20 @@ max-ping-out-of-flying: 1000 # Dit voorkomt dat spelers met hoge latency oneindig één vuurwerkboost met een elytra kunnen gebruiken. max-ping-firework-boost: 1000 +history: + enabled: true + # Hoeveel items worden per pagina weergegeven met /grim history + entries-per-page: 15 + # Welke servernaam moet voor het history‑commando worden ingevoerd? Handig als je dezelfde database voor meerdere servers gebruikt + server-name: Prison + database: + # Gebruik SQLITE voor lokale opslag, MYSQL voor een externe MySQL‑database. Alleen bijgewerkt na een serverherstart + type: SQLITE + # MySQL‑verbindingsgegevens + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/pt.yml b/common/src/main/resources/config/pt.yml index 385befe214..192e2738b1 100644 --- a/common/src/main/resources/config/pt.yml +++ b/common/src/main/resources/config/pt.yml @@ -204,4 +204,20 @@ max-ping-out-of-flying: 1000 # Previne jogadores com ping alto de usarem um foguete ara voar indefinidamente. max-ping-firework-boost: 1000 +history: + enabled: true + # Quantas entradas devem ser mostradas por página com /grim history + entries-per-page: 15 + # Qual nome de servidor deve ser inserido no comando history? Útil se você usa o mesmo banco de dados para vários servidores + server-name: Prison + database: + # Use SQLITE para armazenamento local ou MYSQL para um banco MySQL externo. Atualizado somente ao reiniciar o servidor + type: SQLITE + # Detalhes de conexão MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/ru.yml b/common/src/main/resources/config/ru.yml index d4e8f7d515..4e8a3da11a 100644 --- a/common/src/main/resources/config/ru.yml +++ b/common/src/main/resources/config/ru.yml @@ -195,4 +195,20 @@ max-ping-out-of-flying: 1000 # Это предотвращает возможность для игроков с большим пингом бесконечно использовать один фейерверк‑буст с элитрой. max-ping-firework-boost: 1000 +history: + enabled: true + # Сколько записей показывать на странице команды /grim history + entries-per-page: 15 + # Какое имя сервера вставлять для команды history? Полезно при использовании одной базы для нескольких серверов + server-name: Prison + database: + # Используйте SQLITE для локального хранения или MYSQL для внешней MySQL‑БД. Изменения применяются после перезапуска сервера + type: SQLITE + # Параметры подключения MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/tr.yml b/common/src/main/resources/config/tr.yml index 14f3ef9eba..3cc9143abe 100644 --- a/common/src/main/resources/config/tr.yml +++ b/common/src/main/resources/config/tr.yml @@ -199,4 +199,20 @@ max-ping-out-of-flying: 1000 # Bu, yüksek gecikmeli oyuncuların bir havai fişek ivmesini elytra ile sonsuza kadar kullanmalarını engeller. max-ping-firework-boost: 1000 +history: + enabled: true + # /grim history ile her sayfada kaç kayıt gösterilsin + entries-per-page: 15 + # History komutu için eklenecek sunucu adı nedir? Aynı veritabanını birden fazla sunucuda kullanıyorsanız faydalıdır + server-name: Prison + database: + # Yerel saklama için SQLITE, harici MySQL veritabanı için MYSQL kullanın. Sadece sunucu yeniden başlatıldığında güncellenir + type: SQLITE + # MySQL bağlantı bilgileri + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/zh.yml b/common/src/main/resources/config/zh.yml index 279e0983bd..6afa45eeeb 100644 --- a/common/src/main/resources/config/zh.yml +++ b/common/src/main/resources/config/zh.yml @@ -202,5 +202,21 @@ max-ping-out-of-flying: 1000 # 填写-1则关闭 max-ping-firework-boost: 1000 +history: + enabled: true + # 使用 /grim history 时每页显示多少条记录 + entries-per-page: 15 + # history 指令中插入的服务器名称?如果多个服务器共用同一数据库则很有用 + server-name: Prison + database: + # 本地存储使用 SQLITE,外部数据库使用 MYSQL。仅在重启服务器后生效 + type: SQLITE + # MySQL 连接详情 + host: localhost + port: 3306 + database: grim + username: root + password: "" + # 不要调整这个 config-version: 9 diff --git a/common/src/main/resources/messages/de.yml b/common/src/main/resources/messages/de.yml index 1a1e23f6f2..745d640f5c 100644 --- a/common/src/main/resources/messages/de.yml +++ b/common/src/main/resources/messages/de.yml @@ -60,4 +60,11 @@ help: - "/grim spectate &f- &7Beobachte einen Spieler" - "/grim verbose &f- &7Zeigt dir jeden Verstoß ohne Puffer an" - "/grim log [0-255] &f- &7Lädt ein Debug-Log für Vorhersagefehler hoch" + - "/grim history [Seite] &f- &7Zeigt frühere Warnungen für den Spieler" - "&7======================" + +grim-history-disabled: "%prefix% &cDas History‑Subsystem ist deaktiviert!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bZeige Logs für &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFehlgeschlagen &f%check% (x&c%vl%&f) &7%verbose% (&bvor %timeago%&7)" diff --git a/common/src/main/resources/messages/en.yml b/common/src/main/resources/messages/en.yml index e2223f58f2..3af1ad2ed7 100644 --- a/common/src/main/resources/messages/en.yml +++ b/common/src/main/resources/messages/en.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7Spectate a player" - "/grim verbose &f- &7Shows every flag to you, without buffers" - "/grim log [0-255] &f- &7Uploads a debug log for prediction flags" + - "/grim history [page] &f- &7Shows previous alerts for the player" - "&7======================" + +grim-history-disabled: "%prefix% &cHistory subsystem is disabled!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bShowing logs for &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFailed &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% ago&7)" diff --git a/common/src/main/resources/messages/es.yml b/common/src/main/resources/messages/es.yml index b0799c9416..9863765dd1 100644 --- a/common/src/main/resources/messages/es.yml +++ b/common/src/main/resources/messages/es.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7Espectar a un jugador" - "/grim verbose &f- &7Te muestra todo aviso, sin buffers" - "/grim log [0-255] &f- &7Sube un registro de depuración para avisos de predicciones" + - "/grim history [página] &f- &7Muestra alertas previas del jugador" - "&7======================" + +grim-history-disabled: "%prefix% &c¡El subsistema de historial está deshabilitado!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bMostrando registros de &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFallido &f%check% (x&c%vl%&f) &7%verbose% (&bhace %timeago%&7)" diff --git a/common/src/main/resources/messages/fr.yml b/common/src/main/resources/messages/fr.yml index 6947e7578f..c67eac53cf 100644 --- a/common/src/main/resources/messages/fr.yml +++ b/common/src/main/resources/messages/fr.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7Regarder un joueur" - "/grim verbose &f- &7Affiche chaqu'une de vos violations, sans tampons" - "/grim log [0-255] &f- &7Téléverse un journal de débogage pour les indicateurs de prédiction" + - "/grim history [page] &f- &7Affiche les alertes précédentes du joueur" - "&7======================" + +grim-history-disabled: "%prefix% &cLe sous‑système d’historique est désactivé !" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bAffichage des journaux pour &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bÉchec &f%check% (x&c%vl%&f) &7%verbose% (&bil y a %timeago%&7)" diff --git a/common/src/main/resources/messages/it.yml b/common/src/main/resources/messages/it.yml index f2bbb0cfad..208b662cf1 100644 --- a/common/src/main/resources/messages/it.yml +++ b/common/src/main/resources/messages/it.yml @@ -53,4 +53,11 @@ help: - "/grim spectate &f- &7Osserva un giocatore" - "/grim verbose &f- &7Mostra ogni segnalazione a te, senza buffer" - "/grim log [0-255] &f- &7Carica un registro di debug per le segnalazioni di previsione" + - "/grim history [pagina] &f- &7Mostra gli avvisi precedenti del giocatore" - "&7======================" + +grim-history-disabled: "%prefix% &cIl sottosistema cronologia è disabilitato!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bMostrando log per &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFallito &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% fa&7)" diff --git a/common/src/main/resources/messages/ja.yml b/common/src/main/resources/messages/ja.yml index a68779a147..463b4f4517 100644 --- a/common/src/main/resources/messages/ja.yml +++ b/common/src/main/resources/messages/ja.yml @@ -58,4 +58,11 @@ help: - "/grim spectate &f- &7プレイヤーを観戦します" - "/grim verbose &f- &7全てのフラグをバッファなしで表示します" - "/grim log [0-255] &f- &7予測フラグのデバッグログをアップロードします" + - "/grim history [ページ] &f- &7プレイヤーの過去のアラートを表示します" - "&7======================" + +grim-history-disabled: "%prefix% &c履歴サブシステムは無効です!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &b%player% のログを表示中 (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &b失敗 &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% 前&7)" diff --git a/common/src/main/resources/messages/nl.yml b/common/src/main/resources/messages/nl.yml index 901f950409..ecfb50a410 100644 --- a/common/src/main/resources/messages/nl.yml +++ b/common/src/main/resources/messages/nl.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7Een speler bekijken" - "/grim verbose &f- &7Toont elke flag, zonder buffers" - "/grim log [0-255] &f- &7Uploadt een debug-log voor voorspellings-flaggen" + - "/grim history [pagina] &f- &7Toont eerdere waarschuwingen voor de speler" - "&7======================" + +grim-history-disabled: "%prefix% &cHet geschiedenissubsysteem is uitgeschakeld!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bLogbestanden tonen voor &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bMislukt &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% geleden&7)" diff --git a/common/src/main/resources/messages/pt.yml b/common/src/main/resources/messages/pt.yml index ebec29b72a..5c94d28aa5 100644 --- a/common/src/main/resources/messages/pt.yml +++ b/common/src/main/resources/messages/pt.yml @@ -59,4 +59,11 @@ help: - "/grim spectate &f- &7Especta um jogador." - "/grim verbose &f- &7Esconde as informações extra." - "/grim log [0-255] &f- &7Envia o registro da simulação." + - "/grim history [página] &f- &7Mostra alertas anteriores do jogador" - "&7======================" + +grim-history-disabled: "%prefix% &cO subsistema de histórico está desativado!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bMostrando logs de &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFalhou &f%check% (x&c%vl%&f) &7%verbose% (&bhá %timeago%&7)" diff --git a/common/src/main/resources/messages/ru.yml b/common/src/main/resources/messages/ru.yml index 9cec83dcf6..dcede5bc2d 100644 --- a/common/src/main/resources/messages/ru.yml +++ b/common/src/main/resources/messages/ru.yml @@ -61,4 +61,11 @@ help: - "/grim spectate <игрок> &f- &7Наблюдать за игроком" - "/grim verbose &f- &7Показывает все флаги без буферов" - "/grim log [0-255] &f- &7Загружает журнал отладки для флагов предсказания" + - "/grim history [страница] &f- &7Показывает предыдущие предупреждения игрока" - "&7======================" + +grim-history-disabled: "%prefix% &cПодсистема истории отключена!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bПоказ журналов для &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bПровалено &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% назад&7)" diff --git a/common/src/main/resources/messages/tr.yml b/common/src/main/resources/messages/tr.yml index 8b35924c70..5a53e53942 100644 --- a/common/src/main/resources/messages/tr.yml +++ b/common/src/main/resources/messages/tr.yml @@ -61,7 +61,11 @@ help: - "/grim spectate (oyuncu) &f- &7Bir oyuncuyu izlemenizi sağlar" - "/grim verbose &f- &7Arabelleğe almadan tüm uyarıları görmenizi sağlar" - "/grim log (0-255) &f- &7Uyarılar için bir debug logu çıktısı verir" + - "/grim history [sayfa] &f- &7Oyuncunun önceki uyarılarını gösterir" - "&7======================" -# Translated by Kayera -# Have a nice day ;) +grim-history-disabled: "%prefix% &cGeçmiş alt sistemi devre dışı!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bKayıtlar gösteriliyor: &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bBaşarısız &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% önce&7)" \ No newline at end of file diff --git a/common/src/main/resources/messages/zh.yml b/common/src/main/resources/messages/zh.yml index cf53908b88..8d68ed058d 100644 --- a/common/src/main/resources/messages/zh.yml +++ b/common/src/main/resources/messages/zh.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7观看玩家" - "/grim verbose &f- &7显示无缓冲区的每个拉回" - "/grim log [1-999] &f- &7预测标志的调试日志" + - "/grim history [页码] &f- &7显示玩家之前的警报" - "&7======================" + +grim-history-disabled: "%prefix% &c历史子系统已禁用!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &b显示 &f%player% 的日志 (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &b失败 &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% 前&7)" diff --git a/common/src/main/resources/punishments/de.yml b/common/src/main/resources/punishments/de.yml index ed13d0ea1c..68f4f4d622 100644 --- a/common/src/main/resources/punishments/de.yml +++ b/common/src/main/resources/punishments/de.yml @@ -29,6 +29,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -38,6 +39,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -46,6 +48,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -56,6 +59,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -64,6 +68,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -72,6 +77,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -88,6 +94,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -98,6 +105,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Ab 2.2.10 gibt es keine AutoClicker-Prüfungen mehr und dies ist ein Platzhalter. Grim wird in Zukunft AutoClicker-Prüfungen einbauen. Autoclicker: remove-violations-after: 300 @@ -105,3 +113,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/en.yml b/common/src/main/resources/punishments/en.yml index f4e8735493..2deae7d85b 100644 --- a/common/src/main/resources/punishments/en.yml +++ b/common/src/main/resources/punishments/en.yml @@ -29,6 +29,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -38,6 +39,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -46,6 +48,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -56,6 +59,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -64,6 +68,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -72,6 +77,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -88,6 +94,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -98,6 +105,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # As of 2.2.10, there are no AutoClicker checks and this is a placeholder. Grim will include AutoClicker checks in the future. Autoclicker: remove-violations-after: 300 @@ -105,3 +113,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/es.yml b/common/src/main/resources/punishments/es.yml index af74b1f408..2ebf7d4dfc 100644 --- a/common/src/main/resources/punishments/es.yml +++ b/common/src/main/resources/punishments/es.yml @@ -29,6 +29,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -38,6 +39,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -46,6 +48,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -56,6 +59,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -64,6 +68,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -72,6 +77,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -88,6 +94,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -98,6 +105,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # A partir de 2.2.10, no hay ninguna comprobación de AutoClicker y esto es un placeholder. # Grim incluirá revisiones de AutoClicker en el futuro. Autoclicker: @@ -106,3 +114,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/fr.yml b/common/src/main/resources/punishments/fr.yml index 0d4fa2511d..c53f6d1801 100644 --- a/common/src/main/resources/punishments/fr.yml +++ b/common/src/main/resources/punishments/fr.yml @@ -29,6 +29,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -38,6 +39,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -46,6 +48,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -56,6 +59,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -64,6 +68,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -72,6 +77,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -88,6 +94,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -98,6 +105,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # A partir de la version 2.2.10, il n'y a plus de vérifications pour AutoClicker et c'est un placeholder. Grim inclura des vérifications AutoClicker dans le futur. Autoclicker: remove-violations-after: 300 @@ -105,3 +113,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/it.yml b/common/src/main/resources/punishments/it.yml index 69d499c2ce..28641106a1 100644 --- a/common/src/main/resources/punishments/it.yml +++ b/common/src/main/resources/punishments/it.yml @@ -16,6 +16,7 @@ Punishments: - "NoFall" commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -25,6 +26,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -33,6 +35,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -43,6 +46,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -51,6 +55,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -59,6 +64,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -75,6 +81,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -85,9 +92,11 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" Autoclicker: remove-violations-after: 300 checks: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/ja.yml b/common/src/main/resources/punishments/ja.yml index 9da40e90e5..e779c13d7c 100644 --- a/common/src/main/resources/punishments/ja.yml +++ b/common/src/main/resources/punishments/ja.yml @@ -29,6 +29,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -38,6 +39,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -46,6 +48,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -56,6 +59,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -64,6 +68,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -72,6 +77,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -88,6 +94,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -97,6 +104,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # バージョン2.2.10時点では、AutoClickerのチェックはまだなく、これはプレースホルダーです。Grimは将来的にAutoClickerのチェックを追加予定です。 Autoclicker: remove-violations-after: 300 @@ -104,3 +112,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/nl.yml b/common/src/main/resources/punishments/nl.yml index c88bf88b7a..ae05fe930f 100644 --- a/common/src/main/resources/punishments/nl.yml +++ b/common/src/main/resources/punishments/nl.yml @@ -29,6 +29,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -38,6 +39,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -46,6 +48,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -56,6 +59,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -64,6 +68,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -72,6 +77,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -88,6 +94,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -98,6 +105,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Vanaf 2.2.10 zijn er geen AutoClicker-controles en is dit een placeholder. Grim zal in de toekomst AutoClicker-controles toevoegen. Autoclicker: remove-violations-after: 300 @@ -105,3 +113,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/pt.yml b/common/src/main/resources/punishments/pt.yml index 3702a8d659..5d5a026cf6 100644 --- a/common/src/main/resources/punishments/pt.yml +++ b/common/src/main/resources/punishments/pt.yml @@ -29,6 +29,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -38,6 +39,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -46,6 +48,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -56,6 +59,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -64,6 +68,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -72,6 +77,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -88,6 +94,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -98,6 +105,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Dês da 2.2.10, não temos verificações de AutoClicker, isso é somente um placeholder. Grim vai verificar por AutoClicker no futuro. Autoclicker: remove-violations-after: 300 @@ -105,3 +113,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/ru.yml b/common/src/main/resources/punishments/ru.yml index 19f91e5618..e4bd5e6df1 100644 --- a/common/src/main/resources/punishments/ru.yml +++ b/common/src/main/resources/punishments/ru.yml @@ -29,6 +29,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -38,6 +39,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -46,6 +48,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -56,6 +59,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -64,6 +68,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -72,6 +77,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -88,6 +94,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -98,6 +105,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Начиная с версии 2.2.10, проверки на AutoClicker отсутствуют, на их месте пока используется placeholder. Grim будет включать проверки AutoClicker в будущем. Autoclicker: remove-violations-after: 300 @@ -105,3 +113,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/tr.yml b/common/src/main/resources/punishments/tr.yml index 6a224b56b8..41db78ef87 100644 --- a/common/src/main/resources/punishments/tr.yml +++ b/common/src/main/resources/punishments/tr.yml @@ -27,6 +27,7 @@ Punishments: # Kullanıcı 100 işaretine ulaştığında çalışır ve bundan sonra, 100'ün üzerindeki her 50. işarette çalışır commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -36,6 +37,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -44,6 +46,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -54,6 +57,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -62,6 +66,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -70,6 +75,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -86,6 +92,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -96,6 +103,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # 2.2.10 itibarıyla, AutoClicker denetimleri yoktur ve bu bir yer tutucudur. Grim, gelecekte AutoClicker denetimlerini dahil edecektir. Autoclicker: remove-violations-after: 300 @@ -103,3 +111,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/zh.yml b/common/src/main/resources/punishments/zh.yml index 8e483992e2..c140b05357 100644 --- a/common/src/main/resources/punishments/zh.yml +++ b/common/src/main/resources/punishments/zh.yml @@ -29,6 +29,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -38,6 +39,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -46,6 +48,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -56,6 +59,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -64,6 +68,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -72,6 +77,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -88,6 +94,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -98,6 +105,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Grim2.0.10版本 没有连点器检查,Grim将在未来添加 Autoclicker: remove-violations-after: 300 @@ -105,3 +113,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java b/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java index 6d21d97ccd..7633028a79 100644 --- a/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java +++ b/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java @@ -1,6 +1,6 @@ package ac.grim.grimac.platform.fabric.mc1161; -import ac.grim.grimac.platform.api.PlatformServer; +import ac.grim.grimac.platform.fabric.AbstractFabricPlatformServer; import ac.grim.grimac.platform.fabric.command.FabricPlayerSelectorParser; import ac.grim.grimac.platform.fabric.manager.FabricParserDescriptorFactory; import ac.grim.grimac.platform.fabric.mc1161.command.Fabric1161PlayerSelectorAdapter; @@ -32,7 +32,7 @@ public GrimACFabric1161LoaderPlugin() { } protected GrimACFabric1161LoaderPlugin(FabricPlatformPlayerFactory playerFactory, - PlatformServer platformServer, + AbstractFabricPlatformServer platformServer, IFabricMessageUtil fabricMessageUtil, IFabricConversionUtil fabricConversionUtil) { super( diff --git a/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/Fabric1171PlatformServer.java b/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/Fabric1171PlatformServer.java new file mode 100644 index 0000000000..4fe9c5673a --- /dev/null +++ b/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/Fabric1171PlatformServer.java @@ -0,0 +1,18 @@ +package ac.grim.grimac.platform.fabric.mc1171; + +import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; +import ac.grim.grimac.platform.fabric.mc1161.Fabric1140PlatformServer; +import com.mojang.authlib.GameProfile; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + + +public class Fabric1171PlatformServer extends Fabric1140PlatformServer { + + @Override @Nullable + public GameProfile getProfileByName(String name) { + Optional gameProfile = GrimACFabricLoaderPlugin.FABRIC_SERVER.getUserCache().findByName(name); + return gameProfile.orElse(null); + } +} diff --git a/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/GrimACFabric1170LoaderPlugin.java b/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/GrimACFabric1170LoaderPlugin.java index 7c0efe8194..28080d0702 100644 --- a/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/GrimACFabric1170LoaderPlugin.java +++ b/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/GrimACFabric1170LoaderPlugin.java @@ -1,6 +1,6 @@ package ac.grim.grimac.platform.fabric.mc1171; -import ac.grim.grimac.platform.api.PlatformServer; +import ac.grim.grimac.platform.fabric.AbstractFabricPlatformServer; import ac.grim.grimac.platform.api.manager.ParserDescriptorFactory; import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; import ac.grim.grimac.platform.fabric.command.FabricPlayerSelectorParser; @@ -15,6 +15,7 @@ import ac.grim.grimac.platform.fabric.player.FabricPlatformPlayerFactory; import ac.grim.grimac.platform.fabric.utils.convert.IFabricConversionUtil; import ac.grim.grimac.platform.fabric.utils.message.IFabricMessageUtil; +import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.manager.server.ServerVersion; public class GrimACFabric1170LoaderPlugin extends GrimACFabricLoaderPlugin { @@ -28,7 +29,8 @@ public GrimACFabric1170LoaderPlugin() { Fabric1170GrimEntity::new, Fabric1161PlatformInventory::new ), - new Fabric1140PlatformServer(), + PacketEvents.getAPI().getServerManager().getVersion().isNewerThan(ServerVersion.V_1_17) + ? new Fabric1171PlatformServer() : new Fabric1140PlatformServer(), new Fabric1161MessageUtil(), new Fabric1140ConversionUtil() ); @@ -36,7 +38,7 @@ public GrimACFabric1170LoaderPlugin() { protected GrimACFabric1170LoaderPlugin(ParserDescriptorFactory parserDescriptorFactory, FabricPlatformPlayerFactory playerFactory, - PlatformServer platformServer, + AbstractFabricPlatformServer platformServer, IFabricMessageUtil fabricMessageUtil, IFabricConversionUtil fabricConversionUtil) { super( diff --git a/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/Fabric1190PlatformServer.java b/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/Fabric1190PlatformServer.java index b5f9b0e1a4..4f4551e034 100644 --- a/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/Fabric1190PlatformServer.java +++ b/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/Fabric1190PlatformServer.java @@ -2,10 +2,10 @@ import ac.grim.grimac.platform.api.sender.Sender; import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; -import ac.grim.grimac.platform.fabric.mc1161.Fabric1140PlatformServer; +import ac.grim.grimac.platform.fabric.mc1171.Fabric1171PlatformServer; import net.minecraft.server.command.ServerCommandSource; -public class Fabric1190PlatformServer extends Fabric1140PlatformServer { +public class Fabric1190PlatformServer extends Fabric1171PlatformServer { @Override public void dispatchCommand(Sender sender, String command) { ServerCommandSource commandSource = GrimACFabricLoaderPlugin.LOADER.getFabricSenderFactory().reverse(sender); diff --git a/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/GrimACFabric1190LoaderPlugin.java b/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/GrimACFabric1190LoaderPlugin.java index e3367d5e5c..87c9a18d43 100644 --- a/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/GrimACFabric1190LoaderPlugin.java +++ b/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/GrimACFabric1190LoaderPlugin.java @@ -1,6 +1,6 @@ package ac.grim.grimac.platform.fabric.mc1194; -import ac.grim.grimac.platform.api.PlatformServer; +import ac.grim.grimac.platform.fabric.AbstractFabricPlatformServer; import ac.grim.grimac.platform.api.manager.ParserDescriptorFactory; import ac.grim.grimac.platform.fabric.mc1161.command.Fabric1161PlayerSelectorAdapter; import ac.grim.grimac.platform.fabric.command.FabricPlayerSelectorParser; @@ -40,7 +40,7 @@ public GrimACFabric1190LoaderPlugin() { protected GrimACFabric1190LoaderPlugin( ParserDescriptorFactory parserDescriptorFactory, FabricPlatformPlayerFactory platformPlayerFactory, - PlatformServer platformServer, + AbstractFabricPlatformServer platformServer, IFabricMessageUtil fabricMessageUtil, IFabricConversionUtil fabricConversionUtil) { super(parserDescriptorFactory, platformPlayerFactory, platformServer, fabricMessageUtil, fabricConversionUtil); diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/AbstractFabricPlatformServer.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/AbstractFabricPlatformServer.java index 8848206f7c..ce7464811a 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/AbstractFabricPlatformServer.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/AbstractFabricPlatformServer.java @@ -2,8 +2,10 @@ import ac.grim.grimac.platform.api.PlatformServer; import ac.grim.grimac.platform.api.sender.Sender; +import com.mojang.authlib.GameProfile; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.server.command.ServerCommandSource; +import org.jetbrains.annotations.Nullable; public abstract class AbstractFabricPlatformServer implements PlatformServer { @@ -24,4 +26,9 @@ public Sender getConsoleSender() { public void registerOutgoingPluginChannel(String name) { throw new UnsupportedOperationException(); } + + @Nullable + public GameProfile getProfileByName(String name) { + return GrimACFabricLoaderPlugin.FABRIC_SERVER.getUserCache().findByName(name); + } } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java index b944fd133e..960868a5a4 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java @@ -5,7 +5,6 @@ import ac.grim.grimac.api.GrimAPIProvider; import ac.grim.grimac.api.plugin.GrimPlugin; import ac.grim.grimac.platform.api.PlatformLoader; -import ac.grim.grimac.platform.api.PlatformServer; import ac.grim.grimac.platform.api.manager.*; import ac.grim.grimac.platform.api.sender.Sender; import ac.grim.grimac.platform.api.sender.SenderFactory; @@ -57,14 +56,14 @@ public abstract class GrimACFabricLoaderPlugin implements PlatformLoader { protected final ParserDescriptorFactory parserFactory; protected final FabricPlatformPlayerFactory playerFactory; - protected final PlatformServer platformServer; + protected final AbstractFabricPlatformServer platformServer; protected final IFabricConversionUtil fabricConversionUtil; protected final IFabricMessageUtil fabricMessageUtil; public GrimACFabricLoaderPlugin( ParserDescriptorFactory parserDescriptorFactory, FabricPlatformPlayerFactory playerFactory, - PlatformServer platformServer, + AbstractFabricPlatformServer platformServer, IFabricMessageUtil fabricMessageUtil, IFabricConversionUtil fabricConversionUtil ) { @@ -147,7 +146,7 @@ public FabricPlatformPlayerFactory getPlatformPlayerFactory() { } @Override - public PlatformServer getPlatformServer() { + public AbstractFabricPlatformServer getPlatformServer() { return platformServer; } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricOfflinePlatformPlayer.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricOfflinePlatformPlayer.java new file mode 100644 index 0000000000..98773c9265 --- /dev/null +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricOfflinePlatformPlayer.java @@ -0,0 +1,40 @@ +package ac.grim.grimac.platform.fabric.player; + +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; +import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public class FabricOfflinePlatformPlayer implements OfflinePlatformPlayer { + private final UUID uuid; + private final String name; + + public FabricOfflinePlatformPlayer(@NotNull UUID uuid, @NotNull String name) { + this.uuid = uuid; + this.name = name; + } + + @Override + public boolean isOnline() { + return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayer(uuid) != null; + } + + @Override + public @NotNull String getName() { + return name; + } + + @Override + public @NotNull UUID getUniqueId() { + return uuid; + } + + @Override + public boolean equals(Object o) { + if (o instanceof OfflinePlatformPlayer offlinePlatformPlayer) { + return this.getUniqueId().equals(offlinePlatformPlayer.getUniqueId()); + } + return false; + } +} diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricPlatformPlayerFactory.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricPlatformPlayerFactory.java index fadad12b84..cfba9bd15d 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricPlatformPlayerFactory.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricPlatformPlayerFactory.java @@ -2,18 +2,24 @@ import ac.grim.grimac.platform.api.entity.GrimEntity; import ac.grim.grimac.platform.api.player.AbstractPlatformPlayerFactory; +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; +import com.mojang.authlib.GameProfile; import net.minecraft.entity.Entity; import net.minecraft.server.network.ServerPlayerEntity; import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.NotNull; +import java.nio.charset.StandardCharsets; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; import java.util.function.Function; public class FabricPlatformPlayerFactory extends AbstractPlatformPlayerFactory { + private final Map offlinePlatformPlayerCache = new HashMap<>(); private final Function getPlayerFunction; private final Function getEntityFunction; private final Function getPlayerInventoryFunction; @@ -32,6 +38,11 @@ protected ServerPlayerEntity getNativePlayer(@NotNull UUID uuid) { return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayer(uuid); } + @Override + protected ServerPlayerEntity getNativePlayer(@NonNull String name) { + return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayer(name); + } + @Override protected AbstractFabricPlatformPlayer createPlatformPlayer(@NotNull ServerPlayerEntity nativePlayer) { return getPlayerFunction.apply(nativePlayer); @@ -58,6 +69,43 @@ protected Collection getNativeOnlinePlayers() { return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayerList(); } + @Override + public OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid) { + return null; + } + + @Override + public OfflinePlatformPlayer getOfflineFromName(@NotNull String name) { + OfflinePlatformPlayer result = this.getFromName(name); + if (result == null) { + GameProfile profile = null; + // Only fetch an online UUID in online mode + // TODO (cross-platform) add a config option for "offline-mode" servers with online-mode behind a proxy + if (GrimACFabricLoaderPlugin.FABRIC_SERVER.isOnlineMode()) { + // THIS CAN BLOCK THE CALLING THREAD! + profile = GrimACFabricLoaderPlugin.LOADER.getPlatformServer().getProfileByName(name); + } + + if (profile == null) { + // Make an OfflinePlayer using an offline mode UUID since the name has no profile + result = this.getOfflinePlayer(new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8)), name)); + } else { + // Use the GameProfile even when we get a UUID so we ensure we still have a name + result = this.getOfflinePlayer(profile); + } + } else { + this.offlinePlatformPlayerCache.remove(result.getUniqueId()); + } + + return result; + } + + public OfflinePlatformPlayer getOfflinePlayer(GameProfile profile) { + OfflinePlatformPlayer player = new FabricOfflinePlatformPlayer(profile.getId(), profile.getName()); + this.offlinePlatformPlayerCache.put(profile.getId(), player); + return player; + } + @Override public void replaceNativePlayer(@NonNull UUID uuid, @NonNull ServerPlayerEntity serverPlayerEntity) { super.cache.getPlayer(uuid).replaceNativePlayer(serverPlayerEntity); From bad73fa03ff00f3c5631fe41781972534c8a12f8 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sat, 26 Apr 2025 12:13:32 -0400 Subject: [PATCH 03/34] Add Inventory checks --- .../checks/impl/inventory/InventoryA.java | 42 +++++ .../checks/impl/inventory/InventoryB.java | 35 ++++ .../checks/impl/inventory/InventoryC.java | 30 ++++ .../checks/impl/inventory/InventoryD.java | 85 +++++++++ .../checks/impl/inventory/InventoryE.java | 50 ++++++ .../checks/impl/inventory/InventoryF.java | 46 +++++ .../checks/impl/inventory/InventoryG.java | 40 +++++ .../grimac/checks/type/InventoryCheck.java | 67 +++++++ .../events/packets/CheckManagerListener.java | 3 + .../events/packets/PacketPlayerWindow.java | 163 ++++++++++++++++++ .../ac/grim/grimac/manager/CheckManager.java | 8 + .../manager/init/start/PacketManager.java | 17 +- .../ac/grim/grimac/player/GrimPlayer.java | 4 + .../predictionengine/PointThreeEstimator.java | 6 + .../predictions/PredictionEngine.java | 41 +++-- .../ac/grim/grimac/utils/data/VectorData.java | 30 +++- .../inventory/InventoryDesyncStatus.java | 7 + .../utils/latency/CompensatedInventory.java | 2 +- common/src/main/resources/punishments/de.yml | 9 + common/src/main/resources/punishments/en.yml | 9 + common/src/main/resources/punishments/es.yml | 9 + common/src/main/resources/punishments/fr.yml | 9 + common/src/main/resources/punishments/it.yml | 9 + common/src/main/resources/punishments/ja.yml | 9 + common/src/main/resources/punishments/nl.yml | 9 + common/src/main/resources/punishments/pt.yml | 9 + common/src/main/resources/punishments/ru.yml | 9 + common/src/main/resources/punishments/tr.yml | 9 + common/src/main/resources/punishments/zh.yml | 9 + 29 files changed, 743 insertions(+), 32 deletions(-) create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryA.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryB.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryC.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryD.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryE.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryF.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryG.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/type/InventoryCheck.java create mode 100644 common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerWindow.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/inventory/InventoryDesyncStatus.java diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryA.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryA.java new file mode 100644 index 0000000000..1e15d49bba --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryA.java @@ -0,0 +1,42 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity.InteractAction; + +@CheckData(name = "InventoryA", setback = 3, description = "Attacked an entity while inventory is open") +public class InventoryA extends InventoryCheck { + public InventoryA(GrimPlayer player) { + super(player); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + super.onPacketReceive(event); + + if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) { + WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event); + + if (wrapper.getAction() != InteractAction.ATTACK) return; + + // Is not possible to attack while the inventory is open. + if (player.hasInventoryOpen) { + if (flagAndAlert()) { + // Cancel the packet + if (shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + } + if (!isNoSetbackPermission()) + closeInventory(); + } + } else { + reward(); + } + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryB.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryB.java new file mode 100644 index 0000000000..ee42ad0a2b --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryB.java @@ -0,0 +1,35 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.player.DiggingAction; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging; + +@CheckData(name = "InventoryB", setback = 3, description = "Started digging blocks while inventory is open") +public class InventoryB extends InventoryCheck { + public InventoryB(GrimPlayer player) { + super(player); + } + + public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging wrapper) { + if (wrapper.getAction() != DiggingAction.START_DIGGING) return; + + // Is not possible to start digging a block while the inventory is open. + if (player.hasInventoryOpen) { + if (flagAndAlert()) { + // Cancel the packet + if (shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + } + if (!isNoSetbackPermission()) { + closeInventory(); + } + } + } else { + reward(); + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryC.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryC.java new file mode 100644 index 0000000000..20d3852671 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryC.java @@ -0,0 +1,30 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.anticheat.update.BlockPlace; + +@CheckData(name = "InventoryC", setback = 3, description = "Placed a block while inventory is open") +public class InventoryC extends InventoryCheck { + + public InventoryC(GrimPlayer player) { + super(player); + } + + public void onBlockPlace(final BlockPlace place) { + // It is not possible to place a block while the inventory is open + if (player.hasInventoryOpen) { + if (flagAndAlert()) { + if (shouldModifyPackets()) { + place.resync(); + } + if (!isNoSetbackPermission()) { + closeInventory(); + } + } + } else { + reward(); + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryD.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryD.java new file mode 100644 index 0000000000..b643e4b9c0 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryD.java @@ -0,0 +1,85 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.anticheat.update.PredictionComplete; +import ac.grim.grimac.utils.data.VectorData; +import ac.grim.grimac.utils.data.VectorData.MoveVectorData; +import ac.grim.grimac.utils.data.VehicleData; + +import java.util.StringJoiner; + +@CheckData(name = "InventoryD", setback = 1, decay = 0.25) +public class InventoryD extends InventoryCheck { + private int horseJumpVerbose; + + public InventoryD(GrimPlayer player) { + super(player); + } + + @Override + public void onPredictionComplete(final PredictionComplete predictionComplete) { + if (!predictionComplete.isChecked() || + predictionComplete.getData().isTeleport() || + player.getSetbackTeleportUtil().blockOffsets || + player.packetStateData.lastPacketWasTeleport || + player.packetStateData.isSlowedByUsingItem() || + System.currentTimeMillis() - player.lastBlockPlaceUseItem < 50L) { + return; + } + + if (player.hasInventoryOpen) { + boolean inVehicle = player.inVehicle(); + boolean isJumping, isMoving; + + if (inVehicle) { + VehicleData vehicle = player.vehicleData; + + // Will flag once if player open anything with pressed space bar + isJumping = vehicle.nextHorseJump > 0 && horseJumpVerbose++ >= 1; + isMoving = vehicle.nextVehicleForward != 0 || vehicle.nextVehicleHorizontal != 0; + } else { + MoveVectorData move = findMovement(player.predictedVelocity); + + isJumping = player.predictedVelocity.isJump(); + isMoving = move != null && (move.x != 0 || move.z != 0); + } + + if (!isMoving && !isJumping) { + reward(); + return; + } + + if (flag()) { + if (!isNoSetbackPermission()) + closeInventory(); + + StringJoiner joiner = new StringJoiner(" "); + + if (isMoving) joiner.add("moving"); + if (isJumping) joiner.add("jumping"); + if (inVehicle) joiner.add("inVehicle"); + + alert(joiner.toString()); + } + } else { + horseJumpVerbose = 0; + } + } + + private MoveVectorData findMovement(VectorData vectorData) { + if (vectorData instanceof MoveVectorData) { + return (MoveVectorData) vectorData; + } + + while (vectorData != null) { + vectorData = vectorData.lastVector; + if (vectorData instanceof MoveVectorData) { + return (MoveVectorData) vectorData; + } + } + + return null; + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryE.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryE.java new file mode 100644 index 0000000000..caf6270001 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryE.java @@ -0,0 +1,50 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; + +@CheckData(name = "InventoryE", setback = 3, description = "Sent a held item change packet while inventory is open") +public class InventoryE extends InventoryCheck { + private long lastTransaction = Long.MAX_VALUE; // Impossible transaction ID + + public InventoryE(GrimPlayer player) { + super(player); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + super.onPacketReceive(event); + + if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) { + // It is not possible to change hotbar slots with held item change while the inventory is open + // A container click packet would be sent instead + if (player.hasInventoryOpen) { + if (this.lastTransaction < player.lastTransactionReceived.get() + && flagAndAlert()) { + // Cancel the packet + if (shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + player.getInventory().needResend = true; + } + if (!isNoSetbackPermission()) { + closeInventory(); + } + } + } else { + reward(); + } + } + } + + @Override + public void onPacketSend(PacketSendEvent event) { + if (event.getPacketType() == PacketType.Play.Server.HELD_ITEM_CHANGE) { + this.lastTransaction = player.lastTransactionSent.get(); + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryF.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryF.java new file mode 100644 index 0000000000..28171b20e7 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryF.java @@ -0,0 +1,46 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.inventory.InventoryDesyncStatus; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.manager.server.ServerVersion; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.player.ClientVersion; + +@CheckData(name = "InventoryF", setback = 3, description = "Sent a click window packet without a open inventory", experimental = true) +public class InventoryF extends InventoryCheck { + + public InventoryF(GrimPlayer player) { + super(player); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + // Exempt on 1.9+ server version due to the Via hack done in PacketPlayerWindow, the exemption can be deleted + // once we are ahead of ViaVersion + if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9) + || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) return; + + super.onPacketReceive(event); + + if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) { + if (!player.hasInventoryOpen && player.inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) { + if (flagAndAlert()) { + // Cancel the packet + if (shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + } + if (!isNoSetbackPermission()) { + closeInventory(); + } + } + } else { + reward(); + } + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryG.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryG.java new file mode 100644 index 0000000000..a189df88cd --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryG.java @@ -0,0 +1,40 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction; + +@CheckData(name = "InventoryG", setback = 3, description = "Sent a entity action packet while inventory is open", experimental = true) +public class InventoryG extends InventoryCheck { + + public InventoryG(GrimPlayer player) { + super(player); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + if (player.packetStateData.lastPacketWasTeleport) return; + super.onPacketReceive(event); + + if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) { + WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event); + WrapperPlayClientEntityAction.Action action = wrapper.getAction(); + + if (action == WrapperPlayClientEntityAction.Action.STOP_SNEAKING + || action == WrapperPlayClientEntityAction.Action.STOP_SPRINTING) { + return; + } + + if (player.hasInventoryOpen) { + if (flagAndAlert() && !isNoSetbackPermission()) { + closeInventory(); + } + } else { + reward(); + } + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/type/InventoryCheck.java b/common/src/main/java/ac/grim/grimac/checks/type/InventoryCheck.java new file mode 100644 index 0000000000..5c3f1ed94c --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/type/InventoryCheck.java @@ -0,0 +1,67 @@ +package ac.grim.grimac.checks.type; + +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCloseWindow; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerCloseWindow; +import org.jetbrains.annotations.MustBeInvokedByOverriders; + +public class InventoryCheck extends BlockPlaceCheck implements PacketCheck { + // Impossible transaction ID + protected static final long NONE = Long.MAX_VALUE; + + protected long closeTransaction = NONE; + protected int closePacketsToSkip; + + public InventoryCheck(GrimPlayer player) { + super(player); + } + + @Override + @MustBeInvokedByOverriders + public void onPacketReceive(final PacketReceiveEvent event) { + if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) { + // Disallow any clicks if inventory is closing + if (closeTransaction != NONE && shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + player.getInventory().needResend = true; + } + } else if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) { + // Players with high ping can close inventory faster than send transaction back + if (closeTransaction != NONE && closePacketsToSkip-- <= 0) { + closeTransaction = NONE; + } + } + } + + public void closeInventory() { + if (closeTransaction != NONE) { + return; + } + + int windowId = player.getInventory().openWindowID; + + player.user.writePacket(new WrapperPlayServerCloseWindow(windowId)); + + // Force close inventory on server side + closePacketsToSkip = 1; // Sending close packet to itself, so skip it + PacketEvents.getAPI().getProtocolManager().receivePacket( + player.user.getChannel(), new WrapperPlayClientCloseWindow(windowId) + ); + + player.sendTransaction(); + + int transaction = player.lastTransactionSent.get(); + closeTransaction = transaction; + player.latencyUtils.addRealTimeTask(transaction, () -> { + if (closeTransaction == transaction) { + closeTransaction = NONE; + } + }); + + player.user.flushPackets(); + } +} diff --git a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java index 1a8cee1200..066f38f527 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java @@ -1,6 +1,7 @@ package ac.grim.grimac.events.packets; import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.checks.impl.inventory.InventoryB; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.update.BlockBreak; import ac.grim.grimac.utils.anticheat.update.BlockPlace; @@ -558,6 +559,8 @@ public void onPacketReceive(PacketReceiveEvent event) { final WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event); final DiggingAction action = packet.getAction(); + player.checkManager.getPacketCheck(InventoryB.class).handle(event, packet); + if (action == DiggingAction.START_DIGGING || action == DiggingAction.FINISHED_DIGGING || action == DiggingAction.CANCELLED_DIGGING) { final BlockBreak blockBreak = new BlockBreak(player, packet.getBlockPosition(), packet.getBlockFace(), packet.getBlockFaceId(), action, packet.getSequence(), player.compensatedWorld.getBlock(packet.getBlockPosition())); diff --git a/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerWindow.java b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerWindow.java new file mode 100644 index 0000000000..404bf1b738 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerWindow.java @@ -0,0 +1,163 @@ +package ac.grim.grimac.events.packets; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.data.packetentity.PacketEntitySelf; +import ac.grim.grimac.utils.inventory.InventoryDesyncStatus; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketListenerAbstract; +import com.github.retrooper.packetevents.event.PacketListenerPriority; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.manager.server.ServerVersion; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.player.ClientVersion; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus.Action; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow; + +public class PacketPlayerWindow extends PacketListenerAbstract { + + public PacketPlayerWindow() { + super(PacketListenerPriority.LOWEST); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !event.isCancelled()) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + if (player.hasInventoryOpen && isNearNetherPortal(player)) { + handleInventoryClose(player, InventoryDesyncStatus.NETHER_PORTAL); + } + } + + // Client Status is sent in 1.7-1.8 + if (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS) { + WrapperPlayClientClientStatus wrapper = new WrapperPlayClientClientStatus(event); + + if (wrapper.getAction() == Action.OPEN_INVENTORY_ACHIEVEMENT) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + handleInventoryOpen(player); + } + } + + // We need to do this due to 1.9 not sending anymore the inventory action in the Client Status + if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + // TODO: Remove this check after we finish the before ViaVersion injection + // Explanation: On 1.7 and 1.8 we have OPEN_INVENTORY_ACHIEVEMENT on CLIENT_STATUS packet + // but after Via translation this information gets lost + // This is a workaround to atleast make our inventory checks work "decently" in 1.8 clients for 1.9+ servers + if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9) + && player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) { + handleInventoryOpen(player); + } + + if (player.getClientVersion().isNewerThan(ClientVersion.V_1_8)) { + handleInventoryOpen(player); + } + } + + if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED); + } + } + + @Override + public void onPacketSend(PacketSendEvent event) { + if (event.getPacketType() == PacketType.Play.Server.RESPAWN) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + player.sendTransaction(); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED)); + } else if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) { + WrapperPlayServerOpenWindow wrapper = new WrapperPlayServerOpenWindow(event); + + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + player.sendTransaction(); + + String legacyType = wrapper.getLegacyType(); + int modernType = wrapper.getType(); + InventoryDesyncStatus inventoryDesyncStatus = getContainerDesyncStatus(player, legacyType, modernType); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> { + if (inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) { + handleInventoryOpen(player); + } else { + handleInventoryClose(player, inventoryDesyncStatus); + } + }); + } else if (event.getPacketType() == PacketType.Play.Server.OPEN_HORSE_WINDOW) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + player.sendTransaction(); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> handleInventoryOpen(player)); + } else if (event.getPacketType() == PacketType.Play.Server.CLOSE_WINDOW) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + player.sendTransaction(); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED)); + } + } + + private void handleInventoryOpen(GrimPlayer player) { + if (!player.hasInventoryOpen) { + player.lastInventoryOpen = System.currentTimeMillis(); + } + + player.hasInventoryOpen = true; + } + + private void handleInventoryClose(GrimPlayer player, InventoryDesyncStatus desyncStatus) { + player.hasInventoryOpen = false; + player.inventoryDesyncStatus = desyncStatus; + } + + public InventoryDesyncStatus getContainerDesyncStatus(GrimPlayer player, String legacyType, int modernType) { + // Closing beacon with the cross button cause desync in 1.7-1.8 + if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) && + ("minecraft:beacon".equals(legacyType) || modernType == 8)) { + return player.inventoryDesyncStatus = InventoryDesyncStatus.BEACON; + } + + if (isNearNetherPortal(player)) { + return player.inventoryDesyncStatus = InventoryDesyncStatus.NETHER_PORTAL; + } + + return player.inventoryDesyncStatus = InventoryDesyncStatus.NOT_DESYNCED; + } + + public boolean isNearNetherPortal(GrimPlayer player) { + // Going inside nether portal with opened inventory cause desync, fixed in 1.12.2 + if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_12_1) && + player.pointThreeEstimator.isNearNetherPortal) { + PacketEntitySelf playerEntity = player.compensatedEntities.self; + // Client ignore nether portal if player has passengers or riding an entity + return !playerEntity.inVehicle() && playerEntity.passengers.isEmpty(); + } + + return false; + } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java index d5efbc35da..e46fa33e1b 100644 --- a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java @@ -34,6 +34,7 @@ import ac.grim.grimac.checks.impl.exploit.ExploitB; import ac.grim.grimac.checks.impl.exploit.ExploitC; import ac.grim.grimac.checks.impl.groundspoof.NoFall; +import ac.grim.grimac.checks.impl.inventory.*; import ac.grim.grimac.checks.impl.misc.ClientBrand; import ac.grim.grimac.checks.impl.misc.GhostBlockMitigation; import ac.grim.grimac.checks.impl.misc.TransactionOrder; @@ -155,6 +156,11 @@ public CheckManager(GrimPlayer player) { .put(BadPacketsU.class, new BadPacketsU(player)) .put(BadPacketsV.class, new BadPacketsV(player)) .put(BadPacketsY.class, new BadPacketsY(player)) + .put(InventoryA.class, new InventoryA(player)) + .put(InventoryB.class, new InventoryB(player)) + .put(InventoryE.class, new InventoryE(player)) + .put(InventoryF.class, new InventoryF(player)) + .put(InventoryG.class, new InventoryG(player)) .put(MultiActionsA.class, new MultiActionsA(player)) .put(MultiActionsB.class, new MultiActionsB(player)) .put(MultiActionsC.class, new MultiActionsC(player)) @@ -190,6 +196,7 @@ public CheckManager(GrimPlayer player) { .put(ExplosionHandler.class, new ExplosionHandler(player)) .put(KnockbackHandler.class, new KnockbackHandler(player)) .put(GhostBlockDetector.class, new GhostBlockDetector(player)) + .put(InventoryD.class, new InventoryD(player)) .put(Phase.class, new Phase(player)) .put(Post.class, new Post(player)) .put(PacketOrderA.class, new PacketOrderA(player)) @@ -232,6 +239,7 @@ public CheckManager(GrimPlayer player) { .build(); blockPlaceCheck = new ImmutableClassToInstanceMap.Builder() + .put(InventoryC.class, new InventoryC(player)) .put(InvalidPlaceA.class, new InvalidPlaceA(player)) .put(InvalidPlaceB.class, new InvalidPlaceB(player)) .put(AirLiquidPlace.class, new AirLiquidPlace(player)) diff --git a/common/src/main/java/ac/grim/grimac/manager/init/start/PacketManager.java b/common/src/main/java/ac/grim/grimac/manager/init/start/PacketManager.java index 4c78a402a8..f271465e2f 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/start/PacketManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/start/PacketManager.java @@ -1,20 +1,6 @@ package ac.grim.grimac.manager.init.start; -import ac.grim.grimac.events.packets.CheckManagerListener; -import ac.grim.grimac.events.packets.PacketBlockAction; -import ac.grim.grimac.events.packets.PacketEntityAction; -import ac.grim.grimac.events.packets.PacketHidePlayerInfo; -import ac.grim.grimac.events.packets.PacketPingListener; -import ac.grim.grimac.events.packets.PacketPlayerAttack; -import ac.grim.grimac.events.packets.PacketPlayerCooldown; -import ac.grim.grimac.events.packets.PacketPlayerDigging; -import ac.grim.grimac.events.packets.PacketPlayerJoinQuit; -import ac.grim.grimac.events.packets.PacketPlayerRespawn; -import ac.grim.grimac.events.packets.PacketPlayerSteer; -import ac.grim.grimac.events.packets.PacketSelfMetadataListener; -import ac.grim.grimac.events.packets.PacketServerTags; -import ac.grim.grimac.events.packets.PacketServerTeleport; -import ac.grim.grimac.events.packets.ProxyAlertMessenger; +import ac.grim.grimac.events.packets.*; import ac.grim.grimac.events.packets.worldreader.BasePacketWorldReader; import ac.grim.grimac.events.packets.worldreader.PacketWorldReaderEight; import ac.grim.grimac.events.packets.worldreader.PacketWorldReaderEighteen; @@ -30,6 +16,7 @@ public void start() { PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerJoinQuit()); PacketEvents.getAPI().getEventManager().registerListener(new PacketPingListener()); + PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerWindow()); PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerDigging()); PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerAttack()); PacketEvents.getAPI().getEventManager().registerListener(new PacketEntityAction()); diff --git a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java index 0c2b56aec0..b062e6f042 100644 --- a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java +++ b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java @@ -34,6 +34,7 @@ import ac.grim.grimac.utils.data.tags.SyncedTags; import ac.grim.grimac.utils.enums.FluidTag; import ac.grim.grimac.utils.enums.Pose; +import ac.grim.grimac.utils.inventory.InventoryDesyncStatus; import ac.grim.grimac.utils.latency.CompensatedEntities; import ac.grim.grimac.utils.latency.CompensatedFireworks; import ac.grim.grimac.utils.latency.CompensatedInventory; @@ -240,6 +241,9 @@ public class GrimPlayer implements GrimUser { // possibleEyeHeights[0] = Standing eye heights, [1] = Sneaking. [2] = Elytra, Swimming, and Riptide Trident which only exists in 1.9+ public final double[][] possibleEyeHeights = new double[3][]; public int totalFlyingPacketsSent; + public boolean hasInventoryOpen; + public long lastInventoryOpen; + public InventoryDesyncStatus inventoryDesyncStatus; public final Queue placeUseItemPackets = new LinkedBlockingQueue<>(); public final Queue queuedBreaks = new LinkedBlockingQueue<>(); public final PlayerBlockHistory blockHistory = new PlayerBlockHistory(); diff --git a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java index d5fc7a7564..f93dde9a80 100644 --- a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java +++ b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java @@ -106,6 +106,7 @@ public class PointThreeEstimator { private boolean isNearHorizontalFlowingLiquid = false; // We can't calculate the direction, only a toggle private boolean isNearVerticalFlowingLiquid = false; // We can't calculate exact values, once again a toggle private boolean isNearBubbleColumn = false; // We can't calculate exact values once again + public boolean isNearNetherPortal = false; // We can't calculate exact values once again private int maxPositiveLevitation = Integer.MIN_VALUE; // Positive potion effects [0, 128] private int minNegativeLevitation = Integer.MAX_VALUE; // Negative potion effects [-127, -1]r @@ -249,6 +250,7 @@ private void checkNearbyBlocks(SimpleCollisionBox pointThreeBox) { isNearClimbable = false; isNearBubbleColumn = false; isNearFluid = false; + isNearNetherPortal = false; // Check for flowing water Collisions.hasMaterial(player, pointThreeBox, (pair) -> { @@ -270,6 +272,10 @@ private void checkNearbyBlocks(SimpleCollisionBox pointThreeBox) { isNearFluid = true; } + if (state.getType() == StateTypes.NETHER_PORTAL) { + isNearNetherPortal = true; + } + return false; }); } diff --git a/common/src/main/java/ac/grim/grimac/predictionengine/predictions/PredictionEngine.java b/common/src/main/java/ac/grim/grimac/predictionengine/predictions/PredictionEngine.java index e94fef94cc..914fa6b27a 100644 --- a/common/src/main/java/ac/grim/grimac/predictionengine/predictions/PredictionEngine.java +++ b/common/src/main/java/ac/grim/grimac/predictionengine/predictions/PredictionEngine.java @@ -479,52 +479,63 @@ public int sortVectorData(VectorData a, VectorData b, GrimPlayer player) { // Order priority (to avoid false positives and false flagging future predictions): // Knockback and explosions // 0.03 ticks + // Movement without input // Normal movement // First bread knockback and explosions // Flagging groundspoof // Flagging flip items if (a.isExplosion()) - aScore -= 5; + aScore -= 10; if (a.isKnockback()) - aScore -= 5; + aScore -= 10; if (b.isExplosion()) - bScore -= 5; + bScore -= 10; if (b.isKnockback()) - bScore -= 5; + bScore -= 10; if (a.isFirstBreadExplosion()) - aScore += 1; + aScore += 2; if (b.isFirstBreadExplosion()) - bScore += 1; + bScore += 2; if (a.isFirstBreadKb()) - aScore += 1; + aScore += 2; if (b.isFirstBreadKb()) - bScore += 1; + bScore += 2; if (a.isFlipItem()) - aScore += 3; + aScore += 6; if (b.isFlipItem()) - bScore += 3; + bScore += 6; if (a.isZeroPointZeroThree()) - aScore -= 1; + aScore -= 2; if (b.isZeroPointZeroThree()) + bScore -= 2; + + if (a.isWithInput() || a.isJump()) + aScore += 1; + else + aScore -= 1; + + if (b.isWithInput() || b.isJump()) + bScore += 1; + else bScore -= 1; // If the player is on the ground but the vector leads the player off the ground if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vector.getY() >= 0) - aScore += 2; + aScore += 4; if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vector.getY() >= 0) - bScore += 2; + bScore += 4; if (aScore != bScore) return Integer.compare(aScore, bScore); @@ -812,9 +823,9 @@ private void loopVectors(GrimPlayer player, Set possibleVectors, flo continue; for (int strafe = strafeMin; strafe <= strafeMax; strafe++) { for (int forward = forwardMin; forward <= forwardMax; forward++) { - VectorData result = new VectorData(possibleLastTickOutput.vector.clone() + VectorData result = new VectorData.MoveVectorData(possibleLastTickOutput.vector.clone() .add(getMovementResultFromInput(player, transformInputsToVector(player, new Vector3dm(strafe, 0, forward)), speed, player.xRot)), - possibleLastTickOutput, VectorData.VectorType.InputResult); + possibleLastTickOutput, VectorData.VectorType.InputResult, forward, strafe); result = result.returnNewModified(result.vector.clone().multiply(player.stuckSpeedMultiplier), VectorData.VectorType.StuckMultiplier); result = result.returnNewModified(handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable); // Signal that we need to flip sneaking bounding box diff --git a/common/src/main/java/ac/grim/grimac/utils/data/VectorData.java b/common/src/main/java/ac/grim/grimac/utils/data/VectorData.java index aa77492115..3e6541ff69 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/VectorData.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/VectorData.java @@ -6,13 +6,38 @@ import java.util.Objects; public class VectorData { + public static final class MoveVectorData extends VectorData { + public int x; + public int z; + + public MoveVectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType, int x, int z) { + super(vector, lastVector, vectorType); + this.x = x; + this.z = z; + + if (x != 0 || z != 0) { + addVectorType(VectorType.WithInput); + } + } + + public MoveVectorData(Vector3dm vector, VectorType vectorType, int x, int z) { + super(vector, vectorType); + this.x = x; + this.z = z; + + if (x != 0 || z != 0) { + addVectorType(VectorType.WithInput); + } + } + } + public VectorType vectorType; public VectorData lastVector; public VectorData preUncertainty; public Vector3dm vector; @Getter - private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false; + private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false, isWithInput = false; // For handling replacing the type of vector it is while keeping data public VectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType) { @@ -33,6 +58,7 @@ public VectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType isJump = lastVector.isJump; preUncertainty = lastVector.preUncertainty; isAttackSlow = lastVector.isAttackSlow; + isWithInput = lastVector.isWithInput; } addVectorType(vectorType); @@ -78,6 +104,7 @@ public void addVectorType(VectorType type) { case Flip_Use_Item -> isFlipItem = true; case Jump -> isJump = true; case AttackSlow -> isAttackSlow = true; + case WithInput -> isWithInput = true; } } @@ -104,6 +131,7 @@ public enum VectorType { Explosion, FirstBreadExplosion, InputResult, + WithInput, StuckMultiplier, Spectator, Dead, diff --git a/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryDesyncStatus.java b/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryDesyncStatus.java new file mode 100644 index 0000000000..6c6a7761b4 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryDesyncStatus.java @@ -0,0 +1,7 @@ +package ac.grim.grimac.utils.inventory; + +public enum InventoryDesyncStatus { + BEACON, + NETHER_PORTAL, + NOT_DESYNCED +} diff --git a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedInventory.java b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedInventory.java index 889dc0c621..487b855ff2 100644 --- a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedInventory.java +++ b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedInventory.java @@ -48,7 +48,7 @@ public class CompensatedInventory extends Check implements PacketCheck { public boolean isPacketInventoryActive = true; public boolean needResend = false; public int stateID = 0; // Don't mess up the last sent state ID by changing it - int openWindowID = 0; + public int openWindowID = 0; // Special values: // Player inventory is -1 // Unsupported inventory is -2 diff --git a/common/src/main/resources/punishments/de.yml b/common/src/main/resources/punishments/de.yml index 68f4f4d622..3a55d481c3 100644 --- a/common/src/main/resources/punishments/de.yml +++ b/common/src/main/resources/punishments/de.yml @@ -62,6 +62,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/en.yml b/common/src/main/resources/punishments/en.yml index 2deae7d85b..e9c3432acc 100644 --- a/common/src/main/resources/punishments/en.yml +++ b/common/src/main/resources/punishments/en.yml @@ -62,6 +62,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/es.yml b/common/src/main/resources/punishments/es.yml index 2ebf7d4dfc..3c2def82b9 100644 --- a/common/src/main/resources/punishments/es.yml +++ b/common/src/main/resources/punishments/es.yml @@ -62,6 +62,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/fr.yml b/common/src/main/resources/punishments/fr.yml index c53f6d1801..7112d7a2ab 100644 --- a/common/src/main/resources/punishments/fr.yml +++ b/common/src/main/resources/punishments/fr.yml @@ -62,6 +62,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/it.yml b/common/src/main/resources/punishments/it.yml index 28641106a1..c3d134a9fe 100644 --- a/common/src/main/resources/punishments/it.yml +++ b/common/src/main/resources/punishments/it.yml @@ -49,6 +49,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/ja.yml b/common/src/main/resources/punishments/ja.yml index e779c13d7c..18e2ace191 100644 --- a/common/src/main/resources/punishments/ja.yml +++ b/common/src/main/resources/punishments/ja.yml @@ -62,6 +62,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/nl.yml b/common/src/main/resources/punishments/nl.yml index ae05fe930f..b696f32972 100644 --- a/common/src/main/resources/punishments/nl.yml +++ b/common/src/main/resources/punishments/nl.yml @@ -62,6 +62,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/pt.yml b/common/src/main/resources/punishments/pt.yml index 5d5a026cf6..58e9e354e4 100644 --- a/common/src/main/resources/punishments/pt.yml +++ b/common/src/main/resources/punishments/pt.yml @@ -62,6 +62,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/ru.yml b/common/src/main/resources/punishments/ru.yml index e4bd5e6df1..cbba0606f1 100644 --- a/common/src/main/resources/punishments/ru.yml +++ b/common/src/main/resources/punishments/ru.yml @@ -62,6 +62,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/tr.yml b/common/src/main/resources/punishments/tr.yml index 41db78ef87..638bdd90e2 100644 --- a/common/src/main/resources/punishments/tr.yml +++ b/common/src/main/resources/punishments/tr.yml @@ -60,6 +60,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/zh.yml b/common/src/main/resources/punishments/zh.yml index c140b05357..59da6e36e9 100644 --- a/common/src/main/resources/punishments/zh.yml +++ b/common/src/main/resources/punishments/zh.yml @@ -62,6 +62,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: From 6aed9df889a2a2dbc362238891e51ecbb6846efe Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Tue, 29 Apr 2025 23:40:37 -0400 Subject: [PATCH 04/34] - Re-add HitboxBlock and HitboxEntity (renamed to WallHit and EntityPierce) - Reduce unnecessary reach vectors used for calculations in versions --- .../checks/impl/combat/EntityPierce.java | 13 ++ .../grim/grimac/checks/impl/combat/Reach.java | 99 +++++++--- .../grimac/checks/impl/combat/WallHit.java | 13 ++ .../events/packets/CheckManagerListener.java | 6 +- .../ac/grim/grimac/manager/CheckManager.java | 7 +- .../ac/grim/grimac/player/GrimPlayer.java | 43 +++++ .../predictionengine/PointThreeEstimator.java | 4 + .../utils/anticheat/update/BlockPlace.java | 6 +- .../grim/grimac/utils/data/BlockHitData.java | 32 ++++ .../grim/grimac/utils/data/EntityHitData.java | 23 +++ .../ac/grim/grimac/utils/data/HitData.java | 20 +- .../utils/data/ReachInterpolationData.java | 127 +++++++++++++ .../utils/data/packetentity/PacketEntity.java | 9 + .../grimac/utils/nmsutil/WorldRayTrace.java | 177 +++++++++++++++++- common/src/main/resources/punishments/de.yml | 18 ++ common/src/main/resources/punishments/en.yml | 18 ++ common/src/main/resources/punishments/es.yml | 18 ++ common/src/main/resources/punishments/fr.yml | 18 ++ common/src/main/resources/punishments/it.yml | 18 ++ common/src/main/resources/punishments/ja.yml | 18 ++ common/src/main/resources/punishments/nl.yml | 18 ++ common/src/main/resources/punishments/pt.yml | 18 ++ common/src/main/resources/punishments/ru.yml | 18 ++ common/src/main/resources/punishments/tr.yml | 18 ++ common/src/main/resources/punishments/zh.yml | 18 ++ 25 files changed, 719 insertions(+), 58 deletions(-) create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/data/BlockHitData.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/data/EntityHitData.java diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java new file mode 100644 index 0000000000..2a24a3b649 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java @@ -0,0 +1,13 @@ +package ac.grim.grimac.checks.impl.combat; + +import ac.grim.grimac.checks.Check; +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.PacketCheck; +import ac.grim.grimac.player.GrimPlayer; + +@CheckData(name = "Entity Pierce", configName = "EntityPierce", setback = 30) +public class EntityPierce extends Check implements PacketCheck { + public EntityPierce(GrimPlayer player) { + super(player); + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java index c594a441bc..a28e307642 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java @@ -21,10 +21,15 @@ import ac.grim.grimac.checks.type.PacketCheck; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; +import ac.grim.grimac.utils.data.BlockHitData; +import ac.grim.grimac.utils.data.EntityHitData; +import ac.grim.grimac.utils.data.HitData; +import ac.grim.grimac.utils.data.Pair; import ac.grim.grimac.utils.data.packetentity.PacketEntity; import ac.grim.grimac.utils.data.packetentity.dragon.PacketEntityEnderDragonPart; import ac.grim.grimac.utils.math.Vector3dm; import ac.grim.grimac.utils.nmsutil.ReachUtils; +import ac.grim.grimac.utils.nmsutil.WorldRayTrace; import com.github.retrooper.packetevents.event.PacketReceiveEvent; import com.github.retrooper.packetevents.protocol.attribute.Attributes; import com.github.retrooper.packetevents.protocol.entity.type.EntityType; @@ -32,16 +37,17 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.player.ClientVersion; import com.github.retrooper.packetevents.protocol.player.GameMode; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; import com.github.retrooper.packetevents.util.Vector3d; +import com.github.retrooper.packetevents.util.Vector3i; import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; // You may not copy the check unless you are licensed under GPL @CheckData(name = "Reach", setback = 10) @@ -55,8 +61,13 @@ public class Reach extends Check implements PacketCheck { // Only one flag per reach attack, per entity, per tick. // We store position because lastX isn't reliable on teleports. private final Int2ObjectMap playerAttackQueue = new Int2ObjectOpenHashMap<>(); + // temporarily used to prevent falses in the wall hit check + private final Set blocksChangedThisTick = new HashSet<>(); + // extra distance to raytrace beyond player reach distance so we know how far beyond the legit distance a cheater hit + public static final double extraSearchDistance = 3; + private boolean cancelImpossibleHits; - private double threshold; + public double threshold; private double cancelBuffer; // For the next 4 hits after using reach, we aggressively cancel reach public Reach(GrimPlayer player) { @@ -114,8 +125,9 @@ public void onPacketReceive(final PacketReceiveEvent event) { } // If the player set their look, or we know they have a new tick + final boolean isFlying = WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()); if (isUpdate(event.getPacketType())) { - tickBetterReachCheckWithAngle(); + tickBetterReachCheckWithAngle(isFlying); } } @@ -149,7 +161,7 @@ private boolean isKnownInvalid(PacketEntity reachEntity) { } } - private void tickBetterReachCheckWithAngle() { + private void tickBetterReachCheckWithAngle(boolean isFlying) { for (Int2ObjectMap.Entry attack : playerAttackQueue.int2ObjectEntrySet()) { PacketEntity reachEntity = player.compensatedEntities.entityMap.get(attack.getIntKey()); if (reachEntity == null) continue; @@ -164,10 +176,21 @@ private void tickBetterReachCheckWithAngle() { String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey(); player.checkManager.getCheck(Hitboxes.class).flagAndAlert(result.verbose() + added); } + case WALL_HIT -> { + String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey(); + player.checkManager.getCheck(WallHit.class).flagAndAlert(result.verbose() + added); + } + case ENTITY_PIERCE -> { + String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey(); + player.checkManager.getCheck(EntityPierce.class).flagAndAlert(result.verbose() + added); + } } } playerAttackQueue.clear(); + // We can't use transactions for this because of this problem: + // transaction -> block changed applied -> 2nd transaction -> list cleared -> attack packet -> flying -> reach block hit checked, falses + if (isFlying) blocksChangedThisTick.clear(); } @NotNull @@ -196,28 +219,15 @@ private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean double minDistance = Double.MAX_VALUE; - // https://bugs.mojang.com/browse/MC-67665 - List possibleLookDirs = new ArrayList<>(Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot))); - - // If we are a tick behind, we don't know their next look so don't bother doing this - if (!isPrediction) { - possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.yRot)); - - // 1.9+ players could be a tick behind because we don't get skipped ticks - if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) { - possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.lastYRot)); - } - - // 1.7 players do not have any of these issues! They are always on the latest look vector - if (player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) { - possibleLookDirs = Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot)); - } - } + // will store all lookVecsAndEyeHeight pairs that landed a hit on the target entity + // We only need to check for blocking intersections for these + List> lookVecsAndEyeHeights = new ArrayList<>(); final double maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE); // +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox" final double distance = maxReach + 3; final double[] possibleEyeHeights = player.getPossibleEyeHeights(); + final Vector3dm[] possibleLookDirs = player.getPossibleLookVectors(isPrediction); final Vector3dm eyePos = new Vector3dm(from.getX(), 0, from.getZ()); for (Vector3dm lookVec : possibleLookDirs) { for (double eye : possibleEyeHeights) { @@ -233,13 +243,41 @@ private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean if (intercept != null) { minDistance = Math.min(eyePos.distance(intercept), minDistance); + lookVecsAndEyeHeights.add(new Pair<>(lookVec, eye)); } } } + HitData foundHitData = null; + // If the entity is within range of the player (we'll flag anyway if not, so no point checking blocks in this case) + // Ignore when could be hitting through a moving shulker, piston blocks. They are just too glitchy/uncertain to check. + if (minDistance <= distance - extraSearchDistance && !player.compensatedWorld.isNearHardEntity(player.boundingBox.copy().expand(4))) { + // we can optimize didRayTraceHit more to only rayTrace up to the maximize distance of all rays that hit to the target... + // I'm too lazy to do that and we don't need to optimize that much yet so... + final @Nullable Pair hitResult = WorldRayTrace.didRayTraceHit(player, reachEntity, lookVecsAndEyeHeights, from); + HitData hitData = hitResult.second(); + // If the returned hit result was NOT the target entity we flag the check + if (hitData instanceof EntityHitData && + player.compensatedEntities.getPacketEntityID(((EntityHitData) hitData).getEntity()) != player.compensatedEntities.getPacketEntityID(reachEntity)) { + minDistance = Double.MIN_VALUE; + foundHitData = hitData; + // until I fix block modeling exempt any blocks changed this tick + } else if (hitData instanceof BlockHitData && !blocksChangedThisTick.contains(((BlockHitData) hitData).getPosition())) { + minDistance = Double.MIN_VALUE; + foundHitData = hitData; + } + } + // if the entity is not exempt and the entity is alive if ((!blacklisted.contains(reachEntity.getType()) && reachEntity.isLivingEntity()) || reachEntity.getType() == EntityTypes.END_CRYSTAL) { - if (minDistance == Double.MAX_VALUE) { + if (minDistance == Double.MIN_VALUE && foundHitData != null) { + cancelBuffer = 1; + if (foundHitData instanceof BlockHitData) { + return new CheckResult(ResultType.WALL_HIT, "Hit block=" + ((BlockHitData) foundHitData).getState().getType().getName() + " "); + } else { // entity hit data + return new CheckResult(ResultType.ENTITY_PIERCE, "Hit entity=" + ((EntityHitData) foundHitData).getEntity().getType().getName() + " "); + } + } else if (minDistance == Double.MAX_VALUE) { cancelBuffer = 1; return new CheckResult(ResultType.HITBOX, ""); } else if (minDistance > maxReach) { @@ -260,7 +298,7 @@ public void onReload(ConfigManager config) { } private enum ResultType { - REACH, HITBOX, NONE + REACH, HITBOX, WALL_HIT, ENTITY_PIERCE, NONE } private record CheckResult(ResultType type, String verbose) { @@ -268,4 +306,13 @@ public boolean isFlag() { return type != ResultType.NONE; } } + + public void handleBlockChange(Vector3i vector3i, WrappedBlockState state) { + if (blocksChangedThisTick.size() >= 40) return; // Don't let players freeze movement packets to grow this + // Only do this for nearby blocks + if (new Vector3dm(vector3i.x, vector3i.y, vector3i.z).distanceSquared(new Vector3dm(player.x, player.y, player.z)) > 6) return; + // Only do this if the state really had any world impact + if (state.equals(player.compensatedWorld.getBlock(vector3i))) return; + blocksChangedThisTick.add(vector3i); + } } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java new file mode 100644 index 0000000000..f61279e1b2 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java @@ -0,0 +1,13 @@ +package ac.grim.grimac.checks.impl.combat; + +import ac.grim.grimac.checks.Check; +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.PacketCheck; +import ac.grim.grimac.player.GrimPlayer; + +@CheckData(name = "Wall Hit", configName = "WallHit", setback = 20) +public class WallHit extends Check implements PacketCheck { + public WallHit(GrimPlayer player) { + super(player); + } +} diff --git a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java index 066f38f527..837fcb0056 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java @@ -66,7 +66,7 @@ public CheckManagerListener() { } private static void placeWaterLavaSnowBucket(GrimPlayer player, ItemStack held, StateType toPlace, InteractionHand hand, int sequence) { - HitData data = WorldRayTrace.getNearestBlockHitResult(player, StateTypes.AIR, false, true, true); + BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, StateTypes.AIR, false, true, true); if (data != null) { BlockPlace blockPlace = new BlockPlace(player, hand, data.getPosition(), data.getClosestDirection().getFaceValue(), data.getClosestDirection(), held, data, sequence); @@ -282,7 +282,7 @@ private static void handleBlockPlaceOrUseItem(PacketWrapper packet, GrimPlaye } private static void placeBucket(GrimPlayer player, InteractionHand hand, int sequence) { - HitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true); + BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true); if (data != null) { BlockPlace blockPlace = new BlockPlace(player, hand, data.getPosition(), data.getClosestDirection().getFaceValue(), data.getClosestDirection(), ItemStack.EMPTY, data, sequence); @@ -361,7 +361,7 @@ public static void setPlayerItem(GrimPlayer player, InteractionHand hand, ItemTy } private static void placeLilypad(GrimPlayer player, InteractionHand hand, int sequence) { - HitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true); + BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true); if (data != null) { // A lilypad cannot replace a fluid diff --git a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java index e46fa33e1b..50c57dbb5f 100644 --- a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java @@ -16,10 +16,7 @@ import ac.grim.grimac.checks.impl.breaking.PositionBreakB; import ac.grim.grimac.checks.impl.breaking.RotationBreak; import ac.grim.grimac.checks.impl.breaking.WrongBreak; -import ac.grim.grimac.checks.impl.combat.Hitboxes; -import ac.grim.grimac.checks.impl.combat.MultiInteractA; -import ac.grim.grimac.checks.impl.combat.MultiInteractB; -import ac.grim.grimac.checks.impl.combat.Reach; +import ac.grim.grimac.checks.impl.combat.*; import ac.grim.grimac.checks.impl.crash.*; import ac.grim.grimac.checks.impl.elytra.ElytraA; import ac.grim.grimac.checks.impl.elytra.ElytraB; @@ -295,6 +292,8 @@ public CheckManager(GrimPlayer player) { .put(TransactionOrder.class, new TransactionOrder(player)) .put(VehicleC.class, new VehicleC(player)) .put(Hitboxes.class, new Hitboxes(player)) // Hitboxes is invoked by Reach + .put(WallHit.class, new WallHit(player)) + .put(EntityPierce.class, new EntityPierce(player)) .build(); allChecks = new ImmutableClassToInstanceMap.Builder() diff --git a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java index b062e6f042..c9453d9c2e 100644 --- a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java +++ b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java @@ -46,6 +46,7 @@ import ac.grim.grimac.utils.math.Vector3dm; import ac.grim.grimac.utils.nmsutil.BlockProperties; import ac.grim.grimac.utils.nmsutil.GetBoundingBox; +import ac.grim.grimac.utils.nmsutil.ReachUtils; import ac.grim.grimac.utils.reflection.ViaVersionUtil; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.event.PacketSendEvent; @@ -647,6 +648,48 @@ public double[] getPossibleEyeHeights() { // We don't return sleeping eye height } } + // 1.8-1.10.2 specific mouse delay fix (MC-67665) + // https://bugs.mojang.com/browse/MC-67665 + // 1.9-1.21.1 specific desync due to skipped ticks + // Players can be a tick behind on both pitch and yaw together + // 1.21.2+ added end tick input packet, fixing skipped tick issues + public Vector3dm[] getPossibleLookVectors(boolean isPrediction) { + // If we are a tick behind, we don't know their next look so only use current + if (isPrediction) { + return new Vector3dm[]{ReachUtils.getLook(this, this.xRot, this.yRot)}; + } + + // 1.9-1.10.2: All three vectors (normal, mouse delay, tick desync) + if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) && + this.getClientVersion().isOlderThan(ClientVersion.V_1_11)) { + return new Vector3dm[]{ + ReachUtils.getLook(this, this.xRot, this.yRot), + ReachUtils.getLook(this, this.lastXRot, this.yRot), + ReachUtils.getLook(this, this.lastXRot, this.lastYRot) + }; + } + // 1.11-1.21.1: Two vectors (normal, tick desync) + else if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_11) && + this.getClientVersion().isOlderThan(ClientVersion.V_1_21_2)) { + return new Vector3dm[]{ + ReachUtils.getLook(this, this.xRot, this.yRot), + ReachUtils.getLook(this, this.lastXRot, this.lastYRot) + }; + } + // 1.8: Two vectors (normal, mouse delay) + else if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8) && + this.getClientVersion().isOlderThan(ClientVersion.V_1_9)) { + return new Vector3dm[]{ + ReachUtils.getLook(this, this.xRot, this.yRot), + ReachUtils.getLook(this, this.lastXRot, this.yRot) + }; + } + // 1.7 and 1.21.2+: Just normal vector + else { + return new Vector3dm[]{ReachUtils.getLook(this, this.xRot, this.yRot)}; + } + } + @Override public int getTransactionPing() { return GrimMath.floor(transactionPing / 1e6); diff --git a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java index f93dde9a80..dcc1472a20 100644 --- a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java +++ b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java @@ -1,5 +1,6 @@ package ac.grim.grimac.predictionengine; +import ac.grim.grimac.checks.impl.combat.Reach; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.predictionengine.predictions.PredictionEngine; import ac.grim.grimac.utils.collisions.CollisionData; @@ -20,6 +21,7 @@ import com.github.retrooper.packetevents.protocol.world.states.defaulttags.BlockTags; import com.github.retrooper.packetevents.protocol.world.states.type.StateType; import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; +import com.github.retrooper.packetevents.util.Vector3i; import lombok.Getter; import lombok.Setter; @@ -155,6 +157,8 @@ public void handleChangeBlock(int x, int y, int z, WrappedBlockState state) { isNearFluid = true; } + player.checkManager.getPacketCheck(Reach.class).handleBlockChange(new Vector3i(x, y, z), state); + if (pointThreeBox.isIntersected(new SimpleCollisionBox(x, y, z))) { // https://github.com/MWHunter/Grim/issues/613 int controllingEntityId = player.inVehicle() ? player.getRidingVehicleId() : player.entityID; diff --git a/common/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockPlace.java b/common/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockPlace.java index 021656ee7c..f4bfc26c25 100644 --- a/common/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockPlace.java +++ b/common/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockPlace.java @@ -9,7 +9,7 @@ import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; import ac.grim.grimac.utils.collisions.datatypes.ComplexCollisionBox; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; -import ac.grim.grimac.utils.data.HitData; +import ac.grim.grimac.utils.data.BlockHitData; import ac.grim.grimac.utils.data.packetentity.PacketEntity; import ac.grim.grimac.utils.latency.CompensatedWorld; import ac.grim.grimac.utils.math.GrimMath; @@ -69,7 +69,7 @@ public class BlockPlace { @Getter StateType material; @Getter - @Nullable HitData hitData; + @Nullable BlockHitData hitData; @Getter int faceId; BlockFace face; @@ -81,7 +81,7 @@ public class BlockPlace { Vector3f cursor; public final int sequence; - public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i blockPosition, int faceId, BlockFace face, ItemStack itemStack, HitData hitData, int sequence) { + public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i blockPosition, int faceId, BlockFace face, ItemStack itemStack, BlockHitData hitData, int sequence) { this.player = player; this.hand = hand; this.blockPosition = blockPosition; diff --git a/common/src/main/java/ac/grim/grimac/utils/data/BlockHitData.java b/common/src/main/java/ac/grim/grimac/utils/data/BlockHitData.java new file mode 100644 index 0000000000..78fc4c128f --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/data/BlockHitData.java @@ -0,0 +1,32 @@ +package ac.grim.grimac.utils.data; + +import ac.grim.grimac.utils.math.Vector3dm; +import com.github.retrooper.packetevents.protocol.world.BlockFace; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; +import com.github.retrooper.packetevents.util.Vector3d; +import com.github.retrooper.packetevents.util.Vector3i; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class BlockHitData extends HitData { + Vector3i position; + WrappedBlockState state; + BlockFace closestDirection; +// public Boolean success; + + public BlockHitData(Vector3i position, Vector3dm blockHitLocation, BlockFace closestDirection, WrappedBlockState state +// , Boolean success + ) { + super(blockHitLocation); + this.position = position; + this.closestDirection = closestDirection; + this.state = state; +// this.success = success; + } + + public Vector3d getRelativeBlockHitLocation() { + return new Vector3d(blockHitLocation.getX() - position.getX(), blockHitLocation.getY() - position.getY(), blockHitLocation.getZ() - position.getZ()); + } +} diff --git a/common/src/main/java/ac/grim/grimac/utils/data/EntityHitData.java b/common/src/main/java/ac/grim/grimac/utils/data/EntityHitData.java new file mode 100644 index 0000000000..addb7e95f0 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/data/EntityHitData.java @@ -0,0 +1,23 @@ +package ac.grim.grimac.utils.data; + +import ac.grim.grimac.utils.data.packetentity.PacketEntity; +import ac.grim.grimac.utils.math.Vector3dm; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class EntityHitData extends HitData { + private final PacketEntity entity; + + public EntityHitData(PacketEntity packetEntity) { + this(packetEntity, new Vector3dm(packetEntity.trackedServerPosition.getPos().x, + packetEntity.trackedServerPosition.getPos().y, + packetEntity.trackedServerPosition.getPos().z)); + } + + public EntityHitData(PacketEntity packetEntity, Vector3dm intersectionPoint) { + super(intersectionPoint); // Use actual intersection point + this.entity = packetEntity; + } +} diff --git a/common/src/main/java/ac/grim/grimac/utils/data/HitData.java b/common/src/main/java/ac/grim/grimac/utils/data/HitData.java index 8f8effd875..449f8fb134 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/HitData.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/HitData.java @@ -1,29 +1,15 @@ package ac.grim.grimac.utils.data; import ac.grim.grimac.utils.math.Vector3dm; -import com.github.retrooper.packetevents.protocol.world.BlockFace; -import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; -import com.github.retrooper.packetevents.util.Vector3d; -import com.github.retrooper.packetevents.util.Vector3i; -import lombok.Getter; -import lombok.ToString; -@Getter -@ToString public class HitData { - Vector3i position; Vector3dm blockHitLocation; - WrappedBlockState state; - BlockFace closestDirection; - public HitData(Vector3i position, Vector3dm blockHitLocation, BlockFace closestDirection, WrappedBlockState state) { - this.position = position; + public HitData(Vector3dm blockHitLocation) { this.blockHitLocation = blockHitLocation; - this.closestDirection = closestDirection; - this.state = state; } - public Vector3d getRelativeBlockHitLocation() { - return new Vector3d(blockHitLocation.getX() - position.getX(), blockHitLocation.getY() - position.getY(), blockHitLocation.getZ() - position.getZ()); + public ac.grim.grimac.utils.math.Vector3dm getBlockHitLocation() { + return this.blockHitLocation; } } diff --git a/common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java b/common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java index 8110030328..08798ef932 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java @@ -196,6 +196,133 @@ public SimpleCollisionBox getPossibleHitboxCombined() { return minimumInterpLocation; } + public CollisionBox getOverlapHitboxCombined() { + int interpSteps = getInterpolationSteps(); + + // Calculate step increments for each axis + double stepMinX = (targetLocation.minX - startingLocation.minX) / (double) interpSteps; + double stepMaxX = (targetLocation.maxX - startingLocation.maxX) / (double) interpSteps; + double stepMinY = (targetLocation.minY - startingLocation.minY) / (double) interpSteps; + double stepMaxY = (targetLocation.maxY - startingLocation.maxY) / (double) interpSteps; + double stepMinZ = (targetLocation.minZ - startingLocation.minZ) / (double) interpSteps; + double stepMaxZ = (targetLocation.maxZ - startingLocation.maxZ) / (double) interpSteps; + + // Track the intersection of all expanded hitboxes + double overallMinX = Double.NEGATIVE_INFINITY; + double overallMaxX = Double.POSITIVE_INFINITY; + double overallMinY = Double.NEGATIVE_INFINITY; + double overallMaxY = Double.POSITIVE_INFINITY; + double overallMinZ = Double.NEGATIVE_INFINITY; + double overallMaxZ = Double.POSITIVE_INFINITY; + + boolean isFirstStep = true; + + for (int step = interpolationStepsLowBound; step <= interpolationStepsHighBound; step++) { + // Compute interpolated position for this step + double currentMinX = startingLocation.minX + (step * stepMinX); + double currentMaxX = startingLocation.maxX + (step * stepMaxX); + double currentMinY = startingLocation.minY + (step * stepMinY); + double currentMaxY = startingLocation.maxY + (step * stepMaxY); + double currentMinZ = startingLocation.minZ + (step * stepMinZ); + double currentMaxZ = startingLocation.maxZ + (step * stepMaxZ); + + // Create the collision box for this step's position + // Create boxes for each bottom corner + SimpleCollisionBox[] cornerBoxes = new SimpleCollisionBox[4]; + + // Bottom corners: (minX,minY,minZ), (maxX,minY,minZ), (minX,minY,maxZ), (maxX,minY,maxZ) + cornerBoxes[0] = new SimpleCollisionBox(currentMinX, currentMinY, currentMinZ, + currentMinX, currentMinY, currentMinZ); + cornerBoxes[1] = new SimpleCollisionBox(currentMaxX, currentMinY, currentMinZ, + currentMaxX, currentMinY, currentMinZ); + cornerBoxes[2] = new SimpleCollisionBox(currentMinX, currentMinY, currentMaxZ, + currentMinX, currentMinY, currentMaxZ); + cornerBoxes[3] = new SimpleCollisionBox(currentMaxX, currentMinY, currentMaxZ, + currentMaxX, currentMinY, currentMaxZ); + + // Expand each corner box by entity dimensions + for (SimpleCollisionBox cornerBox : cornerBoxes) { + GetBoundingBox.expandBoundingBoxByEntityDimensions(cornerBox, player, entity); + } + + // Get the overlap of the 4 corner boxes + CollisionBox stepOverlap = getOverlapOfBoxes(cornerBoxes); + if (stepOverlap == NoCollisionBox.INSTANCE) + return NoCollisionBox.INSTANCE; + SimpleCollisionBox stepBox = (SimpleCollisionBox) stepOverlap; + + // Initialize overall bounds with the first expanded box + if (isFirstStep) { + overallMinX = stepBox.minX; + overallMaxX = stepBox.maxX; + overallMinY = stepBox.minY; + overallMaxY = stepBox.maxY; + overallMinZ = stepBox.minZ; + overallMaxZ = stepBox.maxZ; + isFirstStep = false; + } else { + // Update bounds to the intersection of all expanded boxes + overallMinX = Math.max(overallMinX, stepBox.minX); + overallMaxX = Math.min(overallMaxX, stepBox.maxX); + overallMinY = Math.max(overallMinY, stepBox.minY); + overallMaxY = Math.min(overallMaxY, stepBox.maxY); + overallMinZ = Math.max(overallMinZ, stepBox.minZ); + overallMaxZ = Math.min(overallMaxZ, stepBox.maxZ); + } + + // Early exit if the intersection becomes empty + if (overallMinX > overallMaxX || overallMinY > overallMaxY || overallMinZ > overallMaxZ) { + return NoCollisionBox.INSTANCE; + } + } + + // Check if the final intersection is valid + if (overallMinX > overallMaxX || overallMinY > overallMaxY || overallMinZ > overallMaxZ) { + return NoCollisionBox.INSTANCE; + } + + return new SimpleCollisionBox( + overallMinX, overallMinY, overallMinZ, + overallMaxX, overallMaxY, overallMaxZ + ); + } + + private CollisionBox getOverlapOfBoxes(SimpleCollisionBox[] boxes) { + double minX = Double.NEGATIVE_INFINITY; + double maxX = Double.POSITIVE_INFINITY; + double minY = Double.NEGATIVE_INFINITY; + double maxY = Double.POSITIVE_INFINITY; + double minZ = Double.NEGATIVE_INFINITY; + double maxZ = Double.POSITIVE_INFINITY; + + boolean first = true; + + for (SimpleCollisionBox box : boxes) { + if (first) { + minX = box.minX; + maxX = box.maxX; + minY = box.minY; + maxY = box.maxY; + minZ = box.minZ; + maxZ = box.maxZ; + first = false; + } else { + minX = Math.max(minX, box.minX); + maxX = Math.min(maxX, box.maxX); + minY = Math.max(minY, box.minY); + maxY = Math.min(maxY, box.maxY); + minZ = Math.max(minZ, box.minZ); + maxZ = Math.min(maxZ, box.maxZ); + } + + if (minX > maxX || minY > maxY || minZ > maxZ) { + return NoCollisionBox.INSTANCE; + } + } + + return new SimpleCollisionBox(minX, minY, minZ, maxX, maxY, maxZ); + } + public void updatePossibleStartingLocation(SimpleCollisionBox possibleLocationCombined) { //GrimACBukkitLoaderPlugin.staticGetLogger().info(ChatColor.BLUE + "Updated new starting location as second trans hasn't arrived " + startingLocation); this.startingLocation = combineCollisionBox(startingLocation, possibleLocationCombined); diff --git a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java index ca80c9f25e..85e86d261c 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java @@ -16,6 +16,7 @@ package ac.grim.grimac.utils.data.packetentity; import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; import ac.grim.grimac.utils.data.ReachInterpolationData; import ac.grim.grimac.utils.data.TrackedPosition; @@ -213,6 +214,14 @@ public SimpleCollisionBox getPossibleCollisionBoxes() { return ReachInterpolationData.combineCollisionBox(oldPacketLocation.getPossibleHitboxCombined(), newPacketLocation.getPossibleHitboxCombined()); } + public CollisionBox getMinimumPossibleCollisionBoxes() { + if (oldPacketLocation == null) { + return newPacketLocation.getOverlapHitboxCombined(); + } + + return ReachInterpolationData.getOverlapHitbox(oldPacketLocation.getOverlapHitboxCombined(), newPacketLocation.getOverlapHitboxCombined()); + } + public PacketEntity getRiding() { return riding; } diff --git a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java index ad8e6b1012..3cc74ac0ed 100644 --- a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java +++ b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java @@ -1,12 +1,19 @@ package ac.grim.grimac.utils.nmsutil; +import ac.grim.grimac.checks.impl.combat.Reach; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.collisions.CollisionData; import ac.grim.grimac.utils.collisions.HitboxData; import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.ComplexCollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; +import ac.grim.grimac.utils.data.BlockHitData; +import ac.grim.grimac.utils.data.EntityHitData; import ac.grim.grimac.utils.data.HitData; import ac.grim.grimac.utils.data.Pair; +import ac.grim.grimac.utils.data.packetentity.PacketEntity; +import ac.grim.grimac.utils.data.packetentity.TypedPacketEntity; import ac.grim.grimac.utils.math.GrimMath; import ac.grim.grimac.utils.math.Vector3dm; import com.github.retrooper.packetevents.protocol.attribute.Attributes; @@ -16,13 +23,15 @@ import com.github.retrooper.packetevents.protocol.world.states.type.StateType; import com.github.retrooper.packetevents.util.Vector3d; import com.github.retrooper.packetevents.util.Vector3i; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; public class WorldRayTrace { - public static HitData getNearestBlockHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) { + public static BlockHitData getNearestBlockHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) { Vector3d startingPos = new Vector3d(player.x, player.y + player.getEyeHeight(), player.z); Vector3dm startingVec = new Vector3dm(startingPos.getX(), startingPos.getY(), startingPos.getZ()); Ray trace = new Ray(player, startingPos.getX(), startingPos.getY(), startingPos.getZ(), player.xRot, player.yRot); @@ -57,7 +66,7 @@ public static HitData getNearestBlockHitResult(GrimPlayer player, StateType held } } if (bestHitLoc != null) { - return new HitData(vector3i, bestHitLoc, bestFace, block); + return new BlockHitData(vector3i, bestHitLoc, bestFace, block); } if (sourcesHaveHitbox && @@ -70,7 +79,7 @@ public static HitData getNearestBlockHitResult(GrimPlayer player, StateType held Pair intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance)); if (intercept.first() != null) { - return new HitData(vector3i, intercept.first(), intercept.second(), block); + return new BlockHitData(vector3i, intercept.first(), intercept.second(), block); } } @@ -83,7 +92,7 @@ public static HitData getNearestBlockHitResult(GrimPlayer player, StateType held // // I do have to admit that I'm starting to like bifunctions/new java 8 things more than I originally did. // although I still don't understand Mojang's obsession with streams in some of the hottest methods... that kills performance - public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, BiFunction predicate) { + public static BlockHitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, BiFunction predicate) { // I guess go back by the collision epsilon? double endX = GrimMath.lerp(-1.0E-7D, end.x, start.x); double endY = GrimMath.lerp(-1.0E-7D, end.y, start.y); @@ -99,7 +108,7 @@ public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d if (start.equals(end)) return null; WrappedBlockState state = player.compensatedWorld.getBlock(floorStartX, floorStartY, floorStartZ); - HitData apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ)); + BlockHitData apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ)); if (apply != null) { return apply; @@ -148,4 +157,162 @@ public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d return null; } + + @Nullable + public static HitData getNearestHitResult(GrimPlayer player, PacketEntity targetEntity, Vector3dm eyePos, Vector3dm lookVec) { + + double maxAttackDistance = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE); + double maxBlockDistance = player.compensatedEntities.self.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE); + + Vector3d startingPos = new Vector3d(eyePos.getX(), eyePos.getY(), eyePos.getZ()); + Vector3dm startingVec = new Vector3dm(startingPos.getX(), startingPos.getY(), startingPos.getZ()); + Ray trace = new Ray(eyePos, lookVec); + Vector3dm endVec = trace.getPointAtDistance(maxBlockDistance); + Vector3d endPos = new Vector3d(endVec.getX(), endVec.getY(), endVec.getZ()); + + // Get block hit + BlockHitData blockHitData = getTraverseResult(player, null, startingPos, startingVec, trace, endPos, false, true, maxBlockDistance, true); + Vector3dm closestHitVec = null; + PacketEntity closestEntity = null; + double closestDistanceSquared = blockHitData != null ? blockHitData.getBlockHitLocation().distanceSquared(startingVec) : maxAttackDistance * maxAttackDistance; + + for (PacketEntity entity : player.compensatedEntities.entityMap.values().stream().filter(TypedPacketEntity::canHit).toList()) { + SimpleCollisionBox box = null; + // 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers) + // Yes, this is vanilla and not uncertainty. All reach checks have this or they are wrong. + + if (entity.equals(targetEntity)) { + box = entity.getPossibleCollisionBoxes(); + box.expand(player.checkManager.getPacketCheck(Reach.class).threshold); + // This is better than adding to the reach, as 0.03 can cause a player to miss their target + // Adds some more than 0.03 uncertainty in some cases, but a good trade off for simplicity + // + // Just give the uncertainty on 1.9+ clients as we have no way of knowing whether they had 0.03 movement + if (!player.packetStateData.didLastLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) + box.expand(player.getMovementThreshold()); + if (ReachUtils.isVecInside(box, eyePos)) { + return new EntityHitData(entity, eyePos); + } + } else { + CollisionBox b = entity.getMinimumPossibleCollisionBoxes(); + if (b instanceof NoCollisionBox) { + continue; + } + box = (SimpleCollisionBox) b; + box.expand(-player.checkManager.getPacketCheck(Reach.class).threshold); + // todo, shrink by reachThreshold as well for non-target entities? + if (!player.packetStateData.didLastLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) + box.expand(-player.getMovementThreshold()); + } + if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) { + box.expand(0.1f); + } + + + Pair intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(Math.sqrt(closestDistanceSquared))); + + if (intercept.first() != null) { + double distSquared = intercept.first().distanceSquared(startingVec); + if (distSquared < closestDistanceSquared) { + closestDistanceSquared = distSquared; + closestHitVec = intercept.first(); + closestEntity = entity; + } + } + } + + return closestEntity == null ? blockHitData : new EntityHitData(closestEntity, closestHitVec); + } + + // Checks if it was possible to hit a target entity + // TODO refactor to return list of rays and why each of them didn't hit instead of closest obstruction + // NOTE: It should be impossible for the returned Pair to be null + // because all of the possibleLookVecsAndEyeHeights passed in should be ones that hit the target entity + // in previous parts of this check when we didn't check for any obstructions like blocks/entities + public static @NotNull Pair<@NotNull Double, @NotNull HitData> didRayTraceHit(GrimPlayer player, PacketEntity targetEntity, + List> possibleLookVecsAndEyeHeights, + Vector3d from) { + HitData firstObstruction = null; + double firstObstructionDistanceSq = 0; + + // Check every possible look direction and every possible eye height + for (Pair vectorDoublePair : possibleLookVecsAndEyeHeights) { + Vector3dm lookVec = vectorDoublePair.first(); + double eye = vectorDoublePair.second(); + + Vector3dm eyes = new Vector3dm(from.getX(), from.getY() + eye, from.getZ()); + // this function is completely 0.03 aware + final HitData hitResult = WorldRayTrace.getNearestHitResult(player, targetEntity, eyes, lookVec); + + // If we hit the target entity, it's a valid hit + if (hitResult instanceof EntityHitData && ((EntityHitData) hitResult).getEntity().equals(targetEntity)) { + double distanceSquared = eyes.distanceSquared(hitResult.getBlockHitLocation()); + return new Pair<>(distanceSquared, hitResult); // Legitimate hit + } else if (hitResult != null && firstObstruction == null) { + // Store the first obstruction only + firstObstruction = hitResult; + firstObstructionDistanceSq = eyes.distanceSquared(hitResult.getBlockHitLocation()); + } + } + + // Return the first obstruction if no valid hit found + // Since we sort eye heights by likeniness, we should in effect return the most likely (first) obstruction + assert firstObstruction != null; + return new Pair<>(firstObstructionDistanceSq, firstObstruction); + } + + // TODO replace shrinkBlocks boolean with a data structure/better way to represent + // 1. We have a target block. Shrink everything by movementThreshold except expand target block (we are checking to see if it matches the target block) + // 2. We do not have a target block. Shrink everything by movementThreshold() + // 3. Do not expand or shrink everything, we do not expect 0.03/0.002 or we legacy example where we want to keep old behaviour + private static BlockHitData getTraverseResult(GrimPlayer player, @Nullable StateType heldItem, Vector3d startingPos, Vector3dm startingVec, Ray trace, Vector3d endPos, boolean sourcesHaveHitbox, boolean checkInside, double knownDistance, boolean shrinkBlocks) { + return traverseBlocks(player, startingPos, endPos, (block, vector3i) -> { + // even though sometimes we are raytracing against a block that is the target block, we pass false to this function because it only applies a change for brewing stands in 1.8 + CollisionBox data = HitboxData.getBlockHitbox(player, heldItem, player.getClientVersion(), block, false, vector3i.getX(), vector3i.getY(), vector3i.getZ()); + SimpleCollisionBox[] boxes = new SimpleCollisionBox[ComplexCollisionBox.DEFAULT_MAX_COLLISION_BOX_SIZE]; + int size = data.downCast(boxes); + + double bestHitResult = Double.MAX_VALUE; + Vector3dm bestHitLoc = null; + BlockFace bestFace = null; + + for (int i = 0; i < size; i++) { + if (shrinkBlocks) boxes[i].expand(-player.getMovementThreshold()); + Pair intercept = ReachUtils.calculateIntercept(boxes[i], trace.getOrigin(), trace.getPointAtDistance(knownDistance)); + if (intercept.first() == null) continue; // No intercept + + Vector3dm hitLoc = intercept.first(); + + // If inside a block, return empty result for reach check (don't bother checking this?) + if (checkInside && ReachUtils.isVecInside(boxes[i], trace.getOrigin())) { + return null; + } + + if (hitLoc.distanceSquared(startingVec) < bestHitResult) { + bestHitResult = hitLoc.distanceSquared(startingVec); + bestHitLoc = hitLoc; + bestFace = intercept.second(); + } + } + + if (bestHitLoc != null) { + return new BlockHitData(vector3i, bestHitLoc, bestFace, block); + } + + if (sourcesHaveHitbox && + (player.compensatedWorld.isWaterSourceBlock(vector3i.getX(), vector3i.getY(), vector3i.getZ()) + || player.compensatedWorld.getLavaFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ()) == (8 / 9f))) { + double waterHeight = player.compensatedWorld.getFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ()); + SimpleCollisionBox box = new SimpleCollisionBox(vector3i.getX(), vector3i.getY(), vector3i.getZ(), vector3i.getX() + 1, vector3i.getY() + waterHeight, vector3i.getZ() + 1); + + Pair intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(knownDistance)); + + if (intercept.first() != null) { + return new BlockHitData(vector3i, intercept.first(), intercept.second(), block); + } + } + + return null; + }); + } } diff --git a/common/src/main/resources/punishments/de.yml b/common/src/main/resources/punishments/de.yml index 3a55d481c3..c63cf4e042 100644 --- a/common/src/main/resources/punishments/de.yml +++ b/common/src/main/resources/punishments/de.yml @@ -89,6 +89,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/en.yml b/common/src/main/resources/punishments/en.yml index e9c3432acc..3a9c3b7ff1 100644 --- a/common/src/main/resources/punishments/en.yml +++ b/common/src/main/resources/punishments/en.yml @@ -89,6 +89,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/es.yml b/common/src/main/resources/punishments/es.yml index 3c2def82b9..ce609be7d8 100644 --- a/common/src/main/resources/punishments/es.yml +++ b/common/src/main/resources/punishments/es.yml @@ -89,6 +89,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/fr.yml b/common/src/main/resources/punishments/fr.yml index 7112d7a2ab..d719aeaf46 100644 --- a/common/src/main/resources/punishments/fr.yml +++ b/common/src/main/resources/punishments/fr.yml @@ -89,6 +89,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/it.yml b/common/src/main/resources/punishments/it.yml index c3d134a9fe..51ad464015 100644 --- a/common/src/main/resources/punishments/it.yml +++ b/common/src/main/resources/punishments/it.yml @@ -76,6 +76,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/ja.yml b/common/src/main/resources/punishments/ja.yml index 18e2ace191..362f1c2a0b 100644 --- a/common/src/main/resources/punishments/ja.yml +++ b/common/src/main/resources/punishments/ja.yml @@ -89,6 +89,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/nl.yml b/common/src/main/resources/punishments/nl.yml index b696f32972..99e5aad171 100644 --- a/common/src/main/resources/punishments/nl.yml +++ b/common/src/main/resources/punishments/nl.yml @@ -89,6 +89,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/pt.yml b/common/src/main/resources/punishments/pt.yml index 58e9e354e4..887bac4389 100644 --- a/common/src/main/resources/punishments/pt.yml +++ b/common/src/main/resources/punishments/pt.yml @@ -89,6 +89,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/ru.yml b/common/src/main/resources/punishments/ru.yml index cbba0606f1..459d3c66ce 100644 --- a/common/src/main/resources/punishments/ru.yml +++ b/common/src/main/resources/punishments/ru.yml @@ -89,6 +89,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/tr.yml b/common/src/main/resources/punishments/tr.yml index 638bdd90e2..e04443b91d 100644 --- a/common/src/main/resources/punishments/tr.yml +++ b/common/src/main/resources/punishments/tr.yml @@ -87,6 +87,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/zh.yml b/common/src/main/resources/punishments/zh.yml index 59da6e36e9..c362690607 100644 --- a/common/src/main/resources/punishments/zh.yml +++ b/common/src/main/resources/punishments/zh.yml @@ -89,6 +89,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: From a8bf6ac1e1e911f01bc1bbc046fd7ef7ef0e66ad Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:17:22 -0400 Subject: [PATCH 05/34] Add support for Hitbox Debugging --- .../checks/debug/AbstractDebugHandler.java | 2 +- .../checks/debug/HitboxDebugHandler.java | 206 ++++++++++++++++++ .../grim/grimac/checks/impl/combat/Reach.java | 62 ++++++ .../checks/impl/prediction/DebugHandler.java | 10 +- .../grimac/command/commands/GrimDebug.java | 50 +++++ .../ac/grim/grimac/manager/CheckManager.java | 2 + 6 files changed, 329 insertions(+), 3 deletions(-) create mode 100644 common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java diff --git a/common/src/main/java/ac/grim/grimac/checks/debug/AbstractDebugHandler.java b/common/src/main/java/ac/grim/grimac/checks/debug/AbstractDebugHandler.java index eb4a1ee92f..7f344f01c1 100644 --- a/common/src/main/java/ac/grim/grimac/checks/debug/AbstractDebugHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/debug/AbstractDebugHandler.java @@ -16,7 +16,7 @@ public GrimPlayer getPlayer() { return grimPlayer; } - public abstract void toggleListener(GrimPlayer player); + public abstract boolean toggleListener(GrimPlayer player); public abstract boolean toggleConsoleOutput(); } diff --git a/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java b/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java new file mode 100644 index 0000000000..05177b2ab0 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java @@ -0,0 +1,206 @@ +package ac.grim.grimac.checks.debug; + +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; +import ac.grim.grimac.utils.data.Pair; +import ac.grim.grimac.utils.math.Vector3dm; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Handles debug visualization of hitboxes and reach calculations for GrimAC. + * Sends debug data to clients through plugin messages that can be visualized by compatible clients. + */ +public class HitboxDebugHandler extends AbstractDebugHandler { + + /** + * Set of players currently listening to hitbox debug data + */ + private final Set listeners = new CopyOnWriteArraySet<>(new HashSet<>()); + + /** + * Creates a new HitboxDebugHandler for the specified player + * + * @param grimPlayer The GrimAC player instance to debug + */ + public HitboxDebugHandler(GrimPlayer grimPlayer) { + super(grimPlayer); + } + + /** + * Toggles whether a player receives hitbox debug visualization data. + * If the player is already listening, they will be removed. If not, they will be added. + * + * @param player The player to toggle debug visualization for + * @return {@code true} if the player is now listening (was added), + * {@code false} if the player is no longer listening (was removed). + */ + @Override + public boolean toggleListener(GrimPlayer player) { + boolean wasPresent = listeners.remove(player); + + if (wasPresent) { + return false; + } else { + listeners.add(player); + return true; + } + } + + @Override + public boolean toggleConsoleOutput() { + throw new UnsupportedOperationException(); + } + + /** + * Sends debug visualization data for reach calculations to all listening players. + * The data includes hitboxes, target entities, look vectors, and eye heights used in reach calculations. + * + * @param hitboxes Map of entity IDs to their collision boxes + * @param targetEntities Set of entity IDs that are considered targets + * @param lookVecsAndEyeHeights List of pairs containing look vectors and their corresponding eye heights + * @param basePos Base position before eye height adjustments + * @param isPrediction Whether these hitboxes are from a prediction calculation + * + * Packet Format (Version 1): + * - Byte: Version (0) + * - Byte: Global flags + * - Bit 0: isPrediction + * - Bits 1-7: Reserved + * - Double: Player reach/interact distance + * - Vector3d: Base position (3 doubles) + * - VarInt: Number of ray traces + * - For each ray trace: + * - Double: Eye height delta + * - Vector3d: Look vector (3 doubles) + * - VarInt: Number of hitboxes + * - For each hitbox: + * - VarInt: Entity ID + * - Byte: Flags + * - Bit 0: Is target entity + * - Bit 1: Is no collision + * - Bits 2-7: Reserved + * - If not NoCollisionBox: + * - Double: minX + * - Double: minY + * - Double: minZ + * - Double: maxX + * - Double: maxY + * - Double: maxZ + */ + public void sendHitboxData(Map hitboxes, Set targetEntities, + List> lookVecsAndEyeHeights, Vector3dm basePos, + boolean isPrediction, double reachDistance) { + if (!isEnabled()) return; + + ByteBuf buffer = Unpooled.buffer(); + try { + // Version Header + buffer.writeByte(1); + + // Global Flags Header + // Pack boolean flags into a single byte + byte global_flags = 0; + global_flags |= (isPrediction ? 1 : 0); // Bit 0: are the hitboxes from a prediction? + // Bits 2-7 reserved for future use + buffer.writeByte(global_flags); + + // Write reach distance + buffer.writeDouble(reachDistance); + + // Write base position + writeVector(buffer, basePos); + + // Write number of ray traces + ByteBufHelper.writeVarInt(buffer, lookVecsAndEyeHeights.size()); + + // Write all possible ray traces + for (Pair pair : lookVecsAndEyeHeights) { + Vector3dm lookVec = pair.first(); + double eyeHeight = pair.second(); + + // Write eye height delta and look vector + // we make them 3 meters shorter because all of the vectors passed into here are made 3 meters longer + // then they need to be so grim can report correct reach distance for too far away hits + buffer.writeDouble(eyeHeight); + writeVector(buffer, lookVec); + } + + // Write number of hitboxes + ByteBufHelper.writeVarInt(buffer, hitboxes.size()); + + // Write hitbox data + for (Map.Entry entry : hitboxes.entrySet()) { + int entityId = entry.getKey(); + CollisionBox box = entry.getValue(); + + // Write entity ID + ByteBufHelper.writeVarInt(buffer, entityId); + + // Pack boolean flags into a single byte + byte flags = 0; + flags |= (targetEntities.contains(entityId) ? 1 : 0); // Bit 0: Is target + flags |= (box == NoCollisionBox.INSTANCE ? 2 : 0); // Bit 1: Is no collision + // Bits 2-7 reserved for future use + buffer.writeByte(flags); + + // Write box coordinates if it's not a NoCollisionBox + if ((flags & 2) == 0) { + SimpleCollisionBox simpleCollisionBox = (SimpleCollisionBox) box; + buffer.writeDouble(simpleCollisionBox.minX); + buffer.writeDouble(simpleCollisionBox.minY); + buffer.writeDouble(simpleCollisionBox.minZ); + buffer.writeDouble(simpleCollisionBox.maxX); + buffer.writeDouble(simpleCollisionBox.maxY); + buffer.writeDouble(simpleCollisionBox.maxZ); + } + } + + // Convert buffer to byte array + byte[] data = new byte[buffer.readableBytes()]; + buffer.readBytes(data); + + // Create and send packet + WrapperPlayServerPluginMessage packet = new WrapperPlayServerPluginMessage( + "grim:debug_hitbox", + data + ); + + // Send to all listeners + for (GrimPlayer listener : listeners) { + if (listener != null) { + PacketEvents.getAPI().getPlayerManager().sendPacket(listener.user, packet); + } + } + } finally { + // Release buffer to prevent memory leaks + buffer.release(); + } + } + + public boolean isEnabled() { + return !listeners.isEmpty(); + } + + /** + * Helper method to write a Vector to the ByteBuf + * @param buffer The buffer to write to + * @param vector The vector to write + */ + private void writeVector(ByteBuf buffer, Vector3dm vector) { + buffer.writeDouble(vector.getX()); + buffer.writeDouble(vector.getY()); + buffer.writeDouble(vector.getZ()); + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java index a28e307642..7956a79415 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java @@ -18,8 +18,11 @@ import ac.grim.grimac.api.config.ConfigManager; import ac.grim.grimac.checks.Check; import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.debug.HitboxDebugHandler; import ac.grim.grimac.checks.type.PacketCheck; import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; import ac.grim.grimac.utils.data.BlockHitData; import ac.grim.grimac.utils.data.EntityHitData; @@ -248,6 +251,9 @@ private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean } } + if (hitboxDebuggingEnabled()) + sendHitboxDebugData(reachEntity, from, lookVecsAndEyeHeights, isPrediction); + HitData foundHitData = null; // If the entity is within range of the player (we'll flag anyway if not, so no point checking blocks in this case) // Ignore when could be hitting through a moving shulker, piston blocks. They are just too glitchy/uncertain to check. @@ -315,4 +321,60 @@ public void handleBlockChange(Vector3i vector3i, WrappedBlockState state) { if (state.equals(player.compensatedWorld.getBlock(vector3i))) return; blocksChangedThisTick.add(vector3i); } + + private boolean hitboxDebuggingEnabled() { + return player.checkManager.getCheck(HitboxDebugHandler.class).isEnabled(); + } + + private void sendHitboxDebugData(PacketEntity reachEntity, Vector3d from, List> lookVecsAndEyeHeights, boolean isPrediction) { + Map hitboxes = new HashMap<>(); + for (Int2ObjectMap.Entry entry : player.compensatedEntities.entityMap.int2ObjectEntrySet()) { + PacketEntity entity = entry.getValue(); + if (!entity.canHit()) continue; + + CollisionBox box; + + if (entity.equals(reachEntity)) { + // Target entity gets expanded hitbox + box = entity.getPossibleCollisionBoxes(); + SimpleCollisionBox sBox = (SimpleCollisionBox) box; + sBox.expand(threshold); + + // Add movement threshold uncertainty for 1.9+ or non-position updates + if (!player.packetStateData.didLastLastMovementIncludePosition + || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) { + sBox.expand(player.getMovementThreshold()); + } + } else { + // Non-target entities + box = entity.getMinimumPossibleCollisionBoxes(); + if (box instanceof NoCollisionBox) { + hitboxes.put(entry.getIntKey(), NoCollisionBox.INSTANCE); + continue; + } else if (box instanceof SimpleCollisionBox) { + SimpleCollisionBox sBox = (SimpleCollisionBox) box; + sBox.expand(-threshold); + // Shrink non-target entities by movement threshold when applicable + if (!player.packetStateData.didLastLastMovementIncludePosition + || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) { + sBox.expand(-player.getMovementThreshold()); + } + } + } + + // Add 1.8 and below extra hitbox size + if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9) + && box instanceof SimpleCollisionBox) { + ((SimpleCollisionBox) box).expand(0.1f); + } + + hitboxes.put(entry.getIntKey(), box); + } + + player.checkManager.getCheck(HitboxDebugHandler.class).sendHitboxData(hitboxes, + Collections.singleton(player.compensatedEntities.getPacketEntityID(reachEntity)), + lookVecsAndEyeHeights, + new Vector3dm(from.getX(), from.getY(), from.getZ()), + isPrediction, player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE)); + } } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/DebugHandler.java b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/DebugHandler.java index ea2650dd4d..5f4d07981e 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/DebugHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/DebugHandler.java @@ -133,8 +133,14 @@ private String pickColor(double offset, double totalOffset) { } @Override - public void toggleListener(GrimPlayer player) { - if (!listeners.remove(player)) listeners.add(player); + public boolean toggleListener(GrimPlayer player) { + boolean wasPresent = listeners.remove(player); + if (wasPresent) { + return false; + } else { + listeners.add(player); + return true; + } } @Override diff --git a/common/src/main/java/ac/grim/grimac/command/commands/GrimDebug.java b/common/src/main/java/ac/grim/grimac/command/commands/GrimDebug.java index 71bb6f33aa..756a4f6a3f 100644 --- a/common/src/main/java/ac/grim/grimac/command/commands/GrimDebug.java +++ b/common/src/main/java/ac/grim/grimac/command/commands/GrimDebug.java @@ -1,6 +1,7 @@ package ac.grim.grimac.command.commands; import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.checks.debug.HitboxDebugHandler; import ac.grim.grimac.command.BuildableCommand; import ac.grim.grimac.platform.api.command.PlayerSelector; import ac.grim.grimac.platform.api.sender.Sender; @@ -36,9 +37,16 @@ public void register(CommandManager commandManager) { .required("target", GrimAPI.INSTANCE.getParserDescriptors().getSinglePlayer()) .handler(this::handleConsoleDebug); + Command.Builder hitboxDebugCommand = grimCommand + .literal("hitboxdebug", Description.of("Toggle hitbox debug visualization")) + .permission("grim.hitboxdebug") + .optional("target", GrimAPI.INSTANCE.getParserDescriptors().getSinglePlayer(), Description.of("Player to debug (defaults to self if sender is player)")) + .handler(this::handleHitboxDebug); + // Register command commandManager.command(debugCommand); commandManager.command(consoleDebugCommand); + commandManager.command(hitboxDebugCommand); } private void handleDebug(@NonNull CommandContext context) { @@ -81,6 +89,48 @@ private void handleConsoleDebug(@NonNull CommandContext context) { sender.sendMessage(message); } + private void handleHitboxDebug(@NonNull CommandContext context) { + Sender sender = context.sender(); + PlayerSelector playerSelector = context.getOrDefault("target", null); + + // Hitbox debug requires a *player* to be the listener (the one seeing the boxes) + if (!sender.isPlayer()) { + sender.sendMessage(MessageUtil.getParsedComponent(sender, + "hitboxdebug-player-only", + "%prefix% &cHitbox debug can only be toggled by players.") + ); + return; + } + + // Determine the target player whose hitboxes are being debugged + GrimPlayer targetGrimPlayer = parseTarget(sender, playerSelector == null ? sender : playerSelector.getSinglePlayer()); + if (targetGrimPlayer == null) return; + + // Get the sender's GrimPlayer data, as they are the listener + GrimPlayer senderGrimPlayer = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(sender.getUniqueId()); + if (senderGrimPlayer == null) { + sender.sendMessage(Component.text("Could not find your player data to register as a listener.", NamedTextColor.RED)); + return; + } + + // Get the HitboxDebugHandler check instance and toggle the listener + HitboxDebugHandler hitboxHandler = targetGrimPlayer.checkManager.getCheck(HitboxDebugHandler.class); + if (hitboxHandler == null) { + sender.sendMessage(Component.text("HitboxDebugHandler check not found for target player.", NamedTextColor.RED)); + return; + } + + boolean enabled = hitboxHandler.toggleListener(senderGrimPlayer); // Pass the sender/listener + + // Send feedback message + Component message = Component.text() + .append(Component.text("Hitbox debug listener for ", NamedTextColor.GRAY)) + .append(Component.text(targetGrimPlayer.getName(), NamedTextColor.WHITE)) + .append(Component.text(enabled ? " enabled." : " disabled.", NamedTextColor.GRAY)) + .build(); + sender.sendMessage(message); + } + private @Nullable GrimPlayer parseTarget(@NonNull Sender sender, @Nullable Sender t) { if (sender.isConsole() && t == null) { sender.sendMessage(MessageUtil.getParsedComponent(sender, "console-specify-target", "%prefix% &cYou must specify a target as the console!")); diff --git a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java index 50c57dbb5f..24781871fc 100644 --- a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java @@ -2,6 +2,7 @@ import ac.grim.grimac.GrimAPI; import ac.grim.grimac.api.AbstractCheck; +import ac.grim.grimac.checks.debug.HitboxDebugHandler; import ac.grim.grimac.checks.impl.aim.AimDuplicateLook; import ac.grim.grimac.checks.impl.aim.AimModulo360; import ac.grim.grimac.checks.impl.aim.processor.AimProcessor; @@ -294,6 +295,7 @@ public CheckManager(GrimPlayer player) { .put(Hitboxes.class, new Hitboxes(player)) // Hitboxes is invoked by Reach .put(WallHit.class, new WallHit(player)) .put(EntityPierce.class, new EntityPierce(player)) + .put(HitboxDebugHandler.class, new HitboxDebugHandler(player)) .build(); allChecks = new ImmutableClassToInstanceMap.Builder() From 32070a3ca9c22a53176bf56e87d5a81543a34869 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:25:12 -0400 Subject: [PATCH 06/34] Cull dead entities from canHit() --- .../main/java/ac/grim/grimac/checks/impl/combat/Reach.java | 6 ++++++ .../java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java index 7956a79415..301ba98727 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java @@ -69,6 +69,7 @@ public class Reach extends Check implements PacketCheck { // extra distance to raytrace beyond player reach distance so we know how far beyond the legit distance a cheater hit public static final double extraSearchDistance = 3; + private boolean ignoreNonPlayerTargets; private boolean cancelImpossibleHits; public double threshold; private double cancelBuffer; // For the next 4 hits after using reach, we aggressively cancel reach @@ -102,6 +103,10 @@ public void onPacketReceive(final PacketReceiveEvent event) { return; } + if (ignoreNonPlayerTargets && !entity.getType().equals(EntityTypes.PLAYER)) { + return; + } + // Dead entities cause false flags (https://github.com/GrimAnticheat/Grim/issues/546) if (entity.isDead) return; @@ -299,6 +304,7 @@ private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean @Override public void onReload(ConfigManager config) { + this.ignoreNonPlayerTargets = config.getBooleanElse("Reach.ignore-non-player-targets", false); this.cancelImpossibleHits = config.getBooleanElse("Reach.block-impossible-hits", true); this.threshold = config.getDoubleElse("Reach.threshold", 0.0005); } diff --git a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java index 3cc74ac0ed..526bd4ec16 100644 --- a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java +++ b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java @@ -176,7 +176,7 @@ public static HitData getNearestHitResult(GrimPlayer player, PacketEntity target PacketEntity closestEntity = null; double closestDistanceSquared = blockHitData != null ? blockHitData.getBlockHitLocation().distanceSquared(startingVec) : maxAttackDistance * maxAttackDistance; - for (PacketEntity entity : player.compensatedEntities.entityMap.values().stream().filter(TypedPacketEntity::canHit).toList()) { + for (PacketEntity entity : player.compensatedEntities.entityMap.values().stream().filter(PacketEntity::canHit).toList()) { SimpleCollisionBox box = null; // 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers) // Yes, this is vanilla and not uncertainty. All reach checks have this or they are wrong. From da952f841478b24139038ed3da550e524e942482 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:03:53 -0400 Subject: [PATCH 07/34] Update buildscript Notes: - Mark actions builds automatically as alpha on Modrinth - Shorten Fabric version string to be same as Bukkit on Modrinth --- .github/workflows/gradle-publish.yml | 202 ++++++++++++++---- .../grimac/utils/nmsutil/WorldRayTrace.java | 1 - 2 files changed, 162 insertions(+), 41 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index be2a751cb0..06a5604714 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -13,47 +13,169 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: read + contents: write # build-tag-number + mc-publish need these packages: write + actions: write steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - server-id: github # Value of the distributionManagement/repository/id field of the pom.xml - settings-path: ${{ github.workspace }} # location for the settings.xml file - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 # Handles Gradle wrapper validation and basic caching - - - name: Cache Gradle Loom Cache - uses: actions/cache@v4 - with: - path: .gradle/loom-cache # Path to Loom cache relative to workspace root - key: loom-cache-${{ runner.os }}-${{ hashFiles('**/libs.versions.toml', 'fabric/**/build.gradle.kts') }} - restore-keys: | - loom-cache-${{ runner.os }} - - - name: Build with Gradle (Actual Build for Artifacts) - run: ./gradlew build - - - name: Upload Bukkit Artifact - uses: actions/upload-artifact@v4 - with: - name: grimac-bukkit - # Adding if-no-files-found for robustness - path: ${{ github.workspace }}/bukkit/build/libs/grimac-bukkit-*.jar - if-no-files-found: error - - - name: Upload Fabric Artifact - uses: actions/upload-artifact@v4 - with: - name: grimac-fabric - # Adding if-no-files-found for robustness - path: ${{ github.workspace }}/fabric/build/libs/grimac-fabric-*.jar - if-no-files-found: error + #------------------------------------------------------------------ + # 0) checkout + JDK + #------------------------------------------------------------------ + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + server-id: github + settings-path: ${{ github.workspace }} + + #------------------------------------------------------------------ + # 1) Gradle wrapper validation + caches + #------------------------------------------------------------------ + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 # wrapper-validation + caching + + - name: Cache Loom + uses: actions/cache@v4 + with: + path: .gradle/loom-cache + key: loom-cache-${{ runner.os }}-${{ hashFiles('**/libs.versions.toml', + 'fabric/**/build.gradle.kts', + 'bukkit/**/build.gradle.kts') }} + restore-keys: | + loom-cache-${{ runner.os }} + + #------------------------------------------------------------------ + # 2) Resolve VERSIONs **before** building + #------------------------------------------------------------------ + - name: Compute MAIN version (default build) + id: ver_main + run: | + VERSION=$(./gradlew -q printVersion | grep '^VERSION=' | cut -d'=' -f2) + echo "main_version=$VERSION" >> $GITHUB_OUTPUT + echo "MAIN_VERSION=$VERSION" >> $GITHUB_ENV + echo "Main version: $VERSION" + + - name: Compute LITE version (-PshadePE=false) + id: ver_lite + run: | + VERSION=$(./gradlew -q -PshadePE=false printVersion | grep '^VERSION=' | cut -d'=' -f2) + echo "lite_version=$VERSION" >> $GITHUB_OUTPUT + echo "LITE_VERSION=$VERSION" >> $GITHUB_ENV + echo "Lite version: $VERSION" + + #------------------------------------------------------------------ + # 3) Build shaded / “main” jars (all modules) + #------------------------------------------------------------------ + - name: Build (all platforms, shaded) + run: ./gradlew build + + #------------------------------------------------------------------ + # 4) Upload MAIN Bukkit + Fabric artefacts (they exist now, lite doesn’t) + #------------------------------------------------------------------ + - name: Upload Bukkit ‑ MAIN + uses: actions/upload-artifact@v4 + with: + name: grimac-bukkit + path: bukkit/build/libs/grimac-bukkit-${{ env.MAIN_VERSION }}.jar + if-no-files-found: error + + - name: Upload Fabric + uses: actions/upload-artifact@v4 + with: + name: grimac-fabric + path: fabric/build/libs/grimac-fabric-${{ env.MAIN_VERSION }}.jar + if-no-files-found: error + + #------------------------------------------------------------------ + # 5) Build **lite** Bukkit jar + #------------------------------------------------------------------ + - name: Build Bukkit-Lite (no shaded PacketEvents) + run: ./gradlew :bukkit:build -PshadePE=false + + #------------------------------------------------------------------ + # 6) Upload LITE artefact + #------------------------------------------------------------------ + - name: Upload Bukkit ‑ LITE + uses: actions/upload-artifact@v4 + with: + name: grimac-bukkit-lite + path: bukkit/build/libs/grimac-bukkit-${{ env.LITE_VERSION }}.jar + if-no-files-found: error + + #------------------------------------------------------------------ + # 7) Generate incremental build-number (main / merge / release only) + #------------------------------------------------------------------ + - name: Generate build number + if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) + id: buildnumber + uses: onyxmueller/build-tag-number@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + #------------------------------------------------------------------ + # 8-A) Publish **Bukkit** jars to Modrinth + #------------------------------------------------------------------ + - name: Publish to Modrinth (Bukkit) + if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) + uses: Kir-Antipov/mc-publish@v3.3 + with: + modrinth-id: ${{ vars.MODRINTH_ID }} # Bukkit & Fabric can share or differ + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + modrinth-featured: true + modrinth-unfeature-mode: subset + + files: | + bukkit/build/libs/grimac-bukkit-${{ env.MAIN_VERSION }}.jar + bukkit/build/libs/grimac-bukkit-${{ env.LITE_VERSION }}.jar + + name: Lightning Grim Anticheat (Bukkit) ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} + version: ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} + version-type: alpha + + loaders: | + bukkit + spigot + paper + folia + purpur + + game-versions: | + >=1.7 + + retry-attempts: 2 + retry-delay: 10000 + fail-mode: fail + + #------------------------------------------------------------------ + # 8-B) Publish **Fabric** jar to Modrinth + #------------------------------------------------------------------ + - name: Publish to Modrinth (Fabric) + if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) + uses: Kir-Antipov/mc-publish@v3.3 + with: + # use MODRINTH_ID_FABRIC if you keep Fabric in a separate project + modrinth-id: ${{ vars.MODRINTH_ID_FABRIC || vars.MODRINTH_ID }} + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + modrinth-featured: true + modrinth-unfeature-mode: subset + + files: | + fabric/build/libs/grimac-fabric-${{ env.MAIN_VERSION }}.jar + + name: Lightning Grim Anticheat (Fabric) ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} + version: ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} + version-type: alpha + + loaders: | + fabric + + game-versions: | + >=1.16.1 + + retry-attempts: 2 + retry-delay: 10000 + fail-mode: fail diff --git a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java index 526bd4ec16..00270a6265 100644 --- a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java +++ b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java @@ -13,7 +13,6 @@ import ac.grim.grimac.utils.data.HitData; import ac.grim.grimac.utils.data.Pair; import ac.grim.grimac.utils.data.packetentity.PacketEntity; -import ac.grim.grimac.utils.data.packetentity.TypedPacketEntity; import ac.grim.grimac.utils.math.GrimMath; import ac.grim.grimac.utils.math.Vector3dm; import com.github.retrooper.packetevents.protocol.attribute.Attributes; From 434d12cc66b78a120e268bcce5fd5c5a599f5885 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:45:19 -0400 Subject: [PATCH 08/34] Buildscript Update & Dirty Painting Hack - Update gradle buildscript to not include branch-name if branch is called "lightning" - Update GitHub Actions to automatically include changelog and to rename the jar LightningGrim-${platform}-${version}.jar - Mark Paintings as entities that cannot be hit to prevent EntityPierce (formally HitboxEntity) falses. --- .github/workflows/gradle-publish.yml | 116 +++++++++++++----- .../src/main/kotlin/versioning/VersionUtil.kt | 1 + .../packetentity/PacketEntityPainting.java | 7 ++ 3 files changed, 91 insertions(+), 33 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 06a5604714..dd10fc65eb 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -1,10 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created -# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle - name: Gradle Package on: [push, workflow_dispatch] @@ -13,15 +6,19 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: write # build-tag-number + mc-publish need these + contents: write # needed by build-tag-number and mc-publish packages: write actions: write steps: #------------------------------------------------------------------ - # 0) checkout + JDK + # 0) Checkout + JDK #------------------------------------------------------------------ - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 + with: + # fetch the entire history and all tags + fetch-depth: 50 - name: Set up JDK 21 uses: actions/setup-java@v4 @@ -35,7 +32,7 @@ jobs: # 1) Gradle wrapper validation + caches #------------------------------------------------------------------ - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 # wrapper-validation + caching + uses: gradle/actions/setup-gradle@v4 - name: Cache Loom uses: actions/cache@v4 @@ -64,7 +61,7 @@ jobs: VERSION=$(./gradlew -q -PshadePE=false printVersion | grep '^VERSION=' | cut -d'=' -f2) echo "lite_version=$VERSION" >> $GITHUB_OUTPUT echo "LITE_VERSION=$VERSION" >> $GITHUB_ENV - echo "Lite version: $VERSION" + echo "Lite version: $VERSION" #------------------------------------------------------------------ # 3) Build shaded / “main” jars (all modules) @@ -73,40 +70,93 @@ jobs: run: ./gradlew build #------------------------------------------------------------------ - # 4) Upload MAIN Bukkit + Fabric artefacts (they exist now, lite doesn’t) + # 4) Build **lite** Bukkit jar + #------------------------------------------------------------------ + - name: Build Bukkit-Lite (no shaded PacketEvents) + run: ./gradlew :bukkit:build -PshadePE=false + + #------------------------------------------------------------------ + # 4.5) Rename jars to LightningGrim-* before we upload/publish + #------------------------------------------------------------------ + - name: Rename jars to LightningGrim + shell: bash + run: | + set -e + mv "bukkit/build/libs/grimac-bukkit-${MAIN_VERSION}.jar" \ + "bukkit/build/libs/LightningGrim-bukkit-${MAIN_VERSION}.jar" + + mv "bukkit/build/libs/grimac-bukkit-${LITE_VERSION}.jar" \ + "bukkit/build/libs/LightningGrim-bukkit-${LITE_VERSION}.jar" + + mv "fabric/build/libs/grimac-fabric-${MAIN_VERSION}.jar" \ + "fabric/build/libs/LightningGrim-fabric-${MAIN_VERSION}.jar" + + #------------------------------------------------------------------ + # 5) Upload MAIN Bukkit + Fabric artefacts #------------------------------------------------------------------ - - name: Upload Bukkit ‑ MAIN + - name: Upload Bukkit – MAIN uses: actions/upload-artifact@v4 with: name: grimac-bukkit - path: bukkit/build/libs/grimac-bukkit-${{ env.MAIN_VERSION }}.jar + path: bukkit/build/libs/LightningGrim-bukkit-${{ env.MAIN_VERSION }}.jar if-no-files-found: error - name: Upload Fabric uses: actions/upload-artifact@v4 with: name: grimac-fabric - path: fabric/build/libs/grimac-fabric-${{ env.MAIN_VERSION }}.jar + path: fabric/build/libs/LightningGrim-fabric-${{ env.MAIN_VERSION }}.jar if-no-files-found: error - #------------------------------------------------------------------ - # 5) Build **lite** Bukkit jar - #------------------------------------------------------------------ - - name: Build Bukkit-Lite (no shaded PacketEvents) - run: ./gradlew :bukkit:build -PshadePE=false - #------------------------------------------------------------------ # 6) Upload LITE artefact #------------------------------------------------------------------ - - name: Upload Bukkit ‑ LITE + - name: Upload Bukkit – LITE uses: actions/upload-artifact@v4 with: name: grimac-bukkit-lite - path: bukkit/build/libs/grimac-bukkit-${{ env.LITE_VERSION }}.jar + path: bukkit/build/libs/LightningGrim-bukkit-${{ env.LITE_VERSION }}.jar if-no-files-found: error #------------------------------------------------------------------ - # 7) Generate incremental build-number (main / merge / release only) + # 7) Auto-generate CHANGELOG (since previous build tag) + #------------------------------------------------------------------ + - name: Generate changelog + id: changelog + if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) + run: | + set -e + git fetch --tags --quiet + + # Most recent tag that looks like “build-*”; empty if none exist + LAST_TAG=$(git describe --tags --match "build-*" --abbrev=0 2>/dev/null || echo "") + echo "Last tag: ${LAST_TAG:-}" + + if [ -z "$LAST_TAG" ]; then + # First build (or no previous tag) → grab latest 50 commits + NOTES=$(git log -n 50 --pretty=format:'* %s (%h)' --no-merges) + else + # Normal case → commits since the last build tag + NOTES=$(git log "$LAST_TAG"..HEAD --pretty=format:'* %s (%h)' --no-merges) + fi + + # Fallback if there were no code changes + if [ -z "$NOTES" ]; then + NOTES="* No code changes since last build" + fi + + # Export multiline output for downstream steps + { + echo "notes<> "$GITHUB_OUTPUT" + + echo "Changelog generated:" + echo "$NOTES" + + #------------------------------------------------------------------ + # 8) Generate incremental build number (after changelog step!) #------------------------------------------------------------------ - name: Generate build number if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) @@ -116,7 +166,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} #------------------------------------------------------------------ - # 8-A) Publish **Bukkit** jars to Modrinth + # 9-A) Publish **Bukkit** jars to Modrinth #------------------------------------------------------------------ - name: Publish to Modrinth (Bukkit) if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) @@ -128,12 +178,13 @@ jobs: modrinth-unfeature-mode: subset files: | - bukkit/build/libs/grimac-bukkit-${{ env.MAIN_VERSION }}.jar - bukkit/build/libs/grimac-bukkit-${{ env.LITE_VERSION }}.jar + bukkit/build/libs/LightningGrim-bukkit-${{ env.MAIN_VERSION }}.jar + bukkit/build/libs/LightningGrim-bukkit-${{ env.LITE_VERSION }}.jar name: Lightning Grim Anticheat (Bukkit) ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} version: ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} version-type: alpha + changelog: ${{ steps.changelog.outputs.notes }} loaders: | bukkit @@ -150,24 +201,24 @@ jobs: fail-mode: fail #------------------------------------------------------------------ - # 8-B) Publish **Fabric** jar to Modrinth + # 9-B) Publish **Fabric** jar to Modrinth #------------------------------------------------------------------ - name: Publish to Modrinth (Fabric) if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) uses: Kir-Antipov/mc-publish@v3.3 with: - # use MODRINTH_ID_FABRIC if you keep Fabric in a separate project modrinth-id: ${{ vars.MODRINTH_ID_FABRIC || vars.MODRINTH_ID }} modrinth-token: ${{ secrets.MODRINTH_TOKEN }} modrinth-featured: true modrinth-unfeature-mode: subset files: | - fabric/build/libs/grimac-fabric-${{ env.MAIN_VERSION }}.jar + fabric/build/libs/LightningGrim-fabric-${{ env.MAIN_VERSION }}.jar name: Lightning Grim Anticheat (Fabric) ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} version: ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} version-type: alpha + changelog: ${{ steps.changelog.outputs.notes }} loaders: | fabric @@ -177,5 +228,4 @@ jobs: retry-attempts: 2 retry-delay: 10000 - fail-mode: fail - + fail-mode: fail \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/versioning/VersionUtil.kt b/buildSrc/src/main/kotlin/versioning/VersionUtil.kt index 058bdb39a7..39a3d34f2b 100644 --- a/buildSrc/src/main/kotlin/versioning/VersionUtil.kt +++ b/buildSrc/src/main/kotlin/versioning/VersionUtil.kt @@ -81,6 +81,7 @@ object VersionUtil { val branch = stdout.toString().trim() return when (branch) { + "lightning" -> null "main", "2.0" -> null // ← ignore these branches else -> branch.replace("/", "_") } diff --git a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntityPainting.java b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntityPainting.java index 7c371b0f0d..22e5c55f15 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntityPainting.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntityPainting.java @@ -16,4 +16,11 @@ public PacketEntityPainting(GrimPlayer player, UUID uuid, double x, double y, do super(player, uuid, EntityTypes.PAINTING, x, y, z); this.direction = direction; } + + // This is incorrect, temporary measure to exempt paintings from HitboxEntity + // Will properly model later + @Override + public boolean canHit() { + return false; + } } From d4a77a8be030f15d9489ffa8a3ee207dda7cb5cf Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sat, 3 May 2025 09:06:04 -0400 Subject: [PATCH 09/34] Heavy memory use optimization - 2.2x reduction in queued task overhead - reduction in size of queued single block changes - up to 8x reduction in size of queued multi block changes - fixed blocks disappearing in B73 caused by incorrect shifting in new block udpate handler --- .../worldreader/BasePacketWorldReader.java | 38 ++-- .../LegacyMultiBlockChangeHandler.java | 30 +++ ...V1160MultiBlockChangeBitRepackHandler.java | 175 ++++++++++++++++++ .../VersionedMultiBlockChangeHandler.java | 22 +++ .../ac/grim/grimac/player/GrimPlayer.java | 8 +- .../grimac/utils/latency/ILatencyUtils.java | 28 +++ .../grimac/utils/latency/LatencyUtils.java | 93 +++++----- 7 files changed, 316 insertions(+), 78 deletions(-) create mode 100644 common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/LegacyMultiBlockChangeHandler.java create mode 100644 common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/V1160MultiBlockChangeBitRepackHandler.java create mode 100644 common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/VersionedMultiBlockChangeHandler.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/latency/ILatencyUtils.java diff --git a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/BasePacketWorldReader.java b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/BasePacketWorldReader.java index 88b5514993..ca806a03fc 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/BasePacketWorldReader.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/BasePacketWorldReader.java @@ -1,12 +1,17 @@ package ac.grim.grimac.events.packets.worldreader; import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.events.packets.worldreader.multiblockchange.LegacyMultiBlockChangeHandler; +import ac.grim.grimac.events.packets.worldreader.multiblockchange.V1160MultiBlockChangeBitRepackHandler; +import ac.grim.grimac.events.packets.worldreader.multiblockchange.VersionedMultiBlockChangeHandler; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.chunks.Column; import ac.grim.grimac.utils.data.TeleportData; +import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.event.PacketListenerAbstract; import com.github.retrooper.packetevents.event.PacketListenerPriority; import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.world.chunk.BaseChunk; import com.github.retrooper.packetevents.util.Vector3i; @@ -16,11 +21,15 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChangeGameState; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkData; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkDataBulk; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUnloadChunk; public class BasePacketWorldReader extends PacketListenerAbstract { + private final static VersionedMultiBlockChangeHandler versionedMultiBlockChangeHandler + = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_16) + ? new V1160MultiBlockChangeBitRepackHandler() + : new LegacyMultiBlockChangeHandler(); + public BasePacketWorldReader() { super(PacketListenerPriority.HIGH); } @@ -161,28 +170,15 @@ public void handleBlockChange(GrimPlayer player, PacketSendEvent event) { player.lastTransSent + 2 < System.currentTimeMillis()) player.sendTransaction(); - player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.compensatedWorld.updateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockChange.getBlockId())); + int x = blockPosition.getX(); + int y = blockPosition.getY(); + int z = blockPosition.getZ(); + int blockId = blockChange.getBlockId(); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.compensatedWorld.updateBlock(x, y, z, blockId)); } public void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event) { - WrapperPlayServerMultiBlockChange multiBlockChange = new WrapperPlayServerMultiBlockChange(event); - - int range = 16; - - final var blocks = multiBlockChange.getBlocks(); - for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) { - // Don't send a transaction unless it's within 16 blocks of the player - if (Math.abs(blockChange.getX() - player.x) < range && Math.abs(blockChange.getY() - player.y) < range && Math.abs(blockChange.getZ() - player.z) < range && player.lastTransSent + 2 < System.currentTimeMillis()) { - player.sendTransaction(); - break; - } - } - - // Add a single runnable to prevent excessive memory use when there are lots of block changes - player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> { - for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) { - player.compensatedWorld.updateBlock(blockChange.getX(), blockChange.getY(), blockChange.getZ(), blockChange.getBlockId()); - } - }); + versionedMultiBlockChangeHandler.handleMultiBlockChange(player, event); } } diff --git a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/LegacyMultiBlockChangeHandler.java b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/LegacyMultiBlockChangeHandler.java new file mode 100644 index 0000000000..7f91ad4e0f --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/LegacyMultiBlockChangeHandler.java @@ -0,0 +1,30 @@ +package ac.grim.grimac.events.packets.worldreader.multiblockchange; + +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange; + +public class LegacyMultiBlockChangeHandler implements VersionedMultiBlockChangeHandler { + + // TODO hande optimize Pre 1.16 code to also reduce memory usage and not use wrapper + @Override + public void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event) { + WrapperPlayServerMultiBlockChange multiBlockChange = new WrapperPlayServerMultiBlockChange(event); + + final var blocks = multiBlockChange.getBlocks(); + for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) { + // Don't send a transaction unless it's within 16 blocks of the player + if (Math.abs(blockChange.getX() - player.x) < RANGE && Math.abs(blockChange.getY() - player.y) < RANGE && Math.abs(blockChange.getZ() - player.z) < RANGE && player.lastTransSent + TRANSACTION_COOLDOWN_MS < System.currentTimeMillis()) { + player.sendTransaction(); + break; + } + } + + // Add a single runnable to prevent excessive memory use when there are lots of block changes + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> { + for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) { + player.compensatedWorld.updateBlock(blockChange.getX(), blockChange.getY(), blockChange.getZ(), blockChange.getBlockId()); + } + }); + } +} diff --git a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/V1160MultiBlockChangeBitRepackHandler.java b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/V1160MultiBlockChangeBitRepackHandler.java new file mode 100644 index 0000000000..e2419b828e --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/V1160MultiBlockChangeBitRepackHandler.java @@ -0,0 +1,175 @@ +package ac.grim.grimac.events.packets.worldreader.multiblockchange; + +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.manager.server.ServerVersion; +import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper; +import io.netty.buffer.ByteBuf; + +/** + *

+ * Minecraft’s MultiBlockChange packet batches many block updates in one shot: + * the server sends a 64-bit section coordinate, an optional trustEdges flag, + * a VarInt count, then that many VarLong-encoded block changes + * (52 bits of global blockStateId + 12 bits of local X/Z/Y + * within the 16×16×16 section). + *

+ * + *

+ * To shrink our on-heap footprint for deferred tasks, we immediately repack + * each VarLong into one 32-bit int, while still using the + * vanilla 64-bit header for coords. + *

+ * + *

Vanilla “on-the-wire” format

+ *
+ * 1) sectionEncodedPosition : Long
+ * 2) [trustEdges?           : Boolean]   // only on protocol ≤1.19.4
+ * 3) recordCount            : VarInt
+ * 4) records[]              : recordCount × VarLong
+ *
+ *    Each VarLong is bits:
+ *     [63……12] blockStateId (52 bits)
+ *     [11……8]  localX       (4 bits, 0–15)
+ *     [7……4]   localZ       (4 bits, 0–15)
+ *     [3……0]   localY       (4 bits, 0–15)
+ * 
+ * + *

Vanilla 64-bit section header “encodedPosition”

+ *
+ * bits   63……42   41……20    19……0
+ *       ┌────────┬─────────┬────────┐
+ *       │  secX  │  secZ   │  secY  │
+ *       │ (22b)  │ (22b)   │ (20b)  │
+ *       └────────┴─────────┴────────┘
+ *
+ *   secX = encoded >> 42
+ *   secZ = encoded << 22 >> 42
+ *   secY = encoded << 44 >> 44
+ * 
+ * + *

Our 32-bit repacked block record (MSB → LSB)

+ *
+ * bits  31…17   16…12   11…8   7…4   3…0
+ *      ┌───────┬───────┬───────┬──────┬─────┐
+ *      │ state │ spare │  lX   │  lZ  │ lY  │
+ *      │ (15b) │ (5b)  │ (4b)  │ (4b) │(4b) │
+ *      └───────┴───────┴───────┴──────┴─────┘
+ *
+ *  • state = (data >>> 12) & 0x7FFF
+ *  • spare = bits 12–16 (unused, reserved for flags)
+ *  • lX    = (data >>>  8) & 0xF
+ *  • lZ    = (data >>>  4) & 0xF
+ *  • lY    =  data          & 0xF
+ *
+ *  Then pack:  packed = (state << 17) | ((lX<<8)|(lZ<<4)|lY)
+ * 
+ */ +public final class V1160MultiBlockChangeBitRepackHandler + implements ac.grim.grimac.events.packets.worldreader.multiblockchange.VersionedMultiBlockChangeHandler { + + /* ---------- bit masks / shifts for the packed int ---------- */ + private static final int SHIFT_STATE = 17; // 32-15 = 17 + private static final int MASK_STATE = 0x7FFF; // 15 bits + + static final int MASK_LOCAL = 0xFFF; // 12 bits + + /* ---------- does this protocol still have trustEdges ? ----- */ + private static final boolean HAS_TRUST_EDGES = + PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_19_4); + + @Override + public void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event) { + // PE resets writer index for us, we don't have to call buffer.writerIndex(originalWriterIndex) + ByteBuf buf = (ByteBuf) event.getByteBuf(); + + /* 1. Section-position header (64 bits) ------------------ */ + long sectionEncodedPosition = ByteBufHelper.readLong(buf); + + if (HAS_TRUST_EDGES) { // skip only when it really exists + buf.skipBytes(1); + } + + /* 2. Record count + packed-int array ------------------- */ + int recordCount = ByteBufHelper.readVarInt(buf); + int[] packed = new int[recordCount]; + + /* 3. Decode packet (still comes as varLong per record) */ + // Unpack section coords once for the “near player” test + int secX = (int) (sectionEncodedPosition >> 42); + int secZ = (int) (sectionEncodedPosition << 22 >> 42); + int secY = (int) (sectionEncodedPosition << 44 >> 44); + + int baseX = secX << 4; + int baseY = secY << 4; + int baseZ = secZ << 4; + + boolean sendTx = false; + + // Use this to not spam the player with transactions if one has been sent within COOLDOWN + long now = System.currentTimeMillis(); + for (int i = 0; i < recordCount; i++) { + + long data = readVarLong(buf); // 52+12 bits + + int local = (int) (data & 0xFFFL); // 12-bit pos + + packed[i] = repackFromLong(data); + + /* -------- near-player distance test -------------- */ + if (!sendTx) { + int lx = (local >>> 8) & 0xF; + int lz = (local >>> 4) & 0xF; + int ly = local & 0xF; + + int wx = baseX + lx, wy = baseY + ly, wz = baseZ + lz; + + if (Math.abs(wx - player.x) < RANGE && + Math.abs(wy - player.y) < RANGE && + Math.abs(wz - player.z) < RANGE && + player.lastTransSent + TRANSACTION_COOLDOWN_MS < now) { + sendTx = true; + } + } + } + + if (sendTx) + player.sendTransaction(); + + /* 4. Queue runnable – captures only int[] + sectionPos */ + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> { + + // unpack section once per execution + int sX = (int) (sectionEncodedPosition >> 42); + int sY = (int) (sectionEncodedPosition << 44 >> 44); + int sZ = (int) (sectionEncodedPosition << 22 >> 42); + + int bx = sX << 4, by = sY << 4, bz = sZ << 4; + + for (int rec : packed) { + int stateId = (rec >>> SHIFT_STATE) & MASK_STATE; + int lx = (rec >>> 8) & 0xF; + int lz = (rec >>> 4) & 0xF; + int ly = rec & 0xF; + + int wx = bx + lx; + int wy = by + ly; + int wz = bz + lz; + + player.compensatedWorld.updateBlock(wx, wy, wz, stateId); + } + }); + } + + public int repackFromLong(long data) { + // 1) extract the 15-bit state from bits 12.. (original >> 12) + int blockState = (int)((data >>> 12) & MASK_STATE); + + // 2) extract the 12-bit local from bits 0..11 + int local = (int)( data & MASK_LOCAL); + + // 3) glue them together + return (blockState << SHIFT_STATE) | local; + } +} diff --git a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/VersionedMultiBlockChangeHandler.java b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/VersionedMultiBlockChangeHandler.java new file mode 100644 index 0000000000..909a53c1a1 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/VersionedMultiBlockChangeHandler.java @@ -0,0 +1,22 @@ +package ac.grim.grimac.events.packets.worldreader.multiblockchange; + +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import io.netty.buffer.ByteBuf; + +public interface VersionedMultiBlockChangeHandler { + + int RANGE = 16; + long TRANSACTION_COOLDOWN_MS = 2; // In milliseconds + + void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event); + default long readVarLong(ByteBuf buf) { + long value = 0; + int size = 0; + int b; + while (((b = buf.readByte()) & 0x80) == 0x80) { + value |= (long) (b & 0x7F) << (size++ * 7); + } + return value | ((long) (b & 0x7F) << (size * 7)); + } +} diff --git a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java index c9453d9c2e..a92d80b0f4 100644 --- a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java +++ b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java @@ -35,11 +35,7 @@ import ac.grim.grimac.utils.enums.FluidTag; import ac.grim.grimac.utils.enums.Pose; import ac.grim.grimac.utils.inventory.InventoryDesyncStatus; -import ac.grim.grimac.utils.latency.CompensatedEntities; -import ac.grim.grimac.utils.latency.CompensatedFireworks; -import ac.grim.grimac.utils.latency.CompensatedInventory; -import ac.grim.grimac.utils.latency.CompensatedWorld; -import ac.grim.grimac.utils.latency.LatencyUtils; +import ac.grim.grimac.utils.latency.*; import ac.grim.grimac.utils.math.GrimMath; import ac.grim.grimac.utils.math.Location; import ac.grim.grimac.utils.math.TrigHandler; @@ -216,7 +212,7 @@ public class GrimPlayer implements GrimUser { public final CompensatedFireworks fireworks; public final CompensatedWorld compensatedWorld; public final CompensatedEntities compensatedEntities; - public final LatencyUtils latencyUtils = new LatencyUtils(this); + public final ILatencyUtils latencyUtils = new LatencyUtils(this); public final PointThreeEstimator pointThreeEstimator; public final TrigHandler trigHandler = new TrigHandler(this); public final PacketStateData packetStateData = new PacketStateData(); diff --git a/common/src/main/java/ac/grim/grimac/utils/latency/ILatencyUtils.java b/common/src/main/java/ac/grim/grimac/utils/latency/ILatencyUtils.java new file mode 100644 index 0000000000..329c7683a0 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/latency/ILatencyUtils.java @@ -0,0 +1,28 @@ +package ac.grim.grimac.utils.latency; + +public interface ILatencyUtils { + /** + * Adds a task to be executed when the corresponding transaction ACK is received. + * + * @param transaction The transaction ID this task is associated with. + * @param runnable The task to execute. + */ + void addRealTimeTask(int transaction, Runnable runnable); + + /** + * Adds a task to be executed asynchronously via the player's event loop + * when the corresponding transaction ACK is received. + * (Note: Benchmark might simplify/ignore the async part unless specifically testing event loop contention) + * + * @param transaction The transaction ID this task is associated with. + * @param runnable The task to execute. + */ + void addRealTimeTaskAsync(int transaction, Runnable runnable); + + /** + * Processes received transaction ACKs and runs associated tasks. + * + * @param receivedTransactionId The ID of the transaction ACK received from the client. + */ + void handleNettySyncTransaction(int receivedTransactionId); +} diff --git a/common/src/main/java/ac/grim/grimac/utils/latency/LatencyUtils.java b/common/src/main/java/ac/grim/grimac/utils/latency/LatencyUtils.java index 20c7010d74..0e87aa751c 100644 --- a/common/src/main/java/ac/grim/grimac/utils/latency/LatencyUtils.java +++ b/common/src/main/java/ac/grim/grimac/utils/latency/LatencyUtils.java @@ -4,19 +4,18 @@ import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.LogUtil; import ac.grim.grimac.utils.anticheat.MessageUtil; -import ac.grim.grimac.utils.data.Pair; import com.github.retrooper.packetevents.netty.channel.ChannelHelper; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.ListIterator; +import java.util.*; -public class LatencyUtils { - private final LinkedList> transactionMap = new LinkedList<>(); - private final GrimPlayer player; +public class LatencyUtils implements ILatencyUtils { + + // Record to replace Pair with primitive int + private record TransactionTask(int transactionId, Runnable task) {} + + private final ArrayDeque transactionMap = new ArrayDeque<>(); - // Built from transactionMap and cleared at start of every handleNettySyncTransaction() call - // The actual usage scope of this variable's use is limited to within the synchronized block of handleNettySyncTransaction + private final GrimPlayer player; private final ArrayList tasksToRun = new ArrayList<>(); public LatencyUtils(GrimPlayer player) { @@ -24,78 +23,70 @@ public LatencyUtils(GrimPlayer player) { } public void addRealTimeTask(int transaction, Runnable runnable) { - addRealTimeTask(transaction, false, runnable); + addRealTimeTaskInternal(transaction, false, runnable); } public void addRealTimeTaskAsync(int transaction, Runnable runnable) { - addRealTimeTask(transaction, true, runnable); + addRealTimeTaskInternal(transaction, true, runnable); } - public void addRealTimeTask(int transaction, boolean async, Runnable runnable) { - if (player.lastTransactionReceived.get() >= transaction) { // If the player already responded to this transaction + private void addRealTimeTaskInternal(int transactionId, boolean async, Runnable runnable) { + if (player.lastTransactionReceived.get() >= transactionId) { if (async) { - ChannelHelper.runInEventLoop(player.user.getChannel(), runnable); // Run it sync to player channel + ChannelHelper.runInEventLoop(player.user.getChannel(), runnable); } else { runnable.run(); } return; } - synchronized (this) { - transactionMap.add(new Pair<>(transaction, runnable)); + synchronized (transactionMap) { + transactionMap.add(new TransactionTask(transactionId, runnable)); } } - public void handleNettySyncTransaction(int transaction) { - /* - * This code uses a two-pass approach within the synchronized block to prevent CMEs. - * First we collect and remove tasks using the iterator, then execute all collected tasks. - * - * The issue: - * We cannot execute tasks during iteration because if a runnable modifies transactionMap - * or calls addRealTimeTask, it will cause a ConcurrentModificationException. - * While only seen on Folia servers, this is theoretically possible everywhere. - * - * Why this solution: - * Rather than documenting "don't modify transactionMap in runnables" and risking subtle - * bugs from future contributions or Check API usage, we prevent the issue entirely - * at a small performance cost. - * - * Future considerations: - * If this becomes a performance bottleneck, we may revisit using a single-pass approach - * on non-Folia servers. We could also explore concurrent data structures or parallel - * execution, but this would lose the guarantee that transactions are processed in order. - */ - synchronized (this) { + @Override + public void handleNettySyncTransaction(int receivedTransactionId) { + synchronized (transactionMap) { tasksToRun.clear(); - // First pass: collect tasks and mark them for removal - ListIterator> iterator = transactionMap.listIterator(); + Iterator iterator = transactionMap.iterator(); while (iterator.hasNext()) { - Pair pair = iterator.next(); + TransactionTask taskEntry = iterator.next(); + int taskTransactionId = taskEntry.transactionId(); - // We are at most a tick ahead when running tasks based on transactions, meaning this is too far - if (transaction + 1 < pair.first()) + // If tasks are added with monotonically increasing IDs, + // once we find one that's too far ahead, all subsequent ones + // will also be too far ahead. + if (receivedTransactionId + 1 < taskTransactionId) { break; + } // This is at most tick ahead of what we want - if (transaction == pair.first() - 1) - continue; + if (receivedTransactionId == taskTransactionId - 1) { + continue; // Skip this specific task + } - tasksToRun.add(pair.second()); - iterator.remove(); + // If we didn't break or continue, the task is eligible + tasksToRun.add(taskEntry.task()); + iterator.remove(); // Remove using the iterator } + // Task execution loop for (Runnable runnable : tasksToRun) { try { runnable.run(); } catch (Exception e) { - LogUtil.error("An error has occurred when running transactions for player: " + player.user.getName(), e); - // Kick the player SO PEOPLE ACTUALLY REPORT PROBLEMS AND KNOW WHEN THEY HAPPEN - if (!Boolean.getBoolean("grim.disable-transaction-kick")) { - player.disconnect(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(player, GrimAPI.INSTANCE.getConfigManager().getDisconnectPacketError()))); - } + handleRunnableError(e); } } } } + + private void handleRunnableError(Exception e) { + LogUtil.error("An error has occurred when running transactions for player: " + player.user.getName(), e); + // Kick the player SO PEOPLE ACTUALLY REPORT PROBLEMS AND KNOW WHEN THEY HAPPEN + if (!Boolean.getBoolean("grim.disable-transaction-kick")) { + player.disconnect(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(player, GrimAPI.INSTANCE.getConfigManager().getDisconnectPacketError()))); + } + } } From 706e3ca0ba96dd168fd165909b478e7c70091f3e Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sun, 4 May 2025 12:35:32 -0400 Subject: [PATCH 10/34] * Remove space in names of new checks: * "Wall Hit" -> "WallHit" * "Entity Pierce" -> "EntityPierce" * Fix modeling experience orb as unhittable entity * Fix sending hitbox with /grim hitboxdebug in the hitboxdebug handler * Update upstream to 35811fe5513df27aaab0488fb38d7d8f14486f4c --- .../java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java | 3 +-- .../java/ac/grim/grimac/checks/impl/combat/EntityPierce.java | 2 +- .../main/java/ac/grim/grimac/checks/impl/combat/WallHit.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java b/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java index 05177b2ab0..60f580f651 100644 --- a/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java @@ -6,7 +6,6 @@ import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; import ac.grim.grimac.utils.data.Pair; import ac.grim.grimac.utils.math.Vector3dm; -import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage; import io.netty.buffer.ByteBuf; @@ -180,7 +179,7 @@ public void sendHitboxData(Map hitboxes, Set tar // Send to all listeners for (GrimPlayer listener : listeners) { if (listener != null) { - PacketEvents.getAPI().getPlayerManager().sendPacket(listener.user, packet); + listener.user.sendPacket(packet); } } } finally { diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java index 2a24a3b649..114306219c 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java @@ -5,7 +5,7 @@ import ac.grim.grimac.checks.type.PacketCheck; import ac.grim.grimac.player.GrimPlayer; -@CheckData(name = "Entity Pierce", configName = "EntityPierce", setback = 30) +@CheckData(name = "EntityPierce", configName = "EntityPierce", setback = 30) public class EntityPierce extends Check implements PacketCheck { public EntityPierce(GrimPlayer player) { super(player); diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java index f61279e1b2..2033e2476f 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java @@ -5,7 +5,7 @@ import ac.grim.grimac.checks.type.PacketCheck; import ac.grim.grimac.player.GrimPlayer; -@CheckData(name = "Wall Hit", configName = "WallHit", setback = 20) +@CheckData(name = "WallHit", configName = "WallHit", setback = 20) public class WallHit extends Check implements PacketCheck { public WallHit(GrimPlayer player) { super(player); From 9d8aaa4d046c1c5b56c28a8aee3fc2e2fc01571e Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 7 May 2025 16:35:55 -0400 Subject: [PATCH 11/34] Optimize Bukkit PistonEvent() usage --- .../bukkit/entity/BukkitGrimEntity.java | 1 - .../platform/bukkit/events/PistonEvent.java | 32 +++++++---------- .../checks/impl/badpackets/BadPacketsH.java | 3 -- .../impl/multiactions/MultiActionsG.java | 1 - .../ac/grim/grimac/utils/data/PistonData.java | 36 ------------------- .../grimac/utils/data/PistonTemplate.java | 11 ++++++ .../grimac/utils/data/PlayerPistonData.java | 23 ++++++++++++ .../utils/latency/CompensatedWorld.java | 31 ++++++++-------- 8 files changed, 63 insertions(+), 75 deletions(-) delete mode 100644 common/src/main/java/ac/grim/grimac/utils/data/PistonData.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/data/PistonTemplate.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/data/PlayerPistonData.java diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/entity/BukkitGrimEntity.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/entity/BukkitGrimEntity.java index c81ad5c280..524e06bf7a 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/entity/BukkitGrimEntity.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/entity/BukkitGrimEntity.java @@ -60,7 +60,6 @@ public PlatformWorld getWorld() { if (bukkitPlatformWorld == null || !bukkitPlatformWorld.getBukkitWorld().equals(entity.getWorld())) { bukkitPlatformWorld = new BukkitPlatformWorld(entity.getWorld()); } - return bukkitPlatformWorld; } diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/events/PistonEvent.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/events/PistonEvent.java index 3bb0c15a0a..c120587ef9 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/events/PistonEvent.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/events/PistonEvent.java @@ -4,7 +4,7 @@ import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; -import ac.grim.grimac.utils.data.PistonData; +import ac.grim.grimac.utils.data.PistonTemplate; import com.github.retrooper.packetevents.protocol.world.BlockFace; import org.bukkit.Material; import org.bukkit.block.Block; @@ -55,17 +55,8 @@ public void onPistonPushEvent(BlockPistonExtendEvent event) { piston.getY() + event.getDirection().getModY(), piston.getZ() + event.getDirection().getModZ())); - boolean finalHasSlimeBlock = hasSlimeBlock; - boolean finalHasHoneyBlock = hasHoneyBlock; - for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) { - int lastTrans = player.lastTransactionSent.get(); - player.runSafely(() -> { - if (player.compensatedWorld.isChunkLoaded(event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4)) { - PistonData data = new PistonData(BukkitConversionUtils.fromBukkitFace(event.getDirection()), boxes, lastTrans, true, finalHasSlimeBlock, finalHasHoneyBlock); - player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data)); - } - }); - } + PistonTemplate data = new PistonTemplate(BukkitConversionUtils.fromBukkitFace(event.getDirection()), boxes, true, hasSlimeBlock, hasHoneyBlock); + addPistonData(data, event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4); } // For some unknown reason, bukkit handles this stupidly @@ -113,15 +104,18 @@ public void onPistonRetractEvent(BlockPistonRetractEvent event) { } } - boolean finalHasSlimeBlock = hasSlimeBlock; - boolean finalHasHoneyBlock = hasHoneyBlock; + PistonTemplate data = new PistonTemplate(face, boxes, false, hasSlimeBlock, hasHoneyBlock); + addPistonData(data, event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4); + } + + private void addPistonData(PistonTemplate pistonTemplate, int chunkX, int chunkZ) { for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) { + if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ)) continue; + int lastTrans = player.lastTransactionSent.get(); - player.runSafely(() -> { - if (player.compensatedWorld.isChunkLoaded(event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4)) { - PistonData data = new PistonData(BukkitConversionUtils.fromBukkitFace(event.getDirection()), boxes, lastTrans, false, finalHasSlimeBlock, finalHasHoneyBlock); - player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data)); - } + + player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> { + player.compensatedWorld.addPiston(pistonTemplate, lastTrans); }); } } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsH.java b/common/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsH.java index 49c62d9d14..1f775a9982 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsH.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsH.java @@ -1,7 +1,6 @@ package ac.grim.grimac.checks.impl.badpackets; import ac.grim.grimac.checks.CheckData; -import ac.grim.grimac.checks.type.BlockBreakCheck; import ac.grim.grimac.checks.type.BlockPlaceCheck; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.update.BlockBreak; @@ -11,8 +10,6 @@ import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.player.ClientVersion; -import com.github.retrooper.packetevents.protocol.player.DiggingAction; -import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging; import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUseItem; @CheckData(name = "BadPacketsH", description = "Sent unexpected sequence id", experimental = true) diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsG.java b/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsG.java index fba8f6949a..ca04cc1be8 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsG.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsG.java @@ -9,7 +9,6 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.player.ClientVersion; import com.github.retrooper.packetevents.protocol.world.BlockFace; -import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerBlockPlacement; @CheckData(name = "MultiActionsG", description = "Attacking or using items while rowing a boat", experimental = true) public class MultiActionsG extends BlockPlaceCheck { diff --git a/common/src/main/java/ac/grim/grimac/utils/data/PistonData.java b/common/src/main/java/ac/grim/grimac/utils/data/PistonData.java deleted file mode 100644 index 0d8657b70d..0000000000 --- a/common/src/main/java/ac/grim/grimac/utils/data/PistonData.java +++ /dev/null @@ -1,36 +0,0 @@ -package ac.grim.grimac.utils.data; - -import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; -import com.github.retrooper.packetevents.protocol.world.BlockFace; - -import java.util.List; - -public class PistonData { - public final boolean isPush; - public final boolean hasSlimeBlock; - public final boolean hasHoneyBlock; - public final BlockFace direction; - public final int lastTransactionSent; - - // Calculate if the player has no-push, and when to end the possibility of applying piston - public int ticksOfPistonBeingAlive = 0; - - // The actual blocks pushed by the piston, plus the piston head itself - public List boxes; - - public PistonData(BlockFace direction, List pushedBlocks, int lastTransactionSent, boolean isPush, boolean hasSlimeBlock, boolean hasHoneyBlock) { - this.direction = direction; - this.boxes = pushedBlocks; - this.lastTransactionSent = lastTransactionSent; - this.isPush = isPush; - this.hasSlimeBlock = hasSlimeBlock; - this.hasHoneyBlock = hasHoneyBlock; - } - - // We don't know when the piston has applied, or what stage of pushing it is on - // Therefore, we need to use what we have - the number of movement packets. - // 10 is a very cautious number - public boolean tickIfGuaranteedFinished() { - return ++ticksOfPistonBeingAlive >= 10; - } -} diff --git a/common/src/main/java/ac/grim/grimac/utils/data/PistonTemplate.java b/common/src/main/java/ac/grim/grimac/utils/data/PistonTemplate.java new file mode 100644 index 0000000000..f21030bb5d --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/data/PistonTemplate.java @@ -0,0 +1,11 @@ +package ac.grim.grimac.utils.data; + +import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; +import com.github.retrooper.packetevents.protocol.world.BlockFace; + +import java.util.List; + +public record PistonTemplate(BlockFace dir, + List boxes, + boolean push, boolean slime, boolean honey) { +} diff --git a/common/src/main/java/ac/grim/grimac/utils/data/PlayerPistonData.java b/common/src/main/java/ac/grim/grimac/utils/data/PlayerPistonData.java new file mode 100644 index 0000000000..4f4f356e27 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/data/PlayerPistonData.java @@ -0,0 +1,23 @@ +package ac.grim.grimac.utils.data; + + + +public class PlayerPistonData { + public final PistonTemplate pistonTemplate; + public final int lastTransactionSent; + + // Calculate if the player has no-push, and when to end the possibility of applying piston + int ticksOfPistonBeingAlive = 0; + + public PlayerPistonData(PistonTemplate playerPistonData, int lastTransactionSent) { + this.pistonTemplate = playerPistonData; + this.lastTransactionSent = lastTransactionSent; + } + + // We don't know when the piston has applied, or what stage of pushing it is on + // Therefore, we need to use what we have - the number of movement packets. + // 10 is a very cautious number + public boolean tickIfGuaranteedFinished() { + return ++ticksOfPistonBeingAlive >= 10; + } +} diff --git a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedWorld.java b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedWorld.java index 3f13118a30..f4d37a273b 100644 --- a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedWorld.java +++ b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedWorld.java @@ -6,10 +6,7 @@ import ac.grim.grimac.utils.chunks.Column; import ac.grim.grimac.utils.collisions.CollisionData; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; -import ac.grim.grimac.utils.data.BlockPrediction; -import ac.grim.grimac.utils.data.Pair; -import ac.grim.grimac.utils.data.PistonData; -import ac.grim.grimac.utils.data.ShulkerData; +import ac.grim.grimac.utils.data.*; import ac.grim.grimac.utils.data.packetentity.PacketEntity; import ac.grim.grimac.utils.data.packetentity.PacketEntityShulker; import ac.grim.grimac.utils.math.GrimMath; @@ -70,7 +67,7 @@ public class CompensatedWorld { public final GrimPlayer player; public final Long2ObjectMap chunks; // Packet locations for blocks - public Set activePistons = new HashSet<>(); + public Set activePistons = new HashSet<>(); public Set openShulkerBoxes = new HashSet<>(); // 1.17 with datapacks, and 1.18, have negative world offset values @Getter @@ -239,8 +236,8 @@ public boolean isNearHardEntity(SimpleCollisionBox playerBox) { } // Pistons are a block entity. - for (PistonData data : activePistons) { - for (SimpleCollisionBox box : data.boxes) { + for (PlayerPistonData data : activePistons) { + for (SimpleCollisionBox box : data.pistonTemplate.boxes()) { if (playerBox.isCollided(box)) { return true; } @@ -359,18 +356,18 @@ public void tickPlayerInPistonPushingArea() { double modY = 0; double modZ = 0; - for (PistonData data : activePistons) { - for (SimpleCollisionBox box : data.boxes) { + for (PlayerPistonData data : activePistons) { + for (SimpleCollisionBox box : data.pistonTemplate.boxes()) { if (playerBox.isCollided(box)) { - modX = Math.max(modX, Math.abs(data.direction.getModX() * 0.51D)); - modY = Math.max(modY, Math.abs(data.direction.getModY() * 0.51D)); - modZ = Math.max(modZ, Math.abs(data.direction.getModZ() * 0.51D)); + modX = Math.max(modX, Math.abs(data.pistonTemplate.dir().getModX() * 0.51D)); + modY = Math.max(modY, Math.abs(data.pistonTemplate.dir().getModY() * 0.51D)); + modZ = Math.max(modZ, Math.abs(data.pistonTemplate.dir().getModZ() * 0.51D)); playerBox.expandMax(modX, modY, modZ); playerBox.expandMin(modX * -1, modY * -1, modZ * -1); - if (data.hasSlimeBlock || (data.hasHoneyBlock && player.getClientVersion().isOlderThan(ClientVersion.V_1_15_2))) { - player.uncertaintyHandler.slimePistonBounces.add(data.direction); + if (data.pistonTemplate.slime() || (data.pistonTemplate.honey() && player.getClientVersion().isOlderThan(ClientVersion.V_1_15_2))) { + player.uncertaintyHandler.slimePistonBounces.add(data.pistonTemplate.dir()); } break; @@ -425,7 +422,7 @@ public void removeInvalidPistonLikeStuff(int transactionId) { activePistons.removeIf(data -> data.lastTransactionSent < transactionId); openShulkerBoxes.removeIf(data -> data.isClosing && data.lastTransactionSent < transactionId); } else { - activePistons.removeIf(PistonData::tickIfGuaranteedFinished); + activePistons.removeIf(PlayerPistonData::tickIfGuaranteedFinished); openShulkerBoxes.removeIf(ShulkerData::tickIfGuaranteedFinished); } // Remove if a shulker is not in this block position anymore @@ -698,4 +695,8 @@ public void setDimension(DimensionType dimension, User user) { public WrappedBlockState getBlock(Vector3dm aboveCCWPos) { return getBlock(aboveCCWPos.getX(), aboveCCWPos.getY(), aboveCCWPos.getZ()); } + + public void addPiston(PistonTemplate pistonTemplate, int transactionID) { + activePistons.add(new PlayerPistonData(pistonTemplate, transactionID)); + } } From cf3c78bc971b52b492c28d8e697a107f1858dc8d Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sun, 20 Apr 2025 01:08:17 -0400 Subject: [PATCH 12/34] Runtime startup check of PE version --- .../manager/init/load/PacketEventsInit.java | 43 +++++++++++++++++++ .../mixins/ServerPlayerEntityMixin.java | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java b/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java index 1c586f9511..690227e642 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java @@ -1,5 +1,6 @@ package ac.grim.grimac.manager.init.load; +import ac.grim.grimac.GrimAPI; import ac.grim.grimac.utils.anticheat.LogUtil; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEventsAPI; @@ -10,12 +11,14 @@ import com.github.retrooper.packetevents.protocol.item.type.ItemTypes; import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes; import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; +import com.github.retrooper.packetevents.util.PEVersion; import java.util.concurrent.Executors; public class PacketEventsInit implements LoadableInitable { PacketEventsAPI packetEventsAPI; + PEVersion MINIMUM_REQUIRED_PE_VERSION = new PEVersion(2, 8, 0, true); public PacketEventsInit(PacketEventsAPI packetEventsAPI) { this.packetEventsAPI = packetEventsAPI; @@ -25,6 +28,17 @@ public PacketEventsInit(PacketEventsAPI packetEventsAPI) { public void load() { LogUtil.info("Loading PacketEvents..."); PacketEvents.setAPI(packetEventsAPI); + + if (!checkPacketEventsVersion()) { + LogUtil.error("\n" + + "******************************************************\n" + + "GrimAC requires PacketEvents >= " + MINIMUM_REQUIRED_PE_VERSION + + (MINIMUM_REQUIRED_PE_VERSION.snapshot() ? "-SNAPSHOT" : "") + "\n" + + "Current version: " + PacketEvents.getAPI().getVersion() + "\n" + + "Please update PacketEvents to a compatible version.\n" + + "*****************************************************"); + } + PacketEvents.getAPI().getSettings() .fullStackTrace(true) .kickOnPacketException(true) @@ -43,4 +57,33 @@ public void load() { ParticleTypes.DUST.getName(); }).start(); } + + private boolean checkPacketEventsVersion() { + PEVersion current = PacketEvents.getAPI().getVersion(); + PEVersion required = MINIMUM_REQUIRED_PE_VERSION; + + // If current version is newer, always accept + if (current.isNewerThan(required)) { + return true; + } + + // If current version is exactly equal to required (including snapshot status), accept + if (current.major() == required.major() + && current.minor() == required.minor() + && current.patch() == required.patch() + && current.snapshot() == required.snapshot()) { + return true; + } + + // If required is a snapshot, accept matching release or snapshot + if (required.snapshot() + && current.major() == required.major() + && current.minor() == required.minor() + && current.patch() == required.patch()) { + return true; + } + + // Otherwise, reject + return false; + } } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/mixins/ServerPlayerEntityMixin.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/mixins/ServerPlayerEntityMixin.java index 95c7e43b63..7a9699ea7f 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/mixins/ServerPlayerEntityMixin.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/mixins/ServerPlayerEntityMixin.java @@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import net.minecraft.server.network.ServerPlayerEntity; - +// Works from 1.14 - latest (1.21.5) @Mixin(ServerPlayerEntity.class) abstract class ServerPlayerEntityMixin { From f54d26560aeb1d633e8784337bb5943419556545 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sun, 20 Apr 2025 08:39:04 -0400 Subject: [PATCH 13/34] Add /grim history command to see historical alerts - Add basic runtime reloading support. - Will only actually reload if current database is different/has different setting than the one requested in config. - Added [log] statements matching [alert] statements --- .../kotlin/grim.shadow-conventions.gradle.kts | 1 + .../player/BukkitOfflinePlatformPlayer.java | 38 +++++ .../player/BukkitPlatformPlayerFactory.java | 21 +++ common/build.gradle.kts | 1 + .../src/main/java/ac/grim/grimac/GrimAPI.java | 3 + .../java/ac/grim/grimac/GrimExternalAPI.java | 1 + .../grimac/command/commands/GrimHistory.java | 103 ++++++++++++++ .../ac/grim/grimac/manager/InitManager.java | 1 + .../grimac/manager/PunishmentManager.java | 5 + .../manager/init/load/PacketEventsInit.java | 1 - .../manager/init/start/CommandRegister.java | 16 +-- .../MySQLViolationDatabase.java | 131 ++++++++++++++++++ .../NoOpViolationDatabase.java | 17 +++ .../SQLiteViolationDatabase.java | 130 +++++++++++++++++ .../manager/violationdatabase/Violation.java | 38 +++++ .../violationdatabase/ViolationDatabase.java | 20 +++ .../ViolationDatabaseManager.java | 101 ++++++++++++++ .../platform/api/entity/GrimEntity.java | 6 +- .../player/AbstractPlatformPlayerFactory.java | 27 +++- .../api/player/OfflinePlatformPlayer.java | 10 ++ .../platform/api/player/PlatformPlayer.java | 14 +- .../api/player/PlatformPlayerFactory.java | 6 + common/src/main/resources/config/de.yml | 16 +++ common/src/main/resources/config/en.yml | 16 +++ common/src/main/resources/config/es.yml | 16 +++ common/src/main/resources/config/fr.yml | 16 +++ common/src/main/resources/config/it.yml | 16 +++ common/src/main/resources/config/ja.yml | 16 +++ common/src/main/resources/config/nl.yml | 16 +++ common/src/main/resources/config/pt.yml | 16 +++ common/src/main/resources/config/ru.yml | 16 +++ common/src/main/resources/config/tr.yml | 16 +++ common/src/main/resources/config/zh.yml | 16 +++ common/src/main/resources/messages/de.yml | 7 + common/src/main/resources/messages/en.yml | 7 + common/src/main/resources/messages/es.yml | 7 + common/src/main/resources/messages/fr.yml | 7 + common/src/main/resources/messages/it.yml | 7 + common/src/main/resources/messages/ja.yml | 7 + common/src/main/resources/messages/nl.yml | 7 + common/src/main/resources/messages/pt.yml | 7 + common/src/main/resources/messages/ru.yml | 7 + common/src/main/resources/messages/tr.yml | 8 +- common/src/main/resources/messages/zh.yml | 7 + common/src/main/resources/punishments/de.yml | 9 ++ common/src/main/resources/punishments/en.yml | 9 ++ common/src/main/resources/punishments/es.yml | 9 ++ common/src/main/resources/punishments/fr.yml | 9 ++ common/src/main/resources/punishments/it.yml | 9 ++ common/src/main/resources/punishments/ja.yml | 9 ++ common/src/main/resources/punishments/nl.yml | 9 ++ common/src/main/resources/punishments/pt.yml | 9 ++ common/src/main/resources/punishments/ru.yml | 9 ++ common/src/main/resources/punishments/tr.yml | 9 ++ common/src/main/resources/punishments/zh.yml | 9 ++ .../mc1161/GrimACFabric1161LoaderPlugin.java | 4 +- .../mc1171/Fabric1171PlatformServer.java | 18 +++ .../mc1171/GrimACFabric1170LoaderPlugin.java | 8 +- .../mc1194/Fabric1190PlatformServer.java | 4 +- .../mc1194/GrimACFabric1190LoaderPlugin.java | 4 +- .../fabric/AbstractFabricPlatformServer.java | 7 + .../fabric/GrimACFabricLoaderPlugin.java | 7 +- .../player/FabricOfflinePlatformPlayer.java | 40 ++++++ .../player/FabricPlatformPlayerFactory.java | 48 +++++++ 64 files changed, 1138 insertions(+), 46 deletions(-) create mode 100644 bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitOfflinePlatformPlayer.java create mode 100644 common/src/main/java/ac/grim/grimac/command/commands/GrimHistory.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/MySQLViolationDatabase.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/NoOpViolationDatabase.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/SQLiteViolationDatabase.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/Violation.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabase.java create mode 100644 common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabaseManager.java create mode 100644 common/src/main/java/ac/grim/grimac/platform/api/player/OfflinePlatformPlayer.java create mode 100644 fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/Fabric1171PlatformServer.java create mode 100644 fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricOfflinePlatformPlayer.java diff --git a/buildSrc/src/main/kotlin/grim.shadow-conventions.gradle.kts b/buildSrc/src/main/kotlin/grim.shadow-conventions.gradle.kts index 56bc68db5c..b4bf0fbb05 100644 --- a/buildSrc/src/main/kotlin/grim.shadow-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/grim.shadow-conventions.gradle.kts @@ -38,6 +38,7 @@ tasks.named("shadowJar") { relocate("org.jetbrains", "ac.grim.grimac.shaded.jetbrains") relocate("org.incendo", "ac.grim.grimac.shaded.incendo") relocate("io.leangen.geantyref", "ac.grim.grimac.shaded.geantyref") // Required by cloud + relocate("com.zaxxer", "ac.grim.grimac.shaded.zaxxer") // Database history } mergeServiceFiles() } diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitOfflinePlatformPlayer.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitOfflinePlatformPlayer.java new file mode 100644 index 0000000000..ec85a7440f --- /dev/null +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitOfflinePlatformPlayer.java @@ -0,0 +1,38 @@ +package ac.grim.grimac.platform.bukkit.player; + +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public class BukkitOfflinePlatformPlayer implements OfflinePlatformPlayer { + private final OfflinePlayer offlinePlayer; + + public BukkitOfflinePlatformPlayer(OfflinePlayer offlinePlayer) { + this.offlinePlayer = offlinePlayer; + } + + @Override + public boolean isOnline() { + return offlinePlayer.isOnline(); + } + + @Override + public @NotNull String getName() { + return offlinePlayer.getName(); + } + + @Override + public @NotNull UUID getUniqueId() { + return offlinePlayer.getUniqueId(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof OfflinePlatformPlayer offlinePlatformPlayer) { + return this.getUniqueId().equals(offlinePlatformPlayer.getUniqueId()); + } + return false; + } +} diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitPlatformPlayerFactory.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitPlatformPlayerFactory.java index ecd49b324d..705840af13 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitPlatformPlayerFactory.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/player/BukkitPlatformPlayerFactory.java @@ -1,9 +1,12 @@ package ac.grim.grimac.platform.bukkit.player; import ac.grim.grimac.platform.api.player.AbstractPlatformPlayerFactory; +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; import ac.grim.grimac.platform.api.player.PlatformPlayer; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -11,11 +14,17 @@ public class BukkitPlatformPlayerFactory extends AbstractPlatformPlayerFactory { + @Override protected Player getNativePlayer(@NotNull UUID uuid) { return Bukkit.getPlayer(uuid); } + @Override + protected Player getNativePlayer(@NonNull String name) { + return Bukkit.getPlayer(name); + } + @Override protected PlatformPlayer createPlatformPlayer(@NotNull Player nativePlayer) { return new BukkitPlatformPlayer(nativePlayer); @@ -44,4 +53,16 @@ protected Collection getNativeOnlinePlayers() { // Cast Collection to Collection return (Collection) Bukkit.getOnlinePlayers(); } + + @Override + public OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid) { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); + return new BukkitOfflinePlatformPlayer(offlinePlayer); + } + + @Override + public OfflinePlatformPlayer getOfflineFromName(@NotNull String name) { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(name); + return new BukkitOfflinePlatformPlayer(offlinePlayer); + } } diff --git a/common/build.gradle.kts b/common/build.gradle.kts index d1ab1daabd..9f805b592c 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -44,6 +44,7 @@ dependencies { api(libs.fastutil) api(libs.adventure.text.minimessage) api(libs.jetbrains.annotations) + api("com.zaxxer:HikariCP:4.0.3") api("ac.grim.grimac:GrimAPI:1.1.0.0") diff --git a/common/src/main/java/ac/grim/grimac/GrimAPI.java b/common/src/main/java/ac/grim/grimac/GrimAPI.java index a36088ce0a..4748b9b9ee 100644 --- a/common/src/main/java/ac/grim/grimac/GrimAPI.java +++ b/common/src/main/java/ac/grim/grimac/GrimAPI.java @@ -10,6 +10,7 @@ import ac.grim.grimac.manager.TickManager; import ac.grim.grimac.manager.config.BaseConfigManager; import ac.grim.grimac.manager.init.Initable; +import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager; import ac.grim.grimac.platform.api.Platform; import ac.grim.grimac.platform.api.PlatformLoader; import ac.grim.grimac.platform.api.PlatformServer; @@ -43,6 +44,7 @@ public final class GrimAPI { private final TickManager tickManager; private final EventBus eventBus; private final GrimExternalAPI externalAPI; + private ViolationDatabaseManager violationDatabaseManager; private PlatformLoader loader; @Getter private InitManager initManager; @@ -69,6 +71,7 @@ private static Platform detectPlatform() { public void load(PlatformLoader platformLoader, Initable... platformSpecificInitables) { this.loader = platformLoader; + this.violationDatabaseManager = new ViolationDatabaseManager(getGrimPlugin()); this.initManager = new InitManager(loader.getPacketEvents(), loader::getCommandManager, platformSpecificInitables); this.initManager.load(); this.initialized = true; diff --git a/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java b/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java index 7ca9a57f3c..fd105ceebb 100644 --- a/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java +++ b/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java @@ -181,6 +181,7 @@ public void onReload(ConfigManager newConfig) { GrimAPI.INSTANCE.getAlertManager().reload(configManager); GrimAPI.INSTANCE.getDiscordManager().reload(); GrimAPI.INSTANCE.getSpectateManager().reload(); + GrimAPI.INSTANCE.getViolationDatabaseManager().reload(); // Don't reload players if the plugin hasn't started yet if (!started) return; // Reload checks for all players diff --git a/common/src/main/java/ac/grim/grimac/command/commands/GrimHistory.java b/common/src/main/java/ac/grim/grimac/command/commands/GrimHistory.java new file mode 100644 index 0000000000..42bdc37dd5 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/command/commands/GrimHistory.java @@ -0,0 +1,103 @@ +package ac.grim.grimac.command.commands; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.command.BuildableCommand; +import ac.grim.grimac.manager.violationdatabase.Violation; +import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager; +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; +import ac.grim.grimac.platform.api.sender.Sender; +import ac.grim.grimac.utils.anticheat.MessageUtil; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.parser.standard.StringParser; + +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class GrimHistory implements BuildableCommand { + + @Override + public void register(CommandManager commandManager) { + commandManager.command( + commandManager.commandBuilder("grim", "grimac") + .literal("history", "hist") + .permission("grim.help") + .required("target", StringParser.stringParser()) + .optional("page", IntegerParser.integerParser()) + .permission("grim.history") + .handler(this::handleHistory) + ); + } + + private void handleHistory(CommandContext context) { + Sender sender = context.sender(); + String target = context.get("target"); + Integer page = context.getOrDefault("page", 1); + + if (!GrimAPI.INSTANCE.getViolationDatabaseManager().isEnabled()) { + String msg = GrimAPI.INSTANCE.getConfigManager().getConfig() + .getStringElse("grim-history-disabled", + "%prefix% &cHistory subsystem is disabled!"); + sender.sendMessage(MessageUtil.miniMessage(msg)); + return; + } + + GrimAPI.INSTANCE.getScheduler().getAsyncScheduler().runNow(GrimAPI.INSTANCE.getGrimPlugin(), () -> { + int entriesPerPage = GrimAPI.INSTANCE.getConfigManager().getConfig().getIntElse("history.entries-per-page", 15); + String header = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("grim-history-header", + "%prefix% &bShowing logs for &f%player% (&f%page%&b/&f%maxPages%&f)"); + String logFormat = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("grim-history-entry", + "%prefix% &8[&f%server%&8] &bFailed &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% ago&7)"); + + OfflinePlatformPlayer targetPlayer = GrimAPI.INSTANCE.getPlatformPlayerFactory().getOfflineFromName(target); + + ViolationDatabaseManager violations = GrimAPI.INSTANCE.getViolationDatabaseManager(); + int logCount = violations.getLogCount(targetPlayer.getUniqueId()); + List logs = violations.getViolations(targetPlayer.getUniqueId(), page, entriesPerPage); + int maxPages = (int) Math.ceil((float) logCount / entriesPerPage); + + sender.sendMessage(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(sender, header + .replace("%player%", targetPlayer.getName()) + .replace("%page%", String.valueOf(page)) + .replace("%maxPages%", String.valueOf(maxPages)) + ))); + + for (int i = logs.size() - 1; i >= 0; i--) { + Violation log = logs.get(i); + sender.sendMessage(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(sender, logFormat + .replace("%player%", targetPlayer.getName()) + .replace("%check%", log.getCheckName()) + .replace("%verbose%", log.getVerbose()) + .replace("%vl%", String.valueOf(log.getVl())) + .replace("%timeago%", getTimeAgo(log.getCreatedAt())) + .replace("%server%", log.getServerName()) + ))); + } + }); + } + + private String getTimeAgo(Date date) { + long durationMillis = new Date().getTime() - date.getTime(); + + long days = TimeUnit.MILLISECONDS.toDays(durationMillis); + durationMillis -= TimeUnit.DAYS.toMillis(days); + + long hours = TimeUnit.MILLISECONDS.toHours(durationMillis); + durationMillis -= TimeUnit.HOURS.toMillis(hours); + + long minutes = TimeUnit.MILLISECONDS.toMinutes(durationMillis); + durationMillis -= TimeUnit.MINUTES.toMillis(minutes); + + long seconds = TimeUnit.MILLISECONDS.toSeconds(durationMillis); + + StringBuilder result = new StringBuilder(); + if (days > 0) result.append(days).append("d "); + if (hours > 0) result.append(hours).append("h "); + if (minutes > 0) result.append(minutes).append("m "); + if (seconds > 0) result.append(seconds).append("s"); + + return result.toString().trim(); + } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/InitManager.java b/common/src/main/java/ac/grim/grimac/manager/InitManager.java index 649d862159..b91c94a51c 100644 --- a/common/src/main/java/ac/grim/grimac/manager/InitManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/InitManager.java @@ -56,6 +56,7 @@ public InitManager(PacketEventsAPI packetEventsAPI, Supplier GrimAPI.INSTANCE.getDiscordManager().sendAlert(player, verbose, check.getDisplayName(), vl); + case "[log]" -> { + int vls = (int) group.violations.values().stream().filter((e) -> e == check).count(); + String verboseWithoutGl = verbose.replaceAll(" /gl .*", ""); + GrimAPI.INSTANCE.getViolationDatabaseManager().logAlert(player, verboseWithoutGl, check.getDisplayName(), vls); + } case "[proxy]" -> ProxyAlertMessenger.sendPluginMessage(cmd); case "[alert]" -> { sentDebug = true; diff --git a/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java b/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java index 690227e642..7ab0690e57 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/load/PacketEventsInit.java @@ -1,6 +1,5 @@ package ac.grim.grimac.manager.init.load; -import ac.grim.grimac.GrimAPI; import ac.grim.grimac.utils.anticheat.LogUtil; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEventsAPI; diff --git a/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java b/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java index 260dc465cc..bddc957aaa 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java @@ -2,20 +2,7 @@ import ac.grim.grimac.GrimAPI; import ac.grim.grimac.command.SenderRequirement; -import ac.grim.grimac.command.commands.GrimAlerts; -import ac.grim.grimac.command.commands.GrimBrands; -import ac.grim.grimac.command.commands.GrimDebug; -import ac.grim.grimac.command.commands.GrimDump; -import ac.grim.grimac.command.commands.GrimHelp; -import ac.grim.grimac.command.commands.GrimLog; -import ac.grim.grimac.command.commands.GrimPerf; -import ac.grim.grimac.command.commands.GrimProfile; -import ac.grim.grimac.command.commands.GrimReload; -import ac.grim.grimac.command.commands.GrimSendAlert; -import ac.grim.grimac.command.commands.GrimSpectate; -import ac.grim.grimac.command.commands.GrimStopSpectating; -import ac.grim.grimac.command.commands.GrimVerbose; -import ac.grim.grimac.command.commands.GrimVersion; +import ac.grim.grimac.command.commands.*; import ac.grim.grimac.command.handler.GrimCommandFailureHandler; import ac.grim.grimac.platform.api.sender.Sender; import ac.grim.grimac.utils.anticheat.MessageUtil; @@ -60,6 +47,7 @@ public static void registerCommands(CommandManager commandManager) { new GrimProfile().register(commandManager); new GrimSendAlert().register(commandManager); new GrimHelp().register(commandManager); + new GrimHistory().register(commandManager); new GrimReload().register(commandManager); new GrimSpectate().register(commandManager); new GrimStopSpectating().register(commandManager); diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/MySQLViolationDatabase.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/MySQLViolationDatabase.java new file mode 100644 index 0000000000..8434748ce6 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/MySQLViolationDatabase.java @@ -0,0 +1,131 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.api.plugin.GrimPlugin; +import ac.grim.grimac.player.GrimPlayer; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; + +public class MySQLViolationDatabase implements ViolationDatabase { + + private final GrimPlugin plugin; + private HikariDataSource dataSource; + + public MySQLViolationDatabase(GrimPlugin plugin, String url, String database, String username, String password) { + this.plugin = plugin; + setupDataSource(url, database, username, password); + } + + private void setupDataSource(String url, String database, String username, String password) { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl("jdbc:mysql://" + url + "/" + database); + config.setUsername(username); + config.setPassword(password); + config.addDataSourceProperty("cachePrepStmts", "true"); + config.addDataSourceProperty("prepStmtCacheSize", "250"); + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + config.setMaximumPoolSize(10); + config.setAutoCommit(true); + dataSource = new HikariDataSource(config); + } + + @Override + public void connect() { + try (Connection connection = dataSource.getConnection()) { + connection.prepareStatement( + "CREATE TABLE IF NOT EXISTS violations(" + + "id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, " + + "server VARCHAR(255) NOT NULL, " + + "uuid CHAR(36) NOT NULL, " + + "check_name TEXT NOT NULL, " + + "verbose TEXT NOT NULL, " + + "vl INTEGER NOT NULL, " + + "created_at BIGINT NOT NULL" + + ")" + ).execute(); + + connection.prepareStatement( + "CREATE INDEX IF NOT EXISTS idx_violations_uuid ON violations(uuid);" + ).execute(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to generate violations database:", ex); + } + } + + @Override + public synchronized void logAlert(GrimPlayer player, String verbose, String checkName, int vls) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement insertAlert = connection.prepareStatement( + "INSERT INTO violations (server, uuid, check_name, verbose, vl, created_at) VALUES (?, ?, ?, ?, ?, ?)" + ) + ) { + insertAlert.setString(1, GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("history.server-name", "Prison")); + insertAlert.setString(2, player.getUniqueId().toString()); + insertAlert.setString(3, checkName); + insertAlert.setString(4, verbose); + insertAlert.setInt(5, vls); + insertAlert.setLong(6, System.currentTimeMillis()); + insertAlert.execute(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to log alert", ex); + } + } + + @Override + public synchronized int getLogCount(UUID player) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement countLogs = connection.prepareStatement( + "SELECT COUNT(*) FROM violations WHERE uuid = ?" + ) + ) { + countLogs.setString(1, player.toString()); + ResultSet result = countLogs.executeQuery(); + if (result.next()) { + return result.getInt(1); + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to count logs", ex); + } + return 0; + } + + @Override + public synchronized List getViolations(UUID player, int page, int limit) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement fetchLogs = connection.prepareStatement( + "SELECT server, uuid, check_name, verbose, vl, created_at FROM violations" + + " WHERE uuid = ? ORDER BY created_at DESC LIMIT ? OFFSET ?" + ) + ) { + fetchLogs.setString(1, player.toString()); + fetchLogs.setInt(2, limit); + fetchLogs.setInt(3, (page - 1) * limit); + return Violation.fromResultSet(fetchLogs.executeQuery()); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to fetch logs", ex); + return null; + } + } + + @Override + public void disconnect() { + if (dataSource != null && !dataSource.isClosed()) { + dataSource.close(); + } + } + + public boolean sameConfig(String host, String db, String user, String pwd) { + String wantUrl = "jdbc:mysql://" + host + "/" + db; + return wantUrl.equalsIgnoreCase(dataSource.getJdbcUrl()) + && user.equals(dataSource.getUsername()) + && pwd .equals(dataSource.getPassword()); // Hikari stores clear text + } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/NoOpViolationDatabase.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/NoOpViolationDatabase.java new file mode 100644 index 0000000000..5d008d5144 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/NoOpViolationDatabase.java @@ -0,0 +1,17 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.player.GrimPlayer; + +import java.util.List; +import java.util.UUID; + +public final class NoOpViolationDatabase implements ViolationDatabase { + public static final NoOpViolationDatabase INSTANCE = new NoOpViolationDatabase(); + private NoOpViolationDatabase() {} + + @Override public void connect() {} + @Override public void disconnect() {} + @Override public void logAlert(GrimPlayer p, String v, String c, int vl) {} + @Override public int getLogCount(UUID player) { return 0; } + @Override public List getViolations(UUID p, int page, int lim) { return List.of(); } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/SQLiteViolationDatabase.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/SQLiteViolationDatabase.java new file mode 100644 index 0000000000..889a14b143 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/SQLiteViolationDatabase.java @@ -0,0 +1,130 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.api.plugin.GrimPlugin; +import ac.grim.grimac.player.GrimPlayer; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; + +public class SQLiteViolationDatabase implements ViolationDatabase { + + private final GrimPlugin plugin; + + private Connection openConnection; + + public SQLiteViolationDatabase(@NotNull GrimPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void connect() { + try (Connection connection = getConnection()) { + connection.prepareStatement( + "CREATE TABLE IF NOT EXISTS violations(" + + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "server VARCHAR(255) NOT NULL, " + + "uuid CHARACTER(36) NOT NULL, " + + "check_name TEXT NOT NULL, " + + "verbose TEXT NOT NULL, " + + "vl INTEGER NOT NULL, " + + "created_at BIGINT NOT NULL" + + ")" + ).execute(); + + connection.prepareStatement( + "CREATE INDEX IF NOT EXISTS idx_violations_uuid ON violations(uuid)" + ).execute(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to generate violations database:", ex); + } + } + + @Override + public synchronized void logAlert(GrimPlayer player, String verbose, String checkName, int vls) { + try ( + Connection connection = getConnection(); + PreparedStatement insertLog = connection.prepareStatement( + "INSERT INTO violations (server, uuid, check_name, verbose, vl, created_at) VALUES (?, ?, ?, ?, ?, ?)" + ) + ) { + insertLog.setString(1, GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("history.server-name", "Prison")); + insertLog.setString(2, player.getUniqueId().toString()); + insertLog.setString(3, verbose); + insertLog.setString(4, checkName); + insertLog.setInt(5, vls); + insertLog.setLong(6, System.currentTimeMillis()); + + insertLog.executeUpdate(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to insert violation:", ex); + } + } + + public synchronized int getLogCount(UUID player) { + try ( + Connection connection = getConnection(); + PreparedStatement fetchLogs = connection.prepareStatement( + "SELECT COUNT(*) FROM violations WHERE uuid = ?" + ) + ) { + fetchLogs.setString(1, player.toString()); + ResultSet resultSet = fetchLogs.executeQuery(); + if (resultSet.next()) { + return resultSet.getInt(1); + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to fetch number of violations:", ex); + } + return 0; + } + + @Override + public synchronized List getViolations(UUID player, int page, int limit) { + List violations = new ArrayList<>(); + try ( + Connection connection = getConnection(); + PreparedStatement fetchLogs = connection.prepareStatement( + "SELECT server, uuid, check_name, verbose, vl, created_at FROM violations" + + " WHERE uuid = ? ORDER BY created_at DESC LIMIT ? OFFSET ?" + ) + ) { + fetchLogs.setString(1, player.toString()); + fetchLogs.setInt(2, limit); + fetchLogs.setInt(3, (page - 1) * limit); + + return Violation.fromResultSet(fetchLogs.executeQuery()); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to fetch violations:", ex); + } + + return violations; + } + + @Override + public void disconnect() { + try { + if (openConnection != null && !openConnection.isClosed()) { + openConnection.close(); + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to close connection", ex); + } + } + + protected synchronized Connection getConnection() throws SQLException { + if (openConnection == null || openConnection.isClosed()) { + openConnection = openConnection(); + } + return openConnection; + } + + protected Connection openConnection() throws SQLException { + return DriverManager.getConnection("jdbc:sqlite:" + plugin.getDataFolder().getAbsolutePath() + File.separator + "violations.sqlite"); + } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/Violation.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/Violation.java new file mode 100644 index 0000000000..7aae419def --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/Violation.java @@ -0,0 +1,38 @@ +package ac.grim.grimac.manager.violationdatabase; + +import lombok.Data; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +@Data +public class Violation { + + private final String serverName; + private final UUID playerUUID; + private final String checkName; + private final String verbose; + private final int vl; + private final Date createdAt; + + public static List fromResultSet(ResultSet resultSet) throws SQLException { + List violations = new ArrayList<>(); + while(resultSet.next()) { + String server = resultSet.getString("server"); + UUID player = UUID.fromString(resultSet.getString("uuid")); + String checkName = resultSet.getString("check_name"); + String verbose = resultSet.getString("verbose"); + int vl = resultSet.getInt("vl"); + Date createdAt = new Date(resultSet.getLong("created_at")); + + violations.add(new Violation(server, player, checkName, verbose, vl, createdAt)); + } + + return violations; + } + +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabase.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabase.java new file mode 100644 index 0000000000..d749758c84 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabase.java @@ -0,0 +1,20 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.player.GrimPlayer; + +import java.util.List; +import java.util.UUID; + +public interface ViolationDatabase { + + void connect(); + + void logAlert(GrimPlayer player, String verbose, String checkName, int vls); + + int getLogCount(UUID player); + + List getViolations(UUID player, int page, int limit); + + void disconnect(); + +} diff --git a/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabaseManager.java b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabaseManager.java new file mode 100644 index 0000000000..c70b573116 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/manager/violationdatabase/ViolationDatabaseManager.java @@ -0,0 +1,101 @@ +package ac.grim.grimac.manager.violationdatabase; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.api.config.ConfigManager; +import ac.grim.grimac.api.plugin.GrimPlugin; +import ac.grim.grimac.manager.init.ReloadableInitable; +import ac.grim.grimac.manager.init.start.StartableInitable; +import ac.grim.grimac.player.GrimPlayer; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; + +public class ViolationDatabaseManager implements StartableInitable, ReloadableInitable { + + private final GrimPlugin plugin; + + private @NonNull ViolationDatabase database; + + public ViolationDatabaseManager(GrimPlugin plugin) { + this.plugin = plugin; + this.database = NoOpViolationDatabase.INSTANCE; + } + + @Override + public void start() { + load(); + } + + @Override + public void reload() { + load(); + } + + public void load() { + ConfigManager cfg = GrimAPI.INSTANCE.getConfigManager().getConfig(); + boolean enabled = cfg.getBooleanElse("history.enabled", false); + String rawType = enabled ? cfg.getStringElse("history.database.type", "SQLITE").toUpperCase() : "NOOP"; + + switch (rawType) { + case "SQLITE" -> { + if (!(database instanceof SQLiteViolationDatabase)) { + database.disconnect(); + try { + // Init sqlite + Class.forName("org.sqlite.JDBC"); + this.database = new SQLiteViolationDatabase(plugin); + } catch (ClassNotFoundException e) { + plugin.getLogger().log(Level.SEVERE, + """ + Could not load SQLite driver for /grim history database. + Download the minecraft-sqlite-jdbc mod/plugin for SQLite support, or change history.database.type + Alternatively set history.enabled=false if /grim history support is not desired""" + ); + this.database = NoOpViolationDatabase.INSTANCE; + } + database.connect(); + } + } + + case "MYSQL" -> { + String host = cfg.getStringElse("history.database.host", "localhost:3306"); + String db = cfg.getStringElse("history.database.database", "grimac"); + String user = cfg.getStringElse("history.database.username", "root"); + String pwd = cfg.getStringElse("history.database.password", "password"); + + if (database instanceof MySQLViolationDatabase mysql + && mysql.sameConfig(host, db, user, pwd)) { + break; // nothing changed → keep pool + } + database.disconnect(); + database = new MySQLViolationDatabase(plugin, host, db, user, pwd); + database.connect(); + } + + default -> { // NOOP or invalid + if (!(database instanceof NoOpViolationDatabase)) { + database.disconnect(); + database = NoOpViolationDatabase.INSTANCE; + } + } + } + } + + public void logAlert(GrimPlayer player, String verbose, String checkName, int vls) { + GrimAPI.INSTANCE.getScheduler().getAsyncScheduler().runNow(plugin, () -> database.logAlert(player, verbose, checkName, vls)); + } + + public int getLogCount(UUID player) { + return database.getLogCount(player); + } + + public List getViolations(UUID player, int page, int limit) { + return database.getViolations(player, page, limit); + } + + public boolean isEnabled() { + return !(database instanceof NoOpViolationDatabase); + } +} diff --git a/common/src/main/java/ac/grim/grimac/platform/api/entity/GrimEntity.java b/common/src/main/java/ac/grim/grimac/platform/api/entity/GrimEntity.java index 27a49ee292..82236e60c9 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/entity/GrimEntity.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/entity/GrimEntity.java @@ -1,15 +1,13 @@ package ac.grim.grimac.platform.api.entity; +import ac.grim.grimac.api.GrimIdentity; import ac.grim.grimac.platform.api.world.PlatformWorld; import ac.grim.grimac.utils.math.Location; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.UUID; import java.util.concurrent.CompletableFuture; -public interface GrimEntity { - UUID getUniqueId(); - +public interface GrimEntity extends GrimIdentity { /** * Eject any passenger. * diff --git a/common/src/main/java/ac/grim/grimac/platform/api/player/AbstractPlatformPlayerFactory.java b/common/src/main/java/ac/grim/grimac/platform/api/player/AbstractPlatformPlayerFactory.java index ad17f0e56f..61de0c863a 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/player/AbstractPlatformPlayerFactory.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/player/AbstractPlatformPlayerFactory.java @@ -2,6 +2,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; @@ -11,9 +12,8 @@ public abstract class AbstractPlatformPlayerFactory implements PlatformPlayerFactory { protected final PlatformPlayerCache cache = PlatformPlayerCache.getInstance(); - @Override - public @Nullable - final PlatformPlayer getFromUUID(@NonNull UUID uuid) { + @Override @Nullable + public final PlatformPlayer getFromUUID(@NonNull UUID uuid) { // Check cache first PlatformPlayer cachedPlayer = cache.getPlayer(uuid); if (cachedPlayer != null) { @@ -31,6 +31,18 @@ final PlatformPlayer getFromUUID(@NonNull UUID uuid) { return cache.addOrGetPlayer(uuid, platformPlayer); } + @Override @Nullable + public PlatformPlayer getFromName(@NonNull String name) { + T nativePlayer = getNativePlayer(name); + if (nativePlayer == null) { + return null; + } + + // Create new PlatformPlayer and cache it + PlatformPlayer platformPlayer = createPlatformPlayer(nativePlayer); + return cache.addOrGetPlayer(platformPlayer.getUniqueId(), platformPlayer); + } + @Override public final PlatformPlayer getFromNativePlayerType(@NonNull Object playerObject) { if (!isNativePlayerType(playerObject)) { @@ -81,6 +93,8 @@ public void replaceNativePlayer(@NonNull UUID uuid, @NonNull T player) {} */ protected abstract T getNativePlayer(@NonNull UUID uuid); + protected abstract T getNativePlayer(@NonNull String name); + /** * Creates a PlatformPlayer instance from the native player object. * @@ -118,4 +132,11 @@ public void replaceNativePlayer(@NonNull UUID uuid, @NonNull T player) {} * @return a collection of native player objects */ protected abstract Collection getNativeOnlinePlayers(); + + + @Override + public abstract OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid); + + @Override + public abstract OfflinePlatformPlayer getOfflineFromName(@NotNull String name); } diff --git a/common/src/main/java/ac/grim/grimac/platform/api/player/OfflinePlatformPlayer.java b/common/src/main/java/ac/grim/grimac/platform/api/player/OfflinePlatformPlayer.java new file mode 100644 index 0000000000..cc3f6ad596 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/platform/api/player/OfflinePlatformPlayer.java @@ -0,0 +1,10 @@ +package ac.grim.grimac.platform.api.player; + +import ac.grim.grimac.api.GrimIdentity; + +public interface OfflinePlatformPlayer extends GrimIdentity { + + boolean isOnline(); + + String getName(); +} diff --git a/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayer.java b/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayer.java index fea5591c7c..2a1f7d6952 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayer.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayer.java @@ -7,24 +7,20 @@ import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; -public interface PlatformPlayer extends GrimEntity { +public interface PlatformPlayer extends GrimEntity, OfflinePlatformPlayer { void kickPlayer(String textReason); - boolean hasPermission(String s); - - boolean hasPermission(String s, boolean defaultIfUnset); - boolean isSneaking(); void setSneaking(boolean b); - void sendMessage(String message); + boolean hasPermission(String s); - void sendMessage(Component message); + boolean hasPermission(String s, boolean defaultIfUnset); - boolean isOnline(); + void sendMessage(String message); - String getName(); + void sendMessage(Component message); void updateInventory(); diff --git a/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayerFactory.java b/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayerFactory.java index 0326c3a234..742325b433 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayerFactory.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/player/PlatformPlayerFactory.java @@ -4,6 +4,12 @@ import java.util.UUID; public interface PlatformPlayerFactory { + OfflinePlatformPlayer getOfflineFromUUID(UUID uuid); + + OfflinePlatformPlayer getOfflineFromName(String name); + + PlatformPlayer getFromName(String name); + PlatformPlayer getFromUUID(UUID uuid); PlatformPlayer getFromNativePlayerType(Object playerObject); diff --git a/common/src/main/resources/config/de.yml b/common/src/main/resources/config/de.yml index b0e27ce116..2bb819931f 100644 --- a/common/src/main/resources/config/de.yml +++ b/common/src/main/resources/config/de.yml @@ -202,4 +202,20 @@ max-ping-out-of-flying: 1000 # Dies verhindert, dass Spieler mit hoher Latenz einen einzigen Feuerwerks‑Boost mit Elytra unbegrenzt nutzen können. max-ping-firework-boost: 1000 +history: + enabled: true + # Wie viele Einträge sollen pro Seite mit /grim history angezeigt werden + entries-per-page: 15 + # Welcher Servername soll für den History‑Befehl eingetragen werden? Nützlich, wenn dieselbe Datenbank für mehrere Server verwendet wird + server-name: Prison + database: + # SQLITE für lokale Speicherung, MYSQL für eine externe MySQL‑Datenbank. Wird nur nach Serverneustart aktualisiert + type: SQLITE + # MySQL‑Verbindungsdetails + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/en.yml b/common/src/main/resources/config/en.yml index 6daa065ab7..3264487bee 100644 --- a/common/src/main/resources/config/en.yml +++ b/common/src/main/resources/config/en.yml @@ -202,4 +202,20 @@ max-ping-out-of-flying: 1000 # This prevents high latency players from being able to use 1 firework boost with an elytra forever. max-ping-firework-boost: 1000 +history: + enabled: true + # How many entries should be shown for each page with /grim history + entries-per-page: 15 + # What should the inserted server name be for the history command? This is useful if you use the same database for multiple servers + server-name: Prison + database: + # Use SQLITE for local storage, use MYSQL if you have an external MySQL database. This is only updated on server restart + type: SQLITE + # MySQL connection details + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/es.yml b/common/src/main/resources/config/es.yml index c33facb88f..8a22202a4b 100644 --- a/common/src/main/resources/config/es.yml +++ b/common/src/main/resources/config/es.yml @@ -204,4 +204,20 @@ max-ping-out-of-flying: 1000 # Esto evita que los jugadores con alta latencia puedan usar un solo impulso con fuegos artificiales y una elytra para siempre. max-ping-firework-boost: 1000 +history: + enabled: true + # Cuántas entradas se mostrarán por página con /grim history + entries-per-page: 15 + # ¿Qué nombre de servidor debe insertarse para el comando history? Útil si compartes la misma base de datos entre varios servidores + server-name: Prison + database: + # Usa SQLITE para almacenamiento local o MYSQL si tienes una base de datos MySQL externa. Solo se actualiza al reiniciar el servidor + type: SQLITE + # Detalles de conexión MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/fr.yml b/common/src/main/resources/config/fr.yml index e9a8aa1156..cbe174ab68 100644 --- a/common/src/main/resources/config/fr.yml +++ b/common/src/main/resources/config/fr.yml @@ -199,4 +199,20 @@ max-ping-out-of-flying: 1000 # Cela empêche les joueurs à forte latence d’utiliser indéfiniment un seul boost de feu d’artifice avec une élytra. max-ping-firework-boost: 1000 +history: + enabled: true + # Combien d’entrées doivent être affichées par page avec /grim history + entries-per-page: 15 + # Quel nom de serveur doit être inséré pour la commande history ? Utile si vous utilisez la même base de données pour plusieurs serveurs + server-name: Prison + database: + # Utilisez SQLITE pour un stockage local, MYSQL si vous disposez d’une base MySQL externe. Mis à jour seulement au redémarrage du serveur + type: SQLITE + # Détails de connexion MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/it.yml b/common/src/main/resources/config/it.yml index 6c9f474f7d..336bc75a53 100644 --- a/common/src/main/resources/config/it.yml +++ b/common/src/main/resources/config/it.yml @@ -180,4 +180,20 @@ max-ping-out-of-flying: 1000 # Impedisce ai giocatori con alta latenza di usare all’infinito un solo boost con fuochi d’artificio ed elytra. max-ping-firework-boost: 1000 +history: + enabled: true + # Quante voci devono essere mostrate per pagina con /grim history + entries-per-page: 15 + # Quale nome server deve essere inserito per il comando history? Utile se usi lo stesso database per più server + server-name: Prison + database: + # Usa SQLITE per l’archiviazione locale, MYSQL se hai un database MySQL esterno. Aggiornato solo al riavvio del server + type: SQLITE + # Dettagli di connessione MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/ja.yml b/common/src/main/resources/config/ja.yml index 5a9cd74ff9..99c2999809 100644 --- a/common/src/main/resources/config/ja.yml +++ b/common/src/main/resources/config/ja.yml @@ -211,4 +211,20 @@ max-ping-out-of-flying: 1000 # これにより、レイテンシの高いプレイヤーが1つのロケット花火による加速でエリトラを永久に使用するのを防ぎます。 max-ping-firework-boost: 1000 +history: + enabled: true + # /grim history で 1 ページに表示するエントリー数 + entries-per-page: 15 + # history コマンドで挿入されるサーバー名は? 複数サーバーで同じデータベースを使用する場合に便利 + server-name: Prison + database: + # ローカル保存には SQLITE、外部 MySQL データベースには MYSQL を使用します。サーバー再起動時にのみ反映 + type: SQLITE + # MySQL 接続情報 + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/nl.yml b/common/src/main/resources/config/nl.yml index 8aab2b96ba..b8ac021b07 100644 --- a/common/src/main/resources/config/nl.yml +++ b/common/src/main/resources/config/nl.yml @@ -202,4 +202,20 @@ max-ping-out-of-flying: 1000 # Dit voorkomt dat spelers met hoge latency oneindig één vuurwerkboost met een elytra kunnen gebruiken. max-ping-firework-boost: 1000 +history: + enabled: true + # Hoeveel items worden per pagina weergegeven met /grim history + entries-per-page: 15 + # Welke servernaam moet voor het history‑commando worden ingevoerd? Handig als je dezelfde database voor meerdere servers gebruikt + server-name: Prison + database: + # Gebruik SQLITE voor lokale opslag, MYSQL voor een externe MySQL‑database. Alleen bijgewerkt na een serverherstart + type: SQLITE + # MySQL‑verbindingsgegevens + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/pt.yml b/common/src/main/resources/config/pt.yml index 1241ece763..0dfdac895d 100644 --- a/common/src/main/resources/config/pt.yml +++ b/common/src/main/resources/config/pt.yml @@ -207,4 +207,20 @@ max-ping-out-of-flying: 1000 # Previne jogadores com ping alto de usarem um foguete ara voar indefinidamente. max-ping-firework-boost: 1000 +history: + enabled: true + # Quantas entradas devem ser mostradas por página com /grim history + entries-per-page: 15 + # Qual nome de servidor deve ser inserido no comando history? Útil se você usa o mesmo banco de dados para vários servidores + server-name: Prison + database: + # Use SQLITE para armazenamento local ou MYSQL para um banco MySQL externo. Atualizado somente ao reiniciar o servidor + type: SQLITE + # Detalhes de conexão MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/ru.yml b/common/src/main/resources/config/ru.yml index 651a212134..7077d24e68 100644 --- a/common/src/main/resources/config/ru.yml +++ b/common/src/main/resources/config/ru.yml @@ -198,4 +198,20 @@ max-ping-out-of-flying: 1000 # Это предотвращает возможность для игроков с большим пингом бесконечно использовать один фейерверк‑буст с элитрой. max-ping-firework-boost: 1000 +history: + enabled: true + # Сколько записей показывать на странице команды /grim history + entries-per-page: 15 + # Какое имя сервера вставлять для команды history? Полезно при использовании одной базы для нескольких серверов + server-name: Prison + database: + # Используйте SQLITE для локального хранения или MYSQL для внешней MySQL‑БД. Изменения применяются после перезапуска сервера + type: SQLITE + # Параметры подключения MySQL + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/tr.yml b/common/src/main/resources/config/tr.yml index 851630142e..32e21a494e 100644 --- a/common/src/main/resources/config/tr.yml +++ b/common/src/main/resources/config/tr.yml @@ -202,4 +202,20 @@ max-ping-out-of-flying: 1000 # Bu, yüksek gecikmeli oyuncuların bir havai fişek ivmesini elytra ile sonsuza kadar kullanmalarını engeller. max-ping-firework-boost: 1000 +history: + enabled: true + # /grim history ile her sayfada kaç kayıt gösterilsin + entries-per-page: 15 + # History komutu için eklenecek sunucu adı nedir? Aynı veritabanını birden fazla sunucuda kullanıyorsanız faydalıdır + server-name: Prison + database: + # Yerel saklama için SQLITE, harici MySQL veritabanı için MYSQL kullanın. Sadece sunucu yeniden başlatıldığında güncellenir + type: SQLITE + # MySQL bağlantı bilgileri + host: localhost + port: 3306 + database: grim + username: root + password: "" + config-version: 9 diff --git a/common/src/main/resources/config/zh.yml b/common/src/main/resources/config/zh.yml index 0a056dd186..49c046e5fb 100644 --- a/common/src/main/resources/config/zh.yml +++ b/common/src/main/resources/config/zh.yml @@ -205,5 +205,21 @@ max-ping-out-of-flying: 1000 # 填写-1则关闭 max-ping-firework-boost: 1000 +history: + enabled: true + # 使用 /grim history 时每页显示多少条记录 + entries-per-page: 15 + # history 指令中插入的服务器名称?如果多个服务器共用同一数据库则很有用 + server-name: Prison + database: + # 本地存储使用 SQLITE,外部数据库使用 MYSQL。仅在重启服务器后生效 + type: SQLITE + # MySQL 连接详情 + host: localhost + port: 3306 + database: grim + username: root + password: "" + # 不要调整这个 config-version: 9 diff --git a/common/src/main/resources/messages/de.yml b/common/src/main/resources/messages/de.yml index 1a1e23f6f2..745d640f5c 100644 --- a/common/src/main/resources/messages/de.yml +++ b/common/src/main/resources/messages/de.yml @@ -60,4 +60,11 @@ help: - "/grim spectate &f- &7Beobachte einen Spieler" - "/grim verbose &f- &7Zeigt dir jeden Verstoß ohne Puffer an" - "/grim log [0-255] &f- &7Lädt ein Debug-Log für Vorhersagefehler hoch" + - "/grim history [Seite] &f- &7Zeigt frühere Warnungen für den Spieler" - "&7======================" + +grim-history-disabled: "%prefix% &cDas History‑Subsystem ist deaktiviert!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bZeige Logs für &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFehlgeschlagen &f%check% (x&c%vl%&f) &7%verbose% (&bvor %timeago%&7)" diff --git a/common/src/main/resources/messages/en.yml b/common/src/main/resources/messages/en.yml index e2223f58f2..3af1ad2ed7 100644 --- a/common/src/main/resources/messages/en.yml +++ b/common/src/main/resources/messages/en.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7Spectate a player" - "/grim verbose &f- &7Shows every flag to you, without buffers" - "/grim log [0-255] &f- &7Uploads a debug log for prediction flags" + - "/grim history [page] &f- &7Shows previous alerts for the player" - "&7======================" + +grim-history-disabled: "%prefix% &cHistory subsystem is disabled!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bShowing logs for &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFailed &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% ago&7)" diff --git a/common/src/main/resources/messages/es.yml b/common/src/main/resources/messages/es.yml index b0799c9416..9863765dd1 100644 --- a/common/src/main/resources/messages/es.yml +++ b/common/src/main/resources/messages/es.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7Espectar a un jugador" - "/grim verbose &f- &7Te muestra todo aviso, sin buffers" - "/grim log [0-255] &f- &7Sube un registro de depuración para avisos de predicciones" + - "/grim history [página] &f- &7Muestra alertas previas del jugador" - "&7======================" + +grim-history-disabled: "%prefix% &c¡El subsistema de historial está deshabilitado!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bMostrando registros de &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFallido &f%check% (x&c%vl%&f) &7%verbose% (&bhace %timeago%&7)" diff --git a/common/src/main/resources/messages/fr.yml b/common/src/main/resources/messages/fr.yml index 6947e7578f..c67eac53cf 100644 --- a/common/src/main/resources/messages/fr.yml +++ b/common/src/main/resources/messages/fr.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7Regarder un joueur" - "/grim verbose &f- &7Affiche chaqu'une de vos violations, sans tampons" - "/grim log [0-255] &f- &7Téléverse un journal de débogage pour les indicateurs de prédiction" + - "/grim history [page] &f- &7Affiche les alertes précédentes du joueur" - "&7======================" + +grim-history-disabled: "%prefix% &cLe sous‑système d’historique est désactivé !" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bAffichage des journaux pour &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bÉchec &f%check% (x&c%vl%&f) &7%verbose% (&bil y a %timeago%&7)" diff --git a/common/src/main/resources/messages/it.yml b/common/src/main/resources/messages/it.yml index f2bbb0cfad..208b662cf1 100644 --- a/common/src/main/resources/messages/it.yml +++ b/common/src/main/resources/messages/it.yml @@ -53,4 +53,11 @@ help: - "/grim spectate &f- &7Osserva un giocatore" - "/grim verbose &f- &7Mostra ogni segnalazione a te, senza buffer" - "/grim log [0-255] &f- &7Carica un registro di debug per le segnalazioni di previsione" + - "/grim history [pagina] &f- &7Mostra gli avvisi precedenti del giocatore" - "&7======================" + +grim-history-disabled: "%prefix% &cIl sottosistema cronologia è disabilitato!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bMostrando log per &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFallito &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% fa&7)" diff --git a/common/src/main/resources/messages/ja.yml b/common/src/main/resources/messages/ja.yml index a68779a147..463b4f4517 100644 --- a/common/src/main/resources/messages/ja.yml +++ b/common/src/main/resources/messages/ja.yml @@ -58,4 +58,11 @@ help: - "/grim spectate &f- &7プレイヤーを観戦します" - "/grim verbose &f- &7全てのフラグをバッファなしで表示します" - "/grim log [0-255] &f- &7予測フラグのデバッグログをアップロードします" + - "/grim history [ページ] &f- &7プレイヤーの過去のアラートを表示します" - "&7======================" + +grim-history-disabled: "%prefix% &c履歴サブシステムは無効です!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &b%player% のログを表示中 (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &b失敗 &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% 前&7)" diff --git a/common/src/main/resources/messages/nl.yml b/common/src/main/resources/messages/nl.yml index 901f950409..ecfb50a410 100644 --- a/common/src/main/resources/messages/nl.yml +++ b/common/src/main/resources/messages/nl.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7Een speler bekijken" - "/grim verbose &f- &7Toont elke flag, zonder buffers" - "/grim log [0-255] &f- &7Uploadt een debug-log voor voorspellings-flaggen" + - "/grim history [pagina] &f- &7Toont eerdere waarschuwingen voor de speler" - "&7======================" + +grim-history-disabled: "%prefix% &cHet geschiedenissubsysteem is uitgeschakeld!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bLogbestanden tonen voor &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bMislukt &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% geleden&7)" diff --git a/common/src/main/resources/messages/pt.yml b/common/src/main/resources/messages/pt.yml index ebec29b72a..5c94d28aa5 100644 --- a/common/src/main/resources/messages/pt.yml +++ b/common/src/main/resources/messages/pt.yml @@ -59,4 +59,11 @@ help: - "/grim spectate &f- &7Especta um jogador." - "/grim verbose &f- &7Esconde as informações extra." - "/grim log [0-255] &f- &7Envia o registro da simulação." + - "/grim history [página] &f- &7Mostra alertas anteriores do jogador" - "&7======================" + +grim-history-disabled: "%prefix% &cO subsistema de histórico está desativado!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bMostrando logs de &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bFalhou &f%check% (x&c%vl%&f) &7%verbose% (&bhá %timeago%&7)" diff --git a/common/src/main/resources/messages/ru.yml b/common/src/main/resources/messages/ru.yml index 9cec83dcf6..dcede5bc2d 100644 --- a/common/src/main/resources/messages/ru.yml +++ b/common/src/main/resources/messages/ru.yml @@ -61,4 +61,11 @@ help: - "/grim spectate <игрок> &f- &7Наблюдать за игроком" - "/grim verbose &f- &7Показывает все флаги без буферов" - "/grim log [0-255] &f- &7Загружает журнал отладки для флагов предсказания" + - "/grim history [страница] &f- &7Показывает предыдущие предупреждения игрока" - "&7======================" + +grim-history-disabled: "%prefix% &cПодсистема истории отключена!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bПоказ журналов для &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bПровалено &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% назад&7)" diff --git a/common/src/main/resources/messages/tr.yml b/common/src/main/resources/messages/tr.yml index 8b35924c70..5a53e53942 100644 --- a/common/src/main/resources/messages/tr.yml +++ b/common/src/main/resources/messages/tr.yml @@ -61,7 +61,11 @@ help: - "/grim spectate (oyuncu) &f- &7Bir oyuncuyu izlemenizi sağlar" - "/grim verbose &f- &7Arabelleğe almadan tüm uyarıları görmenizi sağlar" - "/grim log (0-255) &f- &7Uyarılar için bir debug logu çıktısı verir" + - "/grim history [sayfa] &f- &7Oyuncunun önceki uyarılarını gösterir" - "&7======================" -# Translated by Kayera -# Have a nice day ;) +grim-history-disabled: "%prefix% &cGeçmiş alt sistemi devre dışı!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &bKayıtlar gösteriliyor: &f%player% (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &bBaşarısız &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% önce&7)" \ No newline at end of file diff --git a/common/src/main/resources/messages/zh.yml b/common/src/main/resources/messages/zh.yml index cf53908b88..8d68ed058d 100644 --- a/common/src/main/resources/messages/zh.yml +++ b/common/src/main/resources/messages/zh.yml @@ -61,4 +61,11 @@ help: - "/grim spectate &f- &7观看玩家" - "/grim verbose &f- &7显示无缓冲区的每个拉回" - "/grim log [1-999] &f- &7预测标志的调试日志" + - "/grim history [页码] &f- &7显示玩家之前的警报" - "&7======================" + +grim-history-disabled: "%prefix% &c历史子系统已禁用!" +# Valid placeholders: %prefix% %player% %page% %maxPages% +grim-history-header: "%prefix% &b显示 &f%player% 的日志 (&f%page%&b/&f%maxPages%&f)" +# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server% +grim-history-entry: "%prefix% &8[&f%server%&8] &b失败 &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% 前&7)" diff --git a/common/src/main/resources/punishments/de.yml b/common/src/main/resources/punishments/de.yml index ae191ec37d..0e2af5d0b8 100644 --- a/common/src/main/resources/punishments/de.yml +++ b/common/src/main/resources/punishments/de.yml @@ -30,6 +30,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -39,6 +40,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -47,6 +49,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -57,6 +60,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -65,6 +69,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -73,6 +78,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -89,6 +95,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -99,6 +106,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Ab 2.2.10 gibt es keine AutoClicker-Prüfungen mehr und dies ist ein Platzhalter. Grim wird in Zukunft AutoClicker-Prüfungen einbauen. Autoclicker: remove-violations-after: 300 @@ -106,3 +114,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/en.yml b/common/src/main/resources/punishments/en.yml index a2201f628f..46b796cd5a 100644 --- a/common/src/main/resources/punishments/en.yml +++ b/common/src/main/resources/punishments/en.yml @@ -30,6 +30,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -39,6 +40,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -47,6 +49,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -57,6 +60,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -65,6 +69,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -73,6 +78,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -89,6 +95,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -99,6 +106,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # As of 2.2.10, there are no AutoClicker checks and this is a placeholder. Grim will include AutoClicker checks in the future. Autoclicker: remove-violations-after: 300 @@ -106,3 +114,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/es.yml b/common/src/main/resources/punishments/es.yml index 65a9379d0a..33104df860 100644 --- a/common/src/main/resources/punishments/es.yml +++ b/common/src/main/resources/punishments/es.yml @@ -30,6 +30,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -39,6 +40,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -47,6 +49,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -57,6 +60,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -65,6 +69,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -73,6 +78,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -89,6 +95,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -99,6 +106,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # A partir de 2.2.10, no hay ninguna comprobación de AutoClicker y esto es un placeholder. # Grim incluirá revisiones de AutoClicker en el futuro. Autoclicker: @@ -107,3 +115,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/fr.yml b/common/src/main/resources/punishments/fr.yml index f7bba0223a..e90c6d9fae 100644 --- a/common/src/main/resources/punishments/fr.yml +++ b/common/src/main/resources/punishments/fr.yml @@ -30,6 +30,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -39,6 +40,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -47,6 +49,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -57,6 +60,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -65,6 +69,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -73,6 +78,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -89,6 +95,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -99,6 +106,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # A partir de la version 2.2.10, il n'y a plus de vérifications pour AutoClicker et c'est un placeholder. Grim inclura des vérifications AutoClicker dans le futur. Autoclicker: remove-violations-after: 300 @@ -106,3 +114,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/it.yml b/common/src/main/resources/punishments/it.yml index 3294ae26ca..4101960b34 100644 --- a/common/src/main/resources/punishments/it.yml +++ b/common/src/main/resources/punishments/it.yml @@ -17,6 +17,7 @@ Punishments: - "NoFall" commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -26,6 +27,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -34,6 +36,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -44,6 +47,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -52,6 +56,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -60,6 +65,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -76,6 +82,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -86,9 +93,11 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" Autoclicker: remove-violations-after: 300 checks: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/ja.yml b/common/src/main/resources/punishments/ja.yml index e698a5832d..c16f1ed177 100644 --- a/common/src/main/resources/punishments/ja.yml +++ b/common/src/main/resources/punishments/ja.yml @@ -30,6 +30,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -39,6 +40,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -47,6 +49,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -57,6 +60,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -65,6 +69,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -73,6 +78,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -89,6 +95,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -98,6 +105,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # バージョン2.2.10時点では、AutoClickerのチェックはまだなく、これはプレースホルダーです。Grimは将来的にAutoClickerのチェックを追加予定です。 Autoclicker: remove-violations-after: 300 @@ -105,3 +113,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/nl.yml b/common/src/main/resources/punishments/nl.yml index 3791d73be4..5d9736da17 100644 --- a/common/src/main/resources/punishments/nl.yml +++ b/common/src/main/resources/punishments/nl.yml @@ -30,6 +30,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -39,6 +40,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -47,6 +49,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -57,6 +60,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -65,6 +69,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -73,6 +78,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -89,6 +95,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -99,6 +106,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Vanaf 2.2.10 zijn er geen AutoClicker-controles en is dit een placeholder. Grim zal in de toekomst AutoClicker-controles toevoegen. Autoclicker: remove-violations-after: 300 @@ -106,3 +114,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/pt.yml b/common/src/main/resources/punishments/pt.yml index 2e882616be..5d6e82c438 100644 --- a/common/src/main/resources/punishments/pt.yml +++ b/common/src/main/resources/punishments/pt.yml @@ -30,6 +30,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -39,6 +40,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -47,6 +49,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -57,6 +60,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -65,6 +69,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -73,6 +78,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -89,6 +95,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -99,6 +106,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Dês da 2.2.10, não temos verificações de AutoClicker, isso é somente um placeholder. Grim vai verificar por AutoClicker no futuro. Autoclicker: remove-violations-after: 300 @@ -106,3 +114,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/ru.yml b/common/src/main/resources/punishments/ru.yml index d88859a704..7976478600 100644 --- a/common/src/main/resources/punishments/ru.yml +++ b/common/src/main/resources/punishments/ru.yml @@ -30,6 +30,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -39,6 +40,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -47,6 +49,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -57,6 +60,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -65,6 +69,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -73,6 +78,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -89,6 +95,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -99,6 +106,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Начиная с версии 2.2.10, проверки на AutoClicker отсутствуют, на их месте пока используется placeholder. Grim будет включать проверки AutoClicker в будущем. Autoclicker: remove-violations-after: 300 @@ -106,3 +114,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/tr.yml b/common/src/main/resources/punishments/tr.yml index afb34e4bea..b2884e7b7d 100644 --- a/common/src/main/resources/punishments/tr.yml +++ b/common/src/main/resources/punishments/tr.yml @@ -28,6 +28,7 @@ Punishments: # Kullanıcı 100 işaretine ulaştığında çalışır ve bundan sonra, 100'ün üzerindeki her 50. işarette çalışır commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -37,6 +38,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -45,6 +47,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -55,6 +58,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -63,6 +67,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -71,6 +76,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -87,6 +93,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -97,6 +104,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # 2.2.10 itibarıyla, AutoClicker denetimleri yoktur ve bu bir yer tutucudur. Grim, gelecekte AutoClicker denetimlerini dahil edecektir. Autoclicker: remove-violations-after: 300 @@ -104,3 +112,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/common/src/main/resources/punishments/zh.yml b/common/src/main/resources/punishments/zh.yml index 78101b050c..e74b03656b 100644 --- a/common/src/main/resources/punishments/zh.yml +++ b/common/src/main/resources/punishments/zh.yml @@ -30,6 +30,7 @@ Punishments: # commands: - "100:40 [alert]" + - "100:40 [log]" - "100:100 [webhook]" - "100:100 [proxy]" Knockback: @@ -39,6 +40,7 @@ Punishments: - "Explosion" commands: - "5:5 [alert]" + - "5:5 [log]" - "20:20 [webhook]" - "20:20 [proxy]" Post: @@ -47,6 +49,7 @@ Punishments: - "Post" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" BadPackets: @@ -57,6 +60,7 @@ Punishments: - "Crash" commands: - "20:20 [alert]" + - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" Reach: @@ -65,6 +69,7 @@ Punishments: - "Reach" commands: - "1:1 [alert]" + - "1:1 [log]" - "1:1 [webhook]" - "1:1 [proxy]" Hitboxes: @@ -73,6 +78,7 @@ Punishments: - "Hitboxes" commands: - "5:3 [alert]" + - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" Misc: @@ -89,6 +95,7 @@ Punishments: - "Elytra" commands: - "10:5 [alert]" + - "10:5 [log]" - "20:10 [webhook]" - "20:10 [proxy]" Combat: @@ -99,6 +106,7 @@ Punishments: - "Aim" commands: - "20:40 [alert]" + - "20:40 [log]" # Grim2.0.10版本 没有连点器检查,Grim将在未来添加 Autoclicker: remove-violations-after: 300 @@ -106,3 +114,4 @@ Punishments: - "Autoclicker" commands: - "20:40 [alert]" + - "20:40 [log]" diff --git a/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java b/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java index 07b72c1859..7896097c5d 100644 --- a/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java +++ b/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java @@ -1,6 +1,6 @@ package ac.grim.grimac.platform.fabric.mc1161; -import ac.grim.grimac.platform.api.PlatformServer; +import ac.grim.grimac.platform.fabric.AbstractFabricPlatformServer; import ac.grim.grimac.platform.fabric.command.FabricPlayerSelectorParser; import ac.grim.grimac.platform.fabric.manager.FabricParserDescriptorFactory; import ac.grim.grimac.platform.fabric.mc1161.command.Fabric1161PlayerSelectorAdapter; @@ -31,7 +31,7 @@ public GrimACFabric1161LoaderPlugin() { protected GrimACFabric1161LoaderPlugin( FabricPlatformPlayerFactory playerFactory, - PlatformServer platformServer, + AbstractFabricPlatformServer platformServer, IFabricMessageUtil fabricMessageUtil, IFabricConversionUtil fabricConversionUtil ) { diff --git a/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/Fabric1171PlatformServer.java b/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/Fabric1171PlatformServer.java new file mode 100644 index 0000000000..4fe9c5673a --- /dev/null +++ b/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/Fabric1171PlatformServer.java @@ -0,0 +1,18 @@ +package ac.grim.grimac.platform.fabric.mc1171; + +import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; +import ac.grim.grimac.platform.fabric.mc1161.Fabric1140PlatformServer; +import com.mojang.authlib.GameProfile; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + + +public class Fabric1171PlatformServer extends Fabric1140PlatformServer { + + @Override @Nullable + public GameProfile getProfileByName(String name) { + Optional gameProfile = GrimACFabricLoaderPlugin.FABRIC_SERVER.getUserCache().findByName(name); + return gameProfile.orElse(null); + } +} diff --git a/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/GrimACFabric1170LoaderPlugin.java b/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/GrimACFabric1170LoaderPlugin.java index 7c0efe8194..28080d0702 100644 --- a/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/GrimACFabric1170LoaderPlugin.java +++ b/fabric/mc1171/src/main/java/ac/grim/grimac/platform/fabric/mc1171/GrimACFabric1170LoaderPlugin.java @@ -1,6 +1,6 @@ package ac.grim.grimac.platform.fabric.mc1171; -import ac.grim.grimac.platform.api.PlatformServer; +import ac.grim.grimac.platform.fabric.AbstractFabricPlatformServer; import ac.grim.grimac.platform.api.manager.ParserDescriptorFactory; import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; import ac.grim.grimac.platform.fabric.command.FabricPlayerSelectorParser; @@ -15,6 +15,7 @@ import ac.grim.grimac.platform.fabric.player.FabricPlatformPlayerFactory; import ac.grim.grimac.platform.fabric.utils.convert.IFabricConversionUtil; import ac.grim.grimac.platform.fabric.utils.message.IFabricMessageUtil; +import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.manager.server.ServerVersion; public class GrimACFabric1170LoaderPlugin extends GrimACFabricLoaderPlugin { @@ -28,7 +29,8 @@ public GrimACFabric1170LoaderPlugin() { Fabric1170GrimEntity::new, Fabric1161PlatformInventory::new ), - new Fabric1140PlatformServer(), + PacketEvents.getAPI().getServerManager().getVersion().isNewerThan(ServerVersion.V_1_17) + ? new Fabric1171PlatformServer() : new Fabric1140PlatformServer(), new Fabric1161MessageUtil(), new Fabric1140ConversionUtil() ); @@ -36,7 +38,7 @@ public GrimACFabric1170LoaderPlugin() { protected GrimACFabric1170LoaderPlugin(ParserDescriptorFactory parserDescriptorFactory, FabricPlatformPlayerFactory playerFactory, - PlatformServer platformServer, + AbstractFabricPlatformServer platformServer, IFabricMessageUtil fabricMessageUtil, IFabricConversionUtil fabricConversionUtil) { super( diff --git a/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/Fabric1190PlatformServer.java b/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/Fabric1190PlatformServer.java index b5f9b0e1a4..4f4551e034 100644 --- a/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/Fabric1190PlatformServer.java +++ b/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/Fabric1190PlatformServer.java @@ -2,10 +2,10 @@ import ac.grim.grimac.platform.api.sender.Sender; import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; -import ac.grim.grimac.platform.fabric.mc1161.Fabric1140PlatformServer; +import ac.grim.grimac.platform.fabric.mc1171.Fabric1171PlatformServer; import net.minecraft.server.command.ServerCommandSource; -public class Fabric1190PlatformServer extends Fabric1140PlatformServer { +public class Fabric1190PlatformServer extends Fabric1171PlatformServer { @Override public void dispatchCommand(Sender sender, String command) { ServerCommandSource commandSource = GrimACFabricLoaderPlugin.LOADER.getFabricSenderFactory().reverse(sender); diff --git a/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/GrimACFabric1190LoaderPlugin.java b/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/GrimACFabric1190LoaderPlugin.java index e3367d5e5c..87c9a18d43 100644 --- a/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/GrimACFabric1190LoaderPlugin.java +++ b/fabric/mc1194/src/main/java/ac/grim/grimac/platform/fabric/mc1194/GrimACFabric1190LoaderPlugin.java @@ -1,6 +1,6 @@ package ac.grim.grimac.platform.fabric.mc1194; -import ac.grim.grimac.platform.api.PlatformServer; +import ac.grim.grimac.platform.fabric.AbstractFabricPlatformServer; import ac.grim.grimac.platform.api.manager.ParserDescriptorFactory; import ac.grim.grimac.platform.fabric.mc1161.command.Fabric1161PlayerSelectorAdapter; import ac.grim.grimac.platform.fabric.command.FabricPlayerSelectorParser; @@ -40,7 +40,7 @@ public GrimACFabric1190LoaderPlugin() { protected GrimACFabric1190LoaderPlugin( ParserDescriptorFactory parserDescriptorFactory, FabricPlatformPlayerFactory platformPlayerFactory, - PlatformServer platformServer, + AbstractFabricPlatformServer platformServer, IFabricMessageUtil fabricMessageUtil, IFabricConversionUtil fabricConversionUtil) { super(parserDescriptorFactory, platformPlayerFactory, platformServer, fabricMessageUtil, fabricConversionUtil); diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/AbstractFabricPlatformServer.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/AbstractFabricPlatformServer.java index 8848206f7c..ce7464811a 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/AbstractFabricPlatformServer.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/AbstractFabricPlatformServer.java @@ -2,8 +2,10 @@ import ac.grim.grimac.platform.api.PlatformServer; import ac.grim.grimac.platform.api.sender.Sender; +import com.mojang.authlib.GameProfile; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.server.command.ServerCommandSource; +import org.jetbrains.annotations.Nullable; public abstract class AbstractFabricPlatformServer implements PlatformServer { @@ -24,4 +26,9 @@ public Sender getConsoleSender() { public void registerOutgoingPluginChannel(String name) { throw new UnsupportedOperationException(); } + + @Nullable + public GameProfile getProfileByName(String name) { + return GrimACFabricLoaderPlugin.FABRIC_SERVER.getUserCache().findByName(name); + } } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java index a9e21e04cb..0ae028717d 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java @@ -5,7 +5,6 @@ import ac.grim.grimac.api.GrimAPIProvider; import ac.grim.grimac.api.plugin.GrimPlugin; import ac.grim.grimac.platform.api.PlatformLoader; -import ac.grim.grimac.platform.api.PlatformServer; import ac.grim.grimac.platform.api.manager.*; import ac.grim.grimac.platform.api.sender.Sender; import ac.grim.grimac.platform.api.sender.SenderFactory; @@ -58,7 +57,7 @@ public abstract class GrimACFabricLoaderPlugin implements PlatformLoader { protected final ParserDescriptorFactory parserFactory; protected final FabricPlatformPlayerFactory playerFactory; - protected final PlatformServer platformServer; + protected final AbstractFabricPlatformServer platformServer; @Getter protected final IFabricConversionUtil fabricConversionUtil; protected final IFabricMessageUtil fabricMessageUtil; @@ -66,7 +65,7 @@ public abstract class GrimACFabricLoaderPlugin implements PlatformLoader { public GrimACFabricLoaderPlugin( ParserDescriptorFactory parserDescriptorFactory, FabricPlatformPlayerFactory playerFactory, - PlatformServer platformServer, + AbstractFabricPlatformServer platformServer, IFabricMessageUtil fabricMessageUtil, IFabricConversionUtil fabricConversionUtil ) { @@ -149,7 +148,7 @@ public FabricPlatformPlayerFactory getPlatformPlayerFactory() { } @Override - public PlatformServer getPlatformServer() { + public AbstractFabricPlatformServer getPlatformServer() { return platformServer; } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricOfflinePlatformPlayer.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricOfflinePlatformPlayer.java new file mode 100644 index 0000000000..98773c9265 --- /dev/null +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricOfflinePlatformPlayer.java @@ -0,0 +1,40 @@ +package ac.grim.grimac.platform.fabric.player; + +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; +import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public class FabricOfflinePlatformPlayer implements OfflinePlatformPlayer { + private final UUID uuid; + private final String name; + + public FabricOfflinePlatformPlayer(@NotNull UUID uuid, @NotNull String name) { + this.uuid = uuid; + this.name = name; + } + + @Override + public boolean isOnline() { + return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayer(uuid) != null; + } + + @Override + public @NotNull String getName() { + return name; + } + + @Override + public @NotNull UUID getUniqueId() { + return uuid; + } + + @Override + public boolean equals(Object o) { + if (o instanceof OfflinePlatformPlayer offlinePlatformPlayer) { + return this.getUniqueId().equals(offlinePlatformPlayer.getUniqueId()); + } + return false; + } +} diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricPlatformPlayerFactory.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricPlatformPlayerFactory.java index fadad12b84..cfba9bd15d 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricPlatformPlayerFactory.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/player/FabricPlatformPlayerFactory.java @@ -2,18 +2,24 @@ import ac.grim.grimac.platform.api.entity.GrimEntity; import ac.grim.grimac.platform.api.player.AbstractPlatformPlayerFactory; +import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer; import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; +import com.mojang.authlib.GameProfile; import net.minecraft.entity.Entity; import net.minecraft.server.network.ServerPlayerEntity; import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.NotNull; +import java.nio.charset.StandardCharsets; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; import java.util.function.Function; public class FabricPlatformPlayerFactory extends AbstractPlatformPlayerFactory { + private final Map offlinePlatformPlayerCache = new HashMap<>(); private final Function getPlayerFunction; private final Function getEntityFunction; private final Function getPlayerInventoryFunction; @@ -32,6 +38,11 @@ protected ServerPlayerEntity getNativePlayer(@NotNull UUID uuid) { return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayer(uuid); } + @Override + protected ServerPlayerEntity getNativePlayer(@NonNull String name) { + return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayer(name); + } + @Override protected AbstractFabricPlatformPlayer createPlatformPlayer(@NotNull ServerPlayerEntity nativePlayer) { return getPlayerFunction.apply(nativePlayer); @@ -58,6 +69,43 @@ protected Collection getNativeOnlinePlayers() { return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayerList(); } + @Override + public OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid) { + return null; + } + + @Override + public OfflinePlatformPlayer getOfflineFromName(@NotNull String name) { + OfflinePlatformPlayer result = this.getFromName(name); + if (result == null) { + GameProfile profile = null; + // Only fetch an online UUID in online mode + // TODO (cross-platform) add a config option for "offline-mode" servers with online-mode behind a proxy + if (GrimACFabricLoaderPlugin.FABRIC_SERVER.isOnlineMode()) { + // THIS CAN BLOCK THE CALLING THREAD! + profile = GrimACFabricLoaderPlugin.LOADER.getPlatformServer().getProfileByName(name); + } + + if (profile == null) { + // Make an OfflinePlayer using an offline mode UUID since the name has no profile + result = this.getOfflinePlayer(new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8)), name)); + } else { + // Use the GameProfile even when we get a UUID so we ensure we still have a name + result = this.getOfflinePlayer(profile); + } + } else { + this.offlinePlatformPlayerCache.remove(result.getUniqueId()); + } + + return result; + } + + public OfflinePlatformPlayer getOfflinePlayer(GameProfile profile) { + OfflinePlatformPlayer player = new FabricOfflinePlatformPlayer(profile.getId(), profile.getName()); + this.offlinePlatformPlayerCache.put(profile.getId(), player); + return player; + } + @Override public void replaceNativePlayer(@NonNull UUID uuid, @NonNull ServerPlayerEntity serverPlayerEntity) { super.cache.getPlayer(uuid).replaceNativePlayer(serverPlayerEntity); From 8e9ad26fb397197ccf54831d65a2bbcd66e4e82a Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sat, 26 Apr 2025 12:13:32 -0400 Subject: [PATCH 14/34] Add Inventory checks --- .../checks/impl/inventory/InventoryA.java | 42 +++++ .../checks/impl/inventory/InventoryB.java | 35 ++++ .../checks/impl/inventory/InventoryC.java | 30 ++++ .../checks/impl/inventory/InventoryD.java | 85 +++++++++ .../checks/impl/inventory/InventoryE.java | 50 ++++++ .../checks/impl/inventory/InventoryF.java | 46 +++++ .../checks/impl/inventory/InventoryG.java | 40 +++++ .../grimac/checks/type/InventoryCheck.java | 67 +++++++ .../events/packets/CheckManagerListener.java | 3 + .../events/packets/PacketPlayerWindow.java | 163 ++++++++++++++++++ .../ac/grim/grimac/manager/CheckManager.java | 8 + .../manager/init/start/PacketManager.java | 17 +- .../ac/grim/grimac/player/GrimPlayer.java | 4 + .../predictionengine/PointThreeEstimator.java | 6 + .../predictions/PredictionEngine.java | 41 +++-- .../ac/grim/grimac/utils/data/VectorData.java | 30 +++- .../inventory/InventoryDesyncStatus.java | 7 + .../utils/latency/CompensatedInventory.java | 2 +- common/src/main/resources/punishments/de.yml | 9 + common/src/main/resources/punishments/en.yml | 9 + common/src/main/resources/punishments/es.yml | 9 + common/src/main/resources/punishments/fr.yml | 9 + common/src/main/resources/punishments/it.yml | 9 + common/src/main/resources/punishments/ja.yml | 9 + common/src/main/resources/punishments/nl.yml | 9 + common/src/main/resources/punishments/pt.yml | 9 + common/src/main/resources/punishments/ru.yml | 9 + common/src/main/resources/punishments/tr.yml | 9 + common/src/main/resources/punishments/zh.yml | 9 + 29 files changed, 743 insertions(+), 32 deletions(-) create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryA.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryB.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryC.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryD.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryE.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryF.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryG.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/type/InventoryCheck.java create mode 100644 common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerWindow.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/inventory/InventoryDesyncStatus.java diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryA.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryA.java new file mode 100644 index 0000000000..1e15d49bba --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryA.java @@ -0,0 +1,42 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity.InteractAction; + +@CheckData(name = "InventoryA", setback = 3, description = "Attacked an entity while inventory is open") +public class InventoryA extends InventoryCheck { + public InventoryA(GrimPlayer player) { + super(player); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + super.onPacketReceive(event); + + if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) { + WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event); + + if (wrapper.getAction() != InteractAction.ATTACK) return; + + // Is not possible to attack while the inventory is open. + if (player.hasInventoryOpen) { + if (flagAndAlert()) { + // Cancel the packet + if (shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + } + if (!isNoSetbackPermission()) + closeInventory(); + } + } else { + reward(); + } + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryB.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryB.java new file mode 100644 index 0000000000..ee42ad0a2b --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryB.java @@ -0,0 +1,35 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.player.DiggingAction; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging; + +@CheckData(name = "InventoryB", setback = 3, description = "Started digging blocks while inventory is open") +public class InventoryB extends InventoryCheck { + public InventoryB(GrimPlayer player) { + super(player); + } + + public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging wrapper) { + if (wrapper.getAction() != DiggingAction.START_DIGGING) return; + + // Is not possible to start digging a block while the inventory is open. + if (player.hasInventoryOpen) { + if (flagAndAlert()) { + // Cancel the packet + if (shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + } + if (!isNoSetbackPermission()) { + closeInventory(); + } + } + } else { + reward(); + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryC.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryC.java new file mode 100644 index 0000000000..20d3852671 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryC.java @@ -0,0 +1,30 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.anticheat.update.BlockPlace; + +@CheckData(name = "InventoryC", setback = 3, description = "Placed a block while inventory is open") +public class InventoryC extends InventoryCheck { + + public InventoryC(GrimPlayer player) { + super(player); + } + + public void onBlockPlace(final BlockPlace place) { + // It is not possible to place a block while the inventory is open + if (player.hasInventoryOpen) { + if (flagAndAlert()) { + if (shouldModifyPackets()) { + place.resync(); + } + if (!isNoSetbackPermission()) { + closeInventory(); + } + } + } else { + reward(); + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryD.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryD.java new file mode 100644 index 0000000000..b643e4b9c0 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryD.java @@ -0,0 +1,85 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.anticheat.update.PredictionComplete; +import ac.grim.grimac.utils.data.VectorData; +import ac.grim.grimac.utils.data.VectorData.MoveVectorData; +import ac.grim.grimac.utils.data.VehicleData; + +import java.util.StringJoiner; + +@CheckData(name = "InventoryD", setback = 1, decay = 0.25) +public class InventoryD extends InventoryCheck { + private int horseJumpVerbose; + + public InventoryD(GrimPlayer player) { + super(player); + } + + @Override + public void onPredictionComplete(final PredictionComplete predictionComplete) { + if (!predictionComplete.isChecked() || + predictionComplete.getData().isTeleport() || + player.getSetbackTeleportUtil().blockOffsets || + player.packetStateData.lastPacketWasTeleport || + player.packetStateData.isSlowedByUsingItem() || + System.currentTimeMillis() - player.lastBlockPlaceUseItem < 50L) { + return; + } + + if (player.hasInventoryOpen) { + boolean inVehicle = player.inVehicle(); + boolean isJumping, isMoving; + + if (inVehicle) { + VehicleData vehicle = player.vehicleData; + + // Will flag once if player open anything with pressed space bar + isJumping = vehicle.nextHorseJump > 0 && horseJumpVerbose++ >= 1; + isMoving = vehicle.nextVehicleForward != 0 || vehicle.nextVehicleHorizontal != 0; + } else { + MoveVectorData move = findMovement(player.predictedVelocity); + + isJumping = player.predictedVelocity.isJump(); + isMoving = move != null && (move.x != 0 || move.z != 0); + } + + if (!isMoving && !isJumping) { + reward(); + return; + } + + if (flag()) { + if (!isNoSetbackPermission()) + closeInventory(); + + StringJoiner joiner = new StringJoiner(" "); + + if (isMoving) joiner.add("moving"); + if (isJumping) joiner.add("jumping"); + if (inVehicle) joiner.add("inVehicle"); + + alert(joiner.toString()); + } + } else { + horseJumpVerbose = 0; + } + } + + private MoveVectorData findMovement(VectorData vectorData) { + if (vectorData instanceof MoveVectorData) { + return (MoveVectorData) vectorData; + } + + while (vectorData != null) { + vectorData = vectorData.lastVector; + if (vectorData instanceof MoveVectorData) { + return (MoveVectorData) vectorData; + } + } + + return null; + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryE.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryE.java new file mode 100644 index 0000000000..caf6270001 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryE.java @@ -0,0 +1,50 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; + +@CheckData(name = "InventoryE", setback = 3, description = "Sent a held item change packet while inventory is open") +public class InventoryE extends InventoryCheck { + private long lastTransaction = Long.MAX_VALUE; // Impossible transaction ID + + public InventoryE(GrimPlayer player) { + super(player); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + super.onPacketReceive(event); + + if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) { + // It is not possible to change hotbar slots with held item change while the inventory is open + // A container click packet would be sent instead + if (player.hasInventoryOpen) { + if (this.lastTransaction < player.lastTransactionReceived.get() + && flagAndAlert()) { + // Cancel the packet + if (shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + player.getInventory().needResend = true; + } + if (!isNoSetbackPermission()) { + closeInventory(); + } + } + } else { + reward(); + } + } + } + + @Override + public void onPacketSend(PacketSendEvent event) { + if (event.getPacketType() == PacketType.Play.Server.HELD_ITEM_CHANGE) { + this.lastTransaction = player.lastTransactionSent.get(); + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryF.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryF.java new file mode 100644 index 0000000000..28171b20e7 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryF.java @@ -0,0 +1,46 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.inventory.InventoryDesyncStatus; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.manager.server.ServerVersion; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.player.ClientVersion; + +@CheckData(name = "InventoryF", setback = 3, description = "Sent a click window packet without a open inventory", experimental = true) +public class InventoryF extends InventoryCheck { + + public InventoryF(GrimPlayer player) { + super(player); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + // Exempt on 1.9+ server version due to the Via hack done in PacketPlayerWindow, the exemption can be deleted + // once we are ahead of ViaVersion + if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9) + || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) return; + + super.onPacketReceive(event); + + if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) { + if (!player.hasInventoryOpen && player.inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) { + if (flagAndAlert()) { + // Cancel the packet + if (shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + } + if (!isNoSetbackPermission()) { + closeInventory(); + } + } + } else { + reward(); + } + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryG.java b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryG.java new file mode 100644 index 0000000000..a189df88cd --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/inventory/InventoryG.java @@ -0,0 +1,40 @@ +package ac.grim.grimac.checks.impl.inventory; + +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.InventoryCheck; +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction; + +@CheckData(name = "InventoryG", setback = 3, description = "Sent a entity action packet while inventory is open", experimental = true) +public class InventoryG extends InventoryCheck { + + public InventoryG(GrimPlayer player) { + super(player); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + if (player.packetStateData.lastPacketWasTeleport) return; + super.onPacketReceive(event); + + if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) { + WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event); + WrapperPlayClientEntityAction.Action action = wrapper.getAction(); + + if (action == WrapperPlayClientEntityAction.Action.STOP_SNEAKING + || action == WrapperPlayClientEntityAction.Action.STOP_SPRINTING) { + return; + } + + if (player.hasInventoryOpen) { + if (flagAndAlert() && !isNoSetbackPermission()) { + closeInventory(); + } + } else { + reward(); + } + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/type/InventoryCheck.java b/common/src/main/java/ac/grim/grimac/checks/type/InventoryCheck.java new file mode 100644 index 0000000000..5c3f1ed94c --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/type/InventoryCheck.java @@ -0,0 +1,67 @@ +package ac.grim.grimac.checks.type; + +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCloseWindow; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerCloseWindow; +import org.jetbrains.annotations.MustBeInvokedByOverriders; + +public class InventoryCheck extends BlockPlaceCheck implements PacketCheck { + // Impossible transaction ID + protected static final long NONE = Long.MAX_VALUE; + + protected long closeTransaction = NONE; + protected int closePacketsToSkip; + + public InventoryCheck(GrimPlayer player) { + super(player); + } + + @Override + @MustBeInvokedByOverriders + public void onPacketReceive(final PacketReceiveEvent event) { + if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) { + // Disallow any clicks if inventory is closing + if (closeTransaction != NONE && shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + player.getInventory().needResend = true; + } + } else if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) { + // Players with high ping can close inventory faster than send transaction back + if (closeTransaction != NONE && closePacketsToSkip-- <= 0) { + closeTransaction = NONE; + } + } + } + + public void closeInventory() { + if (closeTransaction != NONE) { + return; + } + + int windowId = player.getInventory().openWindowID; + + player.user.writePacket(new WrapperPlayServerCloseWindow(windowId)); + + // Force close inventory on server side + closePacketsToSkip = 1; // Sending close packet to itself, so skip it + PacketEvents.getAPI().getProtocolManager().receivePacket( + player.user.getChannel(), new WrapperPlayClientCloseWindow(windowId) + ); + + player.sendTransaction(); + + int transaction = player.lastTransactionSent.get(); + closeTransaction = transaction; + player.latencyUtils.addRealTimeTask(transaction, () -> { + if (closeTransaction == transaction) { + closeTransaction = NONE; + } + }); + + player.user.flushPackets(); + } +} diff --git a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java index 12dd35c221..df59d019f0 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java @@ -1,6 +1,7 @@ package ac.grim.grimac.events.packets; import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.checks.impl.inventory.InventoryB; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.update.BlockBreak; import ac.grim.grimac.utils.anticheat.update.BlockPlace; @@ -564,6 +565,8 @@ public void onPacketReceive(PacketReceiveEvent event) { final WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event); final DiggingAction action = packet.getAction(); + player.checkManager.getPacketCheck(InventoryB.class).handle(event, packet); + if (action == DiggingAction.START_DIGGING || action == DiggingAction.FINISHED_DIGGING || action == DiggingAction.CANCELLED_DIGGING) { final BlockBreak blockBreak = new BlockBreak(player, packet.getBlockPosition(), packet.getBlockFace(), packet.getBlockFaceId(), action, packet.getSequence(), player.compensatedWorld.getBlock(packet.getBlockPosition())); diff --git a/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerWindow.java b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerWindow.java new file mode 100644 index 0000000000..404bf1b738 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerWindow.java @@ -0,0 +1,163 @@ +package ac.grim.grimac.events.packets; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.data.packetentity.PacketEntitySelf; +import ac.grim.grimac.utils.inventory.InventoryDesyncStatus; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketListenerAbstract; +import com.github.retrooper.packetevents.event.PacketListenerPriority; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.manager.server.ServerVersion; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.player.ClientVersion; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus.Action; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow; + +public class PacketPlayerWindow extends PacketListenerAbstract { + + public PacketPlayerWindow() { + super(PacketListenerPriority.LOWEST); + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !event.isCancelled()) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + if (player.hasInventoryOpen && isNearNetherPortal(player)) { + handleInventoryClose(player, InventoryDesyncStatus.NETHER_PORTAL); + } + } + + // Client Status is sent in 1.7-1.8 + if (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS) { + WrapperPlayClientClientStatus wrapper = new WrapperPlayClientClientStatus(event); + + if (wrapper.getAction() == Action.OPEN_INVENTORY_ACHIEVEMENT) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + handleInventoryOpen(player); + } + } + + // We need to do this due to 1.9 not sending anymore the inventory action in the Client Status + if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + // TODO: Remove this check after we finish the before ViaVersion injection + // Explanation: On 1.7 and 1.8 we have OPEN_INVENTORY_ACHIEVEMENT on CLIENT_STATUS packet + // but after Via translation this information gets lost + // This is a workaround to atleast make our inventory checks work "decently" in 1.8 clients for 1.9+ servers + if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9) + && player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) { + handleInventoryOpen(player); + } + + if (player.getClientVersion().isNewerThan(ClientVersion.V_1_8)) { + handleInventoryOpen(player); + } + } + + if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED); + } + } + + @Override + public void onPacketSend(PacketSendEvent event) { + if (event.getPacketType() == PacketType.Play.Server.RESPAWN) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + player.sendTransaction(); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED)); + } else if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) { + WrapperPlayServerOpenWindow wrapper = new WrapperPlayServerOpenWindow(event); + + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + player.sendTransaction(); + + String legacyType = wrapper.getLegacyType(); + int modernType = wrapper.getType(); + InventoryDesyncStatus inventoryDesyncStatus = getContainerDesyncStatus(player, legacyType, modernType); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> { + if (inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) { + handleInventoryOpen(player); + } else { + handleInventoryClose(player, inventoryDesyncStatus); + } + }); + } else if (event.getPacketType() == PacketType.Play.Server.OPEN_HORSE_WINDOW) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + player.sendTransaction(); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> handleInventoryOpen(player)); + } else if (event.getPacketType() == PacketType.Play.Server.CLOSE_WINDOW) { + GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + player.sendTransaction(); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED)); + } + } + + private void handleInventoryOpen(GrimPlayer player) { + if (!player.hasInventoryOpen) { + player.lastInventoryOpen = System.currentTimeMillis(); + } + + player.hasInventoryOpen = true; + } + + private void handleInventoryClose(GrimPlayer player, InventoryDesyncStatus desyncStatus) { + player.hasInventoryOpen = false; + player.inventoryDesyncStatus = desyncStatus; + } + + public InventoryDesyncStatus getContainerDesyncStatus(GrimPlayer player, String legacyType, int modernType) { + // Closing beacon with the cross button cause desync in 1.7-1.8 + if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) && + ("minecraft:beacon".equals(legacyType) || modernType == 8)) { + return player.inventoryDesyncStatus = InventoryDesyncStatus.BEACON; + } + + if (isNearNetherPortal(player)) { + return player.inventoryDesyncStatus = InventoryDesyncStatus.NETHER_PORTAL; + } + + return player.inventoryDesyncStatus = InventoryDesyncStatus.NOT_DESYNCED; + } + + public boolean isNearNetherPortal(GrimPlayer player) { + // Going inside nether portal with opened inventory cause desync, fixed in 1.12.2 + if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_12_1) && + player.pointThreeEstimator.isNearNetherPortal) { + PacketEntitySelf playerEntity = player.compensatedEntities.self; + // Client ignore nether portal if player has passengers or riding an entity + return !playerEntity.inVehicle() && playerEntity.passengers.isEmpty(); + } + + return false; + } +} diff --git a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java index f132705602..7542f1bfdd 100644 --- a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java @@ -34,6 +34,7 @@ import ac.grim.grimac.checks.impl.exploit.ExploitB; import ac.grim.grimac.checks.impl.exploit.ExploitC; import ac.grim.grimac.checks.impl.groundspoof.NoFall; +import ac.grim.grimac.checks.impl.inventory.*; import ac.grim.grimac.checks.impl.misc.ClientBrand; import ac.grim.grimac.checks.impl.misc.GhostBlockMitigation; import ac.grim.grimac.checks.impl.misc.TransactionOrder; @@ -154,6 +155,11 @@ public CheckManager(GrimPlayer player) { .put(BadPacketsU.class, new BadPacketsU(player)) .put(BadPacketsV.class, new BadPacketsV(player)) .put(BadPacketsY.class, new BadPacketsY(player)) + .put(InventoryA.class, new InventoryA(player)) + .put(InventoryB.class, new InventoryB(player)) + .put(InventoryE.class, new InventoryE(player)) + .put(InventoryF.class, new InventoryF(player)) + .put(InventoryG.class, new InventoryG(player)) .put(MultiActionsA.class, new MultiActionsA(player)) .put(MultiActionsB.class, new MultiActionsB(player)) .put(MultiActionsC.class, new MultiActionsC(player)) @@ -189,6 +195,7 @@ public CheckManager(GrimPlayer player) { .put(ExplosionHandler.class, new ExplosionHandler(player)) .put(KnockbackHandler.class, new KnockbackHandler(player)) .put(GhostBlockDetector.class, new GhostBlockDetector(player)) + .put(InventoryD.class, new InventoryD(player)) .put(Phase.class, new Phase(player)) .put(Post.class, new Post(player)) .put(PacketOrderA.class, new PacketOrderA(player)) @@ -231,6 +238,7 @@ public CheckManager(GrimPlayer player) { .build(); blockPlaceCheck = new ImmutableClassToInstanceMap.Builder() + .put(InventoryC.class, new InventoryC(player)) .put(InvalidPlaceA.class, new InvalidPlaceA(player)) .put(InvalidPlaceB.class, new InvalidPlaceB(player)) .put(AirLiquidPlace.class, new AirLiquidPlace(player)) diff --git a/common/src/main/java/ac/grim/grimac/manager/init/start/PacketManager.java b/common/src/main/java/ac/grim/grimac/manager/init/start/PacketManager.java index 4c78a402a8..f271465e2f 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/start/PacketManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/start/PacketManager.java @@ -1,20 +1,6 @@ package ac.grim.grimac.manager.init.start; -import ac.grim.grimac.events.packets.CheckManagerListener; -import ac.grim.grimac.events.packets.PacketBlockAction; -import ac.grim.grimac.events.packets.PacketEntityAction; -import ac.grim.grimac.events.packets.PacketHidePlayerInfo; -import ac.grim.grimac.events.packets.PacketPingListener; -import ac.grim.grimac.events.packets.PacketPlayerAttack; -import ac.grim.grimac.events.packets.PacketPlayerCooldown; -import ac.grim.grimac.events.packets.PacketPlayerDigging; -import ac.grim.grimac.events.packets.PacketPlayerJoinQuit; -import ac.grim.grimac.events.packets.PacketPlayerRespawn; -import ac.grim.grimac.events.packets.PacketPlayerSteer; -import ac.grim.grimac.events.packets.PacketSelfMetadataListener; -import ac.grim.grimac.events.packets.PacketServerTags; -import ac.grim.grimac.events.packets.PacketServerTeleport; -import ac.grim.grimac.events.packets.ProxyAlertMessenger; +import ac.grim.grimac.events.packets.*; import ac.grim.grimac.events.packets.worldreader.BasePacketWorldReader; import ac.grim.grimac.events.packets.worldreader.PacketWorldReaderEight; import ac.grim.grimac.events.packets.worldreader.PacketWorldReaderEighteen; @@ -30,6 +16,7 @@ public void start() { PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerJoinQuit()); PacketEvents.getAPI().getEventManager().registerListener(new PacketPingListener()); + PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerWindow()); PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerDigging()); PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerAttack()); PacketEvents.getAPI().getEventManager().registerListener(new PacketEntityAction()); diff --git a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java index 2ca21a01fd..45b770b4dc 100644 --- a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java +++ b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java @@ -34,6 +34,7 @@ import ac.grim.grimac.utils.data.tags.SyncedTags; import ac.grim.grimac.utils.enums.FluidTag; import ac.grim.grimac.utils.enums.Pose; +import ac.grim.grimac.utils.inventory.InventoryDesyncStatus; import ac.grim.grimac.utils.latency.CompensatedEntities; import ac.grim.grimac.utils.latency.CompensatedFireworks; import ac.grim.grimac.utils.latency.CompensatedInventory; @@ -240,6 +241,9 @@ public class GrimPlayer implements GrimUser { // possibleEyeHeights[0] = Standing eye heights, [1] = Sneaking. [2] = Elytra, Swimming, and Riptide Trident which only exists in 1.9+ public final double[][] possibleEyeHeights = new double[3][]; public int totalFlyingPacketsSent; + public boolean hasInventoryOpen; + public long lastInventoryOpen; + public InventoryDesyncStatus inventoryDesyncStatus; public final Queue placeUseItemPackets = new LinkedBlockingQueue<>(); public final Queue queuedBreaks = new LinkedBlockingQueue<>(); public final PlayerBlockHistory blockHistory = new PlayerBlockHistory(); diff --git a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java index 6a5d28e595..7ad0a66499 100644 --- a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java +++ b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java @@ -106,6 +106,7 @@ public class PointThreeEstimator { private boolean isNearHorizontalFlowingLiquid = false; // We can't calculate the direction, only a toggle private boolean isNearVerticalFlowingLiquid = false; // We can't calculate exact values, once again a toggle private boolean isNearBubbleColumn = false; // We can't calculate exact values once again + public boolean isNearNetherPortal = false; // We can't calculate exact values once again private int maxPositiveLevitation = Integer.MIN_VALUE; // Positive potion effects [0, 128] private int minNegativeLevitation = Integer.MAX_VALUE; // Negative potion effects [-127, -1]r @@ -249,6 +250,7 @@ private void checkNearbyBlocks(SimpleCollisionBox pointThreeBox) { isNearClimbable = false; isNearBubbleColumn = false; isNearFluid = false; + isNearNetherPortal = false; // Check for flowing water Collisions.hasMaterial(player, pointThreeBox, (pair) -> { @@ -270,6 +272,10 @@ private void checkNearbyBlocks(SimpleCollisionBox pointThreeBox) { isNearFluid = true; } + if (state.getType() == StateTypes.NETHER_PORTAL) { + isNearNetherPortal = true; + } + return false; }); } diff --git a/common/src/main/java/ac/grim/grimac/predictionengine/predictions/PredictionEngine.java b/common/src/main/java/ac/grim/grimac/predictionengine/predictions/PredictionEngine.java index e94fef94cc..914fa6b27a 100644 --- a/common/src/main/java/ac/grim/grimac/predictionengine/predictions/PredictionEngine.java +++ b/common/src/main/java/ac/grim/grimac/predictionengine/predictions/PredictionEngine.java @@ -479,52 +479,63 @@ public int sortVectorData(VectorData a, VectorData b, GrimPlayer player) { // Order priority (to avoid false positives and false flagging future predictions): // Knockback and explosions // 0.03 ticks + // Movement without input // Normal movement // First bread knockback and explosions // Flagging groundspoof // Flagging flip items if (a.isExplosion()) - aScore -= 5; + aScore -= 10; if (a.isKnockback()) - aScore -= 5; + aScore -= 10; if (b.isExplosion()) - bScore -= 5; + bScore -= 10; if (b.isKnockback()) - bScore -= 5; + bScore -= 10; if (a.isFirstBreadExplosion()) - aScore += 1; + aScore += 2; if (b.isFirstBreadExplosion()) - bScore += 1; + bScore += 2; if (a.isFirstBreadKb()) - aScore += 1; + aScore += 2; if (b.isFirstBreadKb()) - bScore += 1; + bScore += 2; if (a.isFlipItem()) - aScore += 3; + aScore += 6; if (b.isFlipItem()) - bScore += 3; + bScore += 6; if (a.isZeroPointZeroThree()) - aScore -= 1; + aScore -= 2; if (b.isZeroPointZeroThree()) + bScore -= 2; + + if (a.isWithInput() || a.isJump()) + aScore += 1; + else + aScore -= 1; + + if (b.isWithInput() || b.isJump()) + bScore += 1; + else bScore -= 1; // If the player is on the ground but the vector leads the player off the ground if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vector.getY() >= 0) - aScore += 2; + aScore += 4; if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vector.getY() >= 0) - bScore += 2; + bScore += 4; if (aScore != bScore) return Integer.compare(aScore, bScore); @@ -812,9 +823,9 @@ private void loopVectors(GrimPlayer player, Set possibleVectors, flo continue; for (int strafe = strafeMin; strafe <= strafeMax; strafe++) { for (int forward = forwardMin; forward <= forwardMax; forward++) { - VectorData result = new VectorData(possibleLastTickOutput.vector.clone() + VectorData result = new VectorData.MoveVectorData(possibleLastTickOutput.vector.clone() .add(getMovementResultFromInput(player, transformInputsToVector(player, new Vector3dm(strafe, 0, forward)), speed, player.xRot)), - possibleLastTickOutput, VectorData.VectorType.InputResult); + possibleLastTickOutput, VectorData.VectorType.InputResult, forward, strafe); result = result.returnNewModified(result.vector.clone().multiply(player.stuckSpeedMultiplier), VectorData.VectorType.StuckMultiplier); result = result.returnNewModified(handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable); // Signal that we need to flip sneaking bounding box diff --git a/common/src/main/java/ac/grim/grimac/utils/data/VectorData.java b/common/src/main/java/ac/grim/grimac/utils/data/VectorData.java index aa77492115..3e6541ff69 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/VectorData.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/VectorData.java @@ -6,13 +6,38 @@ import java.util.Objects; public class VectorData { + public static final class MoveVectorData extends VectorData { + public int x; + public int z; + + public MoveVectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType, int x, int z) { + super(vector, lastVector, vectorType); + this.x = x; + this.z = z; + + if (x != 0 || z != 0) { + addVectorType(VectorType.WithInput); + } + } + + public MoveVectorData(Vector3dm vector, VectorType vectorType, int x, int z) { + super(vector, vectorType); + this.x = x; + this.z = z; + + if (x != 0 || z != 0) { + addVectorType(VectorType.WithInput); + } + } + } + public VectorType vectorType; public VectorData lastVector; public VectorData preUncertainty; public Vector3dm vector; @Getter - private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false; + private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false, isWithInput = false; // For handling replacing the type of vector it is while keeping data public VectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType) { @@ -33,6 +58,7 @@ public VectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType isJump = lastVector.isJump; preUncertainty = lastVector.preUncertainty; isAttackSlow = lastVector.isAttackSlow; + isWithInput = lastVector.isWithInput; } addVectorType(vectorType); @@ -78,6 +104,7 @@ public void addVectorType(VectorType type) { case Flip_Use_Item -> isFlipItem = true; case Jump -> isJump = true; case AttackSlow -> isAttackSlow = true; + case WithInput -> isWithInput = true; } } @@ -104,6 +131,7 @@ public enum VectorType { Explosion, FirstBreadExplosion, InputResult, + WithInput, StuckMultiplier, Spectator, Dead, diff --git a/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryDesyncStatus.java b/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryDesyncStatus.java new file mode 100644 index 0000000000..6c6a7761b4 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryDesyncStatus.java @@ -0,0 +1,7 @@ +package ac.grim.grimac.utils.inventory; + +public enum InventoryDesyncStatus { + BEACON, + NETHER_PORTAL, + NOT_DESYNCED +} diff --git a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedInventory.java b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedInventory.java index 889dc0c621..487b855ff2 100644 --- a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedInventory.java +++ b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedInventory.java @@ -48,7 +48,7 @@ public class CompensatedInventory extends Check implements PacketCheck { public boolean isPacketInventoryActive = true; public boolean needResend = false; public int stateID = 0; // Don't mess up the last sent state ID by changing it - int openWindowID = 0; + public int openWindowID = 0; // Special values: // Player inventory is -1 // Unsupported inventory is -2 diff --git a/common/src/main/resources/punishments/de.yml b/common/src/main/resources/punishments/de.yml index 0e2af5d0b8..d57e942ed0 100644 --- a/common/src/main/resources/punishments/de.yml +++ b/common/src/main/resources/punishments/de.yml @@ -63,6 +63,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/en.yml b/common/src/main/resources/punishments/en.yml index 46b796cd5a..64846a5554 100644 --- a/common/src/main/resources/punishments/en.yml +++ b/common/src/main/resources/punishments/en.yml @@ -63,6 +63,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/es.yml b/common/src/main/resources/punishments/es.yml index 33104df860..5ac7af8203 100644 --- a/common/src/main/resources/punishments/es.yml +++ b/common/src/main/resources/punishments/es.yml @@ -63,6 +63,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/fr.yml b/common/src/main/resources/punishments/fr.yml index e90c6d9fae..37571bebaa 100644 --- a/common/src/main/resources/punishments/fr.yml +++ b/common/src/main/resources/punishments/fr.yml @@ -63,6 +63,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/it.yml b/common/src/main/resources/punishments/it.yml index 4101960b34..bf7d5cf6a3 100644 --- a/common/src/main/resources/punishments/it.yml +++ b/common/src/main/resources/punishments/it.yml @@ -50,6 +50,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/ja.yml b/common/src/main/resources/punishments/ja.yml index c16f1ed177..908af88bed 100644 --- a/common/src/main/resources/punishments/ja.yml +++ b/common/src/main/resources/punishments/ja.yml @@ -63,6 +63,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/nl.yml b/common/src/main/resources/punishments/nl.yml index 5d9736da17..27445bda51 100644 --- a/common/src/main/resources/punishments/nl.yml +++ b/common/src/main/resources/punishments/nl.yml @@ -63,6 +63,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/pt.yml b/common/src/main/resources/punishments/pt.yml index 5d6e82c438..91173d44ac 100644 --- a/common/src/main/resources/punishments/pt.yml +++ b/common/src/main/resources/punishments/pt.yml @@ -63,6 +63,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/ru.yml b/common/src/main/resources/punishments/ru.yml index 7976478600..bdd6e1bfdf 100644 --- a/common/src/main/resources/punishments/ru.yml +++ b/common/src/main/resources/punishments/ru.yml @@ -63,6 +63,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/tr.yml b/common/src/main/resources/punishments/tr.yml index b2884e7b7d..f29e3ce8bb 100644 --- a/common/src/main/resources/punishments/tr.yml +++ b/common/src/main/resources/punishments/tr.yml @@ -61,6 +61,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/zh.yml b/common/src/main/resources/punishments/zh.yml index e74b03656b..126286b408 100644 --- a/common/src/main/resources/punishments/zh.yml +++ b/common/src/main/resources/punishments/zh.yml @@ -63,6 +63,15 @@ Punishments: - "20:20 [log]" - "40:40 [webhook]" - "40:40 [proxy]" + Inventory: + remove-violations-after: 300 + checks: + - "Inventory" + commands: + - "10:10 [alert]" + - "10:10 [log]" + - "20:20 [webhook]" + - "20:20 [proxy]" Reach: remove-violations-after: 300 checks: From d434c4bacbef726b3a621e791e9970954d735232 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Tue, 29 Apr 2025 23:40:37 -0400 Subject: [PATCH 15/34] - Re-add HitboxBlock and HitboxEntity (renamed to WallHit and EntityPierce) - Reduce unnecessary reach vectors used for calculations in versions --- .../checks/impl/combat/EntityPierce.java | 13 ++ .../grim/grimac/checks/impl/combat/Reach.java | 99 +++++++--- .../grimac/checks/impl/combat/WallHit.java | 13 ++ .../events/packets/CheckManagerListener.java | 6 +- .../ac/grim/grimac/manager/CheckManager.java | 7 +- .../ac/grim/grimac/player/GrimPlayer.java | 43 +++++ .../predictionengine/PointThreeEstimator.java | 4 + .../utils/anticheat/update/BlockPlace.java | 6 +- .../grim/grimac/utils/data/BlockHitData.java | 32 ++++ .../grim/grimac/utils/data/EntityHitData.java | 23 +++ .../ac/grim/grimac/utils/data/HitData.java | 20 +- .../utils/data/ReachInterpolationData.java | 127 +++++++++++++ .../utils/data/packetentity/PacketEntity.java | 13 ++ .../grimac/utils/nmsutil/WorldRayTrace.java | 177 +++++++++++++++++- common/src/main/resources/punishments/de.yml | 18 ++ common/src/main/resources/punishments/en.yml | 18 ++ common/src/main/resources/punishments/es.yml | 18 ++ common/src/main/resources/punishments/fr.yml | 18 ++ common/src/main/resources/punishments/it.yml | 18 ++ common/src/main/resources/punishments/ja.yml | 18 ++ common/src/main/resources/punishments/nl.yml | 18 ++ common/src/main/resources/punishments/pt.yml | 18 ++ common/src/main/resources/punishments/ru.yml | 18 ++ common/src/main/resources/punishments/tr.yml | 18 ++ common/src/main/resources/punishments/zh.yml | 18 ++ 25 files changed, 723 insertions(+), 58 deletions(-) create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/data/BlockHitData.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/data/EntityHitData.java diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java new file mode 100644 index 0000000000..2a24a3b649 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java @@ -0,0 +1,13 @@ +package ac.grim.grimac.checks.impl.combat; + +import ac.grim.grimac.checks.Check; +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.PacketCheck; +import ac.grim.grimac.player.GrimPlayer; + +@CheckData(name = "Entity Pierce", configName = "EntityPierce", setback = 30) +public class EntityPierce extends Check implements PacketCheck { + public EntityPierce(GrimPlayer player) { + super(player); + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java index 43cb2cc49e..2b85177ad5 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java @@ -21,10 +21,15 @@ import ac.grim.grimac.checks.type.PacketCheck; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; +import ac.grim.grimac.utils.data.BlockHitData; +import ac.grim.grimac.utils.data.EntityHitData; +import ac.grim.grimac.utils.data.HitData; +import ac.grim.grimac.utils.data.Pair; import ac.grim.grimac.utils.data.packetentity.PacketEntity; import ac.grim.grimac.utils.data.packetentity.dragon.PacketEntityEnderDragonPart; import ac.grim.grimac.utils.math.Vector3dm; import ac.grim.grimac.utils.nmsutil.ReachUtils; +import ac.grim.grimac.utils.nmsutil.WorldRayTrace; import com.github.retrooper.packetevents.event.PacketReceiveEvent; import com.github.retrooper.packetevents.protocol.attribute.Attributes; import com.github.retrooper.packetevents.protocol.entity.type.EntityType; @@ -32,16 +37,17 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.player.ClientVersion; import com.github.retrooper.packetevents.protocol.player.GameMode; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; import com.github.retrooper.packetevents.util.Vector3d; +import com.github.retrooper.packetevents.util.Vector3i; import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; // You may not copy the check unless you are licensed under GPL @CheckData(name = "Reach", setback = 10) @@ -55,8 +61,13 @@ public class Reach extends Check implements PacketCheck { // Only one flag per reach attack, per entity, per tick. // We store position because lastX isn't reliable on teleports. private final Int2ObjectMap playerAttackQueue = new Int2ObjectOpenHashMap<>(); + // temporarily used to prevent falses in the wall hit check + private final Set blocksChangedThisTick = new HashSet<>(); + // extra distance to raytrace beyond player reach distance so we know how far beyond the legit distance a cheater hit + public static final double extraSearchDistance = 3; + private boolean cancelImpossibleHits; - private double threshold; + public double threshold; private double cancelBuffer; // For the next 4 hits after using reach, we aggressively cancel reach public Reach(GrimPlayer player) { @@ -114,8 +125,9 @@ public void onPacketReceive(final PacketReceiveEvent event) { } // If the player set their look, or we know they have a new tick + final boolean isFlying = WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()); if (isUpdate(event.getPacketType())) { - tickBetterReachCheckWithAngle(); + tickBetterReachCheckWithAngle(isFlying); } } @@ -149,7 +161,7 @@ private boolean isKnownInvalid(PacketEntity reachEntity) { } } - private void tickBetterReachCheckWithAngle() { + private void tickBetterReachCheckWithAngle(boolean isFlying) { for (Int2ObjectMap.Entry attack : playerAttackQueue.int2ObjectEntrySet()) { PacketEntity reachEntity = player.compensatedEntities.entityMap.get(attack.getIntKey()); if (reachEntity == null) continue; @@ -164,10 +176,21 @@ private void tickBetterReachCheckWithAngle() { String added = "type=" + reachEntity.getType().getName().getKey(); player.checkManager.getCheck(Hitboxes.class).flagAndAlert(result.verbose() + added); } + case WALL_HIT -> { + String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey(); + player.checkManager.getCheck(WallHit.class).flagAndAlert(result.verbose() + added); + } + case ENTITY_PIERCE -> { + String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey(); + player.checkManager.getCheck(EntityPierce.class).flagAndAlert(result.verbose() + added); + } } } playerAttackQueue.clear(); + // We can't use transactions for this because of this problem: + // transaction -> block changed applied -> 2nd transaction -> list cleared -> attack packet -> flying -> reach block hit checked, falses + if (isFlying) blocksChangedThisTick.clear(); } @NotNull @@ -196,28 +219,15 @@ private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean double minDistance = Double.MAX_VALUE; - // https://bugs.mojang.com/browse/MC-67665 - List possibleLookDirs = new ArrayList<>(Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot))); - - // If we are a tick behind, we don't know their next look so don't bother doing this - if (!isPrediction) { - possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.yRot)); - - // 1.9+ players could be a tick behind because we don't get skipped ticks - if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) { - possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.lastYRot)); - } - - // 1.7 players do not have any of these issues! They are always on the latest look vector - if (player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) { - possibleLookDirs = Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot)); - } - } + // will store all lookVecsAndEyeHeight pairs that landed a hit on the target entity + // We only need to check for blocking intersections for these + List> lookVecsAndEyeHeights = new ArrayList<>(); final double maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE); // +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox" final double distance = maxReach + 3; final double[] possibleEyeHeights = player.getPossibleEyeHeights(); + final Vector3dm[] possibleLookDirs = player.getPossibleLookVectors(isPrediction); final Vector3dm eyePos = new Vector3dm(from.getX(), 0, from.getZ()); for (Vector3dm lookVec : possibleLookDirs) { for (double eye : possibleEyeHeights) { @@ -233,13 +243,41 @@ private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean if (intercept != null) { minDistance = Math.min(eyePos.distance(intercept), minDistance); + lookVecsAndEyeHeights.add(new Pair<>(lookVec, eye)); } } } + HitData foundHitData = null; + // If the entity is within range of the player (we'll flag anyway if not, so no point checking blocks in this case) + // Ignore when could be hitting through a moving shulker, piston blocks. They are just too glitchy/uncertain to check. + if (minDistance <= distance - extraSearchDistance && !player.compensatedWorld.isNearHardEntity(player.boundingBox.copy().expand(4))) { + // we can optimize didRayTraceHit more to only rayTrace up to the maximize distance of all rays that hit to the target... + // I'm too lazy to do that and we don't need to optimize that much yet so... + final @Nullable Pair hitResult = WorldRayTrace.didRayTraceHit(player, reachEntity, lookVecsAndEyeHeights, from); + HitData hitData = hitResult.second(); + // If the returned hit result was NOT the target entity we flag the check + if (hitData instanceof EntityHitData && + player.compensatedEntities.getPacketEntityID(((EntityHitData) hitData).getEntity()) != player.compensatedEntities.getPacketEntityID(reachEntity)) { + minDistance = Double.MIN_VALUE; + foundHitData = hitData; + // until I fix block modeling exempt any blocks changed this tick + } else if (hitData instanceof BlockHitData && !blocksChangedThisTick.contains(((BlockHitData) hitData).getPosition())) { + minDistance = Double.MIN_VALUE; + foundHitData = hitData; + } + } + // if the entity is not exempt and the entity is alive if ((!blacklisted.contains(reachEntity.getType()) && reachEntity.isLivingEntity()) || reachEntity.getType() == EntityTypes.END_CRYSTAL) { - if (minDistance == Double.MAX_VALUE) { + if (minDistance == Double.MIN_VALUE && foundHitData != null) { + cancelBuffer = 1; + if (foundHitData instanceof BlockHitData) { + return new CheckResult(ResultType.WALL_HIT, "Hit block=" + ((BlockHitData) foundHitData).getState().getType().getName() + " "); + } else { // entity hit data + return new CheckResult(ResultType.ENTITY_PIERCE, "Hit entity=" + ((EntityHitData) foundHitData).getEntity().getType().getName() + " "); + } + } else if (minDistance == Double.MAX_VALUE) { cancelBuffer = 1; return new CheckResult(ResultType.HITBOX, ""); } else if (minDistance > maxReach) { @@ -260,7 +298,7 @@ public void onReload(ConfigManager config) { } private enum ResultType { - REACH, HITBOX, NONE + REACH, HITBOX, WALL_HIT, ENTITY_PIERCE, NONE } private record CheckResult(ResultType type, String verbose) { @@ -268,4 +306,13 @@ public boolean isFlag() { return type != ResultType.NONE; } } + + public void handleBlockChange(Vector3i vector3i, WrappedBlockState state) { + if (blocksChangedThisTick.size() >= 40) return; // Don't let players freeze movement packets to grow this + // Only do this for nearby blocks + if (new Vector3dm(vector3i.x, vector3i.y, vector3i.z).distanceSquared(new Vector3dm(player.x, player.y, player.z)) > 6) return; + // Only do this if the state really had any world impact + if (state.equals(player.compensatedWorld.getBlock(vector3i))) return; + blocksChangedThisTick.add(vector3i); + } } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java new file mode 100644 index 0000000000..f61279e1b2 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java @@ -0,0 +1,13 @@ +package ac.grim.grimac.checks.impl.combat; + +import ac.grim.grimac.checks.Check; +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.type.PacketCheck; +import ac.grim.grimac.player.GrimPlayer; + +@CheckData(name = "Wall Hit", configName = "WallHit", setback = 20) +public class WallHit extends Check implements PacketCheck { + public WallHit(GrimPlayer player) { + super(player); + } +} diff --git a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java index df59d019f0..33f227d298 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java @@ -66,7 +66,7 @@ public CheckManagerListener() { } private static void placeWaterLavaSnowBucket(GrimPlayer player, ItemStack held, StateType toPlace, InteractionHand hand, int sequence) { - HitData data = WorldRayTrace.getNearestBlockHitResult(player, StateTypes.AIR, false, true, true); + BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, StateTypes.AIR, false, true, true); if (data != null) { BlockPlace blockPlace = new BlockPlace(player, hand, data.getPosition(), data.getClosestDirection().getFaceValue(), data.getClosestDirection(), held, data, sequence); @@ -282,7 +282,7 @@ private static void handleBlockPlaceOrUseItem(PacketWrapper packet, GrimPlaye } private static void placeBucket(GrimPlayer player, InteractionHand hand, int sequence) { - HitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true); + BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true); if (data != null) { BlockPlace blockPlace = new BlockPlace(player, hand, data.getPosition(), data.getClosestDirection().getFaceValue(), data.getClosestDirection(), ItemStack.EMPTY, data, sequence); @@ -361,7 +361,7 @@ public static void setPlayerItem(GrimPlayer player, InteractionHand hand, ItemTy } private static void placeLilypad(GrimPlayer player, InteractionHand hand, int sequence) { - HitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true); + BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true); if (data != null) { // A lilypad cannot replace a fluid diff --git a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java index 7542f1bfdd..83e4214552 100644 --- a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java @@ -16,10 +16,7 @@ import ac.grim.grimac.checks.impl.breaking.PositionBreakB; import ac.grim.grimac.checks.impl.breaking.RotationBreak; import ac.grim.grimac.checks.impl.breaking.WrongBreak; -import ac.grim.grimac.checks.impl.combat.Hitboxes; -import ac.grim.grimac.checks.impl.combat.MultiInteractA; -import ac.grim.grimac.checks.impl.combat.MultiInteractB; -import ac.grim.grimac.checks.impl.combat.Reach; +import ac.grim.grimac.checks.impl.combat.*; import ac.grim.grimac.checks.impl.crash.*; import ac.grim.grimac.checks.impl.elytra.ElytraA; import ac.grim.grimac.checks.impl.elytra.ElytraB; @@ -295,6 +292,8 @@ public CheckManager(GrimPlayer player) { .put(TransactionOrder.class, new TransactionOrder(player)) .put(VehicleC.class, new VehicleC(player)) .put(Hitboxes.class, new Hitboxes(player)) // Hitboxes is invoked by Reach + .put(WallHit.class, new WallHit(player)) + .put(EntityPierce.class, new EntityPierce(player)) .build(); allChecks = new ImmutableClassToInstanceMap.Builder() diff --git a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java index 45b770b4dc..c3c7ec87e2 100644 --- a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java +++ b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java @@ -46,6 +46,7 @@ import ac.grim.grimac.utils.math.Vector3dm; import ac.grim.grimac.utils.nmsutil.BlockProperties; import ac.grim.grimac.utils.nmsutil.GetBoundingBox; +import ac.grim.grimac.utils.nmsutil.ReachUtils; import ac.grim.grimac.utils.reflection.ViaVersionUtil; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.event.PacketSendEvent; @@ -651,6 +652,48 @@ public double[] getPossibleEyeHeights() { // We don't return sleeping eye height } } + // 1.8-1.10.2 specific mouse delay fix (MC-67665) + // https://bugs.mojang.com/browse/MC-67665 + // 1.9-1.21.1 specific desync due to skipped ticks + // Players can be a tick behind on both pitch and yaw together + // 1.21.2+ added end tick input packet, fixing skipped tick issues + public Vector3dm[] getPossibleLookVectors(boolean isPrediction) { + // If we are a tick behind, we don't know their next look so only use current + if (isPrediction) { + return new Vector3dm[]{ReachUtils.getLook(this, this.xRot, this.yRot)}; + } + + // 1.9-1.10.2: All three vectors (normal, mouse delay, tick desync) + if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) && + this.getClientVersion().isOlderThan(ClientVersion.V_1_11)) { + return new Vector3dm[]{ + ReachUtils.getLook(this, this.xRot, this.yRot), + ReachUtils.getLook(this, this.lastXRot, this.yRot), + ReachUtils.getLook(this, this.lastXRot, this.lastYRot) + }; + } + // 1.11-1.21.1: Two vectors (normal, tick desync) + else if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_11) && + this.getClientVersion().isOlderThan(ClientVersion.V_1_21_2)) { + return new Vector3dm[]{ + ReachUtils.getLook(this, this.xRot, this.yRot), + ReachUtils.getLook(this, this.lastXRot, this.lastYRot) + }; + } + // 1.8: Two vectors (normal, mouse delay) + else if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8) && + this.getClientVersion().isOlderThan(ClientVersion.V_1_9)) { + return new Vector3dm[]{ + ReachUtils.getLook(this, this.xRot, this.yRot), + ReachUtils.getLook(this, this.lastXRot, this.yRot) + }; + } + // 1.7 and 1.21.2+: Just normal vector + else { + return new Vector3dm[]{ReachUtils.getLook(this, this.xRot, this.yRot)}; + } + } + @Override public int getTransactionPing() { return GrimMath.floor(transactionPing / 1e6); diff --git a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java index 7ad0a66499..7db473be8f 100644 --- a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java +++ b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java @@ -1,5 +1,6 @@ package ac.grim.grimac.predictionengine; +import ac.grim.grimac.checks.impl.combat.Reach; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.predictionengine.predictions.PredictionEngine; import ac.grim.grimac.utils.collisions.CollisionData; @@ -20,6 +21,7 @@ import com.github.retrooper.packetevents.protocol.world.states.defaulttags.BlockTags; import com.github.retrooper.packetevents.protocol.world.states.type.StateType; import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; +import com.github.retrooper.packetevents.util.Vector3i; import lombok.Getter; import lombok.Setter; @@ -155,6 +157,8 @@ public void handleChangeBlock(int x, int y, int z, WrappedBlockState state) { isNearFluid = true; } + player.checkManager.getPacketCheck(Reach.class).handleBlockChange(new Vector3i(x, y, z), state); + if (pointThreeBox.isIntersected(new SimpleCollisionBox(x, y, z))) { // https://github.com/MWHunter/Grim/issues/613 int controllingEntityId = player.inVehicle() ? player.getRidingVehicleId() : player.entityID; diff --git a/common/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockPlace.java b/common/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockPlace.java index 021656ee7c..f4bfc26c25 100644 --- a/common/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockPlace.java +++ b/common/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockPlace.java @@ -9,7 +9,7 @@ import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; import ac.grim.grimac.utils.collisions.datatypes.ComplexCollisionBox; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; -import ac.grim.grimac.utils.data.HitData; +import ac.grim.grimac.utils.data.BlockHitData; import ac.grim.grimac.utils.data.packetentity.PacketEntity; import ac.grim.grimac.utils.latency.CompensatedWorld; import ac.grim.grimac.utils.math.GrimMath; @@ -69,7 +69,7 @@ public class BlockPlace { @Getter StateType material; @Getter - @Nullable HitData hitData; + @Nullable BlockHitData hitData; @Getter int faceId; BlockFace face; @@ -81,7 +81,7 @@ public class BlockPlace { Vector3f cursor; public final int sequence; - public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i blockPosition, int faceId, BlockFace face, ItemStack itemStack, HitData hitData, int sequence) { + public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i blockPosition, int faceId, BlockFace face, ItemStack itemStack, BlockHitData hitData, int sequence) { this.player = player; this.hand = hand; this.blockPosition = blockPosition; diff --git a/common/src/main/java/ac/grim/grimac/utils/data/BlockHitData.java b/common/src/main/java/ac/grim/grimac/utils/data/BlockHitData.java new file mode 100644 index 0000000000..78fc4c128f --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/data/BlockHitData.java @@ -0,0 +1,32 @@ +package ac.grim.grimac.utils.data; + +import ac.grim.grimac.utils.math.Vector3dm; +import com.github.retrooper.packetevents.protocol.world.BlockFace; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; +import com.github.retrooper.packetevents.util.Vector3d; +import com.github.retrooper.packetevents.util.Vector3i; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class BlockHitData extends HitData { + Vector3i position; + WrappedBlockState state; + BlockFace closestDirection; +// public Boolean success; + + public BlockHitData(Vector3i position, Vector3dm blockHitLocation, BlockFace closestDirection, WrappedBlockState state +// , Boolean success + ) { + super(blockHitLocation); + this.position = position; + this.closestDirection = closestDirection; + this.state = state; +// this.success = success; + } + + public Vector3d getRelativeBlockHitLocation() { + return new Vector3d(blockHitLocation.getX() - position.getX(), blockHitLocation.getY() - position.getY(), blockHitLocation.getZ() - position.getZ()); + } +} diff --git a/common/src/main/java/ac/grim/grimac/utils/data/EntityHitData.java b/common/src/main/java/ac/grim/grimac/utils/data/EntityHitData.java new file mode 100644 index 0000000000..addb7e95f0 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/data/EntityHitData.java @@ -0,0 +1,23 @@ +package ac.grim.grimac.utils.data; + +import ac.grim.grimac.utils.data.packetentity.PacketEntity; +import ac.grim.grimac.utils.math.Vector3dm; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class EntityHitData extends HitData { + private final PacketEntity entity; + + public EntityHitData(PacketEntity packetEntity) { + this(packetEntity, new Vector3dm(packetEntity.trackedServerPosition.getPos().x, + packetEntity.trackedServerPosition.getPos().y, + packetEntity.trackedServerPosition.getPos().z)); + } + + public EntityHitData(PacketEntity packetEntity, Vector3dm intersectionPoint) { + super(intersectionPoint); // Use actual intersection point + this.entity = packetEntity; + } +} diff --git a/common/src/main/java/ac/grim/grimac/utils/data/HitData.java b/common/src/main/java/ac/grim/grimac/utils/data/HitData.java index 8f8effd875..449f8fb134 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/HitData.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/HitData.java @@ -1,29 +1,15 @@ package ac.grim.grimac.utils.data; import ac.grim.grimac.utils.math.Vector3dm; -import com.github.retrooper.packetevents.protocol.world.BlockFace; -import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; -import com.github.retrooper.packetevents.util.Vector3d; -import com.github.retrooper.packetevents.util.Vector3i; -import lombok.Getter; -import lombok.ToString; -@Getter -@ToString public class HitData { - Vector3i position; Vector3dm blockHitLocation; - WrappedBlockState state; - BlockFace closestDirection; - public HitData(Vector3i position, Vector3dm blockHitLocation, BlockFace closestDirection, WrappedBlockState state) { - this.position = position; + public HitData(Vector3dm blockHitLocation) { this.blockHitLocation = blockHitLocation; - this.closestDirection = closestDirection; - this.state = state; } - public Vector3d getRelativeBlockHitLocation() { - return new Vector3d(blockHitLocation.getX() - position.getX(), blockHitLocation.getY() - position.getY(), blockHitLocation.getZ() - position.getZ()); + public ac.grim.grimac.utils.math.Vector3dm getBlockHitLocation() { + return this.blockHitLocation; } } diff --git a/common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java b/common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java index 8110030328..08798ef932 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java @@ -196,6 +196,133 @@ public SimpleCollisionBox getPossibleHitboxCombined() { return minimumInterpLocation; } + public CollisionBox getOverlapHitboxCombined() { + int interpSteps = getInterpolationSteps(); + + // Calculate step increments for each axis + double stepMinX = (targetLocation.minX - startingLocation.minX) / (double) interpSteps; + double stepMaxX = (targetLocation.maxX - startingLocation.maxX) / (double) interpSteps; + double stepMinY = (targetLocation.minY - startingLocation.minY) / (double) interpSteps; + double stepMaxY = (targetLocation.maxY - startingLocation.maxY) / (double) interpSteps; + double stepMinZ = (targetLocation.minZ - startingLocation.minZ) / (double) interpSteps; + double stepMaxZ = (targetLocation.maxZ - startingLocation.maxZ) / (double) interpSteps; + + // Track the intersection of all expanded hitboxes + double overallMinX = Double.NEGATIVE_INFINITY; + double overallMaxX = Double.POSITIVE_INFINITY; + double overallMinY = Double.NEGATIVE_INFINITY; + double overallMaxY = Double.POSITIVE_INFINITY; + double overallMinZ = Double.NEGATIVE_INFINITY; + double overallMaxZ = Double.POSITIVE_INFINITY; + + boolean isFirstStep = true; + + for (int step = interpolationStepsLowBound; step <= interpolationStepsHighBound; step++) { + // Compute interpolated position for this step + double currentMinX = startingLocation.minX + (step * stepMinX); + double currentMaxX = startingLocation.maxX + (step * stepMaxX); + double currentMinY = startingLocation.minY + (step * stepMinY); + double currentMaxY = startingLocation.maxY + (step * stepMaxY); + double currentMinZ = startingLocation.minZ + (step * stepMinZ); + double currentMaxZ = startingLocation.maxZ + (step * stepMaxZ); + + // Create the collision box for this step's position + // Create boxes for each bottom corner + SimpleCollisionBox[] cornerBoxes = new SimpleCollisionBox[4]; + + // Bottom corners: (minX,minY,minZ), (maxX,minY,minZ), (minX,minY,maxZ), (maxX,minY,maxZ) + cornerBoxes[0] = new SimpleCollisionBox(currentMinX, currentMinY, currentMinZ, + currentMinX, currentMinY, currentMinZ); + cornerBoxes[1] = new SimpleCollisionBox(currentMaxX, currentMinY, currentMinZ, + currentMaxX, currentMinY, currentMinZ); + cornerBoxes[2] = new SimpleCollisionBox(currentMinX, currentMinY, currentMaxZ, + currentMinX, currentMinY, currentMaxZ); + cornerBoxes[3] = new SimpleCollisionBox(currentMaxX, currentMinY, currentMaxZ, + currentMaxX, currentMinY, currentMaxZ); + + // Expand each corner box by entity dimensions + for (SimpleCollisionBox cornerBox : cornerBoxes) { + GetBoundingBox.expandBoundingBoxByEntityDimensions(cornerBox, player, entity); + } + + // Get the overlap of the 4 corner boxes + CollisionBox stepOverlap = getOverlapOfBoxes(cornerBoxes); + if (stepOverlap == NoCollisionBox.INSTANCE) + return NoCollisionBox.INSTANCE; + SimpleCollisionBox stepBox = (SimpleCollisionBox) stepOverlap; + + // Initialize overall bounds with the first expanded box + if (isFirstStep) { + overallMinX = stepBox.minX; + overallMaxX = stepBox.maxX; + overallMinY = stepBox.minY; + overallMaxY = stepBox.maxY; + overallMinZ = stepBox.minZ; + overallMaxZ = stepBox.maxZ; + isFirstStep = false; + } else { + // Update bounds to the intersection of all expanded boxes + overallMinX = Math.max(overallMinX, stepBox.minX); + overallMaxX = Math.min(overallMaxX, stepBox.maxX); + overallMinY = Math.max(overallMinY, stepBox.minY); + overallMaxY = Math.min(overallMaxY, stepBox.maxY); + overallMinZ = Math.max(overallMinZ, stepBox.minZ); + overallMaxZ = Math.min(overallMaxZ, stepBox.maxZ); + } + + // Early exit if the intersection becomes empty + if (overallMinX > overallMaxX || overallMinY > overallMaxY || overallMinZ > overallMaxZ) { + return NoCollisionBox.INSTANCE; + } + } + + // Check if the final intersection is valid + if (overallMinX > overallMaxX || overallMinY > overallMaxY || overallMinZ > overallMaxZ) { + return NoCollisionBox.INSTANCE; + } + + return new SimpleCollisionBox( + overallMinX, overallMinY, overallMinZ, + overallMaxX, overallMaxY, overallMaxZ + ); + } + + private CollisionBox getOverlapOfBoxes(SimpleCollisionBox[] boxes) { + double minX = Double.NEGATIVE_INFINITY; + double maxX = Double.POSITIVE_INFINITY; + double minY = Double.NEGATIVE_INFINITY; + double maxY = Double.POSITIVE_INFINITY; + double minZ = Double.NEGATIVE_INFINITY; + double maxZ = Double.POSITIVE_INFINITY; + + boolean first = true; + + for (SimpleCollisionBox box : boxes) { + if (first) { + minX = box.minX; + maxX = box.maxX; + minY = box.minY; + maxY = box.maxY; + minZ = box.minZ; + maxZ = box.maxZ; + first = false; + } else { + minX = Math.max(minX, box.minX); + maxX = Math.min(maxX, box.maxX); + minY = Math.max(minY, box.minY); + maxY = Math.min(maxY, box.maxY); + minZ = Math.max(minZ, box.minZ); + maxZ = Math.min(maxZ, box.maxZ); + } + + if (minX > maxX || minY > maxY || minZ > maxZ) { + return NoCollisionBox.INSTANCE; + } + } + + return new SimpleCollisionBox(minX, minY, minZ, maxX, maxY, maxZ); + } + public void updatePossibleStartingLocation(SimpleCollisionBox possibleLocationCombined) { //GrimACBukkitLoaderPlugin.staticGetLogger().info(ChatColor.BLUE + "Updated new starting location as second trans hasn't arrived " + startingLocation); this.startingLocation = combineCollisionBox(startingLocation, possibleLocationCombined); diff --git a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java index 1a0c69aeea..4e8a0ed06c 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java @@ -16,6 +16,7 @@ package ac.grim.grimac.utils.data.packetentity; import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; import ac.grim.grimac.utils.data.ReachInterpolationData; import ac.grim.grimac.utils.data.TrackedPosition; @@ -214,6 +215,18 @@ public SimpleCollisionBox getPossibleCollisionBoxes() { return ReachInterpolationData.combineCollisionBox(oldPacketLocation.getPossibleHitboxCombined(), newPacketLocation.getPossibleHitboxCombined()); } + public CollisionBox getMinimumPossibleCollisionBoxes() { + if (oldPacketLocation == null) { + return newPacketLocation.getOverlapHitboxCombined(); + } + + return ReachInterpolationData.getOverlapHitbox(oldPacketLocation.getOverlapHitboxCombined(), newPacketLocation.getOverlapHitboxCombined()); + } + + public PacketEntity getRiding() { + return riding; + } + public OptionalInt getPotionEffectLevel(PotionType effect) { final int amplifier = potionsMap == null ? -1 : potionsMap.getInt(effect); return amplifier == -1 ? OptionalInt.empty() : OptionalInt.of(amplifier); diff --git a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java index ad8e6b1012..3cc74ac0ed 100644 --- a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java +++ b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java @@ -1,12 +1,19 @@ package ac.grim.grimac.utils.nmsutil; +import ac.grim.grimac.checks.impl.combat.Reach; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.collisions.CollisionData; import ac.grim.grimac.utils.collisions.HitboxData; import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.ComplexCollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; +import ac.grim.grimac.utils.data.BlockHitData; +import ac.grim.grimac.utils.data.EntityHitData; import ac.grim.grimac.utils.data.HitData; import ac.grim.grimac.utils.data.Pair; +import ac.grim.grimac.utils.data.packetentity.PacketEntity; +import ac.grim.grimac.utils.data.packetentity.TypedPacketEntity; import ac.grim.grimac.utils.math.GrimMath; import ac.grim.grimac.utils.math.Vector3dm; import com.github.retrooper.packetevents.protocol.attribute.Attributes; @@ -16,13 +23,15 @@ import com.github.retrooper.packetevents.protocol.world.states.type.StateType; import com.github.retrooper.packetevents.util.Vector3d; import com.github.retrooper.packetevents.util.Vector3i; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; public class WorldRayTrace { - public static HitData getNearestBlockHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) { + public static BlockHitData getNearestBlockHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) { Vector3d startingPos = new Vector3d(player.x, player.y + player.getEyeHeight(), player.z); Vector3dm startingVec = new Vector3dm(startingPos.getX(), startingPos.getY(), startingPos.getZ()); Ray trace = new Ray(player, startingPos.getX(), startingPos.getY(), startingPos.getZ(), player.xRot, player.yRot); @@ -57,7 +66,7 @@ public static HitData getNearestBlockHitResult(GrimPlayer player, StateType held } } if (bestHitLoc != null) { - return new HitData(vector3i, bestHitLoc, bestFace, block); + return new BlockHitData(vector3i, bestHitLoc, bestFace, block); } if (sourcesHaveHitbox && @@ -70,7 +79,7 @@ public static HitData getNearestBlockHitResult(GrimPlayer player, StateType held Pair intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance)); if (intercept.first() != null) { - return new HitData(vector3i, intercept.first(), intercept.second(), block); + return new BlockHitData(vector3i, intercept.first(), intercept.second(), block); } } @@ -83,7 +92,7 @@ public static HitData getNearestBlockHitResult(GrimPlayer player, StateType held // // I do have to admit that I'm starting to like bifunctions/new java 8 things more than I originally did. // although I still don't understand Mojang's obsession with streams in some of the hottest methods... that kills performance - public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, BiFunction predicate) { + public static BlockHitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, BiFunction predicate) { // I guess go back by the collision epsilon? double endX = GrimMath.lerp(-1.0E-7D, end.x, start.x); double endY = GrimMath.lerp(-1.0E-7D, end.y, start.y); @@ -99,7 +108,7 @@ public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d if (start.equals(end)) return null; WrappedBlockState state = player.compensatedWorld.getBlock(floorStartX, floorStartY, floorStartZ); - HitData apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ)); + BlockHitData apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ)); if (apply != null) { return apply; @@ -148,4 +157,162 @@ public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d return null; } + + @Nullable + public static HitData getNearestHitResult(GrimPlayer player, PacketEntity targetEntity, Vector3dm eyePos, Vector3dm lookVec) { + + double maxAttackDistance = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE); + double maxBlockDistance = player.compensatedEntities.self.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE); + + Vector3d startingPos = new Vector3d(eyePos.getX(), eyePos.getY(), eyePos.getZ()); + Vector3dm startingVec = new Vector3dm(startingPos.getX(), startingPos.getY(), startingPos.getZ()); + Ray trace = new Ray(eyePos, lookVec); + Vector3dm endVec = trace.getPointAtDistance(maxBlockDistance); + Vector3d endPos = new Vector3d(endVec.getX(), endVec.getY(), endVec.getZ()); + + // Get block hit + BlockHitData blockHitData = getTraverseResult(player, null, startingPos, startingVec, trace, endPos, false, true, maxBlockDistance, true); + Vector3dm closestHitVec = null; + PacketEntity closestEntity = null; + double closestDistanceSquared = blockHitData != null ? blockHitData.getBlockHitLocation().distanceSquared(startingVec) : maxAttackDistance * maxAttackDistance; + + for (PacketEntity entity : player.compensatedEntities.entityMap.values().stream().filter(TypedPacketEntity::canHit).toList()) { + SimpleCollisionBox box = null; + // 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers) + // Yes, this is vanilla and not uncertainty. All reach checks have this or they are wrong. + + if (entity.equals(targetEntity)) { + box = entity.getPossibleCollisionBoxes(); + box.expand(player.checkManager.getPacketCheck(Reach.class).threshold); + // This is better than adding to the reach, as 0.03 can cause a player to miss their target + // Adds some more than 0.03 uncertainty in some cases, but a good trade off for simplicity + // + // Just give the uncertainty on 1.9+ clients as we have no way of knowing whether they had 0.03 movement + if (!player.packetStateData.didLastLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) + box.expand(player.getMovementThreshold()); + if (ReachUtils.isVecInside(box, eyePos)) { + return new EntityHitData(entity, eyePos); + } + } else { + CollisionBox b = entity.getMinimumPossibleCollisionBoxes(); + if (b instanceof NoCollisionBox) { + continue; + } + box = (SimpleCollisionBox) b; + box.expand(-player.checkManager.getPacketCheck(Reach.class).threshold); + // todo, shrink by reachThreshold as well for non-target entities? + if (!player.packetStateData.didLastLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) + box.expand(-player.getMovementThreshold()); + } + if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) { + box.expand(0.1f); + } + + + Pair intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(Math.sqrt(closestDistanceSquared))); + + if (intercept.first() != null) { + double distSquared = intercept.first().distanceSquared(startingVec); + if (distSquared < closestDistanceSquared) { + closestDistanceSquared = distSquared; + closestHitVec = intercept.first(); + closestEntity = entity; + } + } + } + + return closestEntity == null ? blockHitData : new EntityHitData(closestEntity, closestHitVec); + } + + // Checks if it was possible to hit a target entity + // TODO refactor to return list of rays and why each of them didn't hit instead of closest obstruction + // NOTE: It should be impossible for the returned Pair to be null + // because all of the possibleLookVecsAndEyeHeights passed in should be ones that hit the target entity + // in previous parts of this check when we didn't check for any obstructions like blocks/entities + public static @NotNull Pair<@NotNull Double, @NotNull HitData> didRayTraceHit(GrimPlayer player, PacketEntity targetEntity, + List> possibleLookVecsAndEyeHeights, + Vector3d from) { + HitData firstObstruction = null; + double firstObstructionDistanceSq = 0; + + // Check every possible look direction and every possible eye height + for (Pair vectorDoublePair : possibleLookVecsAndEyeHeights) { + Vector3dm lookVec = vectorDoublePair.first(); + double eye = vectorDoublePair.second(); + + Vector3dm eyes = new Vector3dm(from.getX(), from.getY() + eye, from.getZ()); + // this function is completely 0.03 aware + final HitData hitResult = WorldRayTrace.getNearestHitResult(player, targetEntity, eyes, lookVec); + + // If we hit the target entity, it's a valid hit + if (hitResult instanceof EntityHitData && ((EntityHitData) hitResult).getEntity().equals(targetEntity)) { + double distanceSquared = eyes.distanceSquared(hitResult.getBlockHitLocation()); + return new Pair<>(distanceSquared, hitResult); // Legitimate hit + } else if (hitResult != null && firstObstruction == null) { + // Store the first obstruction only + firstObstruction = hitResult; + firstObstructionDistanceSq = eyes.distanceSquared(hitResult.getBlockHitLocation()); + } + } + + // Return the first obstruction if no valid hit found + // Since we sort eye heights by likeniness, we should in effect return the most likely (first) obstruction + assert firstObstruction != null; + return new Pair<>(firstObstructionDistanceSq, firstObstruction); + } + + // TODO replace shrinkBlocks boolean with a data structure/better way to represent + // 1. We have a target block. Shrink everything by movementThreshold except expand target block (we are checking to see if it matches the target block) + // 2. We do not have a target block. Shrink everything by movementThreshold() + // 3. Do not expand or shrink everything, we do not expect 0.03/0.002 or we legacy example where we want to keep old behaviour + private static BlockHitData getTraverseResult(GrimPlayer player, @Nullable StateType heldItem, Vector3d startingPos, Vector3dm startingVec, Ray trace, Vector3d endPos, boolean sourcesHaveHitbox, boolean checkInside, double knownDistance, boolean shrinkBlocks) { + return traverseBlocks(player, startingPos, endPos, (block, vector3i) -> { + // even though sometimes we are raytracing against a block that is the target block, we pass false to this function because it only applies a change for brewing stands in 1.8 + CollisionBox data = HitboxData.getBlockHitbox(player, heldItem, player.getClientVersion(), block, false, vector3i.getX(), vector3i.getY(), vector3i.getZ()); + SimpleCollisionBox[] boxes = new SimpleCollisionBox[ComplexCollisionBox.DEFAULT_MAX_COLLISION_BOX_SIZE]; + int size = data.downCast(boxes); + + double bestHitResult = Double.MAX_VALUE; + Vector3dm bestHitLoc = null; + BlockFace bestFace = null; + + for (int i = 0; i < size; i++) { + if (shrinkBlocks) boxes[i].expand(-player.getMovementThreshold()); + Pair intercept = ReachUtils.calculateIntercept(boxes[i], trace.getOrigin(), trace.getPointAtDistance(knownDistance)); + if (intercept.first() == null) continue; // No intercept + + Vector3dm hitLoc = intercept.first(); + + // If inside a block, return empty result for reach check (don't bother checking this?) + if (checkInside && ReachUtils.isVecInside(boxes[i], trace.getOrigin())) { + return null; + } + + if (hitLoc.distanceSquared(startingVec) < bestHitResult) { + bestHitResult = hitLoc.distanceSquared(startingVec); + bestHitLoc = hitLoc; + bestFace = intercept.second(); + } + } + + if (bestHitLoc != null) { + return new BlockHitData(vector3i, bestHitLoc, bestFace, block); + } + + if (sourcesHaveHitbox && + (player.compensatedWorld.isWaterSourceBlock(vector3i.getX(), vector3i.getY(), vector3i.getZ()) + || player.compensatedWorld.getLavaFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ()) == (8 / 9f))) { + double waterHeight = player.compensatedWorld.getFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ()); + SimpleCollisionBox box = new SimpleCollisionBox(vector3i.getX(), vector3i.getY(), vector3i.getZ(), vector3i.getX() + 1, vector3i.getY() + waterHeight, vector3i.getZ() + 1); + + Pair intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(knownDistance)); + + if (intercept.first() != null) { + return new BlockHitData(vector3i, intercept.first(), intercept.second(), block); + } + } + + return null; + }); + } } diff --git a/common/src/main/resources/punishments/de.yml b/common/src/main/resources/punishments/de.yml index d57e942ed0..75677f54c4 100644 --- a/common/src/main/resources/punishments/de.yml +++ b/common/src/main/resources/punishments/de.yml @@ -90,6 +90,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/en.yml b/common/src/main/resources/punishments/en.yml index 64846a5554..7b56d7be4a 100644 --- a/common/src/main/resources/punishments/en.yml +++ b/common/src/main/resources/punishments/en.yml @@ -90,6 +90,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/es.yml b/common/src/main/resources/punishments/es.yml index 5ac7af8203..a12f8c14ad 100644 --- a/common/src/main/resources/punishments/es.yml +++ b/common/src/main/resources/punishments/es.yml @@ -90,6 +90,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/fr.yml b/common/src/main/resources/punishments/fr.yml index 37571bebaa..c9f4030cf7 100644 --- a/common/src/main/resources/punishments/fr.yml +++ b/common/src/main/resources/punishments/fr.yml @@ -90,6 +90,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/it.yml b/common/src/main/resources/punishments/it.yml index bf7d5cf6a3..2bdeb57d39 100644 --- a/common/src/main/resources/punishments/it.yml +++ b/common/src/main/resources/punishments/it.yml @@ -77,6 +77,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/ja.yml b/common/src/main/resources/punishments/ja.yml index 908af88bed..aa0a280128 100644 --- a/common/src/main/resources/punishments/ja.yml +++ b/common/src/main/resources/punishments/ja.yml @@ -90,6 +90,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/nl.yml b/common/src/main/resources/punishments/nl.yml index 27445bda51..dc03cc70c4 100644 --- a/common/src/main/resources/punishments/nl.yml +++ b/common/src/main/resources/punishments/nl.yml @@ -90,6 +90,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/pt.yml b/common/src/main/resources/punishments/pt.yml index 91173d44ac..5c13da85a0 100644 --- a/common/src/main/resources/punishments/pt.yml +++ b/common/src/main/resources/punishments/pt.yml @@ -90,6 +90,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/ru.yml b/common/src/main/resources/punishments/ru.yml index bdd6e1bfdf..de30538e3a 100644 --- a/common/src/main/resources/punishments/ru.yml +++ b/common/src/main/resources/punishments/ru.yml @@ -90,6 +90,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/tr.yml b/common/src/main/resources/punishments/tr.yml index f29e3ce8bb..65d86c78d8 100644 --- a/common/src/main/resources/punishments/tr.yml +++ b/common/src/main/resources/punishments/tr.yml @@ -88,6 +88,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: diff --git a/common/src/main/resources/punishments/zh.yml b/common/src/main/resources/punishments/zh.yml index 126286b408..2e4e6c9f8f 100644 --- a/common/src/main/resources/punishments/zh.yml +++ b/common/src/main/resources/punishments/zh.yml @@ -90,6 +90,24 @@ Punishments: - "5:3 [log]" - "5:3 [webhook]" - "5:3 [proxy]" + WallHit: + remove-violations-after: 300 + checks: + - "WallHit" + commands: + - "10:5 [alert]" + - "10:5 [webhook]" + - "10:5 [proxy]" + - "10:5 [log]" + EntityPierce: + remove-violations-after: 300 + checks: + - "EntityPierce" + commands: + - "15:10 [alert]" + - "15:10 [webhook]" + - "15:10 [proxy]" + - "15:10 [log]" Misc: remove-violations-after: 300 checks: From 9b97ebeb4fc587ca0c5872d8bea1d833b5fa5eb9 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:17:22 -0400 Subject: [PATCH 16/34] Add support for Hitbox Debugging --- .../checks/debug/AbstractDebugHandler.java | 2 +- .../checks/debug/HitboxDebugHandler.java | 206 ++++++++++++++++++ .../grim/grimac/checks/impl/combat/Reach.java | 62 ++++++ .../checks/impl/prediction/DebugHandler.java | 10 +- .../grimac/command/commands/GrimDebug.java | 50 +++++ .../ac/grim/grimac/manager/CheckManager.java | 2 + 6 files changed, 329 insertions(+), 3 deletions(-) create mode 100644 common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java diff --git a/common/src/main/java/ac/grim/grimac/checks/debug/AbstractDebugHandler.java b/common/src/main/java/ac/grim/grimac/checks/debug/AbstractDebugHandler.java index eb4a1ee92f..7f344f01c1 100644 --- a/common/src/main/java/ac/grim/grimac/checks/debug/AbstractDebugHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/debug/AbstractDebugHandler.java @@ -16,7 +16,7 @@ public GrimPlayer getPlayer() { return grimPlayer; } - public abstract void toggleListener(GrimPlayer player); + public abstract boolean toggleListener(GrimPlayer player); public abstract boolean toggleConsoleOutput(); } diff --git a/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java b/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java new file mode 100644 index 0000000000..05177b2ab0 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java @@ -0,0 +1,206 @@ +package ac.grim.grimac.checks.debug; + +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; +import ac.grim.grimac.utils.data.Pair; +import ac.grim.grimac.utils.math.Vector3dm; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Handles debug visualization of hitboxes and reach calculations for GrimAC. + * Sends debug data to clients through plugin messages that can be visualized by compatible clients. + */ +public class HitboxDebugHandler extends AbstractDebugHandler { + + /** + * Set of players currently listening to hitbox debug data + */ + private final Set listeners = new CopyOnWriteArraySet<>(new HashSet<>()); + + /** + * Creates a new HitboxDebugHandler for the specified player + * + * @param grimPlayer The GrimAC player instance to debug + */ + public HitboxDebugHandler(GrimPlayer grimPlayer) { + super(grimPlayer); + } + + /** + * Toggles whether a player receives hitbox debug visualization data. + * If the player is already listening, they will be removed. If not, they will be added. + * + * @param player The player to toggle debug visualization for + * @return {@code true} if the player is now listening (was added), + * {@code false} if the player is no longer listening (was removed). + */ + @Override + public boolean toggleListener(GrimPlayer player) { + boolean wasPresent = listeners.remove(player); + + if (wasPresent) { + return false; + } else { + listeners.add(player); + return true; + } + } + + @Override + public boolean toggleConsoleOutput() { + throw new UnsupportedOperationException(); + } + + /** + * Sends debug visualization data for reach calculations to all listening players. + * The data includes hitboxes, target entities, look vectors, and eye heights used in reach calculations. + * + * @param hitboxes Map of entity IDs to their collision boxes + * @param targetEntities Set of entity IDs that are considered targets + * @param lookVecsAndEyeHeights List of pairs containing look vectors and their corresponding eye heights + * @param basePos Base position before eye height adjustments + * @param isPrediction Whether these hitboxes are from a prediction calculation + * + * Packet Format (Version 1): + * - Byte: Version (0) + * - Byte: Global flags + * - Bit 0: isPrediction + * - Bits 1-7: Reserved + * - Double: Player reach/interact distance + * - Vector3d: Base position (3 doubles) + * - VarInt: Number of ray traces + * - For each ray trace: + * - Double: Eye height delta + * - Vector3d: Look vector (3 doubles) + * - VarInt: Number of hitboxes + * - For each hitbox: + * - VarInt: Entity ID + * - Byte: Flags + * - Bit 0: Is target entity + * - Bit 1: Is no collision + * - Bits 2-7: Reserved + * - If not NoCollisionBox: + * - Double: minX + * - Double: minY + * - Double: minZ + * - Double: maxX + * - Double: maxY + * - Double: maxZ + */ + public void sendHitboxData(Map hitboxes, Set targetEntities, + List> lookVecsAndEyeHeights, Vector3dm basePos, + boolean isPrediction, double reachDistance) { + if (!isEnabled()) return; + + ByteBuf buffer = Unpooled.buffer(); + try { + // Version Header + buffer.writeByte(1); + + // Global Flags Header + // Pack boolean flags into a single byte + byte global_flags = 0; + global_flags |= (isPrediction ? 1 : 0); // Bit 0: are the hitboxes from a prediction? + // Bits 2-7 reserved for future use + buffer.writeByte(global_flags); + + // Write reach distance + buffer.writeDouble(reachDistance); + + // Write base position + writeVector(buffer, basePos); + + // Write number of ray traces + ByteBufHelper.writeVarInt(buffer, lookVecsAndEyeHeights.size()); + + // Write all possible ray traces + for (Pair pair : lookVecsAndEyeHeights) { + Vector3dm lookVec = pair.first(); + double eyeHeight = pair.second(); + + // Write eye height delta and look vector + // we make them 3 meters shorter because all of the vectors passed into here are made 3 meters longer + // then they need to be so grim can report correct reach distance for too far away hits + buffer.writeDouble(eyeHeight); + writeVector(buffer, lookVec); + } + + // Write number of hitboxes + ByteBufHelper.writeVarInt(buffer, hitboxes.size()); + + // Write hitbox data + for (Map.Entry entry : hitboxes.entrySet()) { + int entityId = entry.getKey(); + CollisionBox box = entry.getValue(); + + // Write entity ID + ByteBufHelper.writeVarInt(buffer, entityId); + + // Pack boolean flags into a single byte + byte flags = 0; + flags |= (targetEntities.contains(entityId) ? 1 : 0); // Bit 0: Is target + flags |= (box == NoCollisionBox.INSTANCE ? 2 : 0); // Bit 1: Is no collision + // Bits 2-7 reserved for future use + buffer.writeByte(flags); + + // Write box coordinates if it's not a NoCollisionBox + if ((flags & 2) == 0) { + SimpleCollisionBox simpleCollisionBox = (SimpleCollisionBox) box; + buffer.writeDouble(simpleCollisionBox.minX); + buffer.writeDouble(simpleCollisionBox.minY); + buffer.writeDouble(simpleCollisionBox.minZ); + buffer.writeDouble(simpleCollisionBox.maxX); + buffer.writeDouble(simpleCollisionBox.maxY); + buffer.writeDouble(simpleCollisionBox.maxZ); + } + } + + // Convert buffer to byte array + byte[] data = new byte[buffer.readableBytes()]; + buffer.readBytes(data); + + // Create and send packet + WrapperPlayServerPluginMessage packet = new WrapperPlayServerPluginMessage( + "grim:debug_hitbox", + data + ); + + // Send to all listeners + for (GrimPlayer listener : listeners) { + if (listener != null) { + PacketEvents.getAPI().getPlayerManager().sendPacket(listener.user, packet); + } + } + } finally { + // Release buffer to prevent memory leaks + buffer.release(); + } + } + + public boolean isEnabled() { + return !listeners.isEmpty(); + } + + /** + * Helper method to write a Vector to the ByteBuf + * @param buffer The buffer to write to + * @param vector The vector to write + */ + private void writeVector(ByteBuf buffer, Vector3dm vector) { + buffer.writeDouble(vector.getX()); + buffer.writeDouble(vector.getY()); + buffer.writeDouble(vector.getZ()); + } +} diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java index 2b85177ad5..36a748b444 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java @@ -18,8 +18,11 @@ import ac.grim.grimac.api.config.ConfigManager; import ac.grim.grimac.checks.Check; import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.checks.debug.HitboxDebugHandler; import ac.grim.grimac.checks.type.PacketCheck; import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; import ac.grim.grimac.utils.data.BlockHitData; import ac.grim.grimac.utils.data.EntityHitData; @@ -248,6 +251,9 @@ private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean } } + if (hitboxDebuggingEnabled()) + sendHitboxDebugData(reachEntity, from, lookVecsAndEyeHeights, isPrediction); + HitData foundHitData = null; // If the entity is within range of the player (we'll flag anyway if not, so no point checking blocks in this case) // Ignore when could be hitting through a moving shulker, piston blocks. They are just too glitchy/uncertain to check. @@ -315,4 +321,60 @@ public void handleBlockChange(Vector3i vector3i, WrappedBlockState state) { if (state.equals(player.compensatedWorld.getBlock(vector3i))) return; blocksChangedThisTick.add(vector3i); } + + private boolean hitboxDebuggingEnabled() { + return player.checkManager.getCheck(HitboxDebugHandler.class).isEnabled(); + } + + private void sendHitboxDebugData(PacketEntity reachEntity, Vector3d from, List> lookVecsAndEyeHeights, boolean isPrediction) { + Map hitboxes = new HashMap<>(); + for (Int2ObjectMap.Entry entry : player.compensatedEntities.entityMap.int2ObjectEntrySet()) { + PacketEntity entity = entry.getValue(); + if (!entity.canHit()) continue; + + CollisionBox box; + + if (entity.equals(reachEntity)) { + // Target entity gets expanded hitbox + box = entity.getPossibleCollisionBoxes(); + SimpleCollisionBox sBox = (SimpleCollisionBox) box; + sBox.expand(threshold); + + // Add movement threshold uncertainty for 1.9+ or non-position updates + if (!player.packetStateData.didLastLastMovementIncludePosition + || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) { + sBox.expand(player.getMovementThreshold()); + } + } else { + // Non-target entities + box = entity.getMinimumPossibleCollisionBoxes(); + if (box instanceof NoCollisionBox) { + hitboxes.put(entry.getIntKey(), NoCollisionBox.INSTANCE); + continue; + } else if (box instanceof SimpleCollisionBox) { + SimpleCollisionBox sBox = (SimpleCollisionBox) box; + sBox.expand(-threshold); + // Shrink non-target entities by movement threshold when applicable + if (!player.packetStateData.didLastLastMovementIncludePosition + || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) { + sBox.expand(-player.getMovementThreshold()); + } + } + } + + // Add 1.8 and below extra hitbox size + if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9) + && box instanceof SimpleCollisionBox) { + ((SimpleCollisionBox) box).expand(0.1f); + } + + hitboxes.put(entry.getIntKey(), box); + } + + player.checkManager.getCheck(HitboxDebugHandler.class).sendHitboxData(hitboxes, + Collections.singleton(player.compensatedEntities.getPacketEntityID(reachEntity)), + lookVecsAndEyeHeights, + new Vector3dm(from.getX(), from.getY(), from.getZ()), + isPrediction, player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE)); + } } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/DebugHandler.java b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/DebugHandler.java index ea2650dd4d..5f4d07981e 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/DebugHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/DebugHandler.java @@ -133,8 +133,14 @@ private String pickColor(double offset, double totalOffset) { } @Override - public void toggleListener(GrimPlayer player) { - if (!listeners.remove(player)) listeners.add(player); + public boolean toggleListener(GrimPlayer player) { + boolean wasPresent = listeners.remove(player); + if (wasPresent) { + return false; + } else { + listeners.add(player); + return true; + } } @Override diff --git a/common/src/main/java/ac/grim/grimac/command/commands/GrimDebug.java b/common/src/main/java/ac/grim/grimac/command/commands/GrimDebug.java index 71bb6f33aa..756a4f6a3f 100644 --- a/common/src/main/java/ac/grim/grimac/command/commands/GrimDebug.java +++ b/common/src/main/java/ac/grim/grimac/command/commands/GrimDebug.java @@ -1,6 +1,7 @@ package ac.grim.grimac.command.commands; import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.checks.debug.HitboxDebugHandler; import ac.grim.grimac.command.BuildableCommand; import ac.grim.grimac.platform.api.command.PlayerSelector; import ac.grim.grimac.platform.api.sender.Sender; @@ -36,9 +37,16 @@ public void register(CommandManager commandManager) { .required("target", GrimAPI.INSTANCE.getParserDescriptors().getSinglePlayer()) .handler(this::handleConsoleDebug); + Command.Builder hitboxDebugCommand = grimCommand + .literal("hitboxdebug", Description.of("Toggle hitbox debug visualization")) + .permission("grim.hitboxdebug") + .optional("target", GrimAPI.INSTANCE.getParserDescriptors().getSinglePlayer(), Description.of("Player to debug (defaults to self if sender is player)")) + .handler(this::handleHitboxDebug); + // Register command commandManager.command(debugCommand); commandManager.command(consoleDebugCommand); + commandManager.command(hitboxDebugCommand); } private void handleDebug(@NonNull CommandContext context) { @@ -81,6 +89,48 @@ private void handleConsoleDebug(@NonNull CommandContext context) { sender.sendMessage(message); } + private void handleHitboxDebug(@NonNull CommandContext context) { + Sender sender = context.sender(); + PlayerSelector playerSelector = context.getOrDefault("target", null); + + // Hitbox debug requires a *player* to be the listener (the one seeing the boxes) + if (!sender.isPlayer()) { + sender.sendMessage(MessageUtil.getParsedComponent(sender, + "hitboxdebug-player-only", + "%prefix% &cHitbox debug can only be toggled by players.") + ); + return; + } + + // Determine the target player whose hitboxes are being debugged + GrimPlayer targetGrimPlayer = parseTarget(sender, playerSelector == null ? sender : playerSelector.getSinglePlayer()); + if (targetGrimPlayer == null) return; + + // Get the sender's GrimPlayer data, as they are the listener + GrimPlayer senderGrimPlayer = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(sender.getUniqueId()); + if (senderGrimPlayer == null) { + sender.sendMessage(Component.text("Could not find your player data to register as a listener.", NamedTextColor.RED)); + return; + } + + // Get the HitboxDebugHandler check instance and toggle the listener + HitboxDebugHandler hitboxHandler = targetGrimPlayer.checkManager.getCheck(HitboxDebugHandler.class); + if (hitboxHandler == null) { + sender.sendMessage(Component.text("HitboxDebugHandler check not found for target player.", NamedTextColor.RED)); + return; + } + + boolean enabled = hitboxHandler.toggleListener(senderGrimPlayer); // Pass the sender/listener + + // Send feedback message + Component message = Component.text() + .append(Component.text("Hitbox debug listener for ", NamedTextColor.GRAY)) + .append(Component.text(targetGrimPlayer.getName(), NamedTextColor.WHITE)) + .append(Component.text(enabled ? " enabled." : " disabled.", NamedTextColor.GRAY)) + .build(); + sender.sendMessage(message); + } + private @Nullable GrimPlayer parseTarget(@NonNull Sender sender, @Nullable Sender t) { if (sender.isConsole() && t == null) { sender.sendMessage(MessageUtil.getParsedComponent(sender, "console-specify-target", "%prefix% &cYou must specify a target as the console!")); diff --git a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java index 83e4214552..5669de47cd 100644 --- a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java @@ -2,6 +2,7 @@ import ac.grim.grimac.GrimAPI; import ac.grim.grimac.api.AbstractCheck; +import ac.grim.grimac.checks.debug.HitboxDebugHandler; import ac.grim.grimac.checks.impl.aim.AimDuplicateLook; import ac.grim.grimac.checks.impl.aim.AimModulo360; import ac.grim.grimac.checks.impl.aim.processor.AimProcessor; @@ -294,6 +295,7 @@ public CheckManager(GrimPlayer player) { .put(Hitboxes.class, new Hitboxes(player)) // Hitboxes is invoked by Reach .put(WallHit.class, new WallHit(player)) .put(EntityPierce.class, new EntityPierce(player)) + .put(HitboxDebugHandler.class, new HitboxDebugHandler(player)) .build(); allChecks = new ImmutableClassToInstanceMap.Builder() From 1d5ed18b77ffcb465699fc1248de660818d29e7d Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:25:12 -0400 Subject: [PATCH 17/34] Cull dead entities from canHit() --- .../main/java/ac/grim/grimac/checks/impl/combat/Reach.java | 6 ++++++ .../java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java index 36a748b444..cc61863ec3 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java @@ -69,6 +69,7 @@ public class Reach extends Check implements PacketCheck { // extra distance to raytrace beyond player reach distance so we know how far beyond the legit distance a cheater hit public static final double extraSearchDistance = 3; + private boolean ignoreNonPlayerTargets; private boolean cancelImpossibleHits; public double threshold; private double cancelBuffer; // For the next 4 hits after using reach, we aggressively cancel reach @@ -102,6 +103,10 @@ public void onPacketReceive(final PacketReceiveEvent event) { return; } + if (ignoreNonPlayerTargets && !entity.getType().equals(EntityTypes.PLAYER)) { + return; + } + // Dead entities cause false flags (https://github.com/GrimAnticheat/Grim/issues/546) if (entity.isDead) return; @@ -299,6 +304,7 @@ private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean @Override public void onReload(ConfigManager config) { + this.ignoreNonPlayerTargets = config.getBooleanElse("Reach.ignore-non-player-targets", false); this.cancelImpossibleHits = config.getBooleanElse("Reach.block-impossible-hits", true); this.threshold = config.getDoubleElse("Reach.threshold", 0.0005); } diff --git a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java index 3cc74ac0ed..526bd4ec16 100644 --- a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java +++ b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java @@ -176,7 +176,7 @@ public static HitData getNearestHitResult(GrimPlayer player, PacketEntity target PacketEntity closestEntity = null; double closestDistanceSquared = blockHitData != null ? blockHitData.getBlockHitLocation().distanceSquared(startingVec) : maxAttackDistance * maxAttackDistance; - for (PacketEntity entity : player.compensatedEntities.entityMap.values().stream().filter(TypedPacketEntity::canHit).toList()) { + for (PacketEntity entity : player.compensatedEntities.entityMap.values().stream().filter(PacketEntity::canHit).toList()) { SimpleCollisionBox box = null; // 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers) // Yes, this is vanilla and not uncertainty. All reach checks have this or they are wrong. From a444ffe4aea6c79e1baa8803f13c07fd21fa0dcd Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:03:53 -0400 Subject: [PATCH 18/34] Update buildscript Notes: - Mark actions builds automatically as alpha on Modrinth - Shorten Fabric version string to be same as Bukkit on Modrinth --- .github/workflows/gradle-publish.yml | 202 ++++++++++++++---- .../grimac/utils/nmsutil/WorldRayTrace.java | 1 - 2 files changed, 162 insertions(+), 41 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index be2a751cb0..06a5604714 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -13,47 +13,169 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: read + contents: write # build-tag-number + mc-publish need these packages: write + actions: write steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - server-id: github # Value of the distributionManagement/repository/id field of the pom.xml - settings-path: ${{ github.workspace }} # location for the settings.xml file - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 # Handles Gradle wrapper validation and basic caching - - - name: Cache Gradle Loom Cache - uses: actions/cache@v4 - with: - path: .gradle/loom-cache # Path to Loom cache relative to workspace root - key: loom-cache-${{ runner.os }}-${{ hashFiles('**/libs.versions.toml', 'fabric/**/build.gradle.kts') }} - restore-keys: | - loom-cache-${{ runner.os }} - - - name: Build with Gradle (Actual Build for Artifacts) - run: ./gradlew build - - - name: Upload Bukkit Artifact - uses: actions/upload-artifact@v4 - with: - name: grimac-bukkit - # Adding if-no-files-found for robustness - path: ${{ github.workspace }}/bukkit/build/libs/grimac-bukkit-*.jar - if-no-files-found: error - - - name: Upload Fabric Artifact - uses: actions/upload-artifact@v4 - with: - name: grimac-fabric - # Adding if-no-files-found for robustness - path: ${{ github.workspace }}/fabric/build/libs/grimac-fabric-*.jar - if-no-files-found: error + #------------------------------------------------------------------ + # 0) checkout + JDK + #------------------------------------------------------------------ + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + server-id: github + settings-path: ${{ github.workspace }} + + #------------------------------------------------------------------ + # 1) Gradle wrapper validation + caches + #------------------------------------------------------------------ + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 # wrapper-validation + caching + + - name: Cache Loom + uses: actions/cache@v4 + with: + path: .gradle/loom-cache + key: loom-cache-${{ runner.os }}-${{ hashFiles('**/libs.versions.toml', + 'fabric/**/build.gradle.kts', + 'bukkit/**/build.gradle.kts') }} + restore-keys: | + loom-cache-${{ runner.os }} + + #------------------------------------------------------------------ + # 2) Resolve VERSIONs **before** building + #------------------------------------------------------------------ + - name: Compute MAIN version (default build) + id: ver_main + run: | + VERSION=$(./gradlew -q printVersion | grep '^VERSION=' | cut -d'=' -f2) + echo "main_version=$VERSION" >> $GITHUB_OUTPUT + echo "MAIN_VERSION=$VERSION" >> $GITHUB_ENV + echo "Main version: $VERSION" + + - name: Compute LITE version (-PshadePE=false) + id: ver_lite + run: | + VERSION=$(./gradlew -q -PshadePE=false printVersion | grep '^VERSION=' | cut -d'=' -f2) + echo "lite_version=$VERSION" >> $GITHUB_OUTPUT + echo "LITE_VERSION=$VERSION" >> $GITHUB_ENV + echo "Lite version: $VERSION" + + #------------------------------------------------------------------ + # 3) Build shaded / “main” jars (all modules) + #------------------------------------------------------------------ + - name: Build (all platforms, shaded) + run: ./gradlew build + + #------------------------------------------------------------------ + # 4) Upload MAIN Bukkit + Fabric artefacts (they exist now, lite doesn’t) + #------------------------------------------------------------------ + - name: Upload Bukkit ‑ MAIN + uses: actions/upload-artifact@v4 + with: + name: grimac-bukkit + path: bukkit/build/libs/grimac-bukkit-${{ env.MAIN_VERSION }}.jar + if-no-files-found: error + + - name: Upload Fabric + uses: actions/upload-artifact@v4 + with: + name: grimac-fabric + path: fabric/build/libs/grimac-fabric-${{ env.MAIN_VERSION }}.jar + if-no-files-found: error + + #------------------------------------------------------------------ + # 5) Build **lite** Bukkit jar + #------------------------------------------------------------------ + - name: Build Bukkit-Lite (no shaded PacketEvents) + run: ./gradlew :bukkit:build -PshadePE=false + + #------------------------------------------------------------------ + # 6) Upload LITE artefact + #------------------------------------------------------------------ + - name: Upload Bukkit ‑ LITE + uses: actions/upload-artifact@v4 + with: + name: grimac-bukkit-lite + path: bukkit/build/libs/grimac-bukkit-${{ env.LITE_VERSION }}.jar + if-no-files-found: error + + #------------------------------------------------------------------ + # 7) Generate incremental build-number (main / merge / release only) + #------------------------------------------------------------------ + - name: Generate build number + if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) + id: buildnumber + uses: onyxmueller/build-tag-number@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + #------------------------------------------------------------------ + # 8-A) Publish **Bukkit** jars to Modrinth + #------------------------------------------------------------------ + - name: Publish to Modrinth (Bukkit) + if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) + uses: Kir-Antipov/mc-publish@v3.3 + with: + modrinth-id: ${{ vars.MODRINTH_ID }} # Bukkit & Fabric can share or differ + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + modrinth-featured: true + modrinth-unfeature-mode: subset + + files: | + bukkit/build/libs/grimac-bukkit-${{ env.MAIN_VERSION }}.jar + bukkit/build/libs/grimac-bukkit-${{ env.LITE_VERSION }}.jar + + name: Lightning Grim Anticheat (Bukkit) ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} + version: ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} + version-type: alpha + + loaders: | + bukkit + spigot + paper + folia + purpur + + game-versions: | + >=1.7 + + retry-attempts: 2 + retry-delay: 10000 + fail-mode: fail + + #------------------------------------------------------------------ + # 8-B) Publish **Fabric** jar to Modrinth + #------------------------------------------------------------------ + - name: Publish to Modrinth (Fabric) + if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) + uses: Kir-Antipov/mc-publish@v3.3 + with: + # use MODRINTH_ID_FABRIC if you keep Fabric in a separate project + modrinth-id: ${{ vars.MODRINTH_ID_FABRIC || vars.MODRINTH_ID }} + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + modrinth-featured: true + modrinth-unfeature-mode: subset + + files: | + fabric/build/libs/grimac-fabric-${{ env.MAIN_VERSION }}.jar + + name: Lightning Grim Anticheat (Fabric) ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} + version: ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} + version-type: alpha + + loaders: | + fabric + + game-versions: | + >=1.16.1 + + retry-attempts: 2 + retry-delay: 10000 + fail-mode: fail diff --git a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java index 526bd4ec16..00270a6265 100644 --- a/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java +++ b/common/src/main/java/ac/grim/grimac/utils/nmsutil/WorldRayTrace.java @@ -13,7 +13,6 @@ import ac.grim.grimac.utils.data.HitData; import ac.grim.grimac.utils.data.Pair; import ac.grim.grimac.utils.data.packetentity.PacketEntity; -import ac.grim.grimac.utils.data.packetentity.TypedPacketEntity; import ac.grim.grimac.utils.math.GrimMath; import ac.grim.grimac.utils.math.Vector3dm; import com.github.retrooper.packetevents.protocol.attribute.Attributes; From e1627736b1296e85e7e57dccd6f960a4677d92cf Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:45:19 -0400 Subject: [PATCH 19/34] Buildscript Update & Dirty Painting Hack - Update gradle buildscript to not include branch-name if branch is called "lightning" - Update GitHub Actions to automatically include changelog and to rename the jar LightningGrim-${platform}-${version}.jar - Mark Paintings as entities that cannot be hit to prevent EntityPierce (formally HitboxEntity) falses. --- .github/workflows/gradle-publish.yml | 116 +++++++++++++----- .../src/main/kotlin/versioning/VersionUtil.kt | 1 + .../packetentity/PacketEntityPainting.java | 7 ++ 3 files changed, 91 insertions(+), 33 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 06a5604714..dd10fc65eb 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -1,10 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created -# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle - name: Gradle Package on: [push, workflow_dispatch] @@ -13,15 +6,19 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: write # build-tag-number + mc-publish need these + contents: write # needed by build-tag-number and mc-publish packages: write actions: write steps: #------------------------------------------------------------------ - # 0) checkout + JDK + # 0) Checkout + JDK #------------------------------------------------------------------ - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 + with: + # fetch the entire history and all tags + fetch-depth: 50 - name: Set up JDK 21 uses: actions/setup-java@v4 @@ -35,7 +32,7 @@ jobs: # 1) Gradle wrapper validation + caches #------------------------------------------------------------------ - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 # wrapper-validation + caching + uses: gradle/actions/setup-gradle@v4 - name: Cache Loom uses: actions/cache@v4 @@ -64,7 +61,7 @@ jobs: VERSION=$(./gradlew -q -PshadePE=false printVersion | grep '^VERSION=' | cut -d'=' -f2) echo "lite_version=$VERSION" >> $GITHUB_OUTPUT echo "LITE_VERSION=$VERSION" >> $GITHUB_ENV - echo "Lite version: $VERSION" + echo "Lite version: $VERSION" #------------------------------------------------------------------ # 3) Build shaded / “main” jars (all modules) @@ -73,40 +70,93 @@ jobs: run: ./gradlew build #------------------------------------------------------------------ - # 4) Upload MAIN Bukkit + Fabric artefacts (they exist now, lite doesn’t) + # 4) Build **lite** Bukkit jar + #------------------------------------------------------------------ + - name: Build Bukkit-Lite (no shaded PacketEvents) + run: ./gradlew :bukkit:build -PshadePE=false + + #------------------------------------------------------------------ + # 4.5) Rename jars to LightningGrim-* before we upload/publish + #------------------------------------------------------------------ + - name: Rename jars to LightningGrim + shell: bash + run: | + set -e + mv "bukkit/build/libs/grimac-bukkit-${MAIN_VERSION}.jar" \ + "bukkit/build/libs/LightningGrim-bukkit-${MAIN_VERSION}.jar" + + mv "bukkit/build/libs/grimac-bukkit-${LITE_VERSION}.jar" \ + "bukkit/build/libs/LightningGrim-bukkit-${LITE_VERSION}.jar" + + mv "fabric/build/libs/grimac-fabric-${MAIN_VERSION}.jar" \ + "fabric/build/libs/LightningGrim-fabric-${MAIN_VERSION}.jar" + + #------------------------------------------------------------------ + # 5) Upload MAIN Bukkit + Fabric artefacts #------------------------------------------------------------------ - - name: Upload Bukkit ‑ MAIN + - name: Upload Bukkit – MAIN uses: actions/upload-artifact@v4 with: name: grimac-bukkit - path: bukkit/build/libs/grimac-bukkit-${{ env.MAIN_VERSION }}.jar + path: bukkit/build/libs/LightningGrim-bukkit-${{ env.MAIN_VERSION }}.jar if-no-files-found: error - name: Upload Fabric uses: actions/upload-artifact@v4 with: name: grimac-fabric - path: fabric/build/libs/grimac-fabric-${{ env.MAIN_VERSION }}.jar + path: fabric/build/libs/LightningGrim-fabric-${{ env.MAIN_VERSION }}.jar if-no-files-found: error - #------------------------------------------------------------------ - # 5) Build **lite** Bukkit jar - #------------------------------------------------------------------ - - name: Build Bukkit-Lite (no shaded PacketEvents) - run: ./gradlew :bukkit:build -PshadePE=false - #------------------------------------------------------------------ # 6) Upload LITE artefact #------------------------------------------------------------------ - - name: Upload Bukkit ‑ LITE + - name: Upload Bukkit – LITE uses: actions/upload-artifact@v4 with: name: grimac-bukkit-lite - path: bukkit/build/libs/grimac-bukkit-${{ env.LITE_VERSION }}.jar + path: bukkit/build/libs/LightningGrim-bukkit-${{ env.LITE_VERSION }}.jar if-no-files-found: error #------------------------------------------------------------------ - # 7) Generate incremental build-number (main / merge / release only) + # 7) Auto-generate CHANGELOG (since previous build tag) + #------------------------------------------------------------------ + - name: Generate changelog + id: changelog + if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) + run: | + set -e + git fetch --tags --quiet + + # Most recent tag that looks like “build-*”; empty if none exist + LAST_TAG=$(git describe --tags --match "build-*" --abbrev=0 2>/dev/null || echo "") + echo "Last tag: ${LAST_TAG:-}" + + if [ -z "$LAST_TAG" ]; then + # First build (or no previous tag) → grab latest 50 commits + NOTES=$(git log -n 50 --pretty=format:'* %s (%h)' --no-merges) + else + # Normal case → commits since the last build tag + NOTES=$(git log "$LAST_TAG"..HEAD --pretty=format:'* %s (%h)' --no-merges) + fi + + # Fallback if there were no code changes + if [ -z "$NOTES" ]; then + NOTES="* No code changes since last build" + fi + + # Export multiline output for downstream steps + { + echo "notes<> "$GITHUB_OUTPUT" + + echo "Changelog generated:" + echo "$NOTES" + + #------------------------------------------------------------------ + # 8) Generate incremental build number (after changelog step!) #------------------------------------------------------------------ - name: Generate build number if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) @@ -116,7 +166,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} #------------------------------------------------------------------ - # 8-A) Publish **Bukkit** jars to Modrinth + # 9-A) Publish **Bukkit** jars to Modrinth #------------------------------------------------------------------ - name: Publish to Modrinth (Bukkit) if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) @@ -128,12 +178,13 @@ jobs: modrinth-unfeature-mode: subset files: | - bukkit/build/libs/grimac-bukkit-${{ env.MAIN_VERSION }}.jar - bukkit/build/libs/grimac-bukkit-${{ env.LITE_VERSION }}.jar + bukkit/build/libs/LightningGrim-bukkit-${{ env.MAIN_VERSION }}.jar + bukkit/build/libs/LightningGrim-bukkit-${{ env.LITE_VERSION }}.jar name: Lightning Grim Anticheat (Bukkit) ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} version: ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} version-type: alpha + changelog: ${{ steps.changelog.outputs.notes }} loaders: | bukkit @@ -150,24 +201,24 @@ jobs: fail-mode: fail #------------------------------------------------------------------ - # 8-B) Publish **Fabric** jar to Modrinth + # 9-B) Publish **Fabric** jar to Modrinth #------------------------------------------------------------------ - name: Publish to Modrinth (Fabric) if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) uses: Kir-Antipov/mc-publish@v3.3 with: - # use MODRINTH_ID_FABRIC if you keep Fabric in a separate project modrinth-id: ${{ vars.MODRINTH_ID_FABRIC || vars.MODRINTH_ID }} modrinth-token: ${{ secrets.MODRINTH_TOKEN }} modrinth-featured: true modrinth-unfeature-mode: subset files: | - fabric/build/libs/grimac-fabric-${{ env.MAIN_VERSION }}.jar + fabric/build/libs/LightningGrim-fabric-${{ env.MAIN_VERSION }}.jar name: Lightning Grim Anticheat (Fabric) ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} version: ${{ env.MAIN_VERSION }}-b${{ steps.buildnumber.outputs.build_number }} version-type: alpha + changelog: ${{ steps.changelog.outputs.notes }} loaders: | fabric @@ -177,5 +228,4 @@ jobs: retry-attempts: 2 retry-delay: 10000 - fail-mode: fail - + fail-mode: fail \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/versioning/VersionUtil.kt b/buildSrc/src/main/kotlin/versioning/VersionUtil.kt index 058bdb39a7..39a3d34f2b 100644 --- a/buildSrc/src/main/kotlin/versioning/VersionUtil.kt +++ b/buildSrc/src/main/kotlin/versioning/VersionUtil.kt @@ -81,6 +81,7 @@ object VersionUtil { val branch = stdout.toString().trim() return when (branch) { + "lightning" -> null "main", "2.0" -> null // ← ignore these branches else -> branch.replace("/", "_") } diff --git a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntityPainting.java b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntityPainting.java index 7c371b0f0d..22e5c55f15 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntityPainting.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntityPainting.java @@ -16,4 +16,11 @@ public PacketEntityPainting(GrimPlayer player, UUID uuid, double x, double y, do super(player, uuid, EntityTypes.PAINTING, x, y, z); this.direction = direction; } + + // This is incorrect, temporary measure to exempt paintings from HitboxEntity + // Will properly model later + @Override + public boolean canHit() { + return false; + } } From 6374878380d6b33c2f7f52c8813975d06b1208e9 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sat, 3 May 2025 09:06:04 -0400 Subject: [PATCH 20/34] Heavy memory use optimization - 2.2x reduction in queued task overhead - reduction in size of queued single block changes - up to 8x reduction in size of queued multi block changes - fixed blocks disappearing in B73 caused by incorrect shifting in new block udpate handler --- .../worldreader/BasePacketWorldReader.java | 38 ++-- .../LegacyMultiBlockChangeHandler.java | 30 +++ ...V1160MultiBlockChangeBitRepackHandler.java | 175 ++++++++++++++++++ .../VersionedMultiBlockChangeHandler.java | 22 +++ .../ac/grim/grimac/player/GrimPlayer.java | 8 +- .../grimac/utils/latency/ILatencyUtils.java | 28 +++ .../grimac/utils/latency/LatencyUtils.java | 93 +++++----- 7 files changed, 316 insertions(+), 78 deletions(-) create mode 100644 common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/LegacyMultiBlockChangeHandler.java create mode 100644 common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/V1160MultiBlockChangeBitRepackHandler.java create mode 100644 common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/VersionedMultiBlockChangeHandler.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/latency/ILatencyUtils.java diff --git a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/BasePacketWorldReader.java b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/BasePacketWorldReader.java index 88b5514993..ca806a03fc 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/BasePacketWorldReader.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/BasePacketWorldReader.java @@ -1,12 +1,17 @@ package ac.grim.grimac.events.packets.worldreader; import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.events.packets.worldreader.multiblockchange.LegacyMultiBlockChangeHandler; +import ac.grim.grimac.events.packets.worldreader.multiblockchange.V1160MultiBlockChangeBitRepackHandler; +import ac.grim.grimac.events.packets.worldreader.multiblockchange.VersionedMultiBlockChangeHandler; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.chunks.Column; import ac.grim.grimac.utils.data.TeleportData; +import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.event.PacketListenerAbstract; import com.github.retrooper.packetevents.event.PacketListenerPriority; import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.world.chunk.BaseChunk; import com.github.retrooper.packetevents.util.Vector3i; @@ -16,11 +21,15 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChangeGameState; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkData; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkDataBulk; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUnloadChunk; public class BasePacketWorldReader extends PacketListenerAbstract { + private final static VersionedMultiBlockChangeHandler versionedMultiBlockChangeHandler + = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_16) + ? new V1160MultiBlockChangeBitRepackHandler() + : new LegacyMultiBlockChangeHandler(); + public BasePacketWorldReader() { super(PacketListenerPriority.HIGH); } @@ -161,28 +170,15 @@ public void handleBlockChange(GrimPlayer player, PacketSendEvent event) { player.lastTransSent + 2 < System.currentTimeMillis()) player.sendTransaction(); - player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.compensatedWorld.updateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockChange.getBlockId())); + int x = blockPosition.getX(); + int y = blockPosition.getY(); + int z = blockPosition.getZ(); + int blockId = blockChange.getBlockId(); + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.compensatedWorld.updateBlock(x, y, z, blockId)); } public void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event) { - WrapperPlayServerMultiBlockChange multiBlockChange = new WrapperPlayServerMultiBlockChange(event); - - int range = 16; - - final var blocks = multiBlockChange.getBlocks(); - for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) { - // Don't send a transaction unless it's within 16 blocks of the player - if (Math.abs(blockChange.getX() - player.x) < range && Math.abs(blockChange.getY() - player.y) < range && Math.abs(blockChange.getZ() - player.z) < range && player.lastTransSent + 2 < System.currentTimeMillis()) { - player.sendTransaction(); - break; - } - } - - // Add a single runnable to prevent excessive memory use when there are lots of block changes - player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> { - for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) { - player.compensatedWorld.updateBlock(blockChange.getX(), blockChange.getY(), blockChange.getZ(), blockChange.getBlockId()); - } - }); + versionedMultiBlockChangeHandler.handleMultiBlockChange(player, event); } } diff --git a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/LegacyMultiBlockChangeHandler.java b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/LegacyMultiBlockChangeHandler.java new file mode 100644 index 0000000000..7f91ad4e0f --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/LegacyMultiBlockChangeHandler.java @@ -0,0 +1,30 @@ +package ac.grim.grimac.events.packets.worldreader.multiblockchange; + +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange; + +public class LegacyMultiBlockChangeHandler implements VersionedMultiBlockChangeHandler { + + // TODO hande optimize Pre 1.16 code to also reduce memory usage and not use wrapper + @Override + public void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event) { + WrapperPlayServerMultiBlockChange multiBlockChange = new WrapperPlayServerMultiBlockChange(event); + + final var blocks = multiBlockChange.getBlocks(); + for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) { + // Don't send a transaction unless it's within 16 blocks of the player + if (Math.abs(blockChange.getX() - player.x) < RANGE && Math.abs(blockChange.getY() - player.y) < RANGE && Math.abs(blockChange.getZ() - player.z) < RANGE && player.lastTransSent + TRANSACTION_COOLDOWN_MS < System.currentTimeMillis()) { + player.sendTransaction(); + break; + } + } + + // Add a single runnable to prevent excessive memory use when there are lots of block changes + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> { + for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) { + player.compensatedWorld.updateBlock(blockChange.getX(), blockChange.getY(), blockChange.getZ(), blockChange.getBlockId()); + } + }); + } +} diff --git a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/V1160MultiBlockChangeBitRepackHandler.java b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/V1160MultiBlockChangeBitRepackHandler.java new file mode 100644 index 0000000000..e2419b828e --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/V1160MultiBlockChangeBitRepackHandler.java @@ -0,0 +1,175 @@ +package ac.grim.grimac.events.packets.worldreader.multiblockchange; + +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.manager.server.ServerVersion; +import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper; +import io.netty.buffer.ByteBuf; + +/** + *

+ * Minecraft’s MultiBlockChange packet batches many block updates in one shot: + * the server sends a 64-bit section coordinate, an optional trustEdges flag, + * a VarInt count, then that many VarLong-encoded block changes + * (52 bits of global blockStateId + 12 bits of local X/Z/Y + * within the 16×16×16 section). + *

+ * + *

+ * To shrink our on-heap footprint for deferred tasks, we immediately repack + * each VarLong into one 32-bit int, while still using the + * vanilla 64-bit header for coords. + *

+ * + *

Vanilla “on-the-wire” format

+ *
+ * 1) sectionEncodedPosition : Long
+ * 2) [trustEdges?           : Boolean]   // only on protocol ≤1.19.4
+ * 3) recordCount            : VarInt
+ * 4) records[]              : recordCount × VarLong
+ *
+ *    Each VarLong is bits:
+ *     [63……12] blockStateId (52 bits)
+ *     [11……8]  localX       (4 bits, 0–15)
+ *     [7……4]   localZ       (4 bits, 0–15)
+ *     [3……0]   localY       (4 bits, 0–15)
+ * 
+ * + *

Vanilla 64-bit section header “encodedPosition”

+ *
+ * bits   63……42   41……20    19……0
+ *       ┌────────┬─────────┬────────┐
+ *       │  secX  │  secZ   │  secY  │
+ *       │ (22b)  │ (22b)   │ (20b)  │
+ *       └────────┴─────────┴────────┘
+ *
+ *   secX = encoded >> 42
+ *   secZ = encoded << 22 >> 42
+ *   secY = encoded << 44 >> 44
+ * 
+ * + *

Our 32-bit repacked block record (MSB → LSB)

+ *
+ * bits  31…17   16…12   11…8   7…4   3…0
+ *      ┌───────┬───────┬───────┬──────┬─────┐
+ *      │ state │ spare │  lX   │  lZ  │ lY  │
+ *      │ (15b) │ (5b)  │ (4b)  │ (4b) │(4b) │
+ *      └───────┴───────┴───────┴──────┴─────┘
+ *
+ *  • state = (data >>> 12) & 0x7FFF
+ *  • spare = bits 12–16 (unused, reserved for flags)
+ *  • lX    = (data >>>  8) & 0xF
+ *  • lZ    = (data >>>  4) & 0xF
+ *  • lY    =  data          & 0xF
+ *
+ *  Then pack:  packed = (state << 17) | ((lX<<8)|(lZ<<4)|lY)
+ * 
+ */ +public final class V1160MultiBlockChangeBitRepackHandler + implements ac.grim.grimac.events.packets.worldreader.multiblockchange.VersionedMultiBlockChangeHandler { + + /* ---------- bit masks / shifts for the packed int ---------- */ + private static final int SHIFT_STATE = 17; // 32-15 = 17 + private static final int MASK_STATE = 0x7FFF; // 15 bits + + static final int MASK_LOCAL = 0xFFF; // 12 bits + + /* ---------- does this protocol still have trustEdges ? ----- */ + private static final boolean HAS_TRUST_EDGES = + PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_19_4); + + @Override + public void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event) { + // PE resets writer index for us, we don't have to call buffer.writerIndex(originalWriterIndex) + ByteBuf buf = (ByteBuf) event.getByteBuf(); + + /* 1. Section-position header (64 bits) ------------------ */ + long sectionEncodedPosition = ByteBufHelper.readLong(buf); + + if (HAS_TRUST_EDGES) { // skip only when it really exists + buf.skipBytes(1); + } + + /* 2. Record count + packed-int array ------------------- */ + int recordCount = ByteBufHelper.readVarInt(buf); + int[] packed = new int[recordCount]; + + /* 3. Decode packet (still comes as varLong per record) */ + // Unpack section coords once for the “near player” test + int secX = (int) (sectionEncodedPosition >> 42); + int secZ = (int) (sectionEncodedPosition << 22 >> 42); + int secY = (int) (sectionEncodedPosition << 44 >> 44); + + int baseX = secX << 4; + int baseY = secY << 4; + int baseZ = secZ << 4; + + boolean sendTx = false; + + // Use this to not spam the player with transactions if one has been sent within COOLDOWN + long now = System.currentTimeMillis(); + for (int i = 0; i < recordCount; i++) { + + long data = readVarLong(buf); // 52+12 bits + + int local = (int) (data & 0xFFFL); // 12-bit pos + + packed[i] = repackFromLong(data); + + /* -------- near-player distance test -------------- */ + if (!sendTx) { + int lx = (local >>> 8) & 0xF; + int lz = (local >>> 4) & 0xF; + int ly = local & 0xF; + + int wx = baseX + lx, wy = baseY + ly, wz = baseZ + lz; + + if (Math.abs(wx - player.x) < RANGE && + Math.abs(wy - player.y) < RANGE && + Math.abs(wz - player.z) < RANGE && + player.lastTransSent + TRANSACTION_COOLDOWN_MS < now) { + sendTx = true; + } + } + } + + if (sendTx) + player.sendTransaction(); + + /* 4. Queue runnable – captures only int[] + sectionPos */ + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> { + + // unpack section once per execution + int sX = (int) (sectionEncodedPosition >> 42); + int sY = (int) (sectionEncodedPosition << 44 >> 44); + int sZ = (int) (sectionEncodedPosition << 22 >> 42); + + int bx = sX << 4, by = sY << 4, bz = sZ << 4; + + for (int rec : packed) { + int stateId = (rec >>> SHIFT_STATE) & MASK_STATE; + int lx = (rec >>> 8) & 0xF; + int lz = (rec >>> 4) & 0xF; + int ly = rec & 0xF; + + int wx = bx + lx; + int wy = by + ly; + int wz = bz + lz; + + player.compensatedWorld.updateBlock(wx, wy, wz, stateId); + } + }); + } + + public int repackFromLong(long data) { + // 1) extract the 15-bit state from bits 12.. (original >> 12) + int blockState = (int)((data >>> 12) & MASK_STATE); + + // 2) extract the 12-bit local from bits 0..11 + int local = (int)( data & MASK_LOCAL); + + // 3) glue them together + return (blockState << SHIFT_STATE) | local; + } +} diff --git a/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/VersionedMultiBlockChangeHandler.java b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/VersionedMultiBlockChangeHandler.java new file mode 100644 index 0000000000..909a53c1a1 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/events/packets/worldreader/multiblockchange/VersionedMultiBlockChangeHandler.java @@ -0,0 +1,22 @@ +package ac.grim.grimac.events.packets.worldreader.multiblockchange; + +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import io.netty.buffer.ByteBuf; + +public interface VersionedMultiBlockChangeHandler { + + int RANGE = 16; + long TRANSACTION_COOLDOWN_MS = 2; // In milliseconds + + void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event); + default long readVarLong(ByteBuf buf) { + long value = 0; + int size = 0; + int b; + while (((b = buf.readByte()) & 0x80) == 0x80) { + value |= (long) (b & 0x7F) << (size++ * 7); + } + return value | ((long) (b & 0x7F) << (size * 7)); + } +} diff --git a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java index c3c7ec87e2..8ff0a9ab85 100644 --- a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java +++ b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java @@ -35,11 +35,7 @@ import ac.grim.grimac.utils.enums.FluidTag; import ac.grim.grimac.utils.enums.Pose; import ac.grim.grimac.utils.inventory.InventoryDesyncStatus; -import ac.grim.grimac.utils.latency.CompensatedEntities; -import ac.grim.grimac.utils.latency.CompensatedFireworks; -import ac.grim.grimac.utils.latency.CompensatedInventory; -import ac.grim.grimac.utils.latency.CompensatedWorld; -import ac.grim.grimac.utils.latency.LatencyUtils; +import ac.grim.grimac.utils.latency.*; import ac.grim.grimac.utils.math.GrimMath; import ac.grim.grimac.utils.math.Location; import ac.grim.grimac.utils.math.TrigHandler; @@ -216,7 +212,7 @@ public class GrimPlayer implements GrimUser { public final CompensatedFireworks fireworks; public final CompensatedWorld compensatedWorld; public final CompensatedEntities compensatedEntities; - public final LatencyUtils latencyUtils = new LatencyUtils(this); + public final ILatencyUtils latencyUtils = new LatencyUtils(this); public final PointThreeEstimator pointThreeEstimator; public final TrigHandler trigHandler = new TrigHandler(this); public final PacketStateData packetStateData = new PacketStateData(); diff --git a/common/src/main/java/ac/grim/grimac/utils/latency/ILatencyUtils.java b/common/src/main/java/ac/grim/grimac/utils/latency/ILatencyUtils.java new file mode 100644 index 0000000000..329c7683a0 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/latency/ILatencyUtils.java @@ -0,0 +1,28 @@ +package ac.grim.grimac.utils.latency; + +public interface ILatencyUtils { + /** + * Adds a task to be executed when the corresponding transaction ACK is received. + * + * @param transaction The transaction ID this task is associated with. + * @param runnable The task to execute. + */ + void addRealTimeTask(int transaction, Runnable runnable); + + /** + * Adds a task to be executed asynchronously via the player's event loop + * when the corresponding transaction ACK is received. + * (Note: Benchmark might simplify/ignore the async part unless specifically testing event loop contention) + * + * @param transaction The transaction ID this task is associated with. + * @param runnable The task to execute. + */ + void addRealTimeTaskAsync(int transaction, Runnable runnable); + + /** + * Processes received transaction ACKs and runs associated tasks. + * + * @param receivedTransactionId The ID of the transaction ACK received from the client. + */ + void handleNettySyncTransaction(int receivedTransactionId); +} diff --git a/common/src/main/java/ac/grim/grimac/utils/latency/LatencyUtils.java b/common/src/main/java/ac/grim/grimac/utils/latency/LatencyUtils.java index 20c7010d74..0e87aa751c 100644 --- a/common/src/main/java/ac/grim/grimac/utils/latency/LatencyUtils.java +++ b/common/src/main/java/ac/grim/grimac/utils/latency/LatencyUtils.java @@ -4,19 +4,18 @@ import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.LogUtil; import ac.grim.grimac.utils.anticheat.MessageUtil; -import ac.grim.grimac.utils.data.Pair; import com.github.retrooper.packetevents.netty.channel.ChannelHelper; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.ListIterator; +import java.util.*; -public class LatencyUtils { - private final LinkedList> transactionMap = new LinkedList<>(); - private final GrimPlayer player; +public class LatencyUtils implements ILatencyUtils { + + // Record to replace Pair with primitive int + private record TransactionTask(int transactionId, Runnable task) {} + + private final ArrayDeque transactionMap = new ArrayDeque<>(); - // Built from transactionMap and cleared at start of every handleNettySyncTransaction() call - // The actual usage scope of this variable's use is limited to within the synchronized block of handleNettySyncTransaction + private final GrimPlayer player; private final ArrayList tasksToRun = new ArrayList<>(); public LatencyUtils(GrimPlayer player) { @@ -24,78 +23,70 @@ public LatencyUtils(GrimPlayer player) { } public void addRealTimeTask(int transaction, Runnable runnable) { - addRealTimeTask(transaction, false, runnable); + addRealTimeTaskInternal(transaction, false, runnable); } public void addRealTimeTaskAsync(int transaction, Runnable runnable) { - addRealTimeTask(transaction, true, runnable); + addRealTimeTaskInternal(transaction, true, runnable); } - public void addRealTimeTask(int transaction, boolean async, Runnable runnable) { - if (player.lastTransactionReceived.get() >= transaction) { // If the player already responded to this transaction + private void addRealTimeTaskInternal(int transactionId, boolean async, Runnable runnable) { + if (player.lastTransactionReceived.get() >= transactionId) { if (async) { - ChannelHelper.runInEventLoop(player.user.getChannel(), runnable); // Run it sync to player channel + ChannelHelper.runInEventLoop(player.user.getChannel(), runnable); } else { runnable.run(); } return; } - synchronized (this) { - transactionMap.add(new Pair<>(transaction, runnable)); + synchronized (transactionMap) { + transactionMap.add(new TransactionTask(transactionId, runnable)); } } - public void handleNettySyncTransaction(int transaction) { - /* - * This code uses a two-pass approach within the synchronized block to prevent CMEs. - * First we collect and remove tasks using the iterator, then execute all collected tasks. - * - * The issue: - * We cannot execute tasks during iteration because if a runnable modifies transactionMap - * or calls addRealTimeTask, it will cause a ConcurrentModificationException. - * While only seen on Folia servers, this is theoretically possible everywhere. - * - * Why this solution: - * Rather than documenting "don't modify transactionMap in runnables" and risking subtle - * bugs from future contributions or Check API usage, we prevent the issue entirely - * at a small performance cost. - * - * Future considerations: - * If this becomes a performance bottleneck, we may revisit using a single-pass approach - * on non-Folia servers. We could also explore concurrent data structures or parallel - * execution, but this would lose the guarantee that transactions are processed in order. - */ - synchronized (this) { + @Override + public void handleNettySyncTransaction(int receivedTransactionId) { + synchronized (transactionMap) { tasksToRun.clear(); - // First pass: collect tasks and mark them for removal - ListIterator> iterator = transactionMap.listIterator(); + Iterator iterator = transactionMap.iterator(); while (iterator.hasNext()) { - Pair pair = iterator.next(); + TransactionTask taskEntry = iterator.next(); + int taskTransactionId = taskEntry.transactionId(); - // We are at most a tick ahead when running tasks based on transactions, meaning this is too far - if (transaction + 1 < pair.first()) + // If tasks are added with monotonically increasing IDs, + // once we find one that's too far ahead, all subsequent ones + // will also be too far ahead. + if (receivedTransactionId + 1 < taskTransactionId) { break; + } // This is at most tick ahead of what we want - if (transaction == pair.first() - 1) - continue; + if (receivedTransactionId == taskTransactionId - 1) { + continue; // Skip this specific task + } - tasksToRun.add(pair.second()); - iterator.remove(); + // If we didn't break or continue, the task is eligible + tasksToRun.add(taskEntry.task()); + iterator.remove(); // Remove using the iterator } + // Task execution loop for (Runnable runnable : tasksToRun) { try { runnable.run(); } catch (Exception e) { - LogUtil.error("An error has occurred when running transactions for player: " + player.user.getName(), e); - // Kick the player SO PEOPLE ACTUALLY REPORT PROBLEMS AND KNOW WHEN THEY HAPPEN - if (!Boolean.getBoolean("grim.disable-transaction-kick")) { - player.disconnect(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(player, GrimAPI.INSTANCE.getConfigManager().getDisconnectPacketError()))); - } + handleRunnableError(e); } } } } + + private void handleRunnableError(Exception e) { + LogUtil.error("An error has occurred when running transactions for player: " + player.user.getName(), e); + // Kick the player SO PEOPLE ACTUALLY REPORT PROBLEMS AND KNOW WHEN THEY HAPPEN + if (!Boolean.getBoolean("grim.disable-transaction-kick")) { + player.disconnect(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(player, GrimAPI.INSTANCE.getConfigManager().getDisconnectPacketError()))); + } + } } From 2f08694413947a1405d2daebe0329a2bd69fbf29 Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sun, 4 May 2025 12:35:32 -0400 Subject: [PATCH 21/34] * Remove space in names of new checks: * "Wall Hit" -> "WallHit" * "Entity Pierce" -> "EntityPierce" * Fix modeling experience orb as unhittable entity * Fix sending hitbox with /grim hitboxdebug in the hitboxdebug handler * Update upstream to 35811fe5513df27aaab0488fb38d7d8f14486f4c --- .../java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java | 3 +-- .../java/ac/grim/grimac/checks/impl/combat/EntityPierce.java | 2 +- .../main/java/ac/grim/grimac/checks/impl/combat/WallHit.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java b/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java index 05177b2ab0..60f580f651 100644 --- a/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/debug/HitboxDebugHandler.java @@ -6,7 +6,6 @@ import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; import ac.grim.grimac.utils.data.Pair; import ac.grim.grimac.utils.math.Vector3dm; -import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage; import io.netty.buffer.ByteBuf; @@ -180,7 +179,7 @@ public void sendHitboxData(Map hitboxes, Set tar // Send to all listeners for (GrimPlayer listener : listeners) { if (listener != null) { - PacketEvents.getAPI().getPlayerManager().sendPacket(listener.user, packet); + listener.user.sendPacket(packet); } } } finally { diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java index 2a24a3b649..114306219c 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/EntityPierce.java @@ -5,7 +5,7 @@ import ac.grim.grimac.checks.type.PacketCheck; import ac.grim.grimac.player.GrimPlayer; -@CheckData(name = "Entity Pierce", configName = "EntityPierce", setback = 30) +@CheckData(name = "EntityPierce", configName = "EntityPierce", setback = 30) public class EntityPierce extends Check implements PacketCheck { public EntityPierce(GrimPlayer player) { super(player); diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java index f61279e1b2..2033e2476f 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/WallHit.java @@ -5,7 +5,7 @@ import ac.grim.grimac.checks.type.PacketCheck; import ac.grim.grimac.player.GrimPlayer; -@CheckData(name = "Wall Hit", configName = "WallHit", setback = 20) +@CheckData(name = "WallHit", configName = "WallHit", setback = 20) public class WallHit extends Check implements PacketCheck { public WallHit(GrimPlayer player) { super(player); From eba4105197ecb5907e3f5492a0545a245b733a8c Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 7 May 2025 16:35:55 -0400 Subject: [PATCH 22/34] Optimize Bukkit PistonEvent() usage --- .../bukkit/entity/BukkitGrimEntity.java | 1 - .../platform/bukkit/events/PistonEvent.java | 32 +++++++---------- .../ac/grim/grimac/utils/data/PistonData.java | 36 ------------------- .../grimac/utils/data/PistonTemplate.java | 11 ++++++ .../grimac/utils/data/PlayerPistonData.java | 23 ++++++++++++ .../utils/latency/CompensatedWorld.java | 31 ++++++++-------- 6 files changed, 63 insertions(+), 71 deletions(-) delete mode 100644 common/src/main/java/ac/grim/grimac/utils/data/PistonData.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/data/PistonTemplate.java create mode 100644 common/src/main/java/ac/grim/grimac/utils/data/PlayerPistonData.java diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/entity/BukkitGrimEntity.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/entity/BukkitGrimEntity.java index c81ad5c280..524e06bf7a 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/entity/BukkitGrimEntity.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/entity/BukkitGrimEntity.java @@ -60,7 +60,6 @@ public PlatformWorld getWorld() { if (bukkitPlatformWorld == null || !bukkitPlatformWorld.getBukkitWorld().equals(entity.getWorld())) { bukkitPlatformWorld = new BukkitPlatformWorld(entity.getWorld()); } - return bukkitPlatformWorld; } diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/events/PistonEvent.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/events/PistonEvent.java index 3bb0c15a0a..c120587ef9 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/events/PistonEvent.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/events/PistonEvent.java @@ -4,7 +4,7 @@ import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; -import ac.grim.grimac.utils.data.PistonData; +import ac.grim.grimac.utils.data.PistonTemplate; import com.github.retrooper.packetevents.protocol.world.BlockFace; import org.bukkit.Material; import org.bukkit.block.Block; @@ -55,17 +55,8 @@ public void onPistonPushEvent(BlockPistonExtendEvent event) { piston.getY() + event.getDirection().getModY(), piston.getZ() + event.getDirection().getModZ())); - boolean finalHasSlimeBlock = hasSlimeBlock; - boolean finalHasHoneyBlock = hasHoneyBlock; - for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) { - int lastTrans = player.lastTransactionSent.get(); - player.runSafely(() -> { - if (player.compensatedWorld.isChunkLoaded(event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4)) { - PistonData data = new PistonData(BukkitConversionUtils.fromBukkitFace(event.getDirection()), boxes, lastTrans, true, finalHasSlimeBlock, finalHasHoneyBlock); - player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data)); - } - }); - } + PistonTemplate data = new PistonTemplate(BukkitConversionUtils.fromBukkitFace(event.getDirection()), boxes, true, hasSlimeBlock, hasHoneyBlock); + addPistonData(data, event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4); } // For some unknown reason, bukkit handles this stupidly @@ -113,15 +104,18 @@ public void onPistonRetractEvent(BlockPistonRetractEvent event) { } } - boolean finalHasSlimeBlock = hasSlimeBlock; - boolean finalHasHoneyBlock = hasHoneyBlock; + PistonTemplate data = new PistonTemplate(face, boxes, false, hasSlimeBlock, hasHoneyBlock); + addPistonData(data, event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4); + } + + private void addPistonData(PistonTemplate pistonTemplate, int chunkX, int chunkZ) { for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) { + if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ)) continue; + int lastTrans = player.lastTransactionSent.get(); - player.runSafely(() -> { - if (player.compensatedWorld.isChunkLoaded(event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4)) { - PistonData data = new PistonData(BukkitConversionUtils.fromBukkitFace(event.getDirection()), boxes, lastTrans, false, finalHasSlimeBlock, finalHasHoneyBlock); - player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data)); - } + + player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> { + player.compensatedWorld.addPiston(pistonTemplate, lastTrans); }); } } diff --git a/common/src/main/java/ac/grim/grimac/utils/data/PistonData.java b/common/src/main/java/ac/grim/grimac/utils/data/PistonData.java deleted file mode 100644 index 0d8657b70d..0000000000 --- a/common/src/main/java/ac/grim/grimac/utils/data/PistonData.java +++ /dev/null @@ -1,36 +0,0 @@ -package ac.grim.grimac.utils.data; - -import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; -import com.github.retrooper.packetevents.protocol.world.BlockFace; - -import java.util.List; - -public class PistonData { - public final boolean isPush; - public final boolean hasSlimeBlock; - public final boolean hasHoneyBlock; - public final BlockFace direction; - public final int lastTransactionSent; - - // Calculate if the player has no-push, and when to end the possibility of applying piston - public int ticksOfPistonBeingAlive = 0; - - // The actual blocks pushed by the piston, plus the piston head itself - public List boxes; - - public PistonData(BlockFace direction, List pushedBlocks, int lastTransactionSent, boolean isPush, boolean hasSlimeBlock, boolean hasHoneyBlock) { - this.direction = direction; - this.boxes = pushedBlocks; - this.lastTransactionSent = lastTransactionSent; - this.isPush = isPush; - this.hasSlimeBlock = hasSlimeBlock; - this.hasHoneyBlock = hasHoneyBlock; - } - - // We don't know when the piston has applied, or what stage of pushing it is on - // Therefore, we need to use what we have - the number of movement packets. - // 10 is a very cautious number - public boolean tickIfGuaranteedFinished() { - return ++ticksOfPistonBeingAlive >= 10; - } -} diff --git a/common/src/main/java/ac/grim/grimac/utils/data/PistonTemplate.java b/common/src/main/java/ac/grim/grimac/utils/data/PistonTemplate.java new file mode 100644 index 0000000000..f21030bb5d --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/data/PistonTemplate.java @@ -0,0 +1,11 @@ +package ac.grim.grimac.utils.data; + +import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; +import com.github.retrooper.packetevents.protocol.world.BlockFace; + +import java.util.List; + +public record PistonTemplate(BlockFace dir, + List boxes, + boolean push, boolean slime, boolean honey) { +} diff --git a/common/src/main/java/ac/grim/grimac/utils/data/PlayerPistonData.java b/common/src/main/java/ac/grim/grimac/utils/data/PlayerPistonData.java new file mode 100644 index 0000000000..4f4f356e27 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/utils/data/PlayerPistonData.java @@ -0,0 +1,23 @@ +package ac.grim.grimac.utils.data; + + + +public class PlayerPistonData { + public final PistonTemplate pistonTemplate; + public final int lastTransactionSent; + + // Calculate if the player has no-push, and when to end the possibility of applying piston + int ticksOfPistonBeingAlive = 0; + + public PlayerPistonData(PistonTemplate playerPistonData, int lastTransactionSent) { + this.pistonTemplate = playerPistonData; + this.lastTransactionSent = lastTransactionSent; + } + + // We don't know when the piston has applied, or what stage of pushing it is on + // Therefore, we need to use what we have - the number of movement packets. + // 10 is a very cautious number + public boolean tickIfGuaranteedFinished() { + return ++ticksOfPistonBeingAlive >= 10; + } +} diff --git a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedWorld.java b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedWorld.java index 3f13118a30..f4d37a273b 100644 --- a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedWorld.java +++ b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedWorld.java @@ -6,10 +6,7 @@ import ac.grim.grimac.utils.chunks.Column; import ac.grim.grimac.utils.collisions.CollisionData; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; -import ac.grim.grimac.utils.data.BlockPrediction; -import ac.grim.grimac.utils.data.Pair; -import ac.grim.grimac.utils.data.PistonData; -import ac.grim.grimac.utils.data.ShulkerData; +import ac.grim.grimac.utils.data.*; import ac.grim.grimac.utils.data.packetentity.PacketEntity; import ac.grim.grimac.utils.data.packetentity.PacketEntityShulker; import ac.grim.grimac.utils.math.GrimMath; @@ -70,7 +67,7 @@ public class CompensatedWorld { public final GrimPlayer player; public final Long2ObjectMap chunks; // Packet locations for blocks - public Set activePistons = new HashSet<>(); + public Set activePistons = new HashSet<>(); public Set openShulkerBoxes = new HashSet<>(); // 1.17 with datapacks, and 1.18, have negative world offset values @Getter @@ -239,8 +236,8 @@ public boolean isNearHardEntity(SimpleCollisionBox playerBox) { } // Pistons are a block entity. - for (PistonData data : activePistons) { - for (SimpleCollisionBox box : data.boxes) { + for (PlayerPistonData data : activePistons) { + for (SimpleCollisionBox box : data.pistonTemplate.boxes()) { if (playerBox.isCollided(box)) { return true; } @@ -359,18 +356,18 @@ public void tickPlayerInPistonPushingArea() { double modY = 0; double modZ = 0; - for (PistonData data : activePistons) { - for (SimpleCollisionBox box : data.boxes) { + for (PlayerPistonData data : activePistons) { + for (SimpleCollisionBox box : data.pistonTemplate.boxes()) { if (playerBox.isCollided(box)) { - modX = Math.max(modX, Math.abs(data.direction.getModX() * 0.51D)); - modY = Math.max(modY, Math.abs(data.direction.getModY() * 0.51D)); - modZ = Math.max(modZ, Math.abs(data.direction.getModZ() * 0.51D)); + modX = Math.max(modX, Math.abs(data.pistonTemplate.dir().getModX() * 0.51D)); + modY = Math.max(modY, Math.abs(data.pistonTemplate.dir().getModY() * 0.51D)); + modZ = Math.max(modZ, Math.abs(data.pistonTemplate.dir().getModZ() * 0.51D)); playerBox.expandMax(modX, modY, modZ); playerBox.expandMin(modX * -1, modY * -1, modZ * -1); - if (data.hasSlimeBlock || (data.hasHoneyBlock && player.getClientVersion().isOlderThan(ClientVersion.V_1_15_2))) { - player.uncertaintyHandler.slimePistonBounces.add(data.direction); + if (data.pistonTemplate.slime() || (data.pistonTemplate.honey() && player.getClientVersion().isOlderThan(ClientVersion.V_1_15_2))) { + player.uncertaintyHandler.slimePistonBounces.add(data.pistonTemplate.dir()); } break; @@ -425,7 +422,7 @@ public void removeInvalidPistonLikeStuff(int transactionId) { activePistons.removeIf(data -> data.lastTransactionSent < transactionId); openShulkerBoxes.removeIf(data -> data.isClosing && data.lastTransactionSent < transactionId); } else { - activePistons.removeIf(PistonData::tickIfGuaranteedFinished); + activePistons.removeIf(PlayerPistonData::tickIfGuaranteedFinished); openShulkerBoxes.removeIf(ShulkerData::tickIfGuaranteedFinished); } // Remove if a shulker is not in this block position anymore @@ -698,4 +695,8 @@ public void setDimension(DimensionType dimension, User user) { public WrappedBlockState getBlock(Vector3dm aboveCCWPos) { return getBlock(aboveCCWPos.getX(), aboveCCWPos.getY(), aboveCCWPos.getZ()); } + + public void addPiston(PistonTemplate pistonTemplate, int transactionID) { + activePistons.add(new PlayerPistonData(pistonTemplate, transactionID)); + } } From 0880b5e7bed23cf91ca5d76555f76a4e46efd33e Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Sun, 11 May 2025 02:26:36 -0400 Subject: [PATCH 23/34] Temp stash --- .../src/main/java/ac/grim/grimac/GrimAPI.java | 1 - .../scheduler/FabricAsyncScheduler.java | 21 ++++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/ac/grim/grimac/GrimAPI.java b/common/src/main/java/ac/grim/grimac/GrimAPI.java index 4748b9b9ee..a974009da3 100644 --- a/common/src/main/java/ac/grim/grimac/GrimAPI.java +++ b/common/src/main/java/ac/grim/grimac/GrimAPI.java @@ -29,7 +29,6 @@ import org.incendo.cloud.CommandManager; import org.jetbrains.annotations.NotNull; - @Getter public final class GrimAPI { public static final GrimAPI INSTANCE = new GrimAPI(); diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricAsyncScheduler.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricAsyncScheduler.java index fcddfc9ea7..6a23c81e92 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricAsyncScheduler.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricAsyncScheduler.java @@ -4,7 +4,6 @@ import ac.grim.grimac.platform.api.scheduler.AsyncScheduler; import ac.grim.grimac.platform.api.scheduler.PlatformScheduler; import ac.grim.grimac.platform.api.scheduler.TaskHandle; -import ac.grim.grimac.utils.data.Pair; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -15,9 +14,11 @@ import java.util.concurrent.TimeUnit; public class FabricAsyncScheduler implements AsyncScheduler { - private final Map> asyncTasks = new HashMap<>(); + private final Map asyncTasks = new HashMap<>(); private final GrimPlugin plugin; + record InternalAsyncTask(GrimPlugin plugin, Runnable runnable) {} + public FabricAsyncScheduler(GrimPlugin plugin) { this.plugin = plugin; } @@ -29,7 +30,7 @@ public TaskHandle runNow(@NotNull GrimPlugin plugin, @NotNull Runnable task) { thread.interrupt(); asyncTasks.remove(thread); }; - asyncTasks.put(thread, new Pair<>(plugin, cancellationTask)); + asyncTasks.put(thread, new InternalAsyncTask(plugin, cancellationTask)); thread.start(); return new FabricTaskHandle(cancellationTask, false); } @@ -49,7 +50,7 @@ public TaskHandle runDelayed(@NotNull GrimPlugin plugin, @NotNull Runnable task, thread.interrupt(); asyncTasks.remove(thread); }; - asyncTasks.put(thread, new Pair<>(plugin, cancellationTask)); + asyncTasks.put(thread, new InternalAsyncTask(plugin, cancellationTask)); thread.start(); return new FabricTaskHandle(cancellationTask, false); // false for async } @@ -73,7 +74,7 @@ public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Runnable t thread.interrupt(); asyncTasks.remove(thread); }; - asyncTasks.put(thread, new Pair<>(plugin, cancellationTask)); + asyncTasks.put(thread, new InternalAsyncTask(plugin, cancellationTask)); thread.start(); return new FabricTaskHandle(cancellationTask, false); // false for async } @@ -89,13 +90,13 @@ public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Runnable t @Override public void cancel(@NotNull GrimPlugin plugin) { // Cancel tasks only for the specified plugin - Iterator>> iterator = asyncTasks.entrySet().iterator(); + Iterator> iterator = asyncTasks.entrySet().iterator(); List cancellationTasks = new ArrayList<>(); while (iterator.hasNext()) { - Map.Entry> entry = iterator.next(); - if (entry.getValue().first().equals(plugin)) { - cancellationTasks.add(entry.getValue().second()); + Map.Entry entry = iterator.next(); + if (entry.getValue().plugin().equals(plugin)) { + cancellationTasks.add(entry.getValue().runnable()); iterator.remove(); } } @@ -107,7 +108,7 @@ public void cancel(@NotNull GrimPlugin plugin) { public void cancelAll() { List cancellationTasks = asyncTasks.values().stream() - .map(Pair::second) + .map(InternalAsyncTask::runnable) .toList(); asyncTasks.clear(); From a667d8db2d3e083acea576532bb06cdaf508e441 Mon Sep 17 00:00:00 2001 From: GigaZelensky Date: Sat, 17 May 2025 00:08:20 +0100 Subject: [PATCH 24/34] yay --- build.gradle.kts | 9 +- .../src/main/kotlin/versioning/BuildConfig.kt | 6 + bukkit/build.gradle.kts | 9 +- .../bukkit/initables/BukkitTickEndEvent.java | 2 +- .../manager/BukkitItemResetHandler.java | 155 +++++++-- .../bukkit/utils/anticheat/MultiLibUtil.java | 3 +- .../bukkit/utils/reflection/PaperUtils.java | 2 +- common/build.gradle.kts | 9 +- .../src/main/java/ac/grim/grimac/GrimAPI.java | 30 +- .../java/ac/grim/grimac/GrimExternalAPI.java | 6 +- .../checks/impl/badpackets/BadPacketsE.java | 9 +- .../grim/grimac/checks/impl/combat/Reach.java | 4 +- .../grimac/checks/impl/misc/ClientBrand.java | 2 +- .../impl/multiactions/MultiActionsC.java | 17 +- .../impl/multiactions/MultiActionsD.java | 2 +- .../checks/impl/packetorder/PacketOrderB.java | 68 ++-- .../grim/grimac/checks/impl/timer/Timer.java | 15 +- .../grimac/checks/impl/timer/TimerLimit.java | 55 +++ .../grimac/command/commands/GrimDump.java | 25 +- .../grim/grimac/command/commands/GrimLog.java | 20 +- .../command/commands/GrimSendAlert.java | 2 +- .../events/packets/CheckManagerListener.java | 7 + .../packets/PacketEntityReplication.java | 28 +- .../events/packets/PacketPlayerCooldown.java | 16 +- .../events/packets/PacketPlayerDigging.java | 6 +- .../events/packets/PacketPlayerJoinQuit.java | 21 +- .../packets/PacketSelfMetadataListener.java | 10 +- .../events/packets/ProxyAlertMessenger.java | 15 +- .../grim/grimac/manager/AlertManagerImpl.java | 323 ++++++++++++------ .../ac/grim/grimac/manager/CheckManager.java | 4 +- .../grim/grimac/manager/DiscordManager.java | 11 +- .../ac/grim/grimac/manager/InitManager.java | 7 +- .../grimac/manager/PunishmentManager.java | 22 +- .../manager/config/ConfigManagerFileImpl.java | 2 +- .../manager/init/start/CommandRegister.java | 6 +- .../manager/init/start/JavaVersion.java | 3 +- .../grimac/manager/init/start/SuperDebug.java | 4 +- .../grimac/platform/api/PlatformServer.java | 1 - .../api/manager/ItemResetHandler.java | 4 + .../platform/api/sender/AbstractSender.java | 2 +- .../ac/grim/grimac/player/GrimPlayer.java | 12 +- .../predictionengine/MovementCheckRunner.java | 2 +- .../predictionengine/PointThreeEstimator.java | 4 +- .../grim/grimac/utils/anticheat/LogUtil.java | 13 +- .../grimac/utils/anticheat/MessageUtil.java | 43 ++- .../utils/blockplace/BlockPlaceResult.java | 5 +- .../utils/collisions/CollisionData.java | 118 +++---- .../grimac/utils/collisions/HitboxData.java | 107 +++--- .../datatypes/DynamicCollisionBox.java | 11 +- .../datatypes/SimpleCollisionBox.java | 1 - .../grimac/utils/data/TrackedPosition.java | 16 +- .../utils/data/packetentity/PacketEntity.java | 1 + .../data/packetentity/TypedPacketEntity.java | 37 +- .../dragon/PacketEntityEnderDragon.java | 5 +- .../grimac/utils/inventory/Inventory.java | 13 +- .../utils/inventory/InventoryStorage.java | 6 +- .../utils/latency/CompensatedEntities.java | 2 +- .../CorrectingPlayerInventoryStorage.java | 5 +- .../grimac/utils/lists/HookedListWrapper.java | 4 +- .../ac/grim/grimac/utils/math/Location.java | 31 +- .../ac/grim/grimac/utils/nmsutil/Ray.java | 3 +- .../grimac/utils/reflection/GeyserUtil.java | 7 +- common/src/main/resources/config/de.yml | 3 + common/src/main/resources/config/en.yml | 3 + common/src/main/resources/config/es.yml | 3 + common/src/main/resources/config/fr.yml | 3 + common/src/main/resources/config/it.yml | 7 +- common/src/main/resources/config/ja.yml | 3 + common/src/main/resources/config/nl.yml | 3 + common/src/main/resources/config/pt.yml | 3 + common/src/main/resources/config/ru.yml | 3 + common/src/main/resources/config/tr.yml | 3 + common/src/main/resources/config/zh.yml | 3 + common/src/main/resources/punishments/de.yml | 1 + common/src/main/resources/punishments/en.yml | 1 + common/src/main/resources/punishments/es.yml | 1 + common/src/main/resources/punishments/fr.yml | 1 + common/src/main/resources/punishments/it.yml | 1 + common/src/main/resources/punishments/ja.yml | 1 + common/src/main/resources/punishments/nl.yml | 1 + common/src/main/resources/punishments/pt.yml | 1 + common/src/main/resources/punishments/ru.yml | 1 + common/src/main/resources/punishments/tr.yml | 1 + common/src/main/resources/punishments/zh.yml | 1 + fabric/build.gradle.kts | 9 +- .../mc1161/Fabric1140PlatformServer.java | 1 - .../mc1161/GrimACFabric1161LoaderPlugin.java | 16 +- .../fabric/GrimACFabricLoaderPlugin.java | 6 +- .../manager/FabricItemResetHandler.java | 14 + .../scheduler/FabricAsyncScheduler.java | 24 +- .../scheduler/FabricPlatformScheduler.java | 16 +- .../fabric/sender/FabricSenderFactory.java | 2 +- .../utils/convert/FabricConversionUtil.java | 9 +- .../fabric/utils/metrics/BStatsConfig.java | 10 +- .../utils/metrics/JsonObjectBuilder.java | 2 +- .../fabric/utils/thread/FabricFutureUtil.java | 5 +- 96 files changed, 909 insertions(+), 641 deletions(-) create mode 100644 common/src/main/java/ac/grim/grimac/checks/impl/timer/TimerLimit.java diff --git a/build.gradle.kts b/build.gradle.kts index bc92ef51a6..5da520b73b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,10 +22,11 @@ version = VersionUtil.computeVersion(baseVersion) description = "Libre simulation anticheat designed for 1.21 with 1.8–1.21 support, powered by PacketEvents 2.0." println("⚙️ Build configuration:") -println(" shadePE = ${BuildConfig.shadePE}") -println(" relocate = ${BuildConfig.relocate}") -println(" release = ${BuildConfig.release}") -println(" version = $version") +println(" shadePE = ${BuildConfig.shadePE}") +println(" relocate = ${BuildConfig.relocate}") +println(" mavenLocalOverride = ${BuildConfig.mavenLocalOverride}") +println(" release = ${BuildConfig.release}") +println(" version = $version") tasks.register("printVersion") { group = "versioning" diff --git a/buildSrc/src/main/kotlin/versioning/BuildConfig.kt b/buildSrc/src/main/kotlin/versioning/BuildConfig.kt index 5998f70360..057a77fcf5 100644 --- a/buildSrc/src/main/kotlin/versioning/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/versioning/BuildConfig.kt @@ -36,6 +36,7 @@ import org.gradle.internal.extensions.stdlib.toDefaultLowerCase * @property shadePE If true, shades PacketEvents into the jar. Default: true. * @property relocate If true, relocates shaded dependencies to avoid conflicts. Default: true. * @property release If true, omits commit hash and modifiers from version string. Default: false. + * @property mavenLocalOverride If true, will make artifacts in mavenLocal() will be used instead of their remote counterparts for this build. Default: false */ object BuildConfig { @@ -50,6 +51,7 @@ object BuildConfig { _shadePE = resolveBool(project, "shadePE", altKey = "SHADE_PE", default = true) _relocate = resolveBool(project, "relocate", altKey = "RELOCATE_JAR", default = true) _release = resolveBool(project, "release", default = false) + _mavenLocalOverride = resolveBool(project, "mavenLocalOverride", default = false) } // Unified resolution logic (System > Gradle > Env) @@ -78,6 +80,7 @@ object BuildConfig { private var _shadePE: Boolean? = null private var _relocate: Boolean? = null private var _release: Boolean? = null + private var _mavenLocalOverride: Boolean? = null /** If true, shades PacketEvents into the jar. Default: true. */ val shadePE: Boolean get() = _shadePE @@ -90,4 +93,7 @@ object BuildConfig { /** If true, omits commit hash and modifiers from version string. Default: false. */ val release: Boolean get() = _release ?: error("BuildConfig.release accessed before init() was called") + + val mavenLocalOverride: Boolean get() = _mavenLocalOverride + ?: error("BuildConfig.release accessed before init() was called") } \ No newline at end of file diff --git a/bukkit/build.gradle.kts b/bukkit/build.gradle.kts index 234b8c469a..05255e743b 100644 --- a/bukkit/build.gradle.kts +++ b/bukkit/build.gradle.kts @@ -9,7 +9,9 @@ plugins { } repositories { - mavenLocal() + if (BuildConfig.mavenLocalOverride) { + mavenLocal() + } maven { name = "papermc" url = uri("https://repo.papermc.io/repository/maven-public/") @@ -25,12 +27,9 @@ repositories { maven("https://nexus.scarsz.me/content/repositories/releases") // Configuralize maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate maven("https://repo.opencollab.dev/maven-releases/") // Cumulus (for Floodgate) - maven("https://repo.codemc.io/repository/maven-releases/") // PacketEvents - maven("https://repo.codemc.io/repository/maven-snapshots/") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // placeholderapi - mavenCentral() - // FastUtil + mavenCentral() // FastUtil } dependencies { diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/initables/BukkitTickEndEvent.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/initables/BukkitTickEndEvent.java index 310a059d26..ff6c88871d 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/initables/BukkitTickEndEvent.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/initables/BukkitTickEndEvent.java @@ -86,7 +86,7 @@ public void onIterator() { Unsafe unsafe = (Unsafe) unsafeField.get(null); unsafe.putObject(connection, unsafe.objectFieldOffset(connectionsList), wrapper); } catch (NoSuchFieldException | IllegalAccessException e) { - LogUtil.exception("Failed to inject into the end of tick event via reflection", e); + LogUtil.error("Failed to inject into the end of tick event via reflection", e); return false; } return true; diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/manager/BukkitItemResetHandler.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/manager/BukkitItemResetHandler.java index 90806826fc..cc03556ca6 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/manager/BukkitItemResetHandler.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/manager/BukkitItemResetHandler.java @@ -1,24 +1,29 @@ package ac.grim.grimac.platform.bukkit.manager; +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.platform.api.Platform; import ac.grim.grimac.platform.api.manager.ItemResetHandler; import ac.grim.grimac.platform.api.player.PlatformPlayer; import ac.grim.grimac.platform.bukkit.player.BukkitPlatformPlayer; import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.manager.server.ServerVersion; +import com.github.retrooper.packetevents.protocol.player.InteractionHand; import lombok.SneakyThrows; import org.bukkit.Bukkit; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.reflect.Method; public class BukkitItemResetHandler implements ItemResetHandler { - // resets item usage, then returns whether the player was using an item private final @NotNull ItemUsageReset resetItemUsage = createItemUsageResetFunction(); + private final @NotNull ItemUsageHandGetter itemUsageHandGetter = createItemUsageHandGetterFunction(); + @Override @SneakyThrows public void resetItemUsage(@Nullable PlatformPlayer player) { if (player != null) { @@ -26,11 +31,18 @@ public void resetItemUsage(@Nullable PlatformPlayer player) { } } + @Override + @SneakyThrows + public @Nullable InteractionHand getItemUsageHand(@Nullable PlatformPlayer platformPlayer) { + return platformPlayer == null ? null + : itemUsageHandGetter.apply(((BukkitPlatformPlayer) platformPlayer).getNative()); + } + @SneakyThrows private @NotNull ItemUsageReset createItemUsageResetFunction() { ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion(); if (version.isNewerThan(ServerVersion.V_1_17) && PaperUtils.PAPER) { - if (version.isOlderThan(ServerVersion.V_1_19)) { + if (version.isOlderThan(ServerVersion.V_1_19) || GrimAPI.INSTANCE.getPlatform() == Platform.FOLIA) { return LivingEntity::clearActiveItem; } Method setLivingEntityFlag = Class.forName(version.isOlderThan(ServerVersion.V_1_20_5) ? "net.minecraft.world.entity.EntityLiving" : "net.minecraft.world.entity.LivingEntity") @@ -43,7 +55,7 @@ public void resetItemUsage(@Nullable PlatformPlayer player) { setLivingEntityFlag.setAccessible(true); return player -> { - // don't trigger gameevents + // no gameevent, no exception setLivingEntityFlag.invoke(getHandle.invoke(player), 1, false); player.clearActiveItem(); }; @@ -68,29 +80,134 @@ public void resetItemUsage(@Nullable PlatformPlayer player) { } String nmsPackage = Bukkit.getServer().getClass().getPackageName().split("\\.")[3]; - String livingEntityPackage = version.isNewerThan(ServerVersion.V_1_16_5) ? "net.minecraft.world.entity.EntityLiving" : "net.minecraft.server." + nmsPackage + ".EntityLiving"; Method getHandle = Class.forName("org.bukkit.craftbukkit." + nmsPackage + ".entity.CraftPlayer").getMethod("getHandle"); - Method clearActiveItem = Class.forName(livingEntityPackage).getMethod( - switch (nmsPackage) { - case "v1_9_R1" -> "cz"; - case "v1_9_R2" -> "cA"; - case "v1_10_R1" -> "cE"; - case "v1_11_R1" -> "cF"; - case "v1_12_R1" -> "cN"; - case "v1_13_R1", "v1_13_R2" -> "da"; - case "v1_14_R1" -> "dp"; - case "v1_15_R1" -> "dH"; - case "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1" -> "clearActiveItem"; - case "v1_18_R1" -> "eR"; - case "v1_18_R2" -> "eS"; + if (version.isOlderThan(ServerVersion.V_1_19)) { + String livingEntityPackage = version.isNewerThan(ServerVersion.V_1_16_5) ? "net.minecraft.world.entity.EntityLiving" : "net.minecraft.server." + nmsPackage + ".EntityLiving"; + Method clearActiveItem = Class.forName(livingEntityPackage).getMethod( + switch (nmsPackage) { + case "v1_9_R1" -> "cz"; + case "v1_9_R2" -> "cA"; + case "v1_10_R1" -> "cE"; + case "v1_11_R1" -> "cF"; + case "v1_12_R1" -> "cN"; + case "v1_13_R1", "v1_13_R2" -> "da"; + case "v1_14_R1" -> "dp"; + case "v1_15_R1" -> "dH"; + case "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1" -> "clearActiveItem"; + case "v1_18_R1" -> "eR"; + case "v1_18_R2" -> "eS"; + default -> throw new IllegalStateException("You are using an unsupported server version! (" + version.getReleaseName() + ")"); + } + ); + + return player -> clearActiveItem.invoke(getHandle.invoke(player)); + } else { + Class EntityLiving = Class.forName("net.minecraft.world.entity.EntityLiving"); + Method setLivingEntityFlag = EntityLiving.getDeclaredMethod("c", int.class, boolean.class); + setLivingEntityFlag.setAccessible(true); + + Method clearActiveItem = EntityLiving.getMethod(switch (nmsPackage) { + case "v1_19_R1" -> "eZ"; + case "v1_19_R2" -> "ff"; + case "v1_19_R3" -> "fk"; + case "v1_20_R1" -> "fo"; + case "v1_20_R2" -> "fs"; + case "v1_20_R3" -> "ft"; + case "v1_20_R4" -> "fB"; + case "v1_21_R1" -> "fx"; + case "v1_21_R2", "v1_21_R3", "v1_21_R4" -> "fF"; default -> throw new IllegalStateException("You are using an unsupported server version! (" + version.getReleaseName() + ")"); - } + }); + + return player -> { + final Object handle = getHandle.invoke(player); + // no gameevent, no exception + setLivingEntityFlag.invoke(handle, 1, false); + clearActiveItem.invoke(handle); + }; + } + } + + @SneakyThrows + private @NotNull ItemUsageHandGetter createItemUsageHandGetterFunction() { + ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion(); + if (version.isNewerThanOrEquals(ServerVersion.V_1_16_5) && PaperUtils.PAPER) { + return player -> player.isHandRaised() + ? player.getHandRaised() == EquipmentSlot.OFF_HAND + ? InteractionHand.OFF_HAND + : InteractionHand.MAIN_HAND + : null; + } + + if (version == ServerVersion.V_1_8_8) { + Method getHandle = Class.forName("org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer").getMethod("getHandle"); + Method isUsingItem = Class.forName("net.minecraft.server.v1_8_R3.EntityHuman").getMethod("bS"); + return player -> (boolean) isUsingItem.invoke(getHandle.invoke(player)) ? InteractionHand.MAIN_HAND : null; + } + + String nmsPackage = Bukkit.getServer().getClass().getPackageName().split("\\.")[3]; + Method getHandle = Class.forName("org.bukkit.craftbukkit." + nmsPackage + ".entity.CraftPlayer").getMethod("getHandle"); + Class LivingEntity = Class.forName(version.isNewerThan(ServerVersion.V_1_16_5) + ? "net.minecraft.world.entity.EntityLiving" + : "net.minecraft.server." + nmsPackage + ".EntityLiving" ); - return player -> clearActiveItem.invoke(getHandle.invoke(player)); + Method isUsingItem = LivingEntity.getMethod(switch (nmsPackage) { + case "v1_9_R1" -> "cs"; + case "v1_9_R2" -> "ct"; + case "v1_10_R1" -> "cx"; + case "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2", "v1_14_R1", + "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1" -> "isHandRaised"; + case "v1_18_R1" -> "eL"; + case "v1_18_R2" -> "eM"; + case "v1_19_R1" -> "eT"; + case "v1_19_R2" -> "eZ"; + case "v1_19_R3" -> "fe"; + case "v1_20_R1" -> "fi"; + case "v1_20_R2" -> "fm"; + case "v1_20_R3" -> "fn"; + case "v1_20_R4" -> "fv"; + case "v1_21_R1" -> "fr"; + case "v1_21_R2", "v1_21_R3", "v1_21_R4" -> "fz"; + default -> throw new IllegalStateException("You are using an unsupported server version! (" + version.getReleaseName() + ")"); + }); + Method getUsingItemHand = LivingEntity.getMethod(switch (nmsPackage) { + case "v1_9_R1" -> "ct"; + case "v1_9_R2" -> "cu"; + case "v1_10_R1" -> "cy"; + case "v1_11_R1" -> "cz"; + case "v1_12_R1" -> "cH"; + case "v1_13_R1", "v1_13_R2", "v1_14_R1" -> "cU"; + case "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1" -> "getRaisedHand"; + case "v1_18_R1" -> "eM"; + case "v1_18_R2" -> "eN"; + case "v1_19_R1" -> "eU"; + case "v1_19_R2" -> "fa"; + case "v1_19_R3" -> "ff"; + case "v1_20_R1" -> "fj"; + case "v1_20_R2" -> "fn"; + case "v1_20_R3" -> "fo"; + case "v1_20_R4" -> "fw"; + case "v1_21_R1" -> "fs"; + case "v1_21_R2", "v1_21_R3", "v1_21_R4" -> "fA"; + default -> throw new IllegalStateException("You are using an unsupported server version! (" + version.getReleaseName() + ")"); + }); + + return player -> { + final Object handle = getHandle.invoke(player); + return (boolean) isUsingItem.invoke(handle) + ? ((Enum) getUsingItemHand.invoke(handle)).ordinal() == 0 + ? InteractionHand.MAIN_HAND + : InteractionHand.OFF_HAND + : null; + }; } private interface ItemUsageReset { void accept(@NotNull Player player) throws Throwable; } + + private interface ItemUsageHandGetter { + InteractionHand apply(@NotNull Player player) throws Throwable; + } } diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/utils/anticheat/MultiLibUtil.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/utils/anticheat/MultiLibUtil.java index a4bb6c474d..deced8e1ec 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/utils/anticheat/MultiLibUtil.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/utils/anticheat/MultiLibUtil.java @@ -1,5 +1,6 @@ package ac.grim.grimac.platform.bukkit.utils.anticheat; +import ac.grim.grimac.utils.anticheat.LogUtil; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.manager.server.ServerVersion; import org.bukkit.entity.Player; @@ -25,7 +26,7 @@ public static boolean isExternalPlayer(Player player) { try { return (boolean) externalPlayerMethod.invoke(player); } catch (Exception e) { - e.printStackTrace(); + LogUtil.error("Failed to invoke external player method", e); return false; } } diff --git a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/utils/reflection/PaperUtils.java b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/utils/reflection/PaperUtils.java index 5d1c6927ac..3411964fdb 100644 --- a/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/utils/reflection/PaperUtils.java +++ b/bukkit/src/main/java/ac/grim/grimac/platform/bukkit/utils/reflection/PaperUtils.java @@ -43,7 +43,7 @@ public static boolean registerTickEndEvent(Listener listener, Runnable runnable) (l, event) -> runnable.run(), GrimACBukkitLoaderPlugin.LOADER); return true; } catch (Exception e) { - LogUtil.exception("Failed to register tick end event", e); + LogUtil.error("Failed to register tick end event", e); } } return false; diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 1f63bd8e92..9f805b592c 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -6,7 +6,9 @@ plugins { } repositories { - mavenLocal() + if (BuildConfig.mavenLocalOverride) { + mavenLocal() + } maven { name = "papermc" url = uri("https://repo.papermc.io/repository/maven-public/") @@ -22,11 +24,8 @@ repositories { maven("https://nexus.scarsz.me/content/repositories/releases") // Configuralize maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate maven("https://repo.opencollab.dev/maven-releases/") // Cumulus (for Floodgate) - maven("https://repo.codemc.io/repository/maven-releases/") // PacketEvents - maven("https://repo.codemc.io/repository/maven-snapshots/") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") - mavenCentral() - // FastUtil + mavenCentral() // FastUtil } dependencies { diff --git a/common/src/main/java/ac/grim/grimac/GrimAPI.java b/common/src/main/java/ac/grim/grimac/GrimAPI.java index 026630e335..a974009da3 100644 --- a/common/src/main/java/ac/grim/grimac/GrimAPI.java +++ b/common/src/main/java/ac/grim/grimac/GrimAPI.java @@ -29,14 +29,11 @@ import org.incendo.cloud.CommandManager; import org.jetbrains.annotations.NotNull; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - @Getter public final class GrimAPI { public static final GrimAPI INSTANCE = new GrimAPI(); + @Getter private final Platform platform = detectPlatform(); private final BaseConfigManager configManager; private final AlertManagerImpl alertManager; @@ -48,6 +45,7 @@ public final class GrimAPI { private final GrimExternalAPI externalAPI; private ViolationDatabaseManager violationDatabaseManager; private PlatformLoader loader; + @Getter private InitManager initManager; private boolean initialized = false; @@ -62,18 +60,12 @@ private GrimAPI() { this.externalAPI = new GrimExternalAPI(this); } + // the order matters private static Platform detectPlatform() { - final Map platforms = Collections.unmodifiableMap(new HashMap<>() {{ - put("io.papermc.paper.threadedregions.RegionizedServer", Platform.FOLIA); - put("org.bukkit.Bukkit", Platform.BUKKIT); - put("net.fabricmc.loader.api.FabricLoader", Platform.FABRIC); - }}); - - return platforms.entrySet().stream() - .filter(entry -> ReflectionUtils.hasClass(entry.getKey())) - .map(Map.Entry::getValue) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Unknown platform!")); + if (ReflectionUtils.hasClass("io.papermc.paper.threadedregions.RegionizedServer")) return Platform.FOLIA; + if (ReflectionUtils.hasClass("org.bukkit.Bukkit")) return Platform.BUKKIT; + if (ReflectionUtils.hasClass("net.fabricmc.loader.api.FabricLoader")) return Platform.FABRIC; + throw new IllegalStateException("Unknown platform!"); } public void load(PlatformLoader platformLoader, Initable... platformSpecificInitables) { @@ -106,10 +98,6 @@ public ParserDescriptorFactory getParserDescriptors() { return loader.getParserDescriptorFactory(); } - public InitManager getInitManager() { - return initManager; - } - public GrimPlugin getGrimPlugin() { return loader.getPlugin(); } @@ -147,8 +135,4 @@ private void checkInitialized() { public PermissionRegistrationManager getPermissionManager() { return loader.getPermissionManager(); } - - public Platform getPlatform() { - return platform; - } } diff --git a/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java b/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java index 3c5344a60a..fd105ceebb 100644 --- a/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java +++ b/common/src/main/java/ac/grim/grimac/GrimExternalAPI.java @@ -1,12 +1,12 @@ package ac.grim.grimac; import ac.grim.grimac.api.GrimAbstractAPI; -import ac.grim.grimac.api.plugin.GrimPluginDescription; import ac.grim.grimac.api.GrimUser; import ac.grim.grimac.api.alerts.AlertManager; import ac.grim.grimac.api.config.ConfigManager; import ac.grim.grimac.api.event.EventBus; import ac.grim.grimac.api.event.events.GrimReloadEvent; +import ac.grim.grimac.api.plugin.GrimPluginDescription; import ac.grim.grimac.manager.config.ConfigManagerFileImpl; import ac.grim.grimac.manager.init.start.StartableInitable; import ac.grim.grimac.player.GrimPlayer; @@ -123,7 +123,7 @@ public void start() { try { GrimAPI.INSTANCE.getConfigManager().start(); } catch (Exception e) { - e.printStackTrace(); + LogUtil.error("Failed to start config manager.", e); } } @@ -159,7 +159,7 @@ private boolean successfulReload(ConfigManager config) { () -> GrimAPI.INSTANCE.getEventBus().post(new GrimReloadEvent(true))); return true; } catch (Exception e) { - e.printStackTrace(); + LogUtil.error("Failed to reload config", e); } if (started) GrimAPI.INSTANCE.getScheduler().getAsyncScheduler().runNow(GrimAPI.INSTANCE.getGrimPlugin(), diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsE.java b/common/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsE.java index 1eef677f56..ce60c2d076 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsE.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsE.java @@ -14,6 +14,7 @@ @CheckData(name = "BadPacketsE") public class BadPacketsE extends Check implements PacketCheck { private int noReminderTicks; + private final int maxNoReminderTicks = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) ? 20 : 19; public BadPacketsE(GrimPlayer player) { super(player); @@ -26,15 +27,13 @@ public void onPacketReceive(PacketReceiveEvent event) { event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION) { noReminderTicks = 0; } else if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !player.packetStateData.lastPacketWasTeleport) { - noReminderTicks++; + if (++noReminderTicks > maxNoReminderTicks) { + flagAndAlert("ticks=" + noReminderTicks); + } } else if (event.getPacketType() == PacketType.Play.Client.STEER_VEHICLE || (isViaPleaseStopUsingProtocolHacksOnYourServer && player.inVehicle())) { noReminderTicks = 0; // Exempt vehicles } - - if (noReminderTicks > 20) { - flagAndAlert("ticks=" + noReminderTicks); // ban? I don't know how this would false - } } public void handleRespawn() { diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java index 301ba98727..cc61863ec3 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java @@ -177,11 +177,11 @@ private void tickBetterReachCheckWithAngle(boolean isFlying) { CheckResult result = checkReach(reachEntity, attack.getValue(), false); switch (result.type()) { case REACH -> { - String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : ", type=" + reachEntity.getType().getName().getKey(); + String added = ", type=" + reachEntity.getType().getName().getKey(); flagAndAlert(result.verbose() + added); } case HITBOX -> { - String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey(); + String added = "type=" + reachEntity.getType().getName().getKey(); player.checkManager.getCheck(Hitboxes.class).flagAndAlert(result.verbose() + added); } case WALL_HIT -> { diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/misc/ClientBrand.java b/common/src/main/java/ac/grim/grimac/checks/impl/misc/ClientBrand.java index 9103b6c10d..95b529e406 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/misc/ClientBrand.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/misc/ClientBrand.java @@ -53,7 +53,7 @@ private void handle(String channel, byte[] data) { String message = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("client-brand-format", "%prefix% &f%player% joined using %brand%"); Component component = MessageUtil.replacePlaceholders(player, MessageUtil.miniMessage(message)); - GrimAPI.INSTANCE.getAlertManager().sendBrand(component); + GrimAPI.INSTANCE.getAlertManager().sendBrand(component, null); } } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsC.java b/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsC.java index 3b007007b1..79dda65cc8 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsC.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsC.java @@ -5,13 +5,10 @@ import ac.grim.grimac.checks.type.PacketCheck; import ac.grim.grimac.player.GrimPlayer; import com.github.retrooper.packetevents.event.PacketReceiveEvent; -import com.github.retrooper.packetevents.event.PacketSendEvent; import com.github.retrooper.packetevents.protocol.packettype.PacketType; @CheckData(name = "MultiActionsC", description = "Clicked in inventory while sprinting", experimental = true) public class MultiActionsC extends Check implements PacketCheck { - private boolean serverOpenedInventoryThisTick; - public MultiActionsC(GrimPlayer player) { super(player); } @@ -19,22 +16,10 @@ public MultiActionsC(GrimPlayer player) { @Override public void onPacketReceive(PacketReceiveEvent event) { if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) { - - if (player.isSprinting && !player.isSwimming && !serverOpenedInventoryThisTick && flagAndAlert() && shouldModifyPackets()) { + if (player.isSprinting && !player.isSwimming && !player.serverOpenedInventoryThisTick && flagAndAlert() && shouldModifyPackets()) { event.setCancelled(true); player.onPacketCancel(); } } - - if (isTickPacket(event.getPacketType())) { - serverOpenedInventoryThisTick = false; - } - } - - @Override - public void onPacketSend(PacketSendEvent event) { - if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) { - player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> serverOpenedInventoryThisTick = true); - } } } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsD.java b/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsD.java index a728f6b3f7..1b59137139 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsD.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/multiactions/MultiActionsD.java @@ -16,7 +16,7 @@ public MultiActionsD(GrimPlayer player) { @Override public void onPacketReceive(PacketReceiveEvent event) { if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) { - if (player.isSprinting && !player.isSwimming && flagAndAlert() && shouldModifyPackets()) { + if (player.isSprinting && !player.isSwimming && !player.serverOpenedInventoryThisTick && flagAndAlert() && shouldModifyPackets()) { event.setCancelled(true); player.onPacketCancel(); } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderB.java b/common/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderB.java index 89160262c5..041ec13c84 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderB.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderB.java @@ -13,11 +13,25 @@ @CheckData(name = "PacketOrderB", description = "Did not swing for attack") public class PacketOrderB extends Check implements PacketCheck { - // 1.9 packet order: INTERACT -> ANIMATION // 1.8 packet order: ANIMATION -> INTERACT // I personally think 1.8 made much more sense. You swing and THEN you hit! - private boolean sentAnimation = player.getClientVersion().isNewerThan(ClientVersion.V_1_8); + private final boolean is1_9 = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9); + + // There is a "bug" in ViaRewind + // 1.8 packet order: ANIMATION -> INTERACT + // 1.9 packet order: INTERACT -> ANIMATION + // ViaRewind, on 1.9+ servers, delays a 1.8 client's ANIMATION to be after INTERACT (but before flying). + // Which means we see 1.9 packet order for 1.8 clients + // Due to ViaRewind also delaying the swings, we then see packet order above 20CPS like: + // INTERACT -> INTERACT -> ANIMATION -> ANIMATION + // I will simply disable this check for 1.8- clients on 1.9+ servers as I can't be bothered to find a way around this. + // Stop supporting such old clients on modern servers! + private final boolean exempt = player.getClientVersion().isOlderThan(ClientVersion.V_1_9) + && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9); + + private boolean sentAnimationSinceLastAttack = player.getClientVersion().isNewerThan(ClientVersion.V_1_8); + private boolean sentAttack, sentAnimation, sentSlotSwitch; public PacketOrderB(final GrimPlayer player) { super(player); @@ -25,31 +39,43 @@ public PacketOrderB(final GrimPlayer player) { @Override public void onPacketReceive(PacketReceiveEvent event) { + if (exempt) return; + if (event.getPacketType() == PacketType.Play.Client.ANIMATION) { - sentAnimation = true; - } else if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) { + sentAnimationSinceLastAttack = sentAnimation = true; + sentAttack = sentSlotSwitch = false; + return; + } + + if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) { WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event); - if (packet.getAction() != WrapperPlayClientInteractEntity.InteractAction.ATTACK) return; - - // There is a "bug" in ViaRewind - // 1.8 packet order: ANIMATION -> INTERACT - // 1.9 packet order: INTERACT -> ANIMATION - // ViaRewind, on 1.9+ servers, delays a 1.8 client's ANIMATION to be after INTERACT (but before flying). - // Which means we see 1.9 packet order for 1.8 clients - // Due to ViaRewind also delaying the swings, we then see packet order above 20CPS like: - // INTERACT -> INTERACT -> ANIMATION -> ANIMATION - // I will simply disable this check for 1.8- clients on 1.9+ servers as I can't be bothered to find a way around this. - // Stop supporting such old clients on modern servers! - if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9) - && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)) + if (packet.getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) { + sentAttack = true; + + if (is1_9 ? !sentAnimationSinceLastAttack : !sentAnimation) { + sentAttack = false; // don't flag twice + if (flagAndAlert("pre-attack") && shouldModifyPackets()) { + event.setCancelled(true); + player.onPacketCancel(); + } + } + + sentAnimationSinceLastAttack = sentAnimation = sentSlotSwitch = false; return; + } + } + + if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE && !is1_9 && !sentSlotSwitch) { + sentSlotSwitch = true; + return; // do not set sentAnimation to false + } - if (!sentAnimation && flagAndAlert() && shouldModifyPackets()) { - event.setCancelled(true); - player.onPacketCancel(); + if (event.getPacketType() != PacketType.Play.Client.KEEP_ALIVE) { + if (sentAttack && is1_9) { + flagAndAlert("post-attack"); } - sentAnimation = false; + sentAttack = sentAnimation = sentSlotSwitch = false; } } } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/timer/Timer.java b/common/src/main/java/ac/grim/grimac/checks/impl/timer/Timer.java index 585b974641..616ff4aee7 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/timer/Timer.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/timer/Timer.java @@ -20,8 +20,6 @@ public class Timer extends Check implements PacketCheck { // How long should the player be able to fall back behind their ping? (nanos) // Default: 120 milliseconds long clockDrift; - // At what ping should we start to limit the balance advantage? (nanos) - long limitAbuseOverPing; boolean hasGottenMovementAfterTransaction = false; @@ -76,7 +74,6 @@ public void doCheck(final PacketReceiveEvent event) { if (timerBalanceRealTime > System.nanoTime()) { if (flagAndAlert()) { // Cancel the packet - // Only cancel if not an adjustment setback if (shouldModifyPackets()) { event.setCancelled(true); player.onPacketCancel(); @@ -91,12 +88,11 @@ public void doCheck(final PacketReceiveEvent event) { timerBalanceRealTime -= 50e6; } - // Limit using transaction ping if over 1000ms (default) - long playerClock = lastMovementPlayerClock; - if (System.nanoTime() - playerClock > limitAbuseOverPing) { - playerClock = System.nanoTime() - limitAbuseOverPing; - } - timerBalanceRealTime = Math.max(timerBalanceRealTime, playerClock - clockDrift); + limitFallBehind(); + } + + protected void limitFallBehind() { + timerBalanceRealTime = Math.max(timerBalanceRealTime, lastMovementPlayerClock - clockDrift); } public boolean checkForTransaction(PacketTypeCommon packetType) { @@ -112,6 +108,5 @@ public boolean shouldCountPacketForTimer(PacketTypeCommon packetType) { @Override public void onReload(ConfigManager config) { clockDrift = (long) (config.getDoubleElse(getConfigName() + ".drift", 120.0) * 1e6); - limitAbuseOverPing = (long) (config.getDoubleElse(getConfigName() + ".ping-abuse-limit-threshold", 1000) * 1e6); } } diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/timer/TimerLimit.java b/common/src/main/java/ac/grim/grimac/checks/impl/timer/TimerLimit.java new file mode 100644 index 0000000000..12ec335a79 --- /dev/null +++ b/common/src/main/java/ac/grim/grimac/checks/impl/timer/TimerLimit.java @@ -0,0 +1,55 @@ +package ac.grim.grimac.checks.impl.timer; + +import ac.grim.grimac.api.config.ConfigManager; +import ac.grim.grimac.checks.CheckData; +import ac.grim.grimac.player.GrimPlayer; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; + +// This works around 1.3 timer, to prevent too high abuse - maybe there's a better solution? +@CheckData(name = "TimerLimit", setback = 10) +public class TimerLimit extends Timer { + + // At what ping should we start to limit the balance advantage? (nanos) + private long limitAbuseOverPing; + + public TimerLimit(GrimPlayer player) { + super(player); + } + + @Override + public void doCheck(final PacketReceiveEvent event) { + // 1:1 with Timer minus cancelling the packet + if (timerBalanceRealTime > System.nanoTime()) { + // If timer check already flagged, don't flag. + if (!event.isCancelled()) { + if (flagAndAlert() && shouldSetback()) { + player.getSetbackTeleportUtil().executeNonSimulatingSetback(); + } + } + + // Reset the violation by 1 movement + timerBalanceRealTime -= 50e6; + } + + limitFallBehind(); + } + + @Override + protected void limitFallBehind() { + // Limit using transaction ping if over 1000ms (default) + long playerClock = lastMovementPlayerClock; + if (limitAbuseOverPing != -1 && System.nanoTime() - playerClock > limitAbuseOverPing) { + playerClock = System.nanoTime() - limitAbuseOverPing; + } + timerBalanceRealTime = Math.max(timerBalanceRealTime, playerClock - clockDrift); + } + + @Override + public void onReload(ConfigManager config) { + super.onReload(config); + limitAbuseOverPing = config.getLongElse(getConfigName() + ".ping-abuse-limit-threshold", 1000L); + if (limitAbuseOverPing != -1) { + limitAbuseOverPing *= (long) 1e6; + } + } +} diff --git a/common/src/main/java/ac/grim/grimac/command/commands/GrimDump.java b/common/src/main/java/ac/grim/grimac/command/commands/GrimDump.java index 385aeb1cfa..4dcbd4b61f 100644 --- a/common/src/main/java/ac/grim/grimac/command/commands/GrimDump.java +++ b/common/src/main/java/ac/grim/grimac/command/commands/GrimDump.java @@ -17,8 +17,6 @@ import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.description.Description; -import java.util.Locale; - public class GrimDump implements BuildableCommand { private static final boolean PAPER = ReflectionUtils.hasClass("com.destroystokyo.paper.PaperConfig") @@ -49,7 +47,12 @@ private void handleDump(@NonNull CommandContext context) { GrimLog.sendLogAsync(sender, generateDump(), string -> link = string, "text/yaml"); } - // this will help for debugging & replicating issues + /** + * Generates a diagnostic dump in JSON format that contains various metadata + * about the system, platform, and plugins. This dump is primarily used for + * debugging and finding potential issues with the environment. + * @return A JSON-formatted string containing the diagnostic dump. + */ private String generateDump() { JsonObject base = new JsonObject(); base.addProperty("type", "dump"); @@ -61,16 +64,18 @@ private String generateDump() { versions.addProperty("packetevents", PacketEvents.getAPI().getVersion().toString()); versions.addProperty("server", PacketEvents.getAPI().getServerManager().getVersion().getReleaseName()); versions.addProperty("implementation", GrimAPI.INSTANCE.getPlatformServer().getPlatformImplementationString()); - // properties - JsonArray properties = new JsonArray(); - base.add("properties", properties); - properties.add(GrimAPI.INSTANCE.getPlatform().toString().toLowerCase(Locale.ROOT)); - if (ViaVersionUtil.isAvailable()) properties.add("viaversion"); + // state of different properties + JsonObject states = new JsonObject(); + base.add("states", states); + if (GrimAPI.INSTANCE.isInitialized()) states.addProperty("platform", GrimAPI.INSTANCE.getPlatform().toString()); + if (ViaVersionUtil.isAvailable()) states.addProperty("has_viaversion", true); + if (PAPER) states.addProperty("has_paper", true); // system JsonObject system = new JsonObject(); base.add("system", system); - system.addProperty("os", System.getProperty("os.name")); - system.addProperty("java", System.getProperty("java.version")); + system.addProperty("os_name", System.getProperty("os.name")); + system.addProperty("java_version", System.getProperty("java.version")); + system.addProperty("user_language", System.getProperty("user.language")); // plugins JsonArray plugins = new JsonArray(); base.add("plugins", plugins); diff --git a/common/src/main/java/ac/grim/grimac/command/commands/GrimLog.java b/common/src/main/java/ac/grim/grimac/command/commands/GrimLog.java index 87f50a1c71..fae0977c2e 100644 --- a/common/src/main/java/ac/grim/grimac/command/commands/GrimLog.java +++ b/common/src/main/java/ac/grim/grimac/command/commands/GrimLog.java @@ -7,6 +7,7 @@ import ac.grim.grimac.utils.anticheat.LogUtil; import ac.grim.grimac.utils.anticheat.MessageUtil; import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; import org.incendo.cloud.CommandManager; import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.parser.standard.IntegerParser; @@ -31,7 +32,7 @@ public static void sendLogAsync(Sender sender, String log, Consumer cons } catch (Exception e) { String message = MessageUtil.replacePlaceholders(sender, failure); sender.sendMessage(MessageUtil.miniMessage(message)); - e.printStackTrace(); + LogUtil.error("Failed to send log", e); } }); } @@ -67,13 +68,16 @@ private static void sendLog(Sender sender, String log, String success, String fa @Override public void register(CommandManager commandManager) { - commandManager.command( - commandManager.commandBuilder("grim", "grimac", "gl") - .literal("log", "logs") - .permission("grim.log") - .required("flagId", IntegerParser.integerParser()) - .handler(this::handleLog) - ); + Command command = commandManager.commandBuilder("grim", "grimac") + .literal("log", "logs") + .permission("grim.log") + .required("flagId", IntegerParser.integerParser()) + .handler(this::handleLog) + .manager(commandManager) + .build(); + commandManager + .command(command) + .command(commandManager.commandBuilder("gl").proxies(command)); } private void handleLog(@NonNull CommandContext context) { diff --git a/common/src/main/java/ac/grim/grimac/command/commands/GrimSendAlert.java b/common/src/main/java/ac/grim/grimac/command/commands/GrimSendAlert.java index 97185fec28..73354a6ae6 100644 --- a/common/src/main/java/ac/grim/grimac/command/commands/GrimSendAlert.java +++ b/common/src/main/java/ac/grim/grimac/command/commands/GrimSendAlert.java @@ -26,6 +26,6 @@ private void handleSendAlert(@NonNull CommandContext context) { String string = context.get("message"); string = MessageUtil.replacePlaceholders((Sender) null, string); Component message = MessageUtil.miniMessage(string); - GrimAPI.INSTANCE.getAlertManager().sendAlert(message); + GrimAPI.INSTANCE.getAlertManager().sendAlert(message, null); } } diff --git a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java index 837fcb0056..33f227d298 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java @@ -459,6 +459,10 @@ public void onPacketReceive(PacketReceiveEvent event) { return; } + if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) { + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.serverOpenedInventoryThisTick = true); + } + // Determine if teleport BEFORE we call the pre-prediction vehicle if (event.getPacketType() == PacketType.Play.Client.VEHICLE_MOVE) { WrapperPlayClientVehicleMove move = new WrapperPlayClientVehicleMove(event); @@ -469,6 +473,8 @@ public void onPacketReceive(PacketReceiveEvent event) { TeleportAcceptData teleportData = null; if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) { + player.serverOpenedInventoryThisTick = false; + WrapperPlayClientPlayerFlying flying = new WrapperPlayClientPlayerFlying(event); Vector3d position = VectorUtils.clampVector(flying.getLocation().getPosition()); @@ -692,6 +698,7 @@ public void onPacketReceive(PacketReceiveEvent event) { } if (event.getPacketType() == PacketType.Play.Client.CLIENT_TICK_END) { + player.serverOpenedInventoryThisTick = false; if (!player.packetStateData.didSendMovementBeforeTickEnd) { // The player didn't send a movement packet, so we can predict this like we had idle tick on 1.8 player.packetStateData.didLastLastMovementIncludePosition = player.packetStateData.didLastMovementIncludePosition; diff --git a/common/src/main/java/ac/grim/grimac/events/packets/PacketEntityReplication.java b/common/src/main/java/ac/grim/grimac/events/packets/PacketEntityReplication.java index 2d3e8e626f..1271525b5f 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/PacketEntityReplication.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/PacketEntityReplication.java @@ -19,8 +19,10 @@ import com.github.retrooper.packetevents.protocol.entity.data.EntityData; import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; +import com.github.retrooper.packetevents.protocol.item.ItemStack; import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.player.ClientVersion; +import com.github.retrooper.packetevents.protocol.player.InteractionHand; import com.github.retrooper.packetevents.protocol.player.UserProfile; import com.github.retrooper.packetevents.protocol.potion.PotionType; import com.github.retrooper.packetevents.util.Vector3d; @@ -257,25 +259,23 @@ else if (event.getPacketType() == PacketType.Play.Server.PLAYER_INFO_UPDATE) { WrapperPlayServerSetSlot slot = new WrapperPlayServerSetSlot(event); if (slot.getWindowId() == 0) { - player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> { - if (slot.getSlot() - 36 == player.packetStateData.lastSlotSelected && (player.getInventory().getHeldItem().getType() == slot.getItem().getType() || player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8))) { - player.packetStateData.setSlowedByUsingItem(false); - - if (player.isResetItemUsageOnItemUpdate()) { - GrimAPI.INSTANCE.getItemResetHandler().resetItemUsage(player.platformPlayer); + Runnable task = () -> { + if (slot.getSlot() - 36 == player.packetStateData.lastSlotSelected && ( + !player.getInventory().getHeldItem().is(slot.getItem().getType()) || player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) + ) || slot.getSlot() == 45 && !player.getInventory().getOffHand().is(slot.getItem().getType())) { + InteractionHand hand = slot.getSlot() == 45 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND; + if (hand == player.packetStateData.eatingHand) { + player.packetStateData.setSlowedByUsingItem(false); } - } - }); - player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get() + 1, () -> { - if (slot.getSlot() - 36 == player.packetStateData.lastSlotSelected && (player.getInventory().getHeldItem().getType() == slot.getItem().getType() || player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8))) { - player.packetStateData.setSlowedByUsingItem(false); - - if (player.isResetItemUsageOnItemUpdate()) { + if (player.isResetItemUsageOnItemUpdate() && hand == GrimAPI.INSTANCE.getItemResetHandler().getItemUsageHand(player.platformPlayer)) { GrimAPI.INSTANCE.getItemResetHandler().resetItemUsage(player.platformPlayer); } } - }); + }; + + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), task); + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get() + 1, task); } } else if (event.getPacketType() == PacketType.Play.Server.WINDOW_ITEMS) { WrapperPlayServerWindowItems items = new WrapperPlayServerWindowItems(event); diff --git a/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerCooldown.java b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerCooldown.java index e386557fe4..d3a3f730c9 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerCooldown.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerCooldown.java @@ -25,14 +25,16 @@ public void onPacketSend(PacketSendEvent event) { int lastTransactionSent = player.lastTransactionSent.get(); if (cooldown.getCooldownTicks() == 0) { // for removing the cooldown - player.latencyUtils.addRealTimeTask(lastTransactionSent + 1, () -> { - player.checkManager.getCompensatedCooldown().removeCooldown(cooldown.getCooldownGroup()); - }); + player.latencyUtils.addRealTimeTask(lastTransactionSent + 1, + () -> player.checkManager.getCompensatedCooldown().removeCooldown(cooldown.getCooldownGroup())); } else { // Not for removing the cooldown - player.latencyUtils.addRealTimeTask(lastTransactionSent, () -> { - player.checkManager.getCompensatedCooldown().addCooldown(cooldown.getCooldownGroup(), - cooldown.getCooldownTicks(), lastTransactionSent); - }); + player.latencyUtils.addRealTimeTask(lastTransactionSent, + () -> player.checkManager.getCompensatedCooldown().addCooldown( + cooldown.getCooldownGroup(), + cooldown.getCooldownTicks(), + lastTransactionSent + ) + ); } } } diff --git a/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerDigging.java b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerDigging.java index a1c236053e..0f42d5063d 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerDigging.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerDigging.java @@ -209,13 +209,13 @@ public void onPacketReceive(PacketReceiveEvent event) { // Prevent issues if the player switches slots, while lagging, standing still, and is placing blocks CheckManagerListener.handleQueuedPlaces(player, false, 0, 0, System.currentTimeMillis()); - if (player.packetStateData.lastSlotSelected != slot && player.packetStateData.eatingHand != InteractionHand.OFF_HAND) { - if (player.isResetItemUsageOnSlotChange()) { + if (player.packetStateData.lastSlotSelected != slot) { + if (player.isResetItemUsageOnSlotChange() && GrimAPI.INSTANCE.getItemResetHandler().getItemUsageHand(player.platformPlayer) == InteractionHand.MAIN_HAND) { GrimAPI.INSTANCE.getItemResetHandler().resetItemUsage(player.platformPlayer); } // just assume they tick after this - if (player.canSkipTicks() && !player.isTickingReliablyFor(3)) { + if (player.canSkipTicks() && !player.isTickingReliablyFor(3) && player.packetStateData.eatingHand != InteractionHand.OFF_HAND) { player.packetStateData.setSlowedByUsingItem(false); } } diff --git a/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerJoinQuit.java b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerJoinQuit.java index c7f1fc7615..8d9886aedc 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerJoinQuit.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/PacketPlayerJoinQuit.java @@ -16,6 +16,7 @@ import com.google.common.base.Preconditions; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.UUID; public class PacketPlayerJoinQuit extends PacketListenerAbstract { @@ -65,18 +66,22 @@ public void onUserLogin(UserLoginEvent event) { @Override public void onUserDisconnect(UserDisconnectEvent event) { GrimPlayer grimPlayer = GrimAPI.INSTANCE.getPlayerDataManager().remove(event.getUser()); - if (grimPlayer != null) - GrimAPI.INSTANCE.getEventBus().post(new GrimQuitEvent(grimPlayer)); + if (grimPlayer != null) GrimAPI.INSTANCE.getEventBus().post(new GrimQuitEvent(grimPlayer)); GrimAPI.INSTANCE.getPlayerDataManager().exemptUsers.remove(event.getUser()); + + UUID uuid = event.getUser().getProfile().getUUID(); + //Check if calling async is safe - if (event.getUser().getProfile().getUUID() == null) + if (uuid == null) return; // folia doesn't like null getPlayer() - if (grimPlayer != null) { - GrimAPI.INSTANCE.getAlertManager().handlePlayerQuit(grimPlayer); - GrimAPI.INSTANCE.getSpectateManager().onQuit(grimPlayer.uuid); - } + + GrimAPI.INSTANCE.getAlertManager().handlePlayerQuit( + GrimAPI.INSTANCE.getPlatformPlayerFactory().getFromUUID(uuid) + ); + + GrimAPI.INSTANCE.getSpectateManager().onQuit(uuid); // TODO (Cross-platform) confirm this is 100% correct and will always remove players from cache when necessary - GrimAPI.INSTANCE.getPlatformPlayerFactory().invalidatePlayer(event.getUser().getProfile().getUUID()); + GrimAPI.INSTANCE.getPlatformPlayerFactory().invalidatePlayer(uuid); } } diff --git a/common/src/main/java/ac/grim/grimac/events/packets/PacketSelfMetadataListener.java b/common/src/main/java/ac/grim/grimac/events/packets/PacketSelfMetadataListener.java index c262517c35..0675d75f15 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/PacketSelfMetadataListener.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/PacketSelfMetadataListener.java @@ -122,9 +122,8 @@ public void onPacketSend(PacketSendEvent event) { if (frozen != null) { if (!hasSendTransaction) player.sendTransaction(); hasSendTransaction = true; - player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> { - player.powderSnowFrozenTicks = (int) frozen.getValue(); - }); + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> player.powderSnowFrozenTicks = (int) frozen.getValue()); } } @@ -168,9 +167,8 @@ public void onPacketSend(PacketSendEvent event) { if (!hasSendTransaction) player.sendTransaction(); hasSendTransaction = true; - player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> { - player.isRiptidePose = isRiptiding; - }); + player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), + () -> player.isRiptidePose = isRiptiding); // 1.9 eating: // - Client: I am starting to eat diff --git a/common/src/main/java/ac/grim/grimac/events/packets/ProxyAlertMessenger.java b/common/src/main/java/ac/grim/grimac/events/packets/ProxyAlertMessenger.java index f863179345..4eebbbda7e 100644 --- a/common/src/main/java/ac/grim/grimac/events/packets/ProxyAlertMessenger.java +++ b/common/src/main/java/ac/grim/grimac/events/packets/ProxyAlertMessenger.java @@ -16,12 +16,7 @@ import github.scarsz.configuralize.DynamicConfig; import net.kyori.adventure.text.Component; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; +import java.io.*; // TODO (Cross-Platform) ensure this is correct, and modify to only check appropriate files for each platform public class ProxyAlertMessenger extends PacketListenerAbstract { @@ -51,8 +46,7 @@ public static void sendPluginMessage(String message) { try { new DataOutputStream(messageBytes).writeUTF(message); } catch (IOException exception) { - LogUtil.error("Something went wrong whilst forwarding an alert to other servers!"); - exception.printStackTrace(); + LogUtil.error("Something went wrong whilst forwarding an alert to other servers!", exception); return; } @@ -106,11 +100,10 @@ public void onPacketReceive(final PacketReceiveEvent event) { try { alert = new DataInputStream(new ByteArrayInputStream(messageBytes)).readUTF(); } catch (IOException exception) { - LogUtil.error("Something went wrong whilst reading an alert forwarded from another server!"); - exception.printStackTrace(); + LogUtil.error("Something went wrong whilst reading an alert forwarded from another server!", exception); return; } Component message = MessageUtil.miniMessage(alert); - GrimAPI.INSTANCE.getAlertManager().sendAlert(message); + GrimAPI.INSTANCE.getAlertManager().sendAlert(message, null); } } diff --git a/common/src/main/java/ac/grim/grimac/manager/AlertManagerImpl.java b/common/src/main/java/ac/grim/grimac/manager/AlertManagerImpl.java index ca3e4bbf94..1bb4bff562 100644 --- a/common/src/main/java/ac/grim/grimac/manager/AlertManagerImpl.java +++ b/common/src/main/java/ac/grim/grimac/manager/AlertManagerImpl.java @@ -8,12 +8,14 @@ import ac.grim.grimac.manager.init.start.StartableInitable; import ac.grim.grimac.platform.api.PlatformServer; import ac.grim.grimac.platform.api.player.PlatformPlayer; -import ac.grim.grimac.platform.api.sender.Sender; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.MessageUtil; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; +import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -23,30 +25,60 @@ * Caches toggle messages for performance. */ public final class AlertManagerImpl implements AlertManager, ConfigReloadable, StartableInitable { - private boolean consoleAlertsEnabled; - private boolean consoleVerboseEnabled; - private boolean consoleBrandsEnabled; - - private @NonNull PlatformServer platformServer; + private static @NonNull PlatformServer platformServer; private enum AlertType { NORMAL, VERBOSE, BRAND; + public String enableMessage; public String disableMessage; public final Set players = new CopyOnWriteArraySet<>(); + public boolean console; + + @Contract(pure = true) + public boolean hasListeners() { + return !players.isEmpty() || console; + } + + @Contract(pure = true) + public String getToggleMessage(boolean enabled) { + return enabled ? enableMessage : disableMessage; + } + + /** + * @param component the message to send to listeners + * @param excluding the listeners to exclude, null means console + * @return listeners this message was sent to, null means console + */ + public Set<@Nullable PlatformPlayer> send(Component component, @Nullable Set<@Nullable PlatformPlayer> excluding) { + HashSet listeners = new HashSet<>(players); + if (excluding != null) { + listeners.removeAll(excluding); + } + + for (PlatformPlayer platformPlayer : listeners) { + platformPlayer.sendMessage(component); + } + + if (console && (excluding == null || !excluding.contains(null))) { + platformServer.getConsoleSender().sendMessage(component); + listeners.add(null); + } + + return listeners; + } } @Override public void start() { - this.platformServer = GrimAPI.INSTANCE.getPlatformServer(); + platformServer = GrimAPI.INSTANCE.getPlatformServer(); reload(GrimAPI.INSTANCE.getConfigManager().getConfig()); } @Override public void reload(ConfigManager config) { - this.consoleAlertsEnabled = config.getBooleanElse("alerts.print-to-console", true); - this.consoleVerboseEnabled = config.getBooleanElse("verbose.print-to-console", false); - this.consoleBrandsEnabled = false; + setConsoleAlertsEnabled(config.getBooleanElse("alerts.print-to-console", true), true); + setConsoleVerboseEnabled(config.getBooleanElse("verbose.print-to-console", false), true); AlertType.NORMAL.enableMessage = config.getStringElse("alerts-enabled", "%prefix% &fAlerts enabled"); AlertType.NORMAL.disableMessage = config.getStringElse("alerts-disabled", "%prefix% &fAlerts disabled"); @@ -58,8 +90,8 @@ public void reload(ConfigManager config) { /** * Gets the non-null PlatformPlayer from a GrimUser. - * Throws IllegalArgumentException if the user is not a GrimPlayer. - * Throws NullPointerException if the GrimPlayer's platformPlayer is null. + * @throws IllegalArgumentException if the user is not a GrimPlayer. + * @throws NullPointerException if the GrimPlayer's platformPlayer is null. */ @NonNull private PlatformPlayer requirePlatformPlayerFromUser(@NonNull GrimUser user) { @@ -78,64 +110,33 @@ private PlatformPlayer requirePlatformPlayerFromUser(@NonNull GrimUser user) { return platformPlayer; } - /** Central logic for setting player state and conditionally sending messages. */ - private void setPlayerStateAndNotify(@NonNull GrimUser player, boolean enabled, boolean silent, @NonNull AlertType type) { - // requirePlatformPlayerFromUser handles null checks for player and platformPlayer - // It will throw an exception if platformPlayer is null, stopping execution here. - PlatformPlayer platformPlayer = requirePlatformPlayerFromUser(player); - - boolean changed = enabled ? type.players.add(platformPlayer) : type.players.remove(platformPlayer); - - if (changed && !silent) { - sendToggleMessage(platformPlayer, enabled, type); - } - } - - /** Retrieves the appropriate cached message string based on type and state. */ - @NonNull - private String getCachedToggleMessage(boolean enabled, @NonNull AlertType type) { - return enabled ? type.enableMessage : type.disableMessage; - } - /** Gets the cached message, applies placeholders, and sends it to a PlatformPlayer. */ - private void sendToggleMessage(@NonNull PlatformPlayer player, boolean enabled, @NonNull AlertType type) { - String rawMessage = getCachedToggleMessage(enabled, type); + private static void sendToggleMessage(@NonNull PlatformPlayer player, boolean enabled, @NonNull AlertType type) { + String rawMessage = type.getToggleMessage(enabled); if (rawMessage.isEmpty()) return; String messageWithPlaceholders = MessageUtil.replacePlaceholders(player, rawMessage); player.sendMessage(MessageUtil.miniMessage(messageWithPlaceholders)); } - /** Gets the cached message, applies generic placeholders, and sends it to the Console Sender. */ - private void sendToggleMessage(@NonNull Sender consoleSender, boolean enabled, @NonNull AlertType type) { - String rawMessage = getCachedToggleMessage(enabled, type); - if (rawMessage.isEmpty()) return; - - String messageWithPlaceholders = MessageUtil.replacePlaceholders((PlatformPlayer) null, rawMessage); - consoleSender.sendMessage(MessageUtil.miniMessage(messageWithPlaceholders)); - } - @Override public boolean hasAlertsEnabled(@NonNull GrimUser player) { - return AlertType.NORMAL.players.contains(requirePlatformPlayerFromUser(player)); + return hasAlertsEnabled(requirePlatformPlayerFromUser(player)); } @Override public void setAlertsEnabled(@NonNull GrimUser player, boolean enabled, boolean silent) { - // Let exceptions from requirePlatformPlayerFromUser propagate if called during set - setPlayerStateAndNotify(player, enabled, silent, AlertType.NORMAL); - if (!enabled) setVerboseEnabled(player, false, silent); + setAlertsEnabled(requirePlatformPlayerFromUser(player), enabled, silent); } @Override public boolean hasVerboseEnabled(@NonNull GrimUser player) { - return AlertType.VERBOSE.players.contains(requirePlatformPlayerFromUser(player)); + return hasVerboseEnabled(requirePlatformPlayerFromUser(player)); } @Override public void setVerboseEnabled(@NonNull GrimUser player, boolean enabled, boolean silent) { - if (enabled) setAlertsEnabled(player, true, silent); - setPlayerStateAndNotify(player, enabled, silent, AlertType.VERBOSE); + setVerboseEnabled(requirePlatformPlayerFromUser(player), enabled, silent); } @Override @@ -147,48 +148,105 @@ public boolean hasBrandsEnabled(@NonNull GrimUser player) { // for compatibles sake lets just default to not sending alerts to these players if (grimPlayer.platformPlayer == null) return false; - return AlertType.BRAND.players.contains(grimPlayer.platformPlayer); + return hasBrandsEnabled(grimPlayer.platformPlayer); } @Override public void setBrandsEnabled(@NonNull GrimUser player, boolean enabled, boolean silent) { - setPlayerStateAndNotify(player, enabled, silent, AlertType.BRAND); + setPlayerStateAndNotify(requirePlatformPlayerFromUser(player), enabled, silent, AlertType.BRAND); } - public void handlePlayerQuit(@NonNull GrimUser user) { - Objects.requireNonNull(user, "user cannot be null"); - // We need to get PlatformPlayer *without* throwing an error on quit - if (user instanceof GrimPlayer grimPlayer && grimPlayer.platformPlayer != null) { - handlePlayerQuit(grimPlayer.platformPlayer); - } - } + public void handlePlayerQuit(@Nullable PlatformPlayer platformPlayer) { + if (platformPlayer == null) return; - public void handlePlayerQuit(@NonNull PlatformPlayer platformPlayer) { - // Null check for platformPlayer should be done by the caller if necessary AlertType.NORMAL.players.remove(platformPlayer); AlertType.VERBOSE.players.remove(platformPlayer); AlertType.BRAND.players.remove(platformPlayer); } public boolean toggleConsoleAlerts() { - boolean newState = !this.consoleAlertsEnabled; - this.consoleAlertsEnabled = newState; - sendToggleMessage(platformServer.getConsoleSender(), newState, AlertType.NORMAL); - return newState; + return toggleConsoleAlerts(false); + } + + public boolean toggleConsoleAlerts(boolean silent) { + return setConsoleAlertsEnabled(!hasConsoleAlertsEnabled(), silent); + } + + @Contract("_ -> param1") + public boolean setConsoleAlertsEnabled(boolean enabled) { + return setConsoleAlertsEnabled(enabled, false); + } + + @Contract("_, _ -> param1") + public boolean setConsoleAlertsEnabled(boolean enabled, boolean silent) { + setConsoleStateAndNotify(AlertType.NORMAL, enabled, silent); + if (!enabled) setConsoleVerboseEnabled(false, silent); + return enabled; + } + + @Contract(pure = true) + public boolean hasConsoleAlertsEnabled() { + return AlertType.NORMAL.console; } public boolean toggleConsoleVerbose() { - boolean newState = !this.consoleVerboseEnabled; - this.consoleVerboseEnabled = newState; - sendToggleMessage(platformServer.getConsoleSender(), newState, AlertType.VERBOSE); - return newState; + return toggleConsoleVerbose(false); + } + + public boolean toggleConsoleVerbose(boolean silent) { + return setConsoleVerboseEnabled(!hasConsoleVerboseEnabled(), silent); + } + + @Contract("_ -> param1") + public boolean setConsoleVerboseEnabled(boolean enabled) { + return setConsoleVerboseEnabled(enabled, false); + } + + @Contract("_, _ -> param1") + public boolean setConsoleVerboseEnabled(boolean enabled, boolean silent) { + if (enabled) setConsoleAlertsEnabled(true, silent); + return setConsoleStateAndNotify(AlertType.VERBOSE, enabled, silent); + } + + @Contract(pure = true) + public boolean hasConsoleVerboseEnabled() { + return AlertType.VERBOSE.console; } public boolean toggleConsoleBrands() { - boolean newState = !this.consoleBrandsEnabled; - this.consoleBrandsEnabled = newState; - sendToggleMessage(platformServer.getConsoleSender(), newState, AlertType.BRAND); - return newState; + return toggleConsoleBrands(false); + } + + public boolean toggleConsoleBrands(boolean silent) { + return setConsoleBrandsEnabled(!hasConsoleBrandsEnabled(), silent); + } + + @Contract("_ -> param1") + public boolean setConsoleBrandsEnabled(boolean enabled) { + return setConsoleStateAndNotify(AlertType.BRAND, enabled, false); + } + + @Contract("_, _ -> param1") + public boolean setConsoleBrandsEnabled(boolean enabled, boolean silent) { + return setConsoleStateAndNotify(AlertType.BRAND, enabled, silent); + } + + @Contract(pure = true) + public boolean hasConsoleBrandsEnabled() { + return AlertType.BRAND.console; + } + + @Contract("_, _, _ -> param2") + private boolean setConsoleStateAndNotify(@NonNull AlertType type, boolean enabled, boolean silent) { + if (type.console != enabled && !silent) { + String rawMessage = type.getToggleMessage(enabled); + if (!rawMessage.isEmpty()) { + platformServer.getConsoleSender().sendMessage(MessageUtil.miniMessage(MessageUtil.replacePlaceholders((PlatformPlayer) null, rawMessage))); + } + } + + type.console = enabled; + return enabled; } // All internal code, will replace later @@ -201,63 +259,114 @@ private void setPlayerStateAndNotify(@NonNull PlatformPlayer platformPlayer, boo } } - private boolean togglePlayerStateAndNotify(@NonNull PlatformPlayer platformPlayer, boolean silent, @NonNull AlertType type) { - Objects.requireNonNull(platformPlayer, "platformPlayer cannot be null"); - boolean newState = !type.players.contains(platformPlayer); // The desired state after toggle + public boolean toggleBrands(@NonNull PlatformPlayer player) { + return toggleBrands(player, false); + } - // Use the set method to handle actual state change and notification - setPlayerStateAndNotify(platformPlayer, newState, silent, type); + public boolean toggleBrands(@NonNull PlatformPlayer player, boolean silent) { + return setBrandsEnabled(player, !hasBrandsEnabled(player), silent); + } - return newState; // Return the state *after* the toggle attempt + @Contract("_, _ -> param2") + public boolean setBrandsEnabled(@NonNull PlatformPlayer player, boolean enabled) { + return setBrandsEnabled(player, enabled, false); } - public boolean toggleBrands(@NonNull PlatformPlayer platformPlayer, boolean silent) { - return togglePlayerStateAndNotify(platformPlayer, silent, AlertType.BRAND); + @Contract("_, _, _ -> param2") + public boolean setBrandsEnabled(@NonNull PlatformPlayer player, boolean enabled, boolean silent) { + setPlayerStateAndNotify(player, enabled, silent, AlertType.BRAND); + return enabled; } - public boolean toggleVerbose(@NonNull PlatformPlayer platformPlayer, boolean silent) { - return togglePlayerStateAndNotify(platformPlayer, silent, AlertType.VERBOSE); + @Contract(pure = true) + public boolean hasBrandsEnabled(@NonNull PlatformPlayer player) { + return AlertType.BRAND.players.contains(player); } - public boolean toggleAlerts(@NonNull PlatformPlayer platformPlayer, boolean silent) { - return togglePlayerStateAndNotify(platformPlayer, silent, AlertType.NORMAL); + public boolean toggleVerbose(@NonNull PlatformPlayer player) { + return toggleVerbose(player, false); } - public void sendBrand(Component component) { - for (PlatformPlayer platformPlayer : AlertType.BRAND.players) { - platformPlayer.sendMessage(component); - } + public boolean toggleVerbose(@NonNull PlatformPlayer player, boolean silent) { + return setVerboseEnabled(player, !hasVerboseEnabled(player), silent); + } - if (consoleBrandsEnabled) { - platformServer.getConsoleSender().sendMessage(component); - } + @Contract("_, _ -> param2") + public boolean setVerboseEnabled(@NonNull PlatformPlayer player, boolean enabled) { + return setVerboseEnabled(player, enabled, false); } - public void sendVerbose(Component component) { - for (PlatformPlayer platformPlayer : AlertType.VERBOSE.players) { - platformPlayer.sendMessage(component); - } + @Contract("_, _, _ -> param2") + public boolean setVerboseEnabled(@NonNull PlatformPlayer player, boolean enabled, boolean silent) { + if (enabled) setAlertsEnabled(player, true, silent); + setPlayerStateAndNotify(player, enabled, silent, AlertType.VERBOSE); + return enabled; + } - if (consoleVerboseEnabled) { - platformServer.getConsoleSender().sendMessage(component); - } + @Contract(pure = true) + public boolean hasVerboseEnabled(@NonNull PlatformPlayer player) { + return AlertType.VERBOSE.players.contains(player); } - public void sendAlert(Component component) { - for (PlatformPlayer platformPlayer : AlertType.NORMAL.players) { - platformPlayer.sendMessage(component); - } + public boolean toggleAlerts(@NonNull PlatformPlayer player) { + return toggleAlerts(player, false); + } - if (consoleAlertsEnabled) { - platformServer.getConsoleSender().sendMessage(component); - } + public boolean toggleAlerts(@NonNull PlatformPlayer player, boolean silent) { + return setAlertsEnabled(player, !hasAlertsEnabled(player), silent); + } + + @Contract("_, _ -> param2") + public boolean setAlertsEnabled(@NonNull PlatformPlayer player, boolean enabled) { + return setAlertsEnabled(player, enabled, false); + } + + @Contract("_, _, _ -> param2") + public boolean setAlertsEnabled(@NonNull PlatformPlayer player, boolean enabled, boolean silent) { + setPlayerStateAndNotify(player, enabled, silent, AlertType.NORMAL); + if (!enabled) setVerboseEnabled(player, false, silent); + return enabled; + } + + @Contract(pure = true) + public boolean hasAlertsEnabled(@NonNull PlatformPlayer player) { + return AlertType.NORMAL.players.contains(player); + } + + /** + * @param component the message to send to listeners + * @param excluding the listeners to exclude, null means console + * @return listeners this message was sent to, null means console + */ + public Set sendBrand(Component component, @Nullable Set<@Nullable PlatformPlayer> excluding) { + return AlertType.BRAND.send(component, excluding); + } + + /** + * @param component the message to send to listeners + * @param excluding the listeners to exclude, null means console + * @return listeners this message was sent to, null means console + */ + public Set sendVerbose(Component component, @Nullable Set<@Nullable PlatformPlayer> excluding) { + return AlertType.VERBOSE.send(component, excluding); + } + + /** + * @param component the message to send to listeners + * @param excluding the listeners to exclude, null means console + * @return listeners this message was sent to, null means console + */ + public Set sendAlert(Component component, @Nullable Set<@Nullable PlatformPlayer> excluding) { + return AlertType.NORMAL.send(component, excluding); } + @Contract(pure = true) public boolean hasVerboseListeners() { - return !AlertType.VERBOSE.players.isEmpty() || consoleVerboseEnabled; + return AlertType.VERBOSE.hasListeners(); } + @Contract(pure = true) public boolean hasAlertListeners() { - return !AlertType.NORMAL.players.isEmpty() || consoleAlertsEnabled; + return AlertType.NORMAL.hasListeners(); } } diff --git a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java index 24781871fc..5669de47cd 100644 --- a/common/src/main/java/ac/grim/grimac/manager/CheckManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/CheckManager.java @@ -41,8 +41,6 @@ import ac.grim.grimac.checks.impl.movement.SetbackBlocker; import ac.grim.grimac.checks.impl.movement.VehiclePredictionRunner; import ac.grim.grimac.checks.impl.multiactions.*; -import ac.grim.grimac.checks.impl.packetorder.PacketOrderB; -import ac.grim.grimac.checks.impl.packetorder.PacketOrderC; import ac.grim.grimac.checks.impl.packetorder.*; import ac.grim.grimac.checks.impl.post.Post; import ac.grim.grimac.checks.impl.prediction.DebugHandler; @@ -67,6 +65,7 @@ import ac.grim.grimac.checks.impl.timer.NegativeTimer; import ac.grim.grimac.checks.impl.timer.TickTimer; import ac.grim.grimac.checks.impl.timer.Timer; +import ac.grim.grimac.checks.impl.timer.TimerLimit; import ac.grim.grimac.checks.impl.timer.VehicleTimer; import ac.grim.grimac.checks.impl.vehicle.VehicleA; import ac.grim.grimac.checks.impl.vehicle.VehicleB; @@ -257,6 +256,7 @@ public CheckManager(GrimPlayer player) { prePredictionChecks = new ImmutableClassToInstanceMap.Builder() .put(Timer.class, new Timer(player)) .put(TickTimer.class, new TickTimer(player)) + .put(TimerLimit.class, new TimerLimit(player)) .put(CrashA.class, new CrashA(player)) .put(CrashB.class, new CrashB(player)) .put(CrashC.class, new CrashC(player)) diff --git a/common/src/main/java/ac/grim/grimac/manager/DiscordManager.java b/common/src/main/java/ac/grim/grimac/manager/DiscordManager.java index f250e82034..db9b3a54d4 100644 --- a/common/src/main/java/ac/grim/grimac/manager/DiscordManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/DiscordManager.java @@ -11,7 +11,7 @@ import ac.grim.grimac.utils.webhook.EmbedFooter; import ac.grim.grimac.utils.webhook.WebhookMessage; -import java.awt.Color; +import java.awt.*; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -74,7 +74,7 @@ public void reload() { } staticContent = sb.toString(); } catch (Exception e) { - e.printStackTrace(); + LogUtil.error("Failed to load Discord webhook configuration", e); } } @@ -96,10 +96,9 @@ public void sendAlert(GrimPlayer player, String verbose, String checkName, int v } String content = staticContent; - content = content.replace("%check%", checkName); + content = content.replace("%check%", checkName.replace("_", "\\_")); // just in case any checks are added with an underscore content = content.replace("%violations%", Integer.toString(violations)); - content = MessageUtil.replacePlaceholders(player, content); - content = content.replace("_", "\\_"); + content = MessageUtil.replacePlaceholders(player, content, true); Embed embed = new Embed(content) .imageURL("https://i.stack.imgur.com/Fzh0w.png") @@ -110,7 +109,7 @@ public void sendAlert(GrimPlayer player, String verbose, String checkName, int v .footer(new EmbedFooter("", "https://grim.ac/images/grim.png")); if (!verbose.isEmpty()) { - embed.addFields(new EmbedField("Verbose", verbose, true)); + embed.addFields(new EmbedField("Verbose", MessageUtil.filterDiscordText(verbose), true)); } sendWebhookMessage(new WebhookMessage().addEmbeds(embed)); diff --git a/common/src/main/java/ac/grim/grimac/manager/InitManager.java b/common/src/main/java/ac/grim/grimac/manager/InitManager.java index 80f3f3f9d3..b91c94a51c 100644 --- a/common/src/main/java/ac/grim/grimac/manager/InitManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/InitManager.java @@ -8,6 +8,7 @@ import ac.grim.grimac.manager.init.stop.StoppableInitable; import ac.grim.grimac.manager.init.stop.TerminatePacketEvents; import ac.grim.grimac.platform.api.sender.Sender; +import ac.grim.grimac.utils.anticheat.LogUtil; import com.github.retrooper.packetevents.PacketEventsAPI; import com.google.common.collect.ImmutableList; import lombok.Getter; @@ -73,7 +74,7 @@ public void load() { try { initable.load(); } catch (Exception e) { - e.printStackTrace(); + LogUtil.error("Failed to load " + initable.getClass().getSimpleName(), e); } loaded = true; } @@ -83,7 +84,7 @@ public void start() { try { initable.start(); } catch (Exception e) { - e.printStackTrace(); + LogUtil.error("Failed to start " + initable.getClass().getSimpleName(), e); } started = true; } @@ -93,7 +94,7 @@ public void stop() { try { initable.stop(); } catch (Exception e) { - e.printStackTrace(); + LogUtil.error("Failed to stop " + initable.getClass().getSimpleName(), e); } stopped = true; } diff --git a/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java b/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java index 63d52ffa37..86ea2a31ab 100644 --- a/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java @@ -7,19 +7,16 @@ import ac.grim.grimac.api.event.events.CommandExecuteEvent; import ac.grim.grimac.checks.Check; import ac.grim.grimac.events.packets.ProxyAlertMessenger; +import ac.grim.grimac.platform.api.player.PlatformPlayer; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.LogUtil; import ac.grim.grimac.utils.anticheat.MessageUtil; import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; +import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; public class PunishmentManager implements ConfigReloadable { GrimPlayer player; @@ -94,8 +91,7 @@ public void reload(ConfigManager config) { groups.add(new PunishGroup(checksList, parsed, removeViolationsAfter * 1000)); } } catch (Exception e) { - LogUtil.error("Error while loading punishments.yml! This is likely your fault!"); - e.printStackTrace(); + LogUtil.error("Error while loading punishments.yml! This is likely your fault!", e); } } @@ -121,11 +117,13 @@ public boolean handleAlert(GrimPlayer player, String verbose, Check check) { for (ParsedCommand command : group.commands) { String cmd = replaceAlertPlaceholders(command.command, vl, check, verbose); + @Nullable Set<@Nullable PlatformPlayer> verboseListeners = null; + // Verbose that prints all flags if (GrimAPI.INSTANCE.getAlertManager().hasVerboseListeners() && command.command.equals("[alert]")) { sentDebug = true; Component component = MessageUtil.miniMessage(cmd); - GrimAPI.INSTANCE.getAlertManager().sendVerbose(component); + verboseListeners = GrimAPI.INSTANCE.getAlertManager().sendVerbose(component, null); } if (violationCount >= command.threshold) { @@ -149,9 +147,11 @@ public boolean handleAlert(GrimPlayer player, String verbose, Check check) { sentDebug = true; Component message = MessageUtil.miniMessage(cmd); if (testMode) { // secret test mode - player.user.sendMessage(message); + if (verboseListeners == null || verboseListeners.contains(player.platformPlayer)) { + player.sendMessage(message); + } } else { - GrimAPI.INSTANCE.getAlertManager().sendAlert(message); + GrimAPI.INSTANCE.getAlertManager().sendAlert(message, verboseListeners); } } default -> GrimAPI.INSTANCE.getScheduler().getGlobalRegionScheduler().run(GrimAPI.INSTANCE.getGrimPlugin(), () -> diff --git a/common/src/main/java/ac/grim/grimac/manager/config/ConfigManagerFileImpl.java b/common/src/main/java/ac/grim/grimac/manager/config/ConfigManagerFileImpl.java index 57899c0f22..b711b75066 100644 --- a/common/src/main/java/ac/grim/grimac/manager/config/ConfigManagerFileImpl.java +++ b/common/src/main/java/ac/grim/grimac/manager/config/ConfigManagerFileImpl.java @@ -90,7 +90,7 @@ private void upgrade() { } } catch (IOException e) { - e.printStackTrace(); + LogUtil.error("Failed to upgrade config file", e); } } } diff --git a/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java b/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java index 8218e625e1..bddc957aaa 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java @@ -24,7 +24,7 @@ public class CommandRegister implements StartableInitable { public static final CloudKey> REQUIREMENT_KEY = CloudKey.of( "requirements", - new TypeToken>() {} + new TypeToken<>() {} ); public static final RequirementApplicable.RequirementApplicableFactory commandManager) { protected static void registerExceptionHandler(CommandManager commandManager, Class ex, Function toComponent) { commandManager.exceptionController().registerHandler(ex, - (c) -> { - c.context().sender().sendMessage(toComponent.apply(c.exception()).asComponent().colorIfAbsent(NamedTextColor.RED)); - } + (c) -> c.context().sender().sendMessage(toComponent.apply(c.exception()).asComponent().colorIfAbsent(NamedTextColor.RED)) ); } diff --git a/common/src/main/java/ac/grim/grimac/manager/init/start/JavaVersion.java b/common/src/main/java/ac/grim/grimac/manager/init/start/JavaVersion.java index bdd2e57e9f..532e1bf06a 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/start/JavaVersion.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/start/JavaVersion.java @@ -22,8 +22,7 @@ public void start() { try { version = Integer.parseInt(versionString); } catch (NumberFormatException e) { - LogUtil.error("Failed to determine Java version; could not parse: " + versionString); - e.printStackTrace(); + LogUtil.error("Failed to determine Java version; could not parse: " + versionString, e); return; } diff --git a/common/src/main/java/ac/grim/grimac/manager/init/start/SuperDebug.java b/common/src/main/java/ac/grim/grimac/manager/init/start/SuperDebug.java index 62e16d21fa..342f404d8c 100644 --- a/common/src/main/java/ac/grim/grimac/manager/init/start/SuperDebug.java +++ b/common/src/main/java/ac/grim/grimac/manager/init/start/SuperDebug.java @@ -30,9 +30,9 @@ public final class SuperDebug extends Check implements PostPredictionCheck { Object2IntMap continuedDebug = new Object2IntOpenHashMap<>(); List predicted = new EvictingQueue<>(60); - List actually = new EvictingQueue(60); + List actually = new EvictingQueue<>(60); List locations = new EvictingQueue<>(60); - List startTickClientVel = new EvictingQueue(60); + List startTickClientVel = new EvictingQueue<>(60); List baseTickAddition = new EvictingQueue<>(60); List baseTickWater = new EvictingQueue<>(60); diff --git a/common/src/main/java/ac/grim/grimac/platform/api/PlatformServer.java b/common/src/main/java/ac/grim/grimac/platform/api/PlatformServer.java index d10d324fed..15a67864b1 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/PlatformServer.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/PlatformServer.java @@ -2,7 +2,6 @@ import ac.grim.grimac.platform.api.sender.Sender; - public interface PlatformServer { String getPlatformImplementationString(); diff --git a/common/src/main/java/ac/grim/grimac/platform/api/manager/ItemResetHandler.java b/common/src/main/java/ac/grim/grimac/platform/api/manager/ItemResetHandler.java index e5c3ad5ebb..e96a791871 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/manager/ItemResetHandler.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/manager/ItemResetHandler.java @@ -1,8 +1,12 @@ package ac.grim.grimac.platform.api.manager; import ac.grim.grimac.platform.api.player.PlatformPlayer; +import com.github.retrooper.packetevents.protocol.player.InteractionHand; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; public interface ItemResetHandler { void resetItemUsage(@Nullable PlatformPlayer player); + @Contract("null -> null") + @Nullable InteractionHand getItemUsageHand(@Nullable PlatformPlayer player); } diff --git a/common/src/main/java/ac/grim/grimac/platform/api/sender/AbstractSender.java b/common/src/main/java/ac/grim/grimac/platform/api/sender/AbstractSender.java index 61cf4c80d7..2828170769 100644 --- a/common/src/main/java/ac/grim/grimac/platform/api/sender/AbstractSender.java +++ b/common/src/main/java/ac/grim/grimac/platform/api/sender/AbstractSender.java @@ -84,7 +84,7 @@ public boolean isValid() { @Override public boolean equals(Object o) { if (o == this) return true; - if (!(o instanceof AbstractSender that)) return false; + if (!(o instanceof AbstractSender that)) return false; return this.getUniqueId().equals(that.getUniqueId()); } diff --git a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java index a92d80b0f4..8ff0a9ab85 100644 --- a/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java +++ b/common/src/main/java/ac/grim/grimac/player/GrimPlayer.java @@ -247,6 +247,7 @@ public class GrimPlayer implements GrimUser { public final ArrayDeque pendingRotations = new ArrayDeque<>(); @Getter @Setter private ResyncHandler resyncHandler = new DefaultResyncHandler(this); @Getter private final FeatureManagerImpl featureManager = new FeatureManagerImpl(this); + public boolean serverOpenedInventoryThisTick; // start config private boolean debugPacketCancel = false; private int spamThreshold = 100; @@ -312,7 +313,11 @@ public void onPacketCancel() { cancelledPackets.set(0); if (debugPacketCancel) { - LogUtil.error("Stacktrace for onPacketCancel (debug-packet-cancel=true)", new Exception()); + try { + throw new Exception(); + } catch (Exception e) { + LogUtil.error("Stacktrace for onPacketCancel (debug-packet-cancel=true)", e); + } } } } @@ -511,9 +516,8 @@ public void disconnect(Component reason) { } user.closeConnection(); if (platformPlayer != null) { - GrimAPI.INSTANCE.getScheduler().getEntityScheduler().execute(platformPlayer, GrimAPI.INSTANCE.getGrimPlugin(), () -> { - platformPlayer.kickPlayer(textReason); - }, null, 1); + GrimAPI.INSTANCE.getScheduler().getEntityScheduler().execute(platformPlayer, GrimAPI.INSTANCE.getGrimPlugin(), + () -> platformPlayer.kickPlayer(textReason), null, 1); } } diff --git a/common/src/main/java/ac/grim/grimac/predictionengine/MovementCheckRunner.java b/common/src/main/java/ac/grim/grimac/predictionengine/MovementCheckRunner.java index 08ad9604ee..4a617697de 100644 --- a/common/src/main/java/ac/grim/grimac/predictionengine/MovementCheckRunner.java +++ b/common/src/main/java/ac/grim/grimac/predictionengine/MovementCheckRunner.java @@ -372,7 +372,7 @@ private void check(PositionUpdate update) { if (BlockTags.ICE.contains(data.getType())) { player.uncertaintyHandler.isSteppingOnIce = true; } - if (data.getType() == StateTypes.BUBBLE_COLUMN) { + if (data.getType() == StateTypes.BUBBLE_COLUMN && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) { player.uncertaintyHandler.isSteppingNearBubbleColumn = true; } if (data.getType() == StateTypes.SCAFFOLDING) { diff --git a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java index dcc1472a20..7db473be8f 100644 --- a/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java +++ b/common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java @@ -142,7 +142,7 @@ public void handleChangeBlock(int x, int y, int z, WrappedBlockState state) { if ((Materials.isWater(player.getClientVersion(), state) || stateType == StateTypes.LAVA) && pointThreeBox.isIntersected(new SimpleCollisionBox(x, y, z))) { - if (stateType == StateTypes.BUBBLE_COLUMN) { + if (stateType == StateTypes.BUBBLE_COLUMN && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) { isNearBubbleColumn = true; } @@ -268,7 +268,7 @@ private void checkNearbyBlocks(SimpleCollisionBox pointThreeBox) { isNearClimbable = isNearClimbable || Collisions.trapdoorUsableAsLadder(player, pair.second().getX(), pair.second().getY(), pair.second().getZ(), state); } - if (stateType == StateTypes.BUBBLE_COLUMN) { + if (stateType == StateTypes.BUBBLE_COLUMN && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) { isNearBubbleColumn = true; } diff --git a/common/src/main/java/ac/grim/grimac/utils/anticheat/LogUtil.java b/common/src/main/java/ac/grim/grimac/utils/anticheat/LogUtil.java index 1a8b5495b7..afab39a04e 100644 --- a/common/src/main/java/ac/grim/grimac/utils/anticheat/LogUtil.java +++ b/common/src/main/java/ac/grim/grimac/utils/anticheat/LogUtil.java @@ -24,8 +24,12 @@ public void error(final String error) { } public void error(final String description, final Throwable throwable) { - getLogger().severe(description); - throwable.printStackTrace(); + Logger logger = getLogger(); + if (logger != null) { + logger.severe(description + ": " + getStackTrace(throwable)); + } else { + throwable.printStackTrace(); + } } public Logger getLogger() { @@ -40,10 +44,6 @@ public void console(final Component info) { GrimAPI.INSTANCE.getPlatformServer().getConsoleSender().sendMessage(info); } - public static void exception(String description, Throwable throwable) { - getLogger().severe(description + ": " + getStackTrace(throwable)); - } - private static String getStackTrace(Throwable throwable) { String message = throwable.getMessage(); try (StringWriter sw = new StringWriter()) { @@ -52,7 +52,6 @@ private static String getStackTrace(Throwable throwable) { message = sw.toString(); } } catch (Exception ignored) { - ignored.printStackTrace(); } return message; } diff --git a/common/src/main/java/ac/grim/grimac/utils/anticheat/MessageUtil.java b/common/src/main/java/ac/grim/grimac/utils/anticheat/MessageUtil.java index 657b9fad16..42644db761 100644 --- a/common/src/main/java/ac/grim/grimac/utils/anticheat/MessageUtil.java +++ b/common/src/main/java/ac/grim/grimac/utils/anticheat/MessageUtil.java @@ -33,8 +33,12 @@ public class MessageUtil { return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z; } + public @NotNull String replacePlaceholders(@Nullable GrimPlayer player, @NotNull String string, boolean removeFormatting) { + return replacePlaceholders(player, player == null ? null : player.platformPlayer, string, removeFormatting); + } + public @NotNull String replacePlaceholders(@Nullable GrimPlayer player, @NotNull String string) { - return replacePlaceholders(player, player == null ? null : player.platformPlayer, string); + return replacePlaceholders(player, player == null ? null : player.platformPlayer, string, false); } public @NotNull String replacePlaceholders(@Nullable Sender sender, @NotNull String string) { @@ -42,23 +46,54 @@ public class MessageUtil { } public @NotNull String replacePlaceholders(@Nullable PlatformPlayer player, @NotNull String string) { - return replacePlaceholders(player == null ? null : GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(player.getUniqueId()), player, string); + return replacePlaceholders(player == null ? null : GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(player.getUniqueId()), player, string, false); } - private @NotNull String replacePlaceholders(@Nullable GrimPlayer grimPlayer, @Nullable PlatformPlayer platformPlayer, @NotNull String string) { + private @NotNull String replacePlaceholders(@Nullable GrimPlayer grimPlayer, @Nullable PlatformPlayer platformPlayer, @NotNull String string, boolean removeFormatting) { for (Map.Entry entry : GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements().entrySet()) { string = string.replace(entry.getKey(), entry.getValue()); } if (grimPlayer != null) { for (Map.Entry> entry : GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements().entrySet()) { - string = string.replace(entry.getKey(), entry.getValue().apply(grimPlayer).replace('%', PLACEHOLDER_ESCAPE_CHAR)); + String value = entry.getValue().apply(grimPlayer).replace('%', PLACEHOLDER_ESCAPE_CHAR); + if (removeFormatting) value = filterDiscordText(value); + string = string.replace(entry.getKey(), value); } } return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, string).replace(PLACEHOLDER_ESCAPE_CHAR, '%'); } + public static String filterDiscordText(String message) { + if (message == null || message.isBlank()) return message; + final StringBuilder sb = new StringBuilder(message.length()); + for (int i = 0; i < message.length(); ++i) { + final char c = message.charAt(i); + // Escape a newline + if (c == '\n') { + sb.append("\\n"); + } // Escape Markdown special characters + else if (c == '`' || c == '*' || c == '_' || c == '~' || c == '|') { + sb.append('\\').append(c); + } else { + // Escape "# ", "> ", etc + if (c == '#' || c == '>' || c == '-') { + // check if there's a space next + if (((i + 1 < message.length()) && (message.charAt(i + 1) == ' ')) + && ((i == 0) || (message.charAt(i - 1) == '\n'))) { + sb.append("\\").append(c); + } else { + sb.append(c); + } + } else { + sb.append(c); + } + } + } + return sb.toString(); + } + public @NotNull Component replacePlaceholders(@NotNull GrimPlayer player, @NotNull Component component) { // Replacement config that forces any placeholder replacement to be pure text final TextReplacementConfig safeReplacement = TextReplacementConfig.builder() diff --git a/common/src/main/java/ac/grim/grimac/utils/blockplace/BlockPlaceResult.java b/common/src/main/java/ac/grim/grimac/utils/blockplace/BlockPlaceResult.java index 3e8795ef2e..40208f2c2a 100644 --- a/common/src/main/java/ac/grim/grimac/utils/blockplace/BlockPlaceResult.java +++ b/common/src/main/java/ac/grim/grimac/utils/blockplace/BlockPlaceResult.java @@ -648,7 +648,6 @@ public enum BlockPlaceResult { multiFace.setWest(West.TRUE); break; } - continue; } } @@ -1101,9 +1100,7 @@ public enum BlockPlaceResult { }, ItemTypes.COMMAND_BLOCK, ItemTypes.CHAIN_COMMAND_BLOCK, ItemTypes.REPEATING_COMMAND_BLOCK, ItemTypes.JIGSAW, ItemTypes.STRUCTURE_BLOCK), - NO_DATA((player, place) -> { - place.set(place.getMaterial()); - }, ItemTypes.AIR); + NO_DATA((player, place) -> place.set(place.getMaterial()), ItemTypes.AIR); // This should be an array... but a hashmap will do for now... private static final Map lookupMap = new HashMap<>(); diff --git a/common/src/main/java/ac/grim/grimac/utils/collisions/CollisionData.java b/common/src/main/java/ac/grim/grimac/utils/collisions/CollisionData.java index cbf1e40e62..bd7a8a33f4 100644 --- a/common/src/main/java/ac/grim/grimac/utils/collisions/CollisionData.java +++ b/common/src/main/java/ac/grim/grimac/utils/collisions/CollisionData.java @@ -201,23 +201,19 @@ public enum CollisionData implements CollisionFactory { PIGLIN_HEAD(new HexCollisionBox(3.0D, 0.0D, 3.0D, 13.0D, 8.0D, 13.0D), StateTypes.PIGLIN_HEAD), // Overwrite previous SKULL enum for legacy, where head and wall skull isn't separate - WALL_SKULL((player, version, data, x, y, z) -> { - return switch (data.getFacing()) { - case SOUTH -> new SimpleCollisionBox(0.25F, 0.25F, 0.0F, 0.75F, 0.75F, 0.5F, false); - case WEST -> new SimpleCollisionBox(0.5F, 0.25F, 0.25F, 1.0F, 0.75F, 0.75F, false); - case EAST -> new SimpleCollisionBox(0.0F, 0.25F, 0.25F, 0.5F, 0.75F, 0.75F, false); - default -> new SimpleCollisionBox(0.25F, 0.25F, 0.5F, 0.75F, 0.75F, 1.0F, false); - }; + WALL_SKULL((player, version, data, x, y, z) -> switch (data.getFacing()) { + case SOUTH -> new SimpleCollisionBox(0.25F, 0.25F, 0.0F, 0.75F, 0.75F, 0.5F, false); + case WEST -> new SimpleCollisionBox(0.5F, 0.25F, 0.25F, 1.0F, 0.75F, 0.75F, false); + case EAST -> new SimpleCollisionBox(0.0F, 0.25F, 0.25F, 0.5F, 0.75F, 0.75F, false); + default -> new SimpleCollisionBox(0.25F, 0.25F, 0.5F, 0.75F, 0.75F, 1.0F, false); }, StateTypes.CREEPER_WALL_HEAD, StateTypes.DRAGON_WALL_HEAD, StateTypes.PLAYER_WALL_HEAD, StateTypes.ZOMBIE_WALL_HEAD, StateTypes.SKELETON_WALL_SKULL, StateTypes.WITHER_SKELETON_WALL_SKULL), - PIGLIN_WALL_HEAD((player, version, data, x, y, z) -> { - return switch (data.getFacing()) { - case SOUTH -> new HexCollisionBox(3.0D, 4.0D, 0.0D, 13.0D, 12.0D, 8.0D); - case EAST -> new HexCollisionBox(0.0D, 4.0D, 3.0D, 8.0D, 12.0D, 13.0D); - case WEST -> new HexCollisionBox(8.0D, 4.0D, 3.0D, 16.0D, 12.0D, 13.0D); - default -> new HexCollisionBox(3.0D, 4.0D, 8.0D, 13.0D, 12.0D, 16.0D); - }; + PIGLIN_WALL_HEAD((player, version, data, x, y, z) -> switch (data.getFacing()) { + case SOUTH -> new HexCollisionBox(3.0D, 4.0D, 0.0D, 13.0D, 12.0D, 8.0D); + case EAST -> new HexCollisionBox(0.0D, 4.0D, 3.0D, 8.0D, 12.0D, 13.0D); + case WEST -> new HexCollisionBox(8.0D, 4.0D, 3.0D, 16.0D, 12.0D, 13.0D); + default -> new HexCollisionBox(3.0D, 4.0D, 8.0D, 13.0D, 12.0D, 16.0D); }, StateTypes.PIGLIN_WALL_HEAD), DOOR(new DoorHandler(), BlockTags.DOORS.getStates().toArray(new StateType[0])), @@ -273,9 +269,7 @@ public enum CollisionData implements CollisionFactory { return new SimpleCollisionBox(eatenPosition, 0, 0.0625, 1 - 0.0625, height, 1 - 0.0625, false); }, StateTypes.CAKE), - COCOA_BEANS((player, version, data, x, y, z) -> { - return getCocoa(version, data.getAge(), data.getFacing()); - }, StateTypes.COCOA), + COCOA_BEANS((player, version, data, x, y, z) -> getCocoa(version, data.getAge(), data.getFacing()), StateTypes.COCOA), STONE_CUTTER((player, version, data, x, y, z) -> { if (version.isOlderThanOrEquals(ClientVersion.V_1_13_2)) @@ -710,9 +704,7 @@ public enum CollisionData implements CollisionFactory { 0.625, 0.625, 0.625, false), StateTypes.STRUCTURE_VOID), - END_ROD((player, version, data, x, y, z) -> { - return getEndRod(version, data.getFacing()); - }, StateTypes.END_ROD, StateTypes.LIGHTNING_ROD), + END_ROD((player, version, data, x, y, z) -> getEndRod(version, data.getFacing()), StateTypes.END_ROD, StateTypes.LIGHTNING_ROD), CAULDRON((player, version, data, x, y, z) -> { if (version.isNewerThan(ClientVersion.V_1_13_2)) { // changed in 19w13a, 1.14 Snapshot @@ -758,9 +750,7 @@ public enum CollisionData implements CollisionFactory { SOULSAND(new SimpleCollisionBox(0, 0, 0, 1, 0.875, 1, false), StateTypes.SOUL_SAND), - PICKLE((player, version, data, x, y, z) -> { - return getPicklesBox(version, data.getPickles()); - }, StateTypes.SEA_PICKLE), + PICKLE((player, version, data, x, y, z) -> getPicklesBox(version, data.getPickles()), StateTypes.SEA_PICKLE), TURTLEEGG((player, version, data, x, y, z) -> { // ViaVersion replacement block (West facing cocoa beans) @@ -786,28 +776,22 @@ public enum CollisionData implements CollisionFactory { POT(new HexCollisionBox(5.0D, 0.0D, 5.0D, 11.0D, 6.0D, 11.0D), BlockTags.FLOWER_POTS.getStates().toArray(new StateType[0])), - WALL_SIGN((player, version, data, x, y, z) -> { - return switch (data.getFacing()) { - case NORTH -> new HexCollisionBox(0.0D, 4.5D, 14.0D, 16.0D, 12.5D, 16.0D); - case SOUTH -> new HexCollisionBox(0.0D, 4.5D, 0.0D, 16.0D, 12.5D, 2.0D); - case WEST -> new HexCollisionBox(14.0D, 4.5D, 0.0D, 16.0D, 12.5D, 16.0D); - case EAST -> new HexCollisionBox(0.0D, 4.5D, 0.0D, 2.0D, 12.5D, 16.0D); - default -> NoCollisionBox.INSTANCE; - }; + WALL_SIGN((player, version, data, x, y, z) -> switch (data.getFacing()) { + case NORTH -> new HexCollisionBox(0.0D, 4.5D, 14.0D, 16.0D, 12.5D, 16.0D); + case SOUTH -> new HexCollisionBox(0.0D, 4.5D, 0.0D, 16.0D, 12.5D, 2.0D); + case WEST -> new HexCollisionBox(14.0D, 4.5D, 0.0D, 16.0D, 12.5D, 16.0D); + case EAST -> new HexCollisionBox(0.0D, 4.5D, 0.0D, 2.0D, 12.5D, 16.0D); + default -> NoCollisionBox.INSTANCE; }, BlockTags.WALL_SIGNS.getStates().toArray(new StateType[0])), - WALL_FAN((player, version, data, x, y, z) -> { - return switch (data.getFacing()) { - case NORTH -> new HexCollisionBox(0.0D, 4.0D, 5.0D, 16.0D, 12.0D, 16.0D); - case SOUTH -> new HexCollisionBox(0.0D, 4.0D, 0.0D, 16.0D, 12.0D, 11.0D); - case WEST -> new HexCollisionBox(5.0D, 4.0D, 0.0D, 16.0D, 12.0D, 16.0D); - default -> new HexCollisionBox(0.0D, 4.0D, 0.0D, 11.0D, 12.0D, 16.0D); - }; + WALL_FAN((player, version, data, x, y, z) -> switch (data.getFacing()) { + case NORTH -> new HexCollisionBox(0.0D, 4.0D, 5.0D, 16.0D, 12.0D, 16.0D); + case SOUTH -> new HexCollisionBox(0.0D, 4.0D, 0.0D, 16.0D, 12.0D, 11.0D); + case WEST -> new HexCollisionBox(5.0D, 4.0D, 0.0D, 16.0D, 12.0D, 16.0D); + default -> new HexCollisionBox(0.0D, 4.0D, 0.0D, 11.0D, 12.0D, 16.0D); }, BlockTags.WALL_CORALS.getStates().toArray(new StateType[0])), - CORAL_PLANT((player, version, data, x, y, z) -> { - return new HexCollisionBox(2.0D, 0.0D, 2.0D, 14.0D, 15.0D, 14.0D); - }, Stream.concat( + CORAL_PLANT((player, version, data, x, y, z) -> new HexCollisionBox(2.0D, 0.0D, 2.0D, 14.0D, 15.0D, 14.0D), Stream.concat( Arrays.stream(BlockTags.CORAL_PLANTS.getStates().toArray(new StateType[0])), Stream.of(StateTypes.DEAD_HORN_CORAL, StateTypes.DEAD_TUBE_CORAL, StateTypes.DEAD_BRAIN_CORAL, StateTypes.DEAD_BUBBLE_CORAL, StateTypes.DEAD_FIRE_CORAL) @@ -850,27 +834,23 @@ public enum CollisionData implements CollisionFactory { return new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0D, 8.0D, 16.0D); }, StateTypes.TRIPWIRE), - TRIPWIRE_HOOK((player, version, data, x, y, z) -> { - return switch (data.getFacing()) { - case NORTH -> new HexCollisionBox(5.0D, 0.0D, 10.0D, 11.0D, 10.0D, 16.0D); - case SOUTH -> new HexCollisionBox(5.0D, 0.0D, 0.0D, 11.0D, 10.0D, 6.0D); - case WEST -> new HexCollisionBox(10.0D, 0.0D, 5.0D, 16.0D, 10.0D, 11.0D); - default -> new HexCollisionBox(0.0D, 0.0D, 5.0D, 6.0D, 10.0D, 11.0D); - }; + TRIPWIRE_HOOK((player, version, data, x, y, z) -> switch (data.getFacing()) { + case NORTH -> new HexCollisionBox(5.0D, 0.0D, 10.0D, 11.0D, 10.0D, 16.0D); + case SOUTH -> new HexCollisionBox(5.0D, 0.0D, 0.0D, 11.0D, 10.0D, 6.0D); + case WEST -> new HexCollisionBox(10.0D, 0.0D, 5.0D, 16.0D, 10.0D, 11.0D); + default -> new HexCollisionBox(0.0D, 0.0D, 5.0D, 6.0D, 10.0D, 11.0D); }, StateTypes.TRIPWIRE_HOOK), TORCH(new HexCollisionBox(6.0D, 0.0D, 6.0D, 10.0D, 10.0D, 10.0D), StateTypes.TORCH, StateTypes.REDSTONE_TORCH), - WALL_TORCH((player, version, data, x, y, z) -> { - return switch (data.getFacing()) { - case NORTH -> new HexCollisionBox(5.5D, 3.0D, 11.0D, 10.5D, 13.0D, 16.0D); - case SOUTH -> new HexCollisionBox(5.5D, 3.0D, 0.0D, 10.5D, 13.0D, 5.0D); - case WEST -> new HexCollisionBox(11.0D, 3.0D, 5.5D, 16.0D, 13.0D, 10.5D); - case EAST -> new HexCollisionBox(0.0D, 3.0D, 5.5D, 5.0D, 13.0D, 10.5D); - // 1.13 separates wall and normal torches, 1.12 does not - default -> new HexCollisionBox(6.0D, 0.0D, 6.0D, 10.0D, 10.0D, 10.0D); - }; + WALL_TORCH((player, version, data, x, y, z) -> switch (data.getFacing()) { + case NORTH -> new HexCollisionBox(5.5D, 3.0D, 11.0D, 10.5D, 13.0D, 16.0D); + case SOUTH -> new HexCollisionBox(5.5D, 3.0D, 0.0D, 10.5D, 13.0D, 5.0D); + case WEST -> new HexCollisionBox(11.0D, 3.0D, 5.5D, 16.0D, 13.0D, 10.5D); + case EAST -> new HexCollisionBox(0.0D, 3.0D, 5.5D, 5.0D, 13.0D, 10.5D); + // 1.13 separates wall and normal torches, 1.12 does not + default -> new HexCollisionBox(6.0D, 0.0D, 6.0D, 10.0D, 10.0D, 10.0D); }, StateTypes.WALL_TORCH, StateTypes.REDSTONE_WALL_TORCH), // 1.17 blocks @@ -979,27 +959,17 @@ public enum CollisionData implements CollisionFactory { return new HexCollisionBox(6.0D, 0.0D, 0.0D, 10.0D, 16.0D, 16.0D); }, StateTypes.NETHER_PORTAL), - AZALEA((player, version, data, x, y, z) -> { - return new ComplexCollisionBox(2, - new HexCollisionBox(0.0, 8.0, 0.0, 16.0, 16.0, 16.0), - new HexCollisionBox(6.0, 0.0, 6.0, 10.0, 8.0, 10.0)); - }, StateTypes.AZALEA, StateTypes.FLOWERING_AZALEA), + AZALEA((player, version, data, x, y, z) -> new ComplexCollisionBox(2, + new HexCollisionBox(0.0, 8.0, 0.0, 16.0, 16.0, 16.0), + new HexCollisionBox(6.0, 0.0, 6.0, 10.0, 8.0, 10.0)), StateTypes.AZALEA, StateTypes.FLOWERING_AZALEA), - AMETHYST_CLUSTER((player, version, data, x, y, z) -> { - return getAmethystBox(version, data.getFacing(), 7, 3); - }, StateTypes.AMETHYST_CLUSTER), + AMETHYST_CLUSTER((player, version, data, x, y, z) -> getAmethystBox(version, data.getFacing(), 7, 3), StateTypes.AMETHYST_CLUSTER), - SMALL_AMETHYST_BUD((player, version, data, x, y, z) -> { - return getAmethystBox(version, data.getFacing(), 3, 4); - }, StateTypes.SMALL_AMETHYST_BUD), + SMALL_AMETHYST_BUD((player, version, data, x, y, z) -> getAmethystBox(version, data.getFacing(), 3, 4), StateTypes.SMALL_AMETHYST_BUD), - MEDIUM_AMETHYST_BUD((player, version, data, x, y, z) -> { - return getAmethystBox(version, data.getFacing(), 4, 3); - }, StateTypes.MEDIUM_AMETHYST_BUD), + MEDIUM_AMETHYST_BUD((player, version, data, x, y, z) -> getAmethystBox(version, data.getFacing(), 4, 3), StateTypes.MEDIUM_AMETHYST_BUD), - LARGE_AMETHYST_BUD((player, version, data, x, y, z) -> { - return getAmethystBox(version, data.getFacing(), 5, 3); - }, StateTypes.LARGE_AMETHYST_BUD), + LARGE_AMETHYST_BUD((player, version, data, x, y, z) -> getAmethystBox(version, data.getFacing(), 5, 3), StateTypes.LARGE_AMETHYST_BUD), MUD_BLOCK((player, version, data, x, y, z) -> { if (version.isNewerThanOrEquals(ClientVersion.V_1_19)) { diff --git a/common/src/main/java/ac/grim/grimac/utils/collisions/HitboxData.java b/common/src/main/java/ac/grim/grimac/utils/collisions/HitboxData.java index 4dd8d2a9f9..e258ebdfa2 100644 --- a/common/src/main/java/ac/grim/grimac/utils/collisions/HitboxData.java +++ b/common/src/main/java/ac/grim/grimac/utils/collisions/HitboxData.java @@ -37,30 +37,28 @@ // Expansion to the CollisionData class, which is different than regular ray tracing hitboxes public enum HitboxData implements HitBoxFactory { - RAILS((player, item, version, data, isTargetBlock, x, y, z) -> { - return switch (data.getShape()) { - case ASCENDING_NORTH, ASCENDING_SOUTH, ASCENDING_EAST, ASCENDING_WEST -> { - if (version.isOlderThan(ClientVersion.V_1_8)) { - StateType railType = data.getType(); - // Activator rails always appear as flat detector rails in 1.7.10 because of ViaVersion - // Ascending power rails in 1.7 have flat rail hitbox https://bugs.mojang.com/browse/MC-9134 - if (railType == StateTypes.ACTIVATOR_RAIL || (railType == StateTypes.POWERED_RAIL && data.isPowered())) { - yield new SimpleCollisionBox(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F, false); - } - yield new SimpleCollisionBox(0.0F, 0.0F, 0.0F, 1.0F, 0.625F, 1.0F, false); - } else if (version.isOlderThan(ClientVersion.V_1_9)) { - yield new SimpleCollisionBox(0.0F, 0.0F, 0.0F, 1.0F, 0.625F, 1.0F, false); - } else if (version.isNewerThanOrEquals(ClientVersion.V_1_9) && version.isOlderThan(ClientVersion.V_1_10)) { - // https://bugs.mojang.com/browse/MC-89552 sloped rails in 1.9 - it is slightly taller than a regular rail - yield new SimpleCollisionBox(0.0F, 0.0F, 0.0F, 1.0F, 0.1875F, 1.0F, false); - } else if (version.isOlderThan(ClientVersion.V_1_11)) { - // https://bugs.mojang.com/browse/MC-102638 All sloped rails are full blocks in 1.10 - yield new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true); + RAILS((player, item, version, data, isTargetBlock, x, y, z) -> switch (data.getShape()) { + case ASCENDING_NORTH, ASCENDING_SOUTH, ASCENDING_EAST, ASCENDING_WEST -> { + if (version.isOlderThan(ClientVersion.V_1_8)) { + StateType railType = data.getType(); + // Activator rails always appear as flat detector rails in 1.7.10 because of ViaVersion + // Ascending power rails in 1.7 have flat rail hitbox https://bugs.mojang.com/browse/MC-9134 + if (railType == StateTypes.ACTIVATOR_RAIL || (railType == StateTypes.POWERED_RAIL && data.isPowered())) { + yield new SimpleCollisionBox(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F, false); } - yield new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0D, 8.0D, 16.0D); + yield new SimpleCollisionBox(0.0F, 0.0F, 0.0F, 1.0F, 0.625F, 1.0F, false); + } else if (version.isOlderThan(ClientVersion.V_1_9)) { + yield new SimpleCollisionBox(0.0F, 0.0F, 0.0F, 1.0F, 0.625F, 1.0F, false); + } else if (version.isNewerThanOrEquals(ClientVersion.V_1_9) && version.isOlderThan(ClientVersion.V_1_10)) { + // https://bugs.mojang.com/browse/MC-89552 sloped rails in 1.9 - it is slightly taller than a regular rail + yield new SimpleCollisionBox(0.0F, 0.0F, 0.0F, 1.0F, 0.1875F, 1.0F, false); + } else if (version.isOlderThan(ClientVersion.V_1_11)) { + // https://bugs.mojang.com/browse/MC-102638 All sloped rails are full blocks in 1.10 + yield new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true); } - default -> new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0D, 2.0D, 16.0D); - }; + yield new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0D, 8.0D, 16.0D); + } + default -> new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0D, 2.0D, 16.0D); }, BlockTags.RAILS.getStates().toArray(new StateType[0])), END_PORTAL((player, item, version, data, isTargetBlock, x, y, z) -> { @@ -222,25 +220,21 @@ public enum HitboxData implements HitBoxFactory { WALL(new DynamicHitboxWall(), BlockTags.WALLS.getStates().toArray(new StateType[0])), - WALL_SIGN((player, item, version, data, isTargetBlock, x, y, z) -> { - return switch (data.getFacing()) { - case NORTH -> new HexCollisionBox(0.0, 4.5, 14.0, 16.0, 12.5, 16.0); - case SOUTH -> new HexCollisionBox(0.0, 4.5, 0.0, 16.0, 12.5, 2.0); - case EAST -> new HexCollisionBox(0.0, 4.5, 0.0, 2.0, 12.5, 16.0); - case WEST -> new HexCollisionBox(14.0, 4.5, 0.0, 16.0, 12.5, 16.0); - default -> NoCollisionBox.INSTANCE; - }; + WALL_SIGN((player, item, version, data, isTargetBlock, x, y, z) -> switch (data.getFacing()) { + case NORTH -> new HexCollisionBox(0.0, 4.5, 14.0, 16.0, 12.5, 16.0); + case SOUTH -> new HexCollisionBox(0.0, 4.5, 0.0, 16.0, 12.5, 2.0); + case EAST -> new HexCollisionBox(0.0, 4.5, 0.0, 2.0, 12.5, 16.0); + case WEST -> new HexCollisionBox(14.0, 4.5, 0.0, 16.0, 12.5, 16.0); + default -> NoCollisionBox.INSTANCE; }, BlockTags.WALL_SIGNS.getStates().toArray(new StateType[0])), - WALL_HANGING_SIGN((player, item, version, data, isTargetBlock, x, y, z) -> { - return switch (data.getFacing()) { - case NORTH, SOUTH -> new ComplexCollisionBox(2, - new HexCollisionBox(0.0D, 14.0D, 6.0D, 16.0D, 16.0D, 10.0D), - new HexCollisionBox(1.0D, 0.0D, 7.0D, 15.0D, 10.0D, 9.0D)); - default -> new ComplexCollisionBox(2, - new HexCollisionBox(6.0D, 14.0D, 0.0D, 10.0D, 16.0D, 16.0D), - new HexCollisionBox(7.0D, 0.0D, 1.0D, 9.0D, 10.0D, 15.0D)); - }; + WALL_HANGING_SIGN((player, item, version, data, isTargetBlock, x, y, z) -> switch (data.getFacing()) { + case NORTH, SOUTH -> new ComplexCollisionBox(2, + new HexCollisionBox(0.0D, 14.0D, 6.0D, 16.0D, 16.0D, 10.0D), + new HexCollisionBox(1.0D, 0.0D, 7.0D, 15.0D, 10.0D, 9.0D)); + default -> new ComplexCollisionBox(2, + new HexCollisionBox(6.0D, 14.0D, 0.0D, 10.0D, 16.0D, 16.0D), + new HexCollisionBox(7.0D, 0.0D, 1.0D, 9.0D, 10.0D, 15.0D)); }, BlockTags.WALL_HANGING_SIGNS.getStates().toArray(new StateType[0])), STANDING_SIGN((player, item, version, data, isTargetBlock, x, y, z) -> @@ -326,9 +320,7 @@ public enum HitboxData implements HitBoxFactory { return new HexCollisionBox(1.0D, 0.0D, 1.0D, 15.0D, 16.0D, 15.0D); }, StateTypes.CACTUS), - SNOW((player, item, version, data, isTargetBlock, x, y, z) -> { - return new SimpleCollisionBox(0, 0, 0, 1, data.getLayers() * 0.125, 1); - }, StateTypes.SNOW), + SNOW((player, item, version, data, isTargetBlock, x, y, z) -> new SimpleCollisionBox(0, 0, 0, 1, data.getLayers() * 0.125, 1), StateTypes.SNOW), LECTERN_BLOCK((player, item, version, data, isTargetBlock, x, y, z) -> { ComplexCollisionBox common = new ComplexCollisionBox(5, @@ -408,17 +400,14 @@ public enum HitboxData implements HitBoxFactory { } }, StateTypes.PITCHER_CROP), - WHEAT_BEETROOTS((player, item, version, data, isTargetBlock, x, y, z) -> { - return new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0D, (data.getAge() + 1) * 2, 16.0D); - }, StateTypes.WHEAT, StateTypes.BEETROOTS), + WHEAT_BEETROOTS((player, item, version, data, isTargetBlock, x, y, z) -> + new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0D, (data.getAge() + 1) * 2, 16.0D), StateTypes.WHEAT, StateTypes.BEETROOTS), - CARROT_POTATOES((player, item, version, data, isTargetBlock, x, y, z) -> { - return new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0D, data.getAge() + 2, 16.0D); - }, StateTypes.CARROTS, StateTypes.POTATOES), + CARROT_POTATOES((player, item, version, data, isTargetBlock, x, y, z) -> + new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0D, data.getAge() + 2, 16.0D), StateTypes.CARROTS, StateTypes.POTATOES), - NETHER_WART((player, item, version, data, isTargetBlock, x, y, z) -> { - return new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0, 5 + (data.getAge() * 3), 16.0D); - }, StateTypes.NETHER_WART), + NETHER_WART((player, item, version, data, isTargetBlock, x, y, z) -> + new HexCollisionBox(0.0D, 0.0D, 0.0D, 16.0, 5 + (data.getAge() * 3), 16.0D), StateTypes.NETHER_WART), ATTACHED_PUMPKIN_STEM((player, item, version, data, isTargetBlock, x, y, z) -> { if (version.isOlderThan(ClientVersion.V_1_13)) @@ -432,15 +421,12 @@ public enum HitboxData implements HitBoxFactory { }; }, StateTypes.ATTACHED_MELON_STEM, StateTypes.ATTACHED_PUMPKIN_STEM), - PUMPKIN_STEM((player, item, version, data, isTargetBlock, x, y, z) -> { - return new HexCollisionBox(7, 0, 7, 9, 2 * (data.getAge() + 1), 9); - }, StateTypes.PUMPKIN_STEM, StateTypes.MELON_STEM), + PUMPKIN_STEM((player, item, version, data, isTargetBlock, x, y, z) -> + new HexCollisionBox(7, 0, 7, 9, 2 * (data.getAge() + 1), 9), StateTypes.PUMPKIN_STEM, StateTypes.MELON_STEM), // Hitbox/Outline is Same as Collision - COCOA_BEANS((player, item, version, data, isTargetBlock, x, y, z) -> { - return CollisionData.getCocoa(version, data.getAge(), data.getFacing()); - }, StateTypes.COCOA), - + COCOA_BEANS((player, item, version, data, isTargetBlock, x, y, z) -> + CollisionData.getCocoa(version, data.getAge(), data.getFacing()), StateTypes.COCOA), // Easier to just use no collision box // Redstone wire is very complex with its collision shapes and has many de-syncs @@ -516,9 +502,8 @@ public enum HitboxData implements HitBoxFactory { ? new HexOffsetCollisionBox(data.getType(), 3.0, 0.0, 3.0, 13.0, 16.0, 13.0) : new HexOffsetCollisionBox(data.getType(), 5.0, 0.0, 5.0, 11.0, 16.0, 11.0), StateTypes.BAMBOO), - BAMBOO_SAPLING((player, item, version, data, isTargetBlock, x, y, z) -> { - return new HexOffsetCollisionBox(data.getType(), 4.0D, 0.0D, 4.0D, 12.0D, 12.0D, 12.0D); - }, StateTypes.BAMBOO_SAPLING), + BAMBOO_SAPLING((player, item, version, data, isTargetBlock, x, y, z) -> + new HexOffsetCollisionBox(data.getType(), 4.0D, 0.0D, 4.0D, 12.0D, 12.0D, 12.0D), StateTypes.BAMBOO_SAPLING), SCAFFOLDING((player, item, version, data, isTargetBlock, x, y, z) -> { // If is holding scaffolding or Via replacement - hay bale diff --git a/common/src/main/java/ac/grim/grimac/utils/collisions/datatypes/DynamicCollisionBox.java b/common/src/main/java/ac/grim/grimac/utils/collisions/datatypes/DynamicCollisionBox.java index bb55227610..c87f7848ab 100644 --- a/common/src/main/java/ac/grim/grimac/utils/collisions/datatypes/DynamicCollisionBox.java +++ b/common/src/main/java/ac/grim/grimac/utils/collisions/datatypes/DynamicCollisionBox.java @@ -3,6 +3,7 @@ import ac.grim.grimac.player.GrimPlayer; import com.github.retrooper.packetevents.protocol.player.ClientVersion; import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; +import lombok.Setter; import java.util.List; @@ -10,7 +11,9 @@ public class DynamicCollisionBox implements CollisionBox { private final GrimPlayer player; private final CollisionFactory box; + @Setter private ClientVersion version; + @Setter private WrappedBlockState block; private int x, y, z; @@ -71,12 +74,4 @@ public boolean isNull() { public boolean isFullBlock() { return box.fetch(player, version, block, x, y, z).offset(x, y, z).isFullBlock(); } - - public void setBlock(WrappedBlockState block) { - this.block = block; - } - - public void setVersion(ClientVersion version) { - this.version = version; - } } diff --git a/common/src/main/java/ac/grim/grimac/utils/collisions/datatypes/SimpleCollisionBox.java b/common/src/main/java/ac/grim/grimac/utils/collisions/datatypes/SimpleCollisionBox.java index 7a2f78bbff..1988b34170 100644 --- a/common/src/main/java/ac/grim/grimac/utils/collisions/datatypes/SimpleCollisionBox.java +++ b/common/src/main/java/ac/grim/grimac/utils/collisions/datatypes/SimpleCollisionBox.java @@ -1,7 +1,6 @@ package ac.grim.grimac.utils.collisions.datatypes; import ac.grim.grimac.utils.math.GrimMath; -import ac.grim.grimac.utils.nmsutil.Ray; import ac.grim.grimac.utils.math.Location; import ac.grim.grimac.utils.math.Vector3dm; import ac.grim.grimac.utils.nmsutil.Ray; diff --git a/common/src/main/java/ac/grim/grimac/utils/data/TrackedPosition.java b/common/src/main/java/ac/grim/grimac/utils/data/TrackedPosition.java index bf92f795df..8f4adf85d9 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/TrackedPosition.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/TrackedPosition.java @@ -1,13 +1,17 @@ package ac.grim.grimac.utils.data; import com.github.retrooper.packetevents.util.Vector3d; +import lombok.Getter; +import lombok.Setter; +@Getter public final class TrackedPosition { private static final double MODERN_COORDINATE_SCALE = 4096.0; private static final double LEGACY_COORDINATE_SCALE = 32.0; private final double scale; + @Setter private Vector3d pos = new Vector3d(); public TrackedPosition() { @@ -23,10 +27,6 @@ public static double packLegacy(double value, double scale) { return Math.floor(value * scale); } - public double getScale() { - return scale; - } - private double unpack(long value) { return (double) value / scale; } @@ -35,14 +35,6 @@ private double unpackLegacy(double value) { return value / scale; } - public Vector3d getPos() { - return pos; - } - - public void setPos(Vector3d pos) { - this.pos = pos; - } - // Method since 1.16. public Vector3d withDelta(long x, long y, long z) { if (x == 0L && y == 0L && z == 0L) { diff --git a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java index 85e86d261c..4e8a0ed06c 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/PacketEntity.java @@ -47,6 +47,7 @@ public class PacketEntity extends TypedPacketEntity { // TODO in what cases is UUID null in 1.9+? @Getter private final UUID uuid; // NULL ON VERSIONS BELOW 1.9 (or for some entities, apparently??) + @Getter public PacketEntity riding; public List passengers = new ArrayList<>(0); public boolean isDead = false; diff --git a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/TypedPacketEntity.java b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/TypedPacketEntity.java index 31f0c05c5b..8f17e9e48e 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/TypedPacketEntity.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/TypedPacketEntity.java @@ -2,15 +2,16 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; +import lombok.Getter; +@Getter public abstract class TypedPacketEntity { - private final EntityType type; - private final boolean isLiving, isMinecart, isHorse, isAgeable, isAnimal, isBoat; + private final boolean isLivingEntity, isMinecart, isHorse, isAgeable, isAnimal, isBoat; public TypedPacketEntity(EntityType type) { this.type = type; - this.isLiving = EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY); + this.isLivingEntity = EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY); this.isMinecart = EntityTypes.isTypeInstanceOf(type, EntityTypes.MINECART_ABSTRACT); this.isHorse = EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_HORSE); // isAgeable really means "is there a baby version of this mob" and is no longer the term used in modern Minecraft @@ -23,40 +24,12 @@ public TypedPacketEntity(EntityType type) { this.isBoat = EntityTypes.isTypeInstanceOf(type, EntityTypes.BOAT); } - public boolean isLivingEntity() { - return isLiving; - } - - public boolean isMinecart() { - return isMinecart; - } - - public boolean isHorse() { - return isHorse; - } - - public boolean isAgeable() { - return isAgeable; - } - - public boolean isAnimal() { - return isAnimal; - } - - public boolean isBoat() { - return isBoat; - } - public boolean isPushable() { // Players can only push living entities // Minecarts and boats are the only non-living that can push // Bats, parrots, and armor stands cannot if (type == EntityTypes.ARMOR_STAND || type == EntityTypes.BAT || type == EntityTypes.PARROT) return false; - return isLiving || isBoat || isMinecart; - } - - public EntityType getType() { - return type; + return isLivingEntity || isBoat || isMinecart; } } diff --git a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/dragon/PacketEntityEnderDragon.java b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/dragon/PacketEntityEnderDragon.java index 81d10eee2a..b144b79db3 100644 --- a/common/src/main/java/ac/grim/grimac/utils/data/packetentity/dragon/PacketEntityEnderDragon.java +++ b/common/src/main/java/ac/grim/grimac/utils/data/packetentity/dragon/PacketEntityEnderDragon.java @@ -4,11 +4,13 @@ import ac.grim.grimac.utils.data.packetentity.PacketEntity; import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.Getter; import java.util.ArrayList; import java.util.List; import java.util.UUID; +@Getter public final class PacketEntityEnderDragon extends PacketEntity { private final List parts = new ArrayList<>(); @@ -29,7 +31,4 @@ public PacketEntityEnderDragon(GrimPlayer player, UUID uuid, int entityID, doubl } } - public List getParts() { - return parts; - } } diff --git a/common/src/main/java/ac/grim/grimac/utils/inventory/Inventory.java b/common/src/main/java/ac/grim/grimac/utils/inventory/Inventory.java index f24b44e20b..e1084f2d4b 100644 --- a/common/src/main/java/ac/grim/grimac/utils/inventory/Inventory.java +++ b/common/src/main/java/ac/grim/grimac/utils/inventory/Inventory.java @@ -142,22 +142,17 @@ private int addResource(int slot, ItemStack stack) { inventoryStorage.setItem(slot, itemstack); } - int j = i; - if (i > itemstack.getMaxStackSize() - itemstack.getAmount()) { - j = itemstack.getMaxStackSize() - itemstack.getAmount(); - } + int j = Math.min(i, itemstack.getMaxStackSize() - itemstack.getAmount()); if (j > this.getMaxStackSize() - itemstack.getAmount()) { j = this.getMaxStackSize() - itemstack.getAmount(); } - if (j == 0) { - return i; - } else { - i = i - j; + if (j != 0) { + i -= j; itemstack.grow(j); - return i; } + return i; } public boolean add(int p_36041_, ItemStack p_36042_) { diff --git a/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryStorage.java b/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryStorage.java index c4fcf79e43..5c63493a98 100644 --- a/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryStorage.java +++ b/common/src/main/java/ac/grim/grimac/utils/inventory/InventoryStorage.java @@ -1,9 +1,11 @@ package ac.grim.grimac.utils.inventory; import com.github.retrooper.packetevents.protocol.item.ItemStack; +import lombok.Getter; public class InventoryStorage { protected ItemStack[] items; + @Getter int size; public InventoryStorage(int size) { @@ -15,10 +17,6 @@ public InventoryStorage(int size) { } } - public int getSize() { - return size; - } - public void setItem(int item, ItemStack stack) { items[item] = stack == null ? ItemStack.EMPTY : stack; } diff --git a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedEntities.java b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedEntities.java index c4c57d8abf..cb7757aa52 100644 --- a/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedEntities.java +++ b/common/src/main/java/ac/grim/grimac/utils/latency/CompensatedEntities.java @@ -515,7 +515,7 @@ public void updateEntityMetadata(int entityID, List> watchableObje isElderlyBitMask = 0x04; } - EntityData guardianByte = WatchableIndexUtil.getIndex(watchableObjects, index); + EntityData guardianByte = WatchableIndexUtil.getIndex(watchableObjects, index); if (guardianByte != null) { int info = (Integer) guardianByte.getValue(); // wiki says this is a byte but testing on 1.8 shows its an integer ((PacketEntityGuardian) entity).isElder = (info & isElderlyBitMask) != 0; diff --git a/common/src/main/java/ac/grim/grimac/utils/lists/CorrectingPlayerInventoryStorage.java b/common/src/main/java/ac/grim/grimac/utils/lists/CorrectingPlayerInventoryStorage.java index c21425cf22..4e50ee416f 100644 --- a/common/src/main/java/ac/grim/grimac/utils/lists/CorrectingPlayerInventoryStorage.java +++ b/common/src/main/java/ac/grim/grimac/utils/lists/CorrectingPlayerInventoryStorage.java @@ -105,9 +105,8 @@ private void checkThatBukkitIsSynced(int slot) { ItemStack toPE = player.platformPlayer.getInventory().getStack(bukkitSlot, slot); if (existing.getType() != toPE.getType() || existing.getAmount() != toPE.getAmount()) { - GrimAPI.INSTANCE.getScheduler().getEntityScheduler().execute(player.platformPlayer, GrimAPI.INSTANCE.getGrimPlugin(), () -> { - player.platformPlayer.updateInventory(); - }, null, 0); + GrimAPI.INSTANCE.getScheduler().getEntityScheduler().execute(player.platformPlayer, GrimAPI.INSTANCE.getGrimPlugin(), + () -> player.platformPlayer.updateInventory(), null, 0); setItem(slot, toPE); } } diff --git a/common/src/main/java/ac/grim/grimac/utils/lists/HookedListWrapper.java b/common/src/main/java/ac/grim/grimac/utils/lists/HookedListWrapper.java index 6ceb0c60ea..20c770c9bb 100644 --- a/common/src/main/java/ac/grim/grimac/utils/lists/HookedListWrapper.java +++ b/common/src/main/java/ac/grim/grimac/utils/lists/HookedListWrapper.java @@ -1,5 +1,7 @@ package ac.grim.grimac.utils.lists; +import org.jetbrains.annotations.NotNull; + import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -127,7 +129,7 @@ public boolean containsAll(Collection c) { } @Override - public Object[] toArray(Object[] a) { + public Object @NotNull [] toArray(Object[] a) { return this.base.toArray(a); } } diff --git a/common/src/main/java/ac/grim/grimac/utils/math/Location.java b/common/src/main/java/ac/grim/grimac/utils/math/Location.java index 0ee43c1ff9..8f2e1ea4f9 100644 --- a/common/src/main/java/ac/grim/grimac/utils/math/Location.java +++ b/common/src/main/java/ac/grim/grimac/utils/math/Location.java @@ -159,29 +159,16 @@ public double distanceSquared(@NotNull Location o) { } public boolean equals(Object obj) { - if (obj == null) { - return false; - } else if (this.getClass() != obj.getClass()) { + if (obj == null || this.getClass() != obj.getClass()) { return false; } else { Location other = (Location) obj; - PlatformWorld world = this.world == null ? null : this.world.get(); - PlatformWorld otherWorld = other.world == null ? null : other.world.get(); - if (Objects.equals(world, otherWorld)) { - if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) { - return false; - } else if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) { - return false; - } else if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z)) { - return false; - } else if (Float.floatToIntBits(this.pitch) != Float.floatToIntBits(other.pitch)) { - return false; - } else { - return Float.floatToIntBits(this.yaw) == Float.floatToIntBits(other.yaw); - } - } else { - return false; - } + return Objects.equals(this.world == null ? null : this.world.get(), other.world == null ? null : other.world.get()) + && Double.doubleToLongBits(this.x) == Double.doubleToLongBits(other.x) + && Double.doubleToLongBits(this.y) == Double.doubleToLongBits(other.y) + && Double.doubleToLongBits(this.z) == Double.doubleToLongBits(other.z) + && Float.floatToIntBits(this.pitch) == Float.floatToIntBits(other.pitch) + && Float.floatToIntBits(this.yaw) == Float.floatToIntBits(other.yaw); } } @@ -198,9 +185,7 @@ public int hashCode() { } public String toString() { - PlatformWorld world = this.world == null ? null : this.world.get(); - String var10000 = String.valueOf(world); - return "Location{world=" + var10000 + ",x=" + this.x + ",y=" + this.y + ",z=" + this.z + ",pitch=" + this.pitch + ",yaw=" + this.yaw + "}"; + return "Location{world=" + (this.world == null ? null : this.world.get()) + ",x=" + this.x + ",y=" + this.y + ",z=" + this.z + ",pitch=" + this.pitch + ",yaw=" + this.yaw + "}"; } public @NotNull Location clone() { diff --git a/common/src/main/java/ac/grim/grimac/utils/nmsutil/Ray.java b/common/src/main/java/ac/grim/grimac/utils/nmsutil/Ray.java index 9a5c4b6375..cff8b5009b 100644 --- a/common/src/main/java/ac/grim/grimac/utils/nmsutil/Ray.java +++ b/common/src/main/java/ac/grim/grimac/utils/nmsutil/Ray.java @@ -1,6 +1,7 @@ package ac.grim.grimac.utils.nmsutil; import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.anticheat.LogUtil; import ac.grim.grimac.utils.data.Pair; import ac.grim.grimac.utils.math.Vector3dm; import lombok.Getter; @@ -43,7 +44,7 @@ public Ray clone() { clone.direction = this.direction.clone(); return clone; } catch (CloneNotSupportedException e) { - e.printStackTrace(); + LogUtil.error("Failed to clone ray", e); } return null; } diff --git a/common/src/main/java/ac/grim/grimac/utils/reflection/GeyserUtil.java b/common/src/main/java/ac/grim/grimac/utils/reflection/GeyserUtil.java index e3a765ee46..71695f6f20 100644 --- a/common/src/main/java/ac/grim/grimac/utils/reflection/GeyserUtil.java +++ b/common/src/main/java/ac/grim/grimac/utils/reflection/GeyserUtil.java @@ -1,5 +1,6 @@ package ac.grim.grimac.utils.reflection; +import ac.grim.grimac.utils.anticheat.LogUtil; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.util.reflection.Reflection; @@ -33,7 +34,7 @@ public static boolean isGeyserPlayer(UUID uuid) { ClassLoader classLoader = PacketEvents.getAPI().getPlugin().getClass().getClassLoader(); GEYSER_API_CLASS = classLoader.loadClass("org.geysermc.api.GeyserApiBase"); } catch (ClassNotFoundException e) { - e.printStackTrace(); + LogUtil.error("Failed to load GeyserApiBase class", e); } } if (GEYSER_API_METHOD == null) { @@ -46,7 +47,7 @@ public static boolean isGeyserPlayer(UUID uuid) { try { apiInstance = GEYSER_API_METHOD.invoke(null); } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); + LogUtil.error("Failed to invoke GeyserApiBase.api()", e); } Object connection = null; try { @@ -54,7 +55,7 @@ public static boolean isGeyserPlayer(UUID uuid) { connection = CONNECTION_BY_UUID_METHOD.invoke(apiInstance, uuid); } } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); + LogUtil.error("Failed to invoke GeyserApiBase.connectionByUuid()", e); } return connection != null; } diff --git a/common/src/main/resources/config/de.yml b/common/src/main/resources/config/de.yml index 287a8af156..2bb819931f 100644 --- a/common/src/main/resources/config/de.yml +++ b/common/src/main/resources/config/de.yml @@ -127,6 +127,9 @@ TimerA: # Millisekunden, die der Spieler akkumulieren kann, um sie später zu nutzen, wenn er zurückfällt. # Könnte möglicherweise 1,8 schnelle Nutzung/schnelle Heilung/schnelle Bogenumgehungen ermöglichen, wenn zu hoch eingestellt, 120 ms scheint eine gute Balance zu sein drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # Ping at which the check will start to limit timer balance, to prevent abuse. # Can cause some setbacks for legitimate players but only if they are over this ping threshold. # -1 to disable diff --git a/common/src/main/resources/config/en.yml b/common/src/main/resources/config/en.yml index c7fa811390..3264487bee 100644 --- a/common/src/main/resources/config/en.yml +++ b/common/src/main/resources/config/en.yml @@ -127,6 +127,9 @@ TimerA: # Milliseconds that the player can accumulate for later use when they fall behind # Could potentially allow 1.8 fast use/fast heal/fast bow bypasses if set too high, 120 ms seems like a good balance drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # Ping at which the check will start to limit timer balance, to prevent abuse. # Can cause some setbacks for legitimate players but only if they are over this ping threshold. # -1 to disable diff --git a/common/src/main/resources/config/es.yml b/common/src/main/resources/config/es.yml index 5442139c00..8a22202a4b 100644 --- a/common/src/main/resources/config/es.yml +++ b/common/src/main/resources/config/es.yml @@ -132,6 +132,9 @@ TimerA: # Podría llegar a permitir pasos por alto de fast use/fast heal/fast bow en 1.8 si esta muy alto, 120 ms # parece ser un balance bastante decente. drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # Ping at which the check will start to limit timer balance, to prevent abuse. # Can cause some setbacks for legitimate players but only if they are over this ping threshold. # -1 to disable diff --git a/common/src/main/resources/config/fr.yml b/common/src/main/resources/config/fr.yml index 5c4251bc24..cbe174ab68 100644 --- a/common/src/main/resources/config/fr.yml +++ b/common/src/main/resources/config/fr.yml @@ -128,6 +128,9 @@ TimerA: # Le nombre de millisecondes que le joueur peut accumuler pour une utilisation ultérieure lorsqu'il prend du retard. # Si la valeur est trop élevée, cela pourrait potentiellement permettre de contourner les mécaniques 1.8, comme l'utilisation rapide, la guérison rapide et le tir à l'arc rapide. Une valeur de 120 ms semble être un bon équilibre. drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # Ping at which the check will start to limit timer balance, to prevent abuse. # Can cause some setbacks for legitimate players but only if they are over this ping threshold. # -1 to disable diff --git a/common/src/main/resources/config/it.yml b/common/src/main/resources/config/it.yml index 636a2a4282..336bc75a53 100644 --- a/common/src/main/resources/config/it.yml +++ b/common/src/main/resources/config/it.yml @@ -119,7 +119,12 @@ TimerA: setbackvl: 10 # Millisecondi accumulabili per il timer drift: 120 - # Soglia per l'uso scorretto del timer in base al ping + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: + # Ping at which the check will start to limit timer balance, to prevent abuse. + # Can cause some setbacks for legitimate players but only if they are over this ping threshold. + # -1 to disable ping-abuse-limit-threshold: 1000 NegativeTimer: diff --git a/common/src/main/resources/config/ja.yml b/common/src/main/resources/config/ja.yml index 58b9b3c2c4..99c2999809 100644 --- a/common/src/main/resources/config/ja.yml +++ b/common/src/main/resources/config/ja.yml @@ -131,6 +131,9 @@ TimerA: # この値を高く設定しすぎると、1.8版の高速使用、高速回復、高速弓などの不正な動作をバイパスできる可能性があるため、 # 120ミリ秒がバランスの良い設定とされています。 drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # タイマーバランスの制限を開始し、不正利用を防止するためのPingのしきい値 # 正当なプレイヤーであっても、このPingのしきい値を超えるとセットバックが発生する場合があります # -1で無効化 diff --git a/common/src/main/resources/config/nl.yml b/common/src/main/resources/config/nl.yml index 7fcec87bd3..b8ac021b07 100644 --- a/common/src/main/resources/config/nl.yml +++ b/common/src/main/resources/config/nl.yml @@ -127,6 +127,9 @@ TimerA: # Milliseconden die de speler kan verzamelen om later te gebruiken als hij achterop raakt # Kan mogelijk 1.8 snel gebruik/snelle genezing/snelle bron omleidingen toestaan als het te hoog is ingesteld, 120 ms lijkt een goede balans drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # Ping waarop de controle zal beginnen om de timer-balans te beperken, om misbruik te voorkomen # Kan wat tegenslag veroorzaken voor legitieme spelers, maar alleen als ze boven deze ping drempel zitten. # -1 om uit te schakelen diff --git a/common/src/main/resources/config/pt.yml b/common/src/main/resources/config/pt.yml index 192e2738b1..0dfdac895d 100644 --- a/common/src/main/resources/config/pt.yml +++ b/common/src/main/resources/config/pt.yml @@ -130,6 +130,9 @@ TimerA: # Pode ignorar o uso de fast use / fast heal / fast bow na 1.8 caso o valor seja definido muito alto. # 120ms parece um bom balanceamento. drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # Ping no qual a verificação vai começar a limitar o balançeamento do timer para previnir abuso. # Pode causar recuos a jogadores legítmos, mas somente se estiverem acima desse ping. # -1 desabilitará. diff --git a/common/src/main/resources/config/ru.yml b/common/src/main/resources/config/ru.yml index 4e8a3da11a..7077d24e68 100644 --- a/common/src/main/resources/config/ru.yml +++ b/common/src/main/resources/config/ru.yml @@ -128,6 +128,9 @@ TimerA: # Миллисекунды, которые игрок может накапливать для последующего использования, когда он отстает. # Потенциально может позволить 1.8 обходов: быстрое использование/быстрое исцеление/быстрый лук, если установлено слишком высокое значение, 120 мс кажется хорошим балансом drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # Ping at which the check will start to limit timer balance, to prevent abuse. # Can cause some setbacks for legitimate players but only if they are over this ping threshold. # -1 to disable diff --git a/common/src/main/resources/config/tr.yml b/common/src/main/resources/config/tr.yml index 3cc9143abe..32e21a494e 100644 --- a/common/src/main/resources/config/tr.yml +++ b/common/src/main/resources/config/tr.yml @@ -127,6 +127,9 @@ TimerA: # Oyuncunun geri kaldığında biriktirebileceği milisaniye miktarı # Çok yüksek ayarlandığında 1.8 hızlı kullanma/hızlı iyileşme/hızlı yay atlama durumlarına izin verebilir, 120 ms iyi bir denge gibi görünüyor drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # Kontrolün zamanlayıcı dengesini sınırlamaya başlayacağı ping değeri, kötüye kullanımı önlemek için. # Bu, yalnızca bu ping eşiğini aşan meşru oyuncular için bazı geri dönüşlere neden olabilir. # Devre dışı bırakmak için -1 yazın diff --git a/common/src/main/resources/config/zh.yml b/common/src/main/resources/config/zh.yml index 6afa45eeeb..49c046e5fb 100644 --- a/common/src/main/resources/config/zh.yml +++ b/common/src/main/resources/config/zh.yml @@ -132,6 +132,9 @@ TimerA: # 玩家卡顿时可以累积以供以后使用的毫秒数 # 如果设置得太高,可能会允许 1.8 快速使用/快速治疗/快速弓箭绕过,120 毫秒似乎是一个很好的平衡 drift: 120 + +# This check limits abuse of the TimerA balance by preventing the player's movement falling too far behind realtime +TimerLimit: # 在检查玩家的延迟时对timer balance进行限制, 防止滥用 # 在合法玩家的延迟超过这个延迟阈值时可能会造成误拉回 # 填写-1则关闭 diff --git a/common/src/main/resources/punishments/de.yml b/common/src/main/resources/punishments/de.yml index c63cf4e042..75677f54c4 100644 --- a/common/src/main/resources/punishments/de.yml +++ b/common/src/main/resources/punishments/de.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # Schwellenwert:Intervall Befehl # diff --git a/common/src/main/resources/punishments/en.yml b/common/src/main/resources/punishments/en.yml index 3a9c3b7ff1..7b56d7be4a 100644 --- a/common/src/main/resources/punishments/en.yml +++ b/common/src/main/resources/punishments/en.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # Threshold:Interval Command # diff --git a/common/src/main/resources/punishments/es.yml b/common/src/main/resources/punishments/es.yml index ce609be7d8..a12f8c14ad 100644 --- a/common/src/main/resources/punishments/es.yml +++ b/common/src/main/resources/punishments/es.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # Límite:Intervalo Comando # diff --git a/common/src/main/resources/punishments/fr.yml b/common/src/main/resources/punishments/fr.yml index d719aeaf46..c9f4030cf7 100644 --- a/common/src/main/resources/punishments/fr.yml +++ b/common/src/main/resources/punishments/fr.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # Limite : Intervale entre chaque Commande # diff --git a/common/src/main/resources/punishments/it.yml b/common/src/main/resources/punishments/it.yml index 51ad464015..2bdeb57d39 100644 --- a/common/src/main/resources/punishments/it.yml +++ b/common/src/main/resources/punishments/it.yml @@ -13,6 +13,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" commands: - "100:40 [alert]" diff --git a/common/src/main/resources/punishments/ja.yml b/common/src/main/resources/punishments/ja.yml index 362f1c2a0b..aa0a280128 100644 --- a/common/src/main/resources/punishments/ja.yml +++ b/common/src/main/resources/punishments/ja.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # しきい値:間隔 コマンド # diff --git a/common/src/main/resources/punishments/nl.yml b/common/src/main/resources/punishments/nl.yml index 99e5aad171..dc03cc70c4 100644 --- a/common/src/main/resources/punishments/nl.yml +++ b/common/src/main/resources/punishments/nl.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # Drempel: Interval-commando # diff --git a/common/src/main/resources/punishments/pt.yml b/common/src/main/resources/punishments/pt.yml index 887bac4389..5c13da85a0 100644 --- a/common/src/main/resources/punishments/pt.yml +++ b/common/src/main/resources/punishments/pt.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # Limiar: Intervalo entre comandos # diff --git a/common/src/main/resources/punishments/ru.yml b/common/src/main/resources/punishments/ru.yml index 459d3c66ce..de30538e3a 100644 --- a/common/src/main/resources/punishments/ru.yml +++ b/common/src/main/resources/punishments/ru.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # Порог:Интервальная команда # diff --git a/common/src/main/resources/punishments/tr.yml b/common/src/main/resources/punishments/tr.yml index e04443b91d..65d86c78d8 100644 --- a/common/src/main/resources/punishments/tr.yml +++ b/common/src/main/resources/punishments/tr.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # Eşik:Aralık Komutu # diff --git a/common/src/main/resources/punishments/zh.yml b/common/src/main/resources/punishments/zh.yml index c362690607..2e4e6c9f8f 100644 --- a/common/src/main/resources/punishments/zh.yml +++ b/common/src/main/resources/punishments/zh.yml @@ -17,6 +17,7 @@ Punishments: - "Simulation" - "GroundSpoof" - "Timer" + - "TimerLimit" - "NoFall" # 最大vl:下一次执行需要的vl # diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index f9fa37db38..aab25e584f 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -39,7 +39,9 @@ allprojects { apply(plugin = "maven-publish") repositories { - mavenLocal() + if (BuildConfig.mavenLocalOverride) { + mavenLocal() + } maven { name = "FabricMC" url = uri("https://maven.fabricmc.net/") @@ -55,11 +57,8 @@ allprojects { maven("https://nexus.scarsz.me/content/repositories/releases") // Configuralize maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate maven("https://repo.opencollab.dev/maven-releases/") // Cumulus (for Floodgate) - maven("https://repo.codemc.io/repository/maven-releases/") // PacketEvents - maven("https://repo.codemc.io/repository/maven-snapshots/") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") - mavenCentral() - // FastUtil + mavenCentral() // FastUtil } loom { diff --git a/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/Fabric1140PlatformServer.java b/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/Fabric1140PlatformServer.java index c48999e533..8fe4f5a0e6 100644 --- a/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/Fabric1140PlatformServer.java +++ b/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/Fabric1140PlatformServer.java @@ -5,7 +5,6 @@ import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin; import net.minecraft.server.command.ServerCommandSource; - public class Fabric1140PlatformServer extends AbstractFabricPlatformServer { @Override diff --git a/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java b/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java index 7633028a79..7896097c5d 100644 --- a/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java +++ b/fabric/mc1161/src/main/java/ac/grim/grimac/platform/fabric/mc1161/GrimACFabric1161LoaderPlugin.java @@ -16,8 +16,6 @@ public class GrimACFabric1161LoaderPlugin extends ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin { - - public GrimACFabric1161LoaderPlugin() { this( new FabricPlatformPlayerFactory( @@ -31,14 +29,14 @@ public GrimACFabric1161LoaderPlugin() { ); } - protected GrimACFabric1161LoaderPlugin(FabricPlatformPlayerFactory playerFactory, - AbstractFabricPlatformServer platformServer, - IFabricMessageUtil fabricMessageUtil, - IFabricConversionUtil fabricConversionUtil) { + protected GrimACFabric1161LoaderPlugin( + FabricPlatformPlayerFactory playerFactory, + AbstractFabricPlatformServer platformServer, + IFabricMessageUtil fabricMessageUtil, + IFabricConversionUtil fabricConversionUtil + ) { super( - new FabricParserDescriptorFactory( - new FabricPlayerSelectorParser<>(Fabric1161PlayerSelectorAdapter::new) - ), + new FabricParserDescriptorFactory(new FabricPlayerSelectorParser<>(Fabric1161PlayerSelectorAdapter::new)), playerFactory, platformServer, fabricMessageUtil, diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java index 960868a5a4..0ae028717d 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/GrimACFabricLoaderPlugin.java @@ -19,6 +19,7 @@ import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEventsAPI; import com.github.retrooper.packetevents.manager.server.ServerVersion; +import lombok.Getter; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.metadata.Person; import net.minecraft.server.MinecraftServer; @@ -57,6 +58,7 @@ public abstract class GrimACFabricLoaderPlugin implements PlatformLoader { protected final ParserDescriptorFactory parserFactory; protected final FabricPlatformPlayerFactory playerFactory; protected final AbstractFabricPlatformServer platformServer; + @Getter protected final IFabricConversionUtil fabricConversionUtil; protected final IFabricMessageUtil fabricMessageUtil; @@ -150,10 +152,6 @@ public AbstractFabricPlatformServer getPlatformServer() { return platformServer; } - public IFabricConversionUtil getFabricConversionUtil() { - return fabricConversionUtil; - } - public IFabricMessageUtil getFabricMessageUtils() { return fabricMessageUtil; } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/manager/FabricItemResetHandler.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/manager/FabricItemResetHandler.java index 5c5e35915a..d5677ca673 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/manager/FabricItemResetHandler.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/manager/FabricItemResetHandler.java @@ -3,6 +3,10 @@ import ac.grim.grimac.platform.api.manager.ItemResetHandler; import ac.grim.grimac.platform.api.player.PlatformPlayer; import ac.grim.grimac.platform.fabric.player.AbstractFabricPlatformPlayer; +import ac.grim.grimac.platform.fabric.utils.convert.FabricConversionUtil; +import com.github.retrooper.packetevents.protocol.player.InteractionHand; +import net.minecraft.entity.LivingEntity; +import net.minecraft.server.network.ServerPlayerEntity; import org.jetbrains.annotations.Nullable; public class FabricItemResetHandler implements ItemResetHandler { @@ -12,4 +16,14 @@ public void resetItemUsage(@Nullable PlatformPlayer player) { ((AbstractFabricPlatformPlayer) player).getFabricPlayer().clearActiveItem(); } } + + @Override + public @Nullable InteractionHand getItemUsageHand(@Nullable PlatformPlayer platformPlayer) { + if (platformPlayer == null) { + return null; + } + + ServerPlayerEntity player = ((AbstractFabricPlatformPlayer) platformPlayer).getFabricPlayer(); + return player.isUsingItem() ? FabricConversionUtil.fromFabricHand(player.getActiveHand()) : null; + } } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricAsyncScheduler.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricAsyncScheduler.java index 2a9433694a..6a23c81e92 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricAsyncScheduler.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricAsyncScheduler.java @@ -4,7 +4,6 @@ import ac.grim.grimac.platform.api.scheduler.AsyncScheduler; import ac.grim.grimac.platform.api.scheduler.PlatformScheduler; import ac.grim.grimac.platform.api.scheduler.TaskHandle; -import ac.grim.grimac.utils.data.Pair; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -13,12 +12,13 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; public class FabricAsyncScheduler implements AsyncScheduler { - private final Map> asyncTasks = new HashMap<>(); + private final Map asyncTasks = new HashMap<>(); private final GrimPlugin plugin; + record InternalAsyncTask(GrimPlugin plugin, Runnable runnable) {} + public FabricAsyncScheduler(GrimPlugin plugin) { this.plugin = plugin; } @@ -30,7 +30,7 @@ public TaskHandle runNow(@NotNull GrimPlugin plugin, @NotNull Runnable task) { thread.interrupt(); asyncTasks.remove(thread); }; - asyncTasks.put(thread, new Pair<>(plugin, cancellationTask)); + asyncTasks.put(thread, new InternalAsyncTask(plugin, cancellationTask)); thread.start(); return new FabricTaskHandle(cancellationTask, false); } @@ -50,7 +50,7 @@ public TaskHandle runDelayed(@NotNull GrimPlugin plugin, @NotNull Runnable task, thread.interrupt(); asyncTasks.remove(thread); }; - asyncTasks.put(thread, new Pair<>(plugin, cancellationTask)); + asyncTasks.put(thread, new InternalAsyncTask(plugin, cancellationTask)); thread.start(); return new FabricTaskHandle(cancellationTask, false); // false for async } @@ -74,7 +74,7 @@ public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Runnable t thread.interrupt(); asyncTasks.remove(thread); }; - asyncTasks.put(thread, new Pair<>(plugin, cancellationTask)); + asyncTasks.put(thread, new InternalAsyncTask(plugin, cancellationTask)); thread.start(); return new FabricTaskHandle(cancellationTask, false); // false for async } @@ -90,13 +90,13 @@ public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Runnable t @Override public void cancel(@NotNull GrimPlugin plugin) { // Cancel tasks only for the specified plugin - Iterator>> iterator = asyncTasks.entrySet().iterator(); + Iterator> iterator = asyncTasks.entrySet().iterator(); List cancellationTasks = new ArrayList<>(); while (iterator.hasNext()) { - Map.Entry> entry = iterator.next(); - if (entry.getValue().first().equals(plugin)) { - cancellationTasks.add(entry.getValue().second()); + Map.Entry entry = iterator.next(); + if (entry.getValue().plugin().equals(plugin)) { + cancellationTasks.add(entry.getValue().runnable()); iterator.remove(); } } @@ -108,8 +108,8 @@ public void cancel(@NotNull GrimPlugin plugin) { public void cancelAll() { List cancellationTasks = asyncTasks.values().stream() - .map(Pair::second) - .collect(Collectors.toList()); + .map(InternalAsyncTask::runnable) + .toList(); asyncTasks.clear(); for (Runnable cancellationTask : cancellationTasks) { diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricPlatformScheduler.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricPlatformScheduler.java index b71ea74de1..d50f3a0a3e 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricPlatformScheduler.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/scheduler/FabricPlatformScheduler.java @@ -2,11 +2,8 @@ import ac.grim.grimac.GrimAPI; import ac.grim.grimac.api.plugin.GrimPlugin; -import ac.grim.grimac.platform.api.scheduler.AsyncScheduler; -import ac.grim.grimac.platform.api.scheduler.EntityScheduler; -import ac.grim.grimac.platform.api.scheduler.GlobalRegionScheduler; -import ac.grim.grimac.platform.api.scheduler.PlatformScheduler; -import ac.grim.grimac.platform.api.scheduler.RegionScheduler; +import ac.grim.grimac.platform.api.scheduler.*; +import ac.grim.grimac.utils.anticheat.LogUtil; import net.minecraft.server.MinecraftServer; import org.checkerframework.checker.nullness.qual.NonNull; @@ -31,11 +28,7 @@ public FabricPlatformScheduler() { // Shared method to handle synchronous tasks // Add this to FabricPlatformScheduler.java - public static final ThreadLocal EXECUTING_TASK = new ThreadLocal() { - @Override protected Boolean initialValue() { - return false; - } - }; + public static final ThreadLocal EXECUTING_TASK = ThreadLocal.withInitial(() -> false); protected static void handleSyncTasks(Map taskMap, MinecraftServer server, GrimPlugin plugin) { Iterator iterator = taskMap.keySet().iterator(); @@ -46,8 +39,7 @@ protected static void handleSyncTasks(Map taskMap, Mine EXECUTING_TASK.set(true); task.task.run(); } catch (Exception e) { - plugin.getLogger().warning("Error executing scheduled task: " + e.getMessage()); - e.printStackTrace(); + LogUtil.error("Error executing scheduled task ", e); } finally { EXECUTING_TASK.set(false); } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/sender/FabricSenderFactory.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/sender/FabricSenderFactory.java index 12c8f4a77a..55c5408668 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/sender/FabricSenderFactory.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/sender/FabricSenderFactory.java @@ -87,7 +87,7 @@ protected boolean isConsole(ServerCommandSource sender) { CommandOutput output = sender.output; return output == sender.getMinecraftServer() || // Console output.getClass() == RconCommandOutput.class || // Rcon - (output == CommandOutput.DUMMY && sender.getName().equals("")); // Functions + (output == CommandOutput.DUMMY && sender.getName().isEmpty()); // Functions } @Override diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/convert/FabricConversionUtil.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/convert/FabricConversionUtil.java index 2eabba0087..0b80570fb7 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/convert/FabricConversionUtil.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/convert/FabricConversionUtil.java @@ -2,6 +2,7 @@ import com.github.retrooper.packetevents.protocol.item.ItemStack; import com.github.retrooper.packetevents.protocol.player.GameMode; +import com.github.retrooper.packetevents.protocol.player.InteractionHand; import net.kyori.adventure.text.Component; //import net.minecraft.network.RegistryByteBuf; //import net.minecraft.registry.DynamicRegistryManager; @@ -14,7 +15,6 @@ public abstract class FabricConversionUtil implements IFabricConversionUtil { private IFabricConversionUtil fabricConversionUtilSupplier; - private Function itemStackMapperFunction = (fabricStack) -> { // if (fabricStack.isEmpty()) { // return ItemStack.EMPTY; @@ -79,4 +79,11 @@ public static GameMode fromFabricGameMode(net.minecraft.world.GameMode fabricGam default -> throw new IllegalArgumentException("Unknown Fabric GameMode: " + fabricGameMode); }; } + + public static InteractionHand fromFabricHand(net.minecraft.util.Hand hand) { + return hand == null ? null : switch (hand) { + case OFF_HAND -> InteractionHand.OFF_HAND; + case MAIN_HAND -> InteractionHand.MAIN_HAND; + }; + } } diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/metrics/BStatsConfig.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/metrics/BStatsConfig.java index 65b5ba9e94..25c0c1120a 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/metrics/BStatsConfig.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/metrics/BStatsConfig.java @@ -1,15 +1,11 @@ package ac.grim.grimac.platform.fabric.utils.metrics; +import ac.grim.grimac.utils.anticheat.LogUtil; import net.fabricmc.loader.api.FabricLoader; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; +import java.io.*; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.Map; @@ -69,7 +65,7 @@ public static Config loadConfig() { config.logResponseStatusText = getBoolean(data, "logResponseStatusText", false); } catch (IOException e) { - e.printStackTrace(); + LogUtil.error("Failed to load bStats config. Using default values.", e); // Fallback to default values config.enabled = true; config.serverUuid = UUID.randomUUID().toString(); diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/metrics/JsonObjectBuilder.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/metrics/JsonObjectBuilder.java index 64e76a06bf..f9ca5d763b 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/metrics/JsonObjectBuilder.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/metrics/JsonObjectBuilder.java @@ -20,7 +20,7 @@ public JsonObjectBuilder() { } /** - * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. * *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). diff --git a/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/thread/FabricFutureUtil.java b/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/thread/FabricFutureUtil.java index 32ca55df96..af40192587 100644 --- a/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/thread/FabricFutureUtil.java +++ b/fabric/src/main/java/ac/grim/grimac/platform/fabric/utils/thread/FabricFutureUtil.java @@ -8,9 +8,8 @@ public class FabricFutureUtil { public static CompletableFuture supplySync(Supplier entityTeleportSupplier) { CompletableFuture ret = new CompletableFuture<>(); - GrimAPI.INSTANCE.getScheduler().getGlobalRegionScheduler().run(GrimAPI.INSTANCE.getGrimPlugin(), () -> { - ret.complete(entityTeleportSupplier.get()); - }); + GrimAPI.INSTANCE.getScheduler().getGlobalRegionScheduler().run(GrimAPI.INSTANCE.getGrimPlugin(), + () -> ret.complete(entityTeleportSupplier.get())); return ret; } } From 6cc187887fdc15f61ce03ce57be09723cdb9f69d Mon Sep 17 00:00:00 2001 From: GigaZelensky <85454363+GigaZelensky@users.noreply.github.com> Date: Sat, 17 May 2025 00:55:53 +0100 Subject: [PATCH 25/34] Track scaled VL separately --- .../checks/impl/prediction/OffsetHandler.java | 12 ++++++ .../grimac/manager/PunishmentManager.java | 42 ++++++++++++------- common/src/main/resources/config/de.yml | 2 + common/src/main/resources/config/en.yml | 7 ++++ common/src/main/resources/config/es.yml | 2 + common/src/main/resources/config/fr.yml | 2 + common/src/main/resources/config/it.yml | 2 + common/src/main/resources/config/ja.yml | 2 + common/src/main/resources/config/nl.yml | 2 + common/src/main/resources/config/pt.yml | 2 + common/src/main/resources/config/ru.yml | 2 + common/src/main/resources/config/tr.yml | 2 + common/src/main/resources/config/zh.yml | 2 + 13 files changed, 65 insertions(+), 16 deletions(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java index 3f5197dd33..5a862e0851 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java @@ -20,6 +20,8 @@ public class OffsetHandler extends Check implements PostPredictionCheck { double immediateSetbackThreshold; double maxAdvantage; double maxCeiling; + double vlScale; + double maxVlsPerFlag; double setbackViolationThreshold; // Current advantage gained double advantageGained = 0; @@ -64,6 +66,13 @@ public void onPredictionComplete(final PredictionComplete predictionComplete) { predictionComplete.setIdentifier(flagId); } + double extra = Math.ceil(offset * vlScale) - 1.0; + if (extra > 0) { + int amount = (int) Math.min(maxVlsPerFlag, extra); + violations += amount; + player.punishmentManager.addExtraViolation(this, amount); + } + if ((advantageGained >= maxAdvantage || offset >= immediateSetbackThreshold) && !isNoSetbackPermission() && violations >= setbackViolationThreshold) { @@ -104,9 +113,12 @@ public void onReload(ConfigManager config) { immediateSetbackThreshold = config.getDoubleElse("Simulation.immediate-setback-threshold", 0.1); maxAdvantage = config.getDoubleElse("Simulation.max-advantage", 1); maxCeiling = config.getDoubleElse("Simulation.max-ceiling", 4); + vlScale = Math.max(1.0, config.getDoubleElse("Simulation.vl-scale", 10)); + maxVlsPerFlag = config.getDoubleElse("Simulation.max-vls-per-flag", 5); setbackViolationThreshold = config.getDoubleElse("Simulation.setback-violation-threshold", 1); if (maxAdvantage == -1) maxAdvantage = Double.MAX_VALUE; if (immediateSetbackThreshold == -1) immediateSetbackThreshold = Double.MAX_VALUE; + if (maxVlsPerFlag == -1) maxVlsPerFlag = Double.MAX_VALUE; } public boolean doesOffsetFlag(double offset) { diff --git a/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java b/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java index 86ea2a31ab..5d35af07ef 100644 --- a/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java @@ -113,7 +113,6 @@ public boolean handleAlert(GrimPlayer player, String verbose, Check check) { for (PunishGroup group : groups) { if (group.checks.contains(check)) { final int vl = getViolations(group, check); - final int violationCount = group.violations.size(); for (ParsedCommand command : group.commands) { String cmd = replaceAlertPlaceholders(command.command, vl, check, verbose); @@ -126,11 +125,11 @@ public boolean handleAlert(GrimPlayer player, String verbose, Check check) { verboseListeners = GrimAPI.INSTANCE.getAlertManager().sendVerbose(component, null); } - if (violationCount >= command.threshold) { - // 0 means execute once - // Any other number means execute every X interval - boolean inInterval = command.interval == 0 ? (command.executeCount == 0) : (violationCount % command.interval == 0); - if (inInterval) { + // 0 means execute once + // Any other number means execute every X interval + for (; vl >= (command.threshold + (command.interval * command.executeCount)); command.executeCount++) { + if (command.interval == 0 && command.executeCount > 0) break; + CommandExecuteEvent executeEvent = new CommandExecuteEvent(player, check, verbose, cmd); GrimAPI.INSTANCE.getEventBus().post(executeEvent); if (executeEvent.isCancelled()) continue; @@ -138,9 +137,8 @@ public boolean handleAlert(GrimPlayer player, String verbose, Check check) { switch (command.command) { case "[webhook]" -> GrimAPI.INSTANCE.getDiscordManager().sendAlert(player, verbose, check.getDisplayName(), vl); case "[log]" -> { - int vls = (int) group.violations.values().stream().filter((e) -> e == check).count(); String verboseWithoutGl = verbose.replaceAll(" /gl .*", ""); - GrimAPI.INSTANCE.getViolationDatabaseManager().logAlert(player, verboseWithoutGl, check.getDisplayName(), vls); + GrimAPI.INSTANCE.getViolationDatabaseManager().logAlert(player, verboseWithoutGl, check.getDisplayName(), vl); } case "[proxy]" -> ProxyAlertMessenger.sendPluginMessage(cmd); case "[alert]" -> { @@ -161,8 +159,6 @@ public boolean handleAlert(GrimPlayer player, String verbose, Check check) { ) ); } - } - command.executeCount++; } } @@ -173,21 +169,29 @@ public boolean handleAlert(GrimPlayer player, String verbose, Check check) { } public void handleViolation(Check check) { + addViolation(check, 1); + } + + public void addExtraViolation(Check check, int amount) { + if (amount <= 0) return; + addViolation(check, amount); + } + + private void addViolation(Check check, int amount) { for (PunishGroup group : groups) { if (group.checks.contains(check)) { long currentTime = System.currentTimeMillis(); - - group.violations.put(currentTime, check); + group.violations.put(currentTime, new ViolationRecord(check, amount)); // Remove violations older than the defined time in the config - group.violations.entrySet().removeIf(time -> currentTime - time.getKey() > group.removeViolationsAfter); + group.violations.entrySet().removeIf(entry -> currentTime - entry.getKey() > group.removeViolationsAfter); } } } private int getViolations(PunishGroup group, Check check) { int vl = 0; - for (Check value : group.violations.values()) { - if (value == check) vl++; + for (ViolationRecord value : group.violations.values()) { + if (value.check == check) vl += value.amount; } return vl; } @@ -197,7 +201,7 @@ private int getViolations(PunishGroup group, Check check) { class PunishGroup { public final List checks; public final List commands; - public final Map violations = new HashMap<>(); + public final Map violations = new HashMap<>(); public final int removeViolationsAfter; // time to remove violations after in milliseconds } @@ -208,3 +212,9 @@ class ParsedCommand { public final String command; public int executeCount; } + +@RequiredArgsConstructor +class ViolationRecord { + public final Check check; + public final int amount; +} diff --git a/common/src/main/resources/config/de.yml b/common/src/main/resources/config/de.yml index 2bb819931f..3587733ddd 100644 --- a/common/src/main/resources/config/de.yml +++ b/common/src/main/resources/config/de.yml @@ -66,6 +66,8 @@ Simulation: # Dies soll verhindern, dass der Spieler zu viele Verstöße sammelt und nie in der Lage ist, sie alle zu beseitigen. # Standard-Vorteilsgrenze (x-Achse = Sekunden, y-Achse = 1/1000 Block): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # Schwellenwert für die Verletzungsstufe für den Rückschlag # 1 für das alte Verhalten setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/en.yml b/common/src/main/resources/config/en.yml index 3264487bee..1018508f1c 100644 --- a/common/src/main/resources/config/en.yml +++ b/common/src/main/resources/config/en.yml @@ -66,6 +66,13 @@ Simulation: # This is to stop the player from gathering too many violations and never being able to clear them all # Default advantage ceiling (x axis = seconds, y axis = 1/1000 block): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + # How much should we scale VL gain from a flag by the player's advantage? + # ≤1 for no scaling (old behavior) + vl-scale: 10 + # Limits the amount of VLs the player could gain from each flag + # This is to prevent high latency players from getting large amount of VLs because of lag spikes + # -1 to disable + max-vls-per-flag: 5 # Violation level threshold for setback # 1 for old behavior setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/es.yml b/common/src/main/resources/config/es.yml index 8a22202a4b..9124e08fb8 100644 --- a/common/src/main/resources/config/es.yml +++ b/common/src/main/resources/config/es.yml @@ -67,6 +67,8 @@ Simulation: # Esto es para prevenir que el jugador obtenga muchas violaciones y no pueda ser capaz de borrarlas # Tope de ventaja por defecto (eje x = segundos, eje y = bloque 1/1000): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # Umbral del nivel de violación para el retroceso # 1 para el comportamiento antiguo setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/fr.yml b/common/src/main/resources/config/fr.yml index cbe174ab68..b800dafeef 100644 --- a/common/src/main/resources/config/fr.yml +++ b/common/src/main/resources/config/fr.yml @@ -66,6 +66,8 @@ Simulation: # Cela vise à empêcher le joueur d'accumuler trop de violations et de ne jamais pouvoir toutes les réinitialiser. # Plafond d'avantage par défaut (l"axe x = secondes, l'axe y = 1/1000 de bloc)) : https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # Seuil du niveau de violation pour le setback # 1 pour le comportement ancien setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/it.yml b/common/src/main/resources/config/it.yml index 336bc75a53..5b44512c11 100644 --- a/common/src/main/resources/config/it.yml +++ b/common/src/main/resources/config/it.yml @@ -58,6 +58,8 @@ Simulation: max-advantage: 1 # Limite massimo di vantaggio accumulabile prima di arretrare il giocatore max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # Soglia del livello di violazione per il setback # 1 per il comportamento precedente setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/ja.yml b/common/src/main/resources/config/ja.yml index 99c2999809..3270b1919c 100644 --- a/common/src/main/resources/config/ja.yml +++ b/common/src/main/resources/config/ja.yml @@ -68,6 +68,8 @@ Simulation: # これは、プレイヤーが違反を蓄積しすぎて、違反を解消できなくなるのを防ぐためです。 # デフォルトのアドバンテージの上限 (x軸 = 秒, y軸 = 1/1000ブロック): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # セットバックの違反レベル閾値 # 旧仕様の挙動は 1 です setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/nl.yml b/common/src/main/resources/config/nl.yml index b8ac021b07..e485715510 100644 --- a/common/src/main/resources/config/nl.yml +++ b/common/src/main/resources/config/nl.yml @@ -66,6 +66,8 @@ Simulation: # Dit is om te voorkomen dat de speler te veel schendingen verzamelt en ze nooit allemaal kan opruimen # Standaard voordelenplatform (x-as = seconden, y-as = 1/1000 blok): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # Drempelwaarde voor het schendingenniveau voor de terugslag # 1 voor het oude gedrag setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/pt.yml b/common/src/main/resources/config/pt.yml index 0dfdac895d..8471940ccf 100644 --- a/common/src/main/resources/config/pt.yml +++ b/common/src/main/resources/config/pt.yml @@ -67,6 +67,8 @@ Simulation: # Isso serve para previnir o jogador de alcançar violações demais e nunca conseguir se livrar delas. # Cela de vantagens padrão (eixo X = segundos, eixo Y = 1/1000 blocos): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # Quantas violações para começar a recuar o jogador? # 1 para comportamento antigo setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/ru.yml b/common/src/main/resources/config/ru.yml index 7077d24e68..b6b949da03 100644 --- a/common/src/main/resources/config/ru.yml +++ b/common/src/main/resources/config/ru.yml @@ -66,6 +66,8 @@ Simulation: # Это сделано для того, чтобы игрок не собирал слишком много нарушений и никогда не смог очистить их все. # Потолок преимущества по умолчанию (ось x = секунды, ось y = 1/1000 блока): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # Порог уровня нарушения для отката # 1 для старого поведения setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/tr.yml b/common/src/main/resources/config/tr.yml index 32e21a494e..07db67ab7f 100644 --- a/common/src/main/resources/config/tr.yml +++ b/common/src/main/resources/config/tr.yml @@ -66,6 +66,8 @@ Simulation: # Bu, oyuncunun çok fazla ihlal biriktirmesini ve bunların hepsini temizleyememesini önlemek içindir # Varsayılan avantaj tavanı (x ekseni = saniye, y ekseni = 1/1000 blok): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # Geri alma için ihlal seviyesi eşiği # Eski davranış için 1 setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/zh.yml b/common/src/main/resources/config/zh.yml index 49c046e5fb..ffa976d9e8 100644 --- a/common/src/main/resources/config/zh.yml +++ b/common/src/main/resources/config/zh.yml @@ -71,6 +71,8 @@ Simulation: # 这是为了防止玩家收集过多的违规行为,并且永远无法清除所有的违规行为 # 这是默认配置的样子(x 轴 = seconds ,y 轴 = 1/1000 方块): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 + vl-scale: 10 + max-vls-per-flag: 5 # 设置回退的违规等级阈值 # 1 为旧行为 setback-violation-threshold: 1 From ec893c558d8007e53ed50037f7c09edafc63b606 Mon Sep 17 00:00:00 2001 From: GigaZelensky Date: Sat, 17 May 2025 20:26:33 +0100 Subject: [PATCH 26/34] translate --- common/src/main/resources/config/de.yml | 8 ++++++-- common/src/main/resources/config/en.yml | 4 ++-- common/src/main/resources/config/es.yml | 8 ++++++-- common/src/main/resources/config/fr.yml | 8 ++++++-- common/src/main/resources/config/it.yml | 8 ++++++-- common/src/main/resources/config/ja.yml | 8 ++++++-- common/src/main/resources/config/nl.yml | 8 ++++++-- common/src/main/resources/config/pt.yml | 8 ++++++-- common/src/main/resources/config/ru.yml | 8 ++++++-- common/src/main/resources/config/tr.yml | 8 ++++++-- common/src/main/resources/config/zh.yml | 8 ++++++-- 11 files changed, 62 insertions(+), 22 deletions(-) diff --git a/common/src/main/resources/config/de.yml b/common/src/main/resources/config/de.yml index 3587733ddd..08734fb68b 100644 --- a/common/src/main/resources/config/de.yml +++ b/common/src/main/resources/config/de.yml @@ -66,8 +66,12 @@ Simulation: # Dies soll verhindern, dass der Spieler zu viele Verstöße sammelt und nie in der Lage ist, sie alle zu beseitigen. # Standard-Vorteilsgrenze (x-Achse = Sekunden, y-Achse = 1/1000 Block): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # Wie stark sollen wir den VL-Gewinn anhand des Vorteils des Spielers skaliieren? + # ≤1 für keine Skalierung (altes Verhalten) + vl-scale: 1 + # Beschränkt die Menge an VL, die pro Flag hinzugefügt werden kann + # -1 deaktiviert die Begrenzung + max-vls-per-flag: -1 # Schwellenwert für die Verletzungsstufe für den Rückschlag # 1 für das alte Verhalten setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/en.yml b/common/src/main/resources/config/en.yml index 1018508f1c..5bbda3c997 100644 --- a/common/src/main/resources/config/en.yml +++ b/common/src/main/resources/config/en.yml @@ -68,11 +68,11 @@ Simulation: max-ceiling: 4 # How much should we scale VL gain from a flag by the player's advantage? # ≤1 for no scaling (old behavior) - vl-scale: 10 + vl-scale: 1 # Limits the amount of VLs the player could gain from each flag # This is to prevent high latency players from getting large amount of VLs because of lag spikes # -1 to disable - max-vls-per-flag: 5 + max-vls-per-flag: -1 # Violation level threshold for setback # 1 for old behavior setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/es.yml b/common/src/main/resources/config/es.yml index 9124e08fb8..3e9560151c 100644 --- a/common/src/main/resources/config/es.yml +++ b/common/src/main/resources/config/es.yml @@ -67,8 +67,12 @@ Simulation: # Esto es para prevenir que el jugador obtenga muchas violaciones y no pueda ser capaz de borrarlas # Tope de ventaja por defecto (eje x = segundos, eje y = bloque 1/1000): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # ¿Cuánto deberíamos escalar la ganancia de VL por una bandera según la ventaja del jugador? + # ≤1 para no escalar (comportamiento antiguo) + vl-scale: 1 + # Limita la cantidad de VL que el jugador puede ganar por cada bandera + # -1 para desactivar + max-vls-per-flag: -1 # Umbral del nivel de violación para el retroceso # 1 para el comportamiento antiguo setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/fr.yml b/common/src/main/resources/config/fr.yml index b800dafeef..631f41fd7a 100644 --- a/common/src/main/resources/config/fr.yml +++ b/common/src/main/resources/config/fr.yml @@ -66,8 +66,12 @@ Simulation: # Cela vise à empêcher le joueur d'accumuler trop de violations et de ne jamais pouvoir toutes les réinitialiser. # Plafond d'avantage par défaut (l"axe x = secondes, l'axe y = 1/1000 de bloc)) : https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # Dans quelle mesure doit-on augmenter les VL en fonction de l'avantage du joueur ? + # ≤1 pour conserver l'ancien comportement sans échelle + vl-scale: 1 + # Limite la quantité de VL que le joueur peut gagner par alerte + # -1 pour désactiver + max-vls-per-flag: -1 # Seuil du niveau de violation pour le setback # 1 pour le comportement ancien setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/it.yml b/common/src/main/resources/config/it.yml index 5b44512c11..290750078c 100644 --- a/common/src/main/resources/config/it.yml +++ b/common/src/main/resources/config/it.yml @@ -58,8 +58,12 @@ Simulation: max-advantage: 1 # Limite massimo di vantaggio accumulabile prima di arretrare il giocatore max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # Di quanto dobbiamo scalare l'aumento di VL di un flag in base al vantaggio del giocatore? + # ≤1 per mantenere il vecchio comportamento + vl-scale: 1 + # Limita quante VL si possono ottenere per ogni flag + # -1 per disattivare + max-vls-per-flag: -1 # Soglia del livello di violazione per il setback # 1 per il comportamento precedente setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/ja.yml b/common/src/main/resources/config/ja.yml index 3270b1919c..e2f3c97d3a 100644 --- a/common/src/main/resources/config/ja.yml +++ b/common/src/main/resources/config/ja.yml @@ -68,8 +68,12 @@ Simulation: # これは、プレイヤーが違反を蓄積しすぎて、違反を解消できなくなるのを防ぐためです。 # デフォルトのアドバンテージの上限 (x軸 = 秒, y軸 = 1/1000ブロック): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # プレイヤーのアドバンテージに応じてVL増加量をどれだけスケールさせるか + # 1以下で従来の挙動になります + vl-scale: 1 + # 1回のフラグで増加できるVLの上限 + # -1で無制限 + max-vls-per-flag: -1 # セットバックの違反レベル閾値 # 旧仕様の挙動は 1 です setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/nl.yml b/common/src/main/resources/config/nl.yml index e485715510..5086fd1871 100644 --- a/common/src/main/resources/config/nl.yml +++ b/common/src/main/resources/config/nl.yml @@ -66,8 +66,12 @@ Simulation: # Dit is om te voorkomen dat de speler te veel schendingen verzamelt en ze nooit allemaal kan opruimen # Standaard voordelenplatform (x-as = seconden, y-as = 1/1000 blok): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # Hoeveel moeten we de VL-verhoging van een flag schalen op basis van het voordeel van de speler? + # ≤1 voor geen schaal (oud gedrag) + vl-scale: 1 + # Beperkt hoeveel VL een speler per flag kan krijgen + # -1 om uit te schakelen + max-vls-per-flag: -1 # Drempelwaarde voor het schendingenniveau voor de terugslag # 1 voor het oude gedrag setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/pt.yml b/common/src/main/resources/config/pt.yml index 8471940ccf..97e8488e36 100644 --- a/common/src/main/resources/config/pt.yml +++ b/common/src/main/resources/config/pt.yml @@ -67,8 +67,12 @@ Simulation: # Isso serve para previnir o jogador de alcançar violações demais e nunca conseguir se livrar delas. # Cela de vantagens padrão (eixo X = segundos, eixo Y = 1/1000 blocos): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # Quanto devemos escalar o ganho de VL de um flag com base na vantagem do jogador? + # ≤1 para manter o comportamento antigo + vl-scale: 1 + # Limita a quantidade de VL que o jogador pode ganhar por flag + # -1 para desativar + max-vls-per-flag: -1 # Quantas violações para começar a recuar o jogador? # 1 para comportamento antigo setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/ru.yml b/common/src/main/resources/config/ru.yml index b6b949da03..98b856ce1c 100644 --- a/common/src/main/resources/config/ru.yml +++ b/common/src/main/resources/config/ru.yml @@ -66,8 +66,12 @@ Simulation: # Это сделано для того, чтобы игрок не собирал слишком много нарушений и никогда не смог очистить их все. # Потолок преимущества по умолчанию (ось x = секунды, ось y = 1/1000 блока): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # Насколько увеличивать получение VL в зависимости от преимущества игрока? + # ≤1 — оставить старое поведение + vl-scale: 1 + # Ограничивает количество VL, которое можно получить за один флаг + # -1 для отключения + max-vls-per-flag: -1 # Порог уровня нарушения для отката # 1 для старого поведения setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/tr.yml b/common/src/main/resources/config/tr.yml index 07db67ab7f..bb85317122 100644 --- a/common/src/main/resources/config/tr.yml +++ b/common/src/main/resources/config/tr.yml @@ -66,8 +66,12 @@ Simulation: # Bu, oyuncunun çok fazla ihlal biriktirmesini ve bunların hepsini temizleyememesini önlemek içindir # Varsayılan avantaj tavanı (x ekseni = saniye, y ekseni = 1/1000 blok): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # Oyuncunun avantajına bağlı olarak VL artışını ne kadar ölçekleyelim? + # ≤1 eski davranış için + vl-scale: 1 + # Her bir flagden alınabilecek VL miktarını sınırlar + # -1 devre dışı bırakır + max-vls-per-flag: -1 # Geri alma için ihlal seviyesi eşiği # Eski davranış için 1 setback-violation-threshold: 1 diff --git a/common/src/main/resources/config/zh.yml b/common/src/main/resources/config/zh.yml index ffa976d9e8..ea30e00cd9 100644 --- a/common/src/main/resources/config/zh.yml +++ b/common/src/main/resources/config/zh.yml @@ -71,8 +71,12 @@ Simulation: # 这是为了防止玩家收集过多的违规行为,并且永远无法清除所有的违规行为 # 这是默认配置的样子(x 轴 = seconds ,y 轴 = 1/1000 方块): https://www.desmos.com/calculator/4lovswdarj max-ceiling: 4 - vl-scale: 10 - max-vls-per-flag: 5 + # 根据玩家的优势对VL增量进行缩放 + # ≤1 表示与以前相同 + vl-scale: 1 + # 限制每次触发可增加的VL数量 + # -1 表示不限制 + max-vls-per-flag: -1 # 设置回退的违规等级阈值 # 1 为旧行为 setback-violation-threshold: 1 From 3f2c7764661c404ca90c6da716758d4e80975c47 Mon Sep 17 00:00:00 2001 From: GigaZelensky <85454363+GigaZelensky@users.noreply.github.com> Date: Sat, 17 May 2025 21:44:44 +0100 Subject: [PATCH 27/34] Don't scale by default --- .../ac/grim/grimac/checks/impl/prediction/OffsetHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java index 5a862e0851..8667745de6 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java @@ -113,8 +113,8 @@ public void onReload(ConfigManager config) { immediateSetbackThreshold = config.getDoubleElse("Simulation.immediate-setback-threshold", 0.1); maxAdvantage = config.getDoubleElse("Simulation.max-advantage", 1); maxCeiling = config.getDoubleElse("Simulation.max-ceiling", 4); - vlScale = Math.max(1.0, config.getDoubleElse("Simulation.vl-scale", 10)); - maxVlsPerFlag = config.getDoubleElse("Simulation.max-vls-per-flag", 5); + vlScale = Math.max(1.0, config.getDoubleElse("Simulation.vl-scale", 1)); + maxVlsPerFlag = config.getDoubleElse("Simulation.max-vls-per-flag", -1); setbackViolationThreshold = config.getDoubleElse("Simulation.setback-violation-threshold", 1); if (maxAdvantage == -1) maxAdvantage = Double.MAX_VALUE; if (immediateSetbackThreshold == -1) immediateSetbackThreshold = Double.MAX_VALUE; From 38a71ab869fc1e00c37d68740567aaa1401069c1 Mon Sep 17 00:00:00 2001 From: GigaZelensky Date: Mon, 19 May 2025 10:37:09 +0100 Subject: [PATCH 28/34] Update PunishmentManager.java --- .../main/java/ac/grim/grimac/manager/PunishmentManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java b/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java index 5d35af07ef..834ba6902e 100644 --- a/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java @@ -114,6 +114,9 @@ public boolean handleAlert(GrimPlayer player, String verbose, Check check) { if (group.checks.contains(check)) { final int vl = getViolations(group, check); for (ParsedCommand command : group.commands) { + if (vl < command.threshold) { + command.executeCount = 0; + } String cmd = replaceAlertPlaceholders(command.command, vl, check, verbose); @Nullable Set<@Nullable PlatformPlayer> verboseListeners = null; From 4d6c9c64cd106191ac68c8dd425847ddd2c60b77 Mon Sep 17 00:00:00 2001 From: GigaZelensky <85454363+GigaZelensky@users.noreply.github.com> Date: Mon, 19 May 2025 11:22:37 +0100 Subject: [PATCH 29/34] Fix duplicate comment in English config --- .../grim/grimac/checks/impl/prediction/OffsetHandler.java | 6 +++++- .../main/java/ac/grim/grimac/manager/PunishmentManager.java | 4 +++- common/src/main/resources/config/de.yml | 3 +++ common/src/main/resources/config/en.yml | 3 +++ common/src/main/resources/config/es.yml | 3 +++ common/src/main/resources/config/fr.yml | 3 +++ common/src/main/resources/config/it.yml | 3 +++ common/src/main/resources/config/ja.yml | 3 +++ common/src/main/resources/config/nl.yml | 3 +++ common/src/main/resources/config/pt.yml | 3 +++ common/src/main/resources/config/ru.yml | 3 +++ common/src/main/resources/config/tr.yml | 3 +++ common/src/main/resources/config/zh.yml | 3 +++ 13 files changed, 41 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java index 8667745de6..3cc698232a 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java @@ -21,6 +21,8 @@ public class OffsetHandler extends Check implements PostPredictionCheck { double maxAdvantage; double maxCeiling; double vlScale; + long vlScaleDelay; + long lastScaledFlagTime = 0; double maxVlsPerFlag; double setbackViolationThreshold; // Current advantage gained @@ -67,10 +69,11 @@ public void onPredictionComplete(final PredictionComplete predictionComplete) { } double extra = Math.ceil(offset * vlScale) - 1.0; - if (extra > 0) { + if (extra > 0 && (vlScaleDelay <= 0 || System.currentTimeMillis() - lastScaledFlagTime >= vlScaleDelay)) { int amount = (int) Math.min(maxVlsPerFlag, extra); violations += amount; player.punishmentManager.addExtraViolation(this, amount); + lastScaledFlagTime = System.currentTimeMillis(); } if ((advantageGained >= maxAdvantage || offset >= immediateSetbackThreshold) @@ -114,6 +117,7 @@ public void onReload(ConfigManager config) { maxAdvantage = config.getDoubleElse("Simulation.max-advantage", 1); maxCeiling = config.getDoubleElse("Simulation.max-ceiling", 4); vlScale = Math.max(1.0, config.getDoubleElse("Simulation.vl-scale", 1)); + vlScaleDelay = config.getLongElse("Simulation.vl-scale-delay", 0L); maxVlsPerFlag = config.getDoubleElse("Simulation.max-vls-per-flag", -1); setbackViolationThreshold = config.getDoubleElse("Simulation.setback-violation-threshold", 1); if (maxAdvantage == -1) maxAdvantage = Double.MAX_VALUE; diff --git a/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java b/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java index 834ba6902e..5cdb556c13 100644 --- a/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java +++ b/common/src/main/java/ac/grim/grimac/manager/PunishmentManager.java @@ -24,6 +24,7 @@ public class PunishmentManager implements ConfigReloadable { String experimentalSymbol = "*"; private String alertString; private boolean testMode; + private boolean resetExecuteCount; private String proxyAlertString = ""; public PunishmentManager(GrimPlayer player) { @@ -36,6 +37,7 @@ public void reload(ConfigManager config) { experimentalSymbol = config.getStringElse("experimental-symbol", "*"); alertString = config.getStringElse("alerts-format", "%prefix% &f%player% &bfailed &f%check_name% &f(x&c%vl%&f) &7%verbose%"); testMode = config.getBooleanElse("test-mode", false); + resetExecuteCount = config.getBooleanElse("reset-punishment-execute-count", true); proxyAlertString = config.getStringElse("alerts-format-proxy", "%prefix% &f[&cproxy&f] &f%player% &bfailed &f%check_name% &f(x&c%vl%&f) &7%verbose%"); try { groups.clear(); @@ -114,7 +116,7 @@ public boolean handleAlert(GrimPlayer player, String verbose, Check check) { if (group.checks.contains(check)) { final int vl = getViolations(group, check); for (ParsedCommand command : group.commands) { - if (vl < command.threshold) { + if (resetExecuteCount && vl < command.threshold) { command.executeCount = 0; } String cmd = replaceAlertPlaceholders(command.command, vl, check, verbose); diff --git a/common/src/main/resources/config/de.yml b/common/src/main/resources/config/de.yml index 08734fb68b..29c3f072e7 100644 --- a/common/src/main/resources/config/de.yml +++ b/common/src/main/resources/config/de.yml @@ -69,6 +69,8 @@ Simulation: # Wie stark sollen wir den VL-Gewinn anhand des Vorteils des Spielers skaliieren? # ≤1 für keine Skalierung (altes Verhalten) vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # Beschränkt die Menge an VL, die pro Flag hinzugefügt werden kann # -1 deaktiviert die Begrenzung max-vls-per-flag: -1 @@ -188,6 +190,7 @@ debug-pipeline-on-join: false # Aktiviert experimentelle Prüfungen experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/en.yml b/common/src/main/resources/config/en.yml index 5bbda3c997..cc867d3d46 100644 --- a/common/src/main/resources/config/en.yml +++ b/common/src/main/resources/config/en.yml @@ -69,6 +69,8 @@ Simulation: # How much should we scale VL gain from a flag by the player's advantage? # ≤1 for no scaling (old behavior) vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # Limits the amount of VLs the player could gain from each flag # This is to prevent high latency players from getting large amount of VLs because of lag spikes # -1 to disable @@ -189,6 +191,7 @@ debug-pipeline-on-join: false # Enables experimental checks experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/es.yml b/common/src/main/resources/config/es.yml index 3e9560151c..16092a3c04 100644 --- a/common/src/main/resources/config/es.yml +++ b/common/src/main/resources/config/es.yml @@ -70,6 +70,8 @@ Simulation: # ¿Cuánto deberíamos escalar la ganancia de VL por una bandera según la ventaja del jugador? # ≤1 para no escalar (comportamiento antiguo) vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # Limita la cantidad de VL que el jugador puede ganar por cada bandera # -1 para desactivar max-vls-per-flag: -1 @@ -189,6 +191,7 @@ debug-pipeline-on-join: false # Habilitar comprobaciones experimentales experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/fr.yml b/common/src/main/resources/config/fr.yml index 631f41fd7a..4c4f76177a 100644 --- a/common/src/main/resources/config/fr.yml +++ b/common/src/main/resources/config/fr.yml @@ -69,6 +69,8 @@ Simulation: # Dans quelle mesure doit-on augmenter les VL en fonction de l'avantage du joueur ? # ≤1 pour conserver l'ancien comportement sans échelle vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # Limite la quantité de VL que le joueur peut gagner par alerte # -1 pour désactiver max-vls-per-flag: -1 @@ -184,6 +186,7 @@ debug-pipeline-on-join: false # Active les vérifications expérimentales experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/it.yml b/common/src/main/resources/config/it.yml index 290750078c..0198975ba1 100644 --- a/common/src/main/resources/config/it.yml +++ b/common/src/main/resources/config/it.yml @@ -61,6 +61,8 @@ Simulation: # Di quanto dobbiamo scalare l'aumento di VL di un flag in base al vantaggio del giocatore? # ≤1 per mantenere il vecchio comportamento vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # Limita quante VL si possono ottenere per ogni flag # -1 per disattivare max-vls-per-flag: -1 @@ -166,6 +168,7 @@ debug-pipeline-on-join: false # Enables experimental checks experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/ja.yml b/common/src/main/resources/config/ja.yml index e2f3c97d3a..2c14fe9063 100644 --- a/common/src/main/resources/config/ja.yml +++ b/common/src/main/resources/config/ja.yml @@ -71,6 +71,8 @@ Simulation: # プレイヤーのアドバンテージに応じてVL増加量をどれだけスケールさせるか # 1以下で従来の挙動になります vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # 1回のフラグで増加できるVLの上限 # -1で無制限 max-vls-per-flag: -1 @@ -197,6 +199,7 @@ debug-pipeline-on-join: false # 実験的なチェックを有効にします experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/nl.yml b/common/src/main/resources/config/nl.yml index 5086fd1871..f03bd59ec3 100644 --- a/common/src/main/resources/config/nl.yml +++ b/common/src/main/resources/config/nl.yml @@ -69,6 +69,8 @@ Simulation: # Hoeveel moeten we de VL-verhoging van een flag schalen op basis van het voordeel van de speler? # ≤1 voor geen schaal (oud gedrag) vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # Beperkt hoeveel VL een speler per flag kan krijgen # -1 om uit te schakelen max-vls-per-flag: -1 @@ -188,6 +190,7 @@ debug-pipeline-on-join: false # Experimentele controles inschakelen experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/pt.yml b/common/src/main/resources/config/pt.yml index 97e8488e36..0c4892b74e 100644 --- a/common/src/main/resources/config/pt.yml +++ b/common/src/main/resources/config/pt.yml @@ -70,6 +70,8 @@ Simulation: # Quanto devemos escalar o ganho de VL de um flag com base na vantagem do jogador? # ≤1 para manter o comportamento antigo vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # Limita a quantidade de VL que o jogador pode ganhar por flag # -1 para desativar max-vls-per-flag: -1 @@ -193,6 +195,7 @@ debug-pipeline-on-join: false # Habilita verificações experimentais. experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/ru.yml b/common/src/main/resources/config/ru.yml index 98b856ce1c..c68321ad8b 100644 --- a/common/src/main/resources/config/ru.yml +++ b/common/src/main/resources/config/ru.yml @@ -69,6 +69,8 @@ Simulation: # Насколько увеличивать получение VL в зависимости от преимущества игрока? # ≤1 — оставить старое поведение vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # Ограничивает количество VL, которое можно получить за один флаг # -1 для отключения max-vls-per-flag: -1 @@ -184,6 +186,7 @@ debug-pipeline-on-join: false # Включает экспериментальные проверки experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/tr.yml b/common/src/main/resources/config/tr.yml index bb85317122..22f8383d2a 100644 --- a/common/src/main/resources/config/tr.yml +++ b/common/src/main/resources/config/tr.yml @@ -69,6 +69,8 @@ Simulation: # Oyuncunun avantajına bağlı olarak VL artışını ne kadar ölçekleyelim? # ≤1 eski davranış için vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # Her bir flagden alınabilecek VL miktarını sınırlar # -1 devre dışı bırakır max-vls-per-flag: -1 @@ -188,6 +190,7 @@ debug-pipeline-on-join: false # Deneysel kontrolleri etkinleştir experimental-checks: false +reset-punishment-execute-count: true reset-item-usage-on-item-update: true reset-item-usage-on-attack: true diff --git a/common/src/main/resources/config/zh.yml b/common/src/main/resources/config/zh.yml index ea30e00cd9..c07698e1dc 100644 --- a/common/src/main/resources/config/zh.yml +++ b/common/src/main/resources/config/zh.yml @@ -74,6 +74,8 @@ Simulation: # 根据玩家的优势对VL增量进行缩放 # ≤1 表示与以前相同 vl-scale: 1 + # Delay in milliseconds between flags for VL scaling + vl-scale-delay: 0 # 限制每次触发可增加的VL数量 # -1 表示不限制 max-vls-per-flag: -1 @@ -190,6 +192,7 @@ debug-pipeline-on-join: false # 启用实验性检查 experimental-checks: false +reset-punishment-execute-count: true # 取消有问题的格挡 reset-item-usage-on-item-update: true From 2f51a64c059828418423f77a07e778318eb3d170 Mon Sep 17 00:00:00 2001 From: GigaZelensky Date: Mon, 19 May 2025 18:05:07 +0100 Subject: [PATCH 30/34] undo vl scale delay This reverts commit 4d6c9c64cd106191ac68c8dd425847ddd2c60b77. --- .../grim/grimac/checks/impl/prediction/OffsetHandler.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java index 3cc698232a..8667745de6 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java @@ -21,8 +21,6 @@ public class OffsetHandler extends Check implements PostPredictionCheck { double maxAdvantage; double maxCeiling; double vlScale; - long vlScaleDelay; - long lastScaledFlagTime = 0; double maxVlsPerFlag; double setbackViolationThreshold; // Current advantage gained @@ -69,11 +67,10 @@ public void onPredictionComplete(final PredictionComplete predictionComplete) { } double extra = Math.ceil(offset * vlScale) - 1.0; - if (extra > 0 && (vlScaleDelay <= 0 || System.currentTimeMillis() - lastScaledFlagTime >= vlScaleDelay)) { + if (extra > 0) { int amount = (int) Math.min(maxVlsPerFlag, extra); violations += amount; player.punishmentManager.addExtraViolation(this, amount); - lastScaledFlagTime = System.currentTimeMillis(); } if ((advantageGained >= maxAdvantage || offset >= immediateSetbackThreshold) @@ -117,7 +114,6 @@ public void onReload(ConfigManager config) { maxAdvantage = config.getDoubleElse("Simulation.max-advantage", 1); maxCeiling = config.getDoubleElse("Simulation.max-ceiling", 4); vlScale = Math.max(1.0, config.getDoubleElse("Simulation.vl-scale", 1)); - vlScaleDelay = config.getLongElse("Simulation.vl-scale-delay", 0L); maxVlsPerFlag = config.getDoubleElse("Simulation.max-vls-per-flag", -1); setbackViolationThreshold = config.getDoubleElse("Simulation.setback-violation-threshold", 1); if (maxAdvantage == -1) maxAdvantage = Double.MAX_VALUE; From 69a0d7f82fce3f678b3c3944c950664ac99b3908 Mon Sep 17 00:00:00 2001 From: GigaZelensky <85454363+GigaZelensky@users.noreply.github.com> Date: Mon, 19 May 2025 18:29:05 +0100 Subject: [PATCH 31/34] Add VL scale delay support --- .../checks/impl/prediction/OffsetHandler.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java index 8667745de6..76b0eece25 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java @@ -8,6 +8,9 @@ import ac.grim.grimac.checks.type.PostPredictionCheck; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.update.PredictionComplete; +import ac.grim.grimac.platform.api.scheduler.PlatformScheduler; + +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -21,6 +24,7 @@ public class OffsetHandler extends Check implements PostPredictionCheck { double maxAdvantage; double maxCeiling; double vlScale; + long vlScaleDelay; double maxVlsPerFlag; double setbackViolationThreshold; // Current advantage gained @@ -70,7 +74,24 @@ public void onPredictionComplete(final PredictionComplete predictionComplete) { if (extra > 0) { int amount = (int) Math.min(maxVlsPerFlag, extra); violations += amount; - player.punishmentManager.addExtraViolation(this, amount); + + if (vlScaleDelay > 0 && player.platformPlayer != null) { + for (int i = 0; i < amount; i++) { + long ticks = PlatformScheduler.convertTimeToTicks(vlScaleDelay * (i + 1), TimeUnit.MILLISECONDS); + GrimAPI.INSTANCE.getScheduler().getEntityScheduler().runDelayed( + player.platformPlayer, + GrimAPI.INSTANCE.getGrimPlugin(), + () -> { + player.punishmentManager.addExtraViolation(this, 1); + alert(verbose); + }, + null, + ticks + ); + } + } else { + player.punishmentManager.addExtraViolation(this, amount); + } } if ((advantageGained >= maxAdvantage || offset >= immediateSetbackThreshold) @@ -114,6 +135,7 @@ public void onReload(ConfigManager config) { maxAdvantage = config.getDoubleElse("Simulation.max-advantage", 1); maxCeiling = config.getDoubleElse("Simulation.max-ceiling", 4); vlScale = Math.max(1.0, config.getDoubleElse("Simulation.vl-scale", 1)); + vlScaleDelay = config.getLongElse("Simulation.vl-scale-delay", 0L); maxVlsPerFlag = config.getDoubleElse("Simulation.max-vls-per-flag", -1); setbackViolationThreshold = config.getDoubleElse("Simulation.setback-violation-threshold", 1); if (maxAdvantage == -1) maxAdvantage = Double.MAX_VALUE; From 0a3d73e7908a5317a70f062329e121ba2ca22de6 Mon Sep 17 00:00:00 2001 From: GigaZelensky <85454363+GigaZelensky@users.noreply.github.com> Date: Mon, 19 May 2025 22:59:33 +0100 Subject: [PATCH 32/34] Add translated config comments --- common/src/main/resources/config/de.yml | 3 ++- common/src/main/resources/config/en.yml | 1 + common/src/main/resources/config/es.yml | 3 ++- common/src/main/resources/config/fr.yml | 3 ++- common/src/main/resources/config/it.yml | 3 ++- common/src/main/resources/config/ja.yml | 3 ++- common/src/main/resources/config/nl.yml | 3 ++- common/src/main/resources/config/pt.yml | 3 ++- common/src/main/resources/config/ru.yml | 4 ++-- common/src/main/resources/config/tr.yml | 3 ++- common/src/main/resources/config/zh.yml | 5 ++--- 11 files changed, 21 insertions(+), 13 deletions(-) diff --git a/common/src/main/resources/config/de.yml b/common/src/main/resources/config/de.yml index 29c3f072e7..34f1f9d6c8 100644 --- a/common/src/main/resources/config/de.yml +++ b/common/src/main/resources/config/de.yml @@ -69,7 +69,7 @@ Simulation: # Wie stark sollen wir den VL-Gewinn anhand des Vorteils des Spielers skaliieren? # ≤1 für keine Skalierung (altes Verhalten) vl-scale: 1 - # Delay in milliseconds between flags for VL scaling + # Verzögerung in Millisekunden zwischen Flags für die VL-Skalierung vl-scale-delay: 0 # Beschränkt die Menge an VL, die pro Flag hinzugefügt werden kann # -1 deaktiviert die Begrenzung @@ -190,6 +190,7 @@ debug-pipeline-on-join: false # Aktiviert experimentelle Prüfungen experimental-checks: false +# Setzt die Ausführungsanzahl einer Bestrafung zurück, wenn der VL unter den Schwellenwert fällt reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/en.yml b/common/src/main/resources/config/en.yml index cc867d3d46..fb16a5dfbe 100644 --- a/common/src/main/resources/config/en.yml +++ b/common/src/main/resources/config/en.yml @@ -191,6 +191,7 @@ debug-pipeline-on-join: false # Enables experimental checks experimental-checks: false +# Resets how many times a punishment command has executed when the player's VL drops below its threshold reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/es.yml b/common/src/main/resources/config/es.yml index 16092a3c04..00cd9c1b2c 100644 --- a/common/src/main/resources/config/es.yml +++ b/common/src/main/resources/config/es.yml @@ -70,7 +70,7 @@ Simulation: # ¿Cuánto deberíamos escalar la ganancia de VL por una bandera según la ventaja del jugador? # ≤1 para no escalar (comportamiento antiguo) vl-scale: 1 - # Delay in milliseconds between flags for VL scaling + # Retraso en milisegundos entre flags para el escalado de VL vl-scale-delay: 0 # Limita la cantidad de VL que el jugador puede ganar por cada bandera # -1 para desactivar @@ -191,6 +191,7 @@ debug-pipeline-on-join: false # Habilitar comprobaciones experimentales experimental-checks: false +# Restablece el conteo de ejecuciones del castigo cuando el VL del jugador baja del umbral reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/fr.yml b/common/src/main/resources/config/fr.yml index 4c4f76177a..0a7d499d5d 100644 --- a/common/src/main/resources/config/fr.yml +++ b/common/src/main/resources/config/fr.yml @@ -69,7 +69,7 @@ Simulation: # Dans quelle mesure doit-on augmenter les VL en fonction de l'avantage du joueur ? # ≤1 pour conserver l'ancien comportement sans échelle vl-scale: 1 - # Delay in milliseconds between flags for VL scaling + # Délai en millisecondes entre les flags pour la mise à l'échelle du VL vl-scale-delay: 0 # Limite la quantité de VL que le joueur peut gagner par alerte # -1 pour désactiver @@ -186,6 +186,7 @@ debug-pipeline-on-join: false # Active les vérifications expérimentales experimental-checks: false +# Réinitialise le nombre d'exécutions de la sanction lorsque le VL du joueur repasse sous le seuil reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/it.yml b/common/src/main/resources/config/it.yml index 0198975ba1..e3ab3a92f4 100644 --- a/common/src/main/resources/config/it.yml +++ b/common/src/main/resources/config/it.yml @@ -61,7 +61,7 @@ Simulation: # Di quanto dobbiamo scalare l'aumento di VL di un flag in base al vantaggio del giocatore? # ≤1 per mantenere il vecchio comportamento vl-scale: 1 - # Delay in milliseconds between flags for VL scaling + # Ritardo in millisecondi tra le flag per la scalatura VL vl-scale-delay: 0 # Limita quante VL si possono ottenere per ogni flag # -1 per disattivare @@ -168,6 +168,7 @@ debug-pipeline-on-join: false # Enables experimental checks experimental-checks: false +# Reimposta il conteggio di esecuzione della punizione quando il VL del giocatore scende sotto la soglia reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/ja.yml b/common/src/main/resources/config/ja.yml index 2c14fe9063..85458d04bc 100644 --- a/common/src/main/resources/config/ja.yml +++ b/common/src/main/resources/config/ja.yml @@ -71,7 +71,7 @@ Simulation: # プレイヤーのアドバンテージに応じてVL増加量をどれだけスケールさせるか # 1以下で従来の挙動になります vl-scale: 1 - # Delay in milliseconds between flags for VL scaling + # VLスケーリングのフラグ間の遅延(ミリ秒) vl-scale-delay: 0 # 1回のフラグで増加できるVLの上限 # -1で無制限 @@ -199,6 +199,7 @@ debug-pipeline-on-join: false # 実験的なチェックを有効にします experimental-checks: false +# プレイヤーのVLがしきい値を下回ったときに制裁実行回数をリセット reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/nl.yml b/common/src/main/resources/config/nl.yml index f03bd59ec3..a0a2c6cf6b 100644 --- a/common/src/main/resources/config/nl.yml +++ b/common/src/main/resources/config/nl.yml @@ -69,7 +69,7 @@ Simulation: # Hoeveel moeten we de VL-verhoging van een flag schalen op basis van het voordeel van de speler? # ≤1 voor geen schaal (oud gedrag) vl-scale: 1 - # Delay in milliseconds between flags for VL scaling + # Vertraging in milliseconden tussen vlaggen voor VL-schaal vl-scale-delay: 0 # Beperkt hoeveel VL een speler per flag kan krijgen # -1 om uit te schakelen @@ -190,6 +190,7 @@ debug-pipeline-on-join: false # Experimentele controles inschakelen experimental-checks: false +# Reset het aantal keren dat een straf is uitgevoerd wanneer de VL onder de drempel zakt reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/pt.yml b/common/src/main/resources/config/pt.yml index 0c4892b74e..423390ef26 100644 --- a/common/src/main/resources/config/pt.yml +++ b/common/src/main/resources/config/pt.yml @@ -70,7 +70,7 @@ Simulation: # Quanto devemos escalar o ganho de VL de um flag com base na vantagem do jogador? # ≤1 para manter o comportamento antigo vl-scale: 1 - # Delay in milliseconds between flags for VL scaling + # Atraso em milissegundos entre flags para o escalonamento de VL vl-scale-delay: 0 # Limita a quantidade de VL que o jogador pode ganhar por flag # -1 para desativar @@ -195,6 +195,7 @@ debug-pipeline-on-join: false # Habilita verificações experimentais. experimental-checks: false +# Reinicia a contagem de execuções da punição quando o VL do jogador fica abaixo do limite reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/ru.yml b/common/src/main/resources/config/ru.yml index c68321ad8b..803fe7910e 100644 --- a/common/src/main/resources/config/ru.yml +++ b/common/src/main/resources/config/ru.yml @@ -69,9 +69,8 @@ Simulation: # Насколько увеличивать получение VL в зависимости от преимущества игрока? # ≤1 — оставить старое поведение vl-scale: 1 - # Delay in milliseconds between flags for VL scaling + # Задержка в миллисекундах между флагами для масштабирования VL vl-scale-delay: 0 - # Ограничивает количество VL, которое можно получить за один флаг # -1 для отключения max-vls-per-flag: -1 # Порог уровня нарушения для отката @@ -186,6 +185,7 @@ debug-pipeline-on-join: false # Включает экспериментальные проверки experimental-checks: false +# Сбрасывать количество выполнения наказания, когда VL игрока опускается ниже порога reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/tr.yml b/common/src/main/resources/config/tr.yml index 22f8383d2a..8901fab08c 100644 --- a/common/src/main/resources/config/tr.yml +++ b/common/src/main/resources/config/tr.yml @@ -69,7 +69,7 @@ Simulation: # Oyuncunun avantajına bağlı olarak VL artışını ne kadar ölçekleyelim? # ≤1 eski davranış için vl-scale: 1 - # Delay in milliseconds between flags for VL scaling + # VL ölçeklendirme için bayraklar arasındaki gecikme (ms) vl-scale-delay: 0 # Her bir flagden alınabilecek VL miktarını sınırlar # -1 devre dışı bırakır @@ -190,6 +190,7 @@ debug-pipeline-on-join: false # Deneysel kontrolleri etkinleştir experimental-checks: false +# Oyuncunun VL degeri esik altina dustugunde cezanin yurutulme sayisini sifirlar reset-punishment-execute-count: true reset-item-usage-on-item-update: true diff --git a/common/src/main/resources/config/zh.yml b/common/src/main/resources/config/zh.yml index c07698e1dc..6853a3b56e 100644 --- a/common/src/main/resources/config/zh.yml +++ b/common/src/main/resources/config/zh.yml @@ -74,10 +74,8 @@ Simulation: # 根据玩家的优势对VL增量进行缩放 # ≤1 表示与以前相同 vl-scale: 1 - # Delay in milliseconds between flags for VL scaling vl-scale-delay: 0 - # 限制每次触发可增加的VL数量 - # -1 表示不限制 + # VL缩放标记之间的延迟(毫秒) max-vls-per-flag: -1 # 设置回退的违规等级阈值 # 1 为旧行为 @@ -192,6 +190,7 @@ debug-pipeline-on-join: false # 启用实验性检查 experimental-checks: false +# 当玩家的VL低于阈值时重置头期执行次数 reset-punishment-execute-count: true # 取消有问题的格挡 From 9f5bb99bcc44e037621c92c4e4e7b1883c05a9e7 Mon Sep 17 00:00:00 2001 From: GigaZelensky Date: Mon, 19 May 2025 23:09:04 +0100 Subject: [PATCH 33/34] aesthetic thing --- .../ac/grim/grimac/checks/impl/prediction/OffsetHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java index 76b0eece25..3b278b3207 100644 --- a/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java +++ b/common/src/main/java/ac/grim/grimac/checks/impl/prediction/OffsetHandler.java @@ -11,7 +11,6 @@ import ac.grim.grimac.platform.api.scheduler.PlatformScheduler; import java.util.concurrent.TimeUnit; - import java.util.concurrent.atomic.AtomicInteger; @CheckData(name = "Simulation", decay = 0.02) From 628ced411c649f89acc37dd8b9657ed5dc287b03 Mon Sep 17 00:00:00 2001 From: GigaZelensky Date: Tue, 20 May 2025 00:06:39 +0100 Subject: [PATCH 34/34] fix translation --- common/src/main/resources/config/ru.yml | 1 + common/src/main/resources/config/zh.yml | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/common/src/main/resources/config/ru.yml b/common/src/main/resources/config/ru.yml index 803fe7910e..4eb44941eb 100644 --- a/common/src/main/resources/config/ru.yml +++ b/common/src/main/resources/config/ru.yml @@ -71,6 +71,7 @@ Simulation: vl-scale: 1 # Задержка в миллисекундах между флагами для масштабирования VL vl-scale-delay: 0 + # Ограничивает количество VL, которое можно получить за один флаг # -1 для отключения max-vls-per-flag: -1 # Порог уровня нарушения для отката diff --git a/common/src/main/resources/config/zh.yml b/common/src/main/resources/config/zh.yml index 6853a3b56e..7cdd42be7d 100644 --- a/common/src/main/resources/config/zh.yml +++ b/common/src/main/resources/config/zh.yml @@ -74,8 +74,10 @@ Simulation: # 根据玩家的优势对VL增量进行缩放 # ≤1 表示与以前相同 vl-scale: 1 - vl-scale-delay: 0 # VL缩放标记之间的延迟(毫秒) + vl-scale-delay: 0 + # 限制每次触发可增加的VL数量 + # -1 表示不限制 max-vls-per-flag: -1 # 设置回退的违规等级阈值 # 1 为旧行为