diff --git a/src/main/java/top/infsky/timerecorder/anticheat/Check.java b/src/main/java/top/infsky/timerecorder/anticheat/Check.java new file mode 100644 index 0000000..db60e56 --- /dev/null +++ b/src/main/java/top/infsky/timerecorder/anticheat/Check.java @@ -0,0 +1,35 @@ +package top.infsky.timerecorder.anticheat; + +import lombok.Getter; +import top.infsky.timerecorder.data.PlayerData; +import top.infsky.timerecorder.log.LogUtils; + +@Getter +public abstract class Check { + public String checkName; + public PlayerData player; + + public Check(String checkName, PlayerData player) { + this.checkName = checkName; + this.player = player; + } + + public final void flag() { + assert player.antiCheat != null; + player.antiCheat.violations++; + LogUtils.alert(player.getName(), checkName); + } + + public final void flag(String extraMsg) { + assert player.antiCheat != null; + player.antiCheat.violations++; + LogUtils.alert(player.getName(), checkName, extraMsg); + } + + public abstract void _onTick(); + + public void update() { + if (player == null) return; + _onTick(); + } +} diff --git a/src/main/java/top/infsky/timerecorder/anticheat/CheckManager.java b/src/main/java/top/infsky/timerecorder/anticheat/CheckManager.java new file mode 100644 index 0000000..178dede --- /dev/null +++ b/src/main/java/top/infsky/timerecorder/anticheat/CheckManager.java @@ -0,0 +1,30 @@ +package top.infsky.timerecorder.anticheat; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.infsky.timerecorder.anticheat.checks.FlightA; +import top.infsky.timerecorder.data.PlayerData; + +import java.util.ArrayList; +import java.util.List; + +public class CheckManager { + public List checks = new ArrayList<>(); + public double violations = 0; + public CheckManager(List checks) { + this.checks.addAll(checks); + } + + @Contract("_ -> new") + public static @NotNull CheckManager create(PlayerData playerData) { + return new CheckManager(List.of( + new FlightA(playerData) + )); + } + + public void update() { + for (Check check : checks) { + check.update(); + } + } +} diff --git a/src/main/java/top/infsky/timerecorder/anticheat/checks/FlightA.java b/src/main/java/top/infsky/timerecorder/anticheat/checks/FlightA.java new file mode 100644 index 0000000..54ecbec --- /dev/null +++ b/src/main/java/top/infsky/timerecorder/anticheat/checks/FlightA.java @@ -0,0 +1,43 @@ +package top.infsky.timerecorder.anticheat.checks; + +import net.minecraft.world.phys.Vec3; +import top.infsky.timerecorder.anticheat.Check; +import top.infsky.timerecorder.config.ModConfig; +import top.infsky.timerecorder.data.PlayerData; + +public class FlightA extends Check { + public Vec3 lastPos; + public Vec3 lastPos2; + public Vec3 lastOnGroundPos; + public short disableTick = 0; + + public FlightA(PlayerData player) { + super("FlightA", player); + assert player.player != null; + lastPos = player.player.position(); + } + + @Override + public void _onTick() { + assert player.player != null; + + // fix jump + if (player.player.onGround()) { + lastOnGroundPos = player.player.position(); + disableTick = 8; + } + + if (!player.player.onGround() && disableTick > 0 + && player.player.position().y() - lastOnGroundPos.y() < 1.25219 + ModConfig.INSTANCE.getAntiCheat().getThreshold()) { + disableTick--; + } else if (lastPos2 != null && !player.player.onGround()) { + disableTick = 0; + if (lastPos.y() - player.player.position().y() < ModConfig.INSTANCE.getAntiCheat().getThreshold() && + lastPos2.y() - lastPos.y() <= ModConfig.INSTANCE.getAntiCheat().getThreshold()) { + flag(String.format("Pos:%s OnGround:%s", player.player.position(), player.player.onGround())); + } + } + lastPos2 = lastPos; + lastPos = player.player.position(); + } +} diff --git a/src/main/java/top/infsky/timerecorder/command/AlertCommand.java b/src/main/java/top/infsky/timerecorder/command/AlertCommand.java new file mode 100644 index 0000000..1af1f59 --- /dev/null +++ b/src/main/java/top/infsky/timerecorder/command/AlertCommand.java @@ -0,0 +1,21 @@ +package top.infsky.timerecorder.command; + +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; +import top.infsky.timerecorder.config.ModConfig; + +import java.util.Objects; + +public class AlertCommand { + public static int execute(@NotNull CommandContext context) { + if (context.getSource().isPlayer()) { + ModConfig.INSTANCE.getAntiCheat().getAllowAlertPlayers().add(Objects.requireNonNull(context.getSource().getPlayer()).getStringUUID()); + ModConfig.INSTANCE.save(); + context.getSource().sendSystemMessage(Component.literal("警报已开启").withStyle(ChatFormatting.AQUA).withStyle(ChatFormatting.BOLD)); + } + return 1; + } +} diff --git a/src/main/java/top/infsky/timerecorder/command/ICmdEvent.java b/src/main/java/top/infsky/timerecorder/command/ICmdEvent.java index 8693c82..7654076 100644 --- a/src/main/java/top/infsky/timerecorder/command/ICmdEvent.java +++ b/src/main/java/top/infsky/timerecorder/command/ICmdEvent.java @@ -12,32 +12,35 @@ public class ICmdEvent { public static void register(@NotNull CommandDispatcher dispatcher) { dispatcher.register(literal("tr") // 没参数默认显示帮助信息 .executes(HelpCommand::execute) - .then(literal("help") // 显示帮助信息 - .executes(HelpCommand::execute)) - .then(literal("reload") // 热重载 - .requires(source -> source.hasPermission(2)) - .executes(ReloadCommand::execute)) - .then(literal("dump") - .requires(source -> source.hasPermission(2)) - .then(literal("save") // 保存迄今的统计数据 - .executes(DumpCommand.Save::execute)) - .then(literal("load") // 从文件还原统计数据 - .executes(DumpCommand.Load::execute) - .then(argument("filename", StringArgumentType.string()) - .executes(DumpCommand.Load.Custom::execute)))) - .then(literal("report") // 对自己显示当日截止目前的统计信息 - .executes(ReportCommand::execute)) - .then(literal("reportall") // 对自己显示当日截止目前的所有玩家的统计信息 - .requires(source -> source.hasPermission(2)) - .executes(ReportAllCommand::execute)) - .then(literal("reportqq") // 对所有人显示和发送当日截止目前的统计信息到QQ - .requires(source -> source.hasPermission(2)) - .executes(ReportQQCommand::execute)) - .then(literal("recall") // 撤回上一条消息 - .executes(RecallCommand::execute) - .then(literal("confirm") // 确认撤回(由/tr recall触发) - .then(argument("message_id", IntegerArgumentType.integer()) - .executes(RecallCommand.Confirm::execute)))) + .then(literal("help") // 显示帮助信息 + .executes(HelpCommand::execute)) + .then(literal("reload") // 热重载 + .requires(source -> source.hasPermission(2)) + .executes(ReloadCommand::execute)) + .then(literal("dump") + .requires(source -> source.hasPermission(2)) + .then(literal("save") // 保存迄今的统计数据 + .executes(DumpCommand.Save::execute)) + .then(literal("load") // 从文件还原统计数据 + .executes(DumpCommand.Load::execute) + .then(argument("filename", StringArgumentType.string()) + .executes(DumpCommand.Load.Custom::execute)))) + .then(literal("report") // 对自己显示当日截止目前的统计信息 + .executes(ReportCommand::execute)) + .then(literal("reportall") // 对自己显示当日截止目前的所有玩家的统计信息 + .requires(source -> source.hasPermission(2)) + .executes(ReportAllCommand::execute)) + .then(literal("reportqq") // 对所有人显示和发送当日截止目前的统计信息到QQ + .requires(source -> source.hasPermission(2)) + .executes(ReportQQCommand::execute)) + .then(literal("recall") // 撤回上一条消息 + .executes(RecallCommand::execute) + .then(literal("confirm") // 确认撤回(由/tr recall触发) + .then(argument("message_id", IntegerArgumentType.integer()) + .executes(RecallCommand.Confirm::execute)))) + .then(literal("alert") // 启用警报 + .requires(source -> source.hasPermission(2)) + .executes(AlertCommand::execute)) ); } } diff --git a/src/main/java/top/infsky/timerecorder/config/AntiCheatConfig.java b/src/main/java/top/infsky/timerecorder/config/AntiCheatConfig.java index 8791fdc..dea094d 100644 --- a/src/main/java/top/infsky/timerecorder/config/AntiCheatConfig.java +++ b/src/main/java/top/infsky/timerecorder/config/AntiCheatConfig.java @@ -6,11 +6,18 @@ import lombok.Setter; import org.tomlj.TomlTable; +import java.util.ArrayList; +import java.util.List; + @Getter @Setter public class AntiCheatConfig extends AutoLoadTomlConfig { @TableField(rightComment = "启用反作弊") private boolean enable = false; + @TableField(rightComment = "允许展示警报的玩家") + private List allowAlertPlayers = new ArrayList<>(); + @TableField(rightComment = "最大偏移量") + private double threshold = 0.001; public AntiCheatConfig() { super(null); diff --git a/src/main/java/top/infsky/timerecorder/config/ModConfig.java b/src/main/java/top/infsky/timerecorder/config/ModConfig.java index aac8c68..696e367 100644 --- a/src/main/java/top/infsky/timerecorder/config/ModConfig.java +++ b/src/main/java/top/infsky/timerecorder/config/ModConfig.java @@ -26,8 +26,8 @@ public class ModConfig extends AutoReloadToml { private CommonConfig common = new CommonConfig(); @TableField(value = "addon", topComment = "扩展") private AddonConfig addon = new AddonConfig(); -// @TableField(value = "anticheat", topComment = "反作弊(实验性)") -// private AntiCheatConfig antiCheatConfig = new AntiCheatConfig(); + @TableField(value = "anticheat", topComment = "反作弊(实验性)") + private AntiCheatConfig antiCheat = new AntiCheatConfig(); public ModConfig() { diff --git a/src/main/java/top/infsky/timerecorder/data/PlayerData.java b/src/main/java/top/infsky/timerecorder/data/PlayerData.java index 646be6c..30ec539 100644 --- a/src/main/java/top/infsky/timerecorder/data/PlayerData.java +++ b/src/main/java/top/infsky/timerecorder/data/PlayerData.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import top.infsky.timerecorder.Utils; +import top.infsky.timerecorder.anticheat.CheckManager; import top.infsky.timerecorder.compat.CarpetCompat; import top.infsky.timerecorder.config.ModConfig; import top.infsky.timerecorder.data.mcstats.StatsObject; @@ -23,7 +24,9 @@ public class PlayerData { @Nullable public Player player; // 玩家 @Nullable - public ServerStatsCounter vanillaStats; // 原版统计信息 + public ServerStatsCounter vanillaStats; // 原版统计信息 + @Nullable + public CheckManager antiCheat; // 反作弊模块 public String name; // 名字 @@ -53,6 +56,7 @@ public PlayerData(@NotNull Player gamePlayer, boolean isFakePlayer) { messageSent = new LinkedBlockingDeque<>(); vanillaStats = ((ServerPlayer) player).getStats(); statsObject = new StatsObject((ServerPlayer) player, vanillaStats); + antiCheat = CheckManager.create(this); } /** @@ -98,6 +102,7 @@ public void update() { OP = player.hasPermissions(2); if (fakePlayer) fakePlayer = CarpetCompat.isFakePlayer(player); playTime += 1; + if (ModConfig.INSTANCE.getAntiCheat().isEnable() && antiCheat != null) antiCheat.update(); } /** diff --git a/src/main/java/top/infsky/timerecorder/log/LogUtils.java b/src/main/java/top/infsky/timerecorder/log/LogUtils.java index 03bc7b8..00d0a8f 100644 --- a/src/main/java/top/infsky/timerecorder/log/LogUtils.java +++ b/src/main/java/top/infsky/timerecorder/log/LogUtils.java @@ -1,20 +1,40 @@ package top.infsky.timerecorder.log; import lombok.Getter; +import net.minecraft.network.chat.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import top.infsky.timerecorder.Utils; +import top.infsky.timerecorder.config.ModConfig; + +import java.util.Objects; +import java.util.UUID; @Getter public class LogUtils { public static final Logger LOGGER = LoggerFactory.getLogger("TimeRecorder"); // 抄代码抄的 public static void alert(String player, String module, String extraMsg) { -// LOGGER.info(String.format("§b§lTR§r§l> §r%s 触发了 %s | %s", player, module, extraMsg)); LOGGER.info(String.format("TR> %s 触发了 %s | %s", player, module, extraMsg)); + ModConfig.INSTANCE.getAntiCheat().getAllowAlertPlayers().forEach(string -> { + assert Utils.getSERVER() != null; + try { + Objects.requireNonNull(Utils.getSERVER().getPlayerList().getPlayer(UUID.fromString(string))).sendSystemMessage(Component.literal( + String.format("§b§lTR§r§l> §r%s 触发了 %s | %s", player, module, extraMsg) + )); + } catch (NullPointerException ignored) {} + }); } public static void alert(String player, String module) { -// LOGGER.info(String.format("§b§lTR§r§l> §r%s 触发了 %s", player, module)); LOGGER.info(String.format("TR> %s 触发了 %s", player, module)); + ModConfig.INSTANCE.getAntiCheat().getAllowAlertPlayers().forEach(string -> { + assert Utils.getSERVER() != null; + try { + Objects.requireNonNull(Utils.getSERVER().getPlayerList().getPlayer(UUID.fromString(string))).sendSystemMessage(Component.literal( + String.format("§b§lTR§r§l> §r%s 触发了 %s", player, module) + )); + } catch (NullPointerException ignored) {} + }); } }