From 2fc1f25bc0daa29e2017490adbdf41f9db4f4927 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 22 Dec 2024 14:55:22 -0500 Subject: [PATCH] Traits System Anticheat (#1358) # Description It turns out that there was no system in place for serverside fact checking of whether or not people have a legal traits list. Last night a bug was reported whereby a player used Cheat Engine to give himself every trait in the game, bypassing the points system entirely. It's not actually possible to reduce a trait selection down to a legal list without creating interesting race conditions, which limits my options on how to deal with it. So I made it a vote on the Einstein Engines discord, and the vote was unanimous. PUNISH THE CHEATERS.

Media

https://www.youtube.com/watch?v=X2QMN0a_TrA

# Changelog :cl: - add: Implemented Anti-cheat for Traits. Attempting to join a round with an illegal traits list will result in hilarious consequences. --- Content.Server/Traits/TraitSystem.cs | 61 ++++++++++++++++++++++++++++ Content.Shared/CCVar/CCVars.cs | 7 ++++ 2 files changed, 68 insertions(+) diff --git a/Content.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs index 75771a57432..7a028b381ad 100644 --- a/Content.Server/Traits/TraitSystem.cs +++ b/Content.Server/Traits/TraitSystem.cs @@ -1,14 +1,23 @@ using System.Linq; +using Content.Server.Administration.Logs; +using Content.Server.Administration.Systems; +using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.Players.PlayTimeTracking; +using Content.Shared.CCVar; +using Content.Shared.Chat; using Content.Shared.Customization.Systems; +using Content.Shared.Database; using Content.Shared.Players; using Content.Shared.Roles; using Content.Shared.Traits; +using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Prototypes; +using Robust.Shared.Random; using Robust.Shared.Serialization.Manager; using Robust.Shared.Utility; +using Timer = Robust.Shared.Timing.Timer; namespace Content.Server.Traits; @@ -20,6 +29,11 @@ public sealed class TraitSystem : EntitySystem [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!; [Dependency] private readonly IConfigurationManager _configuration = default!; [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly IAdminLogManager _adminLog = default!; + [Dependency] private readonly AdminSystem _adminSystem = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IChatManager _chatManager = default!; public override void Initialize() { @@ -31,6 +45,9 @@ public override void Initialize() // When the player is spawned in, add all trait components selected during character creation private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args) { + var pointsTotal = _configuration.GetCVar(CCVars.GameTraitsDefaultPoints); + var traitSelections = _configuration.GetCVar(CCVars.GameTraitsMax); + foreach (var traitId in args.Profile.TraitPreferences) { if (!_prototype.TryIndex(traitId, out var traitPrototype)) @@ -47,8 +64,15 @@ private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args) out _)) continue; + // To check for cheaters. :FaridaBirb.png: + pointsTotal += traitPrototype.Points; + --traitSelections; + AddTrait(args.Mob, traitPrototype); } + + if (pointsTotal < 0 || traitSelections < 0) + PunishCheater(args.Mob); } /// @@ -59,4 +83,41 @@ public void AddTrait(EntityUid uid, TraitPrototype traitPrototype) foreach (var function in traitPrototype.Functions) function.OnPlayerSpawn(uid, _componentFactory, EntityManager, _serialization); } + + /// + /// On a non-cheating client, it's not possible to save a character with a negative number of traits. This can however + /// trigger incorrectly if a character was saved, and then at a later point in time an admin changes the traits Cvars to reduce the points. + /// Or if the points costs of traits is increased. + /// + private void PunishCheater(EntityUid uid) + { + _adminLog.Add(LogType.AdminMessage, LogImpact.High, + $"{ToPrettyString(uid):entity} attempted to spawn with an invalid trait list. This might be a mistake, or they might be cheating"); + + if (!_configuration.GetCVar(CCVars.TraitsPunishCheaters) + || !_playerManager.TryGetSessionByEntity(uid, out var targetPlayer)) + return; + + // For maximum comedic effect, this is plenty of time for the cheater to get on station and start interacting with people. + var timeToDestroy = _random.NextFloat(120, 360); + + Timer.Spawn(TimeSpan.FromSeconds(timeToDestroy), () => VaporizeCheater(targetPlayer)); + } + + /// + /// https://www.youtube.com/watch?v=X2QMN0a_TrA + /// + private void VaporizeCheater (Robust.Shared.Player.ICommonSession targetPlayer) + { + _adminSystem.Erase(targetPlayer); + + var feedbackMessage = $"[font size=24][color=#ff0000]{"You have spawned in with an illegal trait point total. If this was a result of cheats, then your nonexistence is a skill issue. Otherwise, feel free to click 'Return To Lobby', and fix your trait selections."}[/color][/font]"; + _chatManager.ChatMessageToOne( + ChatChannel.Emotes, + feedbackMessage, + feedbackMessage, + EntityUid.Invalid, + false, + targetPlayer.Channel); + } } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index ef63d89af96..05a0a7f1883 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -381,6 +381,13 @@ public static readonly CVarDef public static readonly CVarDef GameTraitsDefaultPoints = CVarDef.Create("game.traits_default_points", 10, CVar.REPLICATED); + /// + /// Whether the game will SMITE people who used cheat engine to spawn with all of the traits. + /// Illegal trait totals will still be logged even if this is disabled. + /// If you are intending to decrease the trait points availability, or modify the costs of traits, consider temporarily disabling this. + /// + public static readonly CVarDef TraitsPunishCheaters = + CVarDef.Create("game.traits_punish_cheaters", true, CVar.REPLICATED); /// /// Whether to allow characters to select loadout items.