using System.Linq; using Content.Shared.Administration.Logs; using Content.Server.Administration.Systems; using Content.Shared.CCVar; using Content.Shared.Chat; using Content.Server.Chat.Managers; using Content.Shared.Customization.Systems; using Content.Shared.Database; using Content.Shared.GameTicking; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; using Content.Server.Players.PlayTimeTracking; using Content.Shared.Players; using Content.Shared.Preferences; 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; public sealed class TraitSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly CharacterRequirementsSystem _characterRequirements = default!; [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!; [Dependency] private readonly IConfigurationManager _configuration = default!; [Dependency] private readonly IComponentFactory _componentFactory = 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!; [Dependency] private readonly ISharedAdminLogManager _adminLogManager = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnPlayerSpawnComplete); SubscribeLocalEvent(OnProfileLoad); } // When the player is spawned in, add all trait components selected during character creation private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args) => ApplyTraits(args.Mob, args.JobId, args.Profile, _playTimeTracking.GetTrackerTimes(args.Player), args.Player.ContentData()?.Whitelisted ?? false); private void OnProfileLoad(LoadProfileExtensionsEvent args) => ApplyTraits(args.Mob, args.JobId, args.Profile, _playTimeTracking.GetTrackerTimes(args.Player), args.Player.ContentData()?.Whitelisted ?? false); /// /// Adds the traits selected by a player to an entity. /// public void ApplyTraits(EntityUid uid, ProtoId? jobId, HumanoidCharacterProfile profile, Dictionary playTimes, bool whitelisted, bool punishCheater = true) { var pointsTotal = _configuration.GetCVar(CCVars.GameTraitsDefaultPoints); var traitSelections = _configuration.GetCVar(CCVars.GameTraitsMax); if (jobId is not null && !_prototype.TryIndex(jobId, out var jobPrototype) && jobPrototype is not null && !jobPrototype.ApplyTraits) return; if (_prototype.TryIndex(profile.Species, out var speciesProto)) pointsTotal += speciesProto.BonusTraitPoints; var jobPrototypeToUse = _prototype.Index(jobId ?? _prototype.EnumeratePrototypes().First().ID); var sortedTraits = new List(); foreach (var traitId in profile.TraitPreferences) { if (_prototype.TryIndex(traitId, out var traitPrototype)) { sortedTraits.Add(traitPrototype); } else { DebugTools.Assert($"No trait found with ID {traitId}!"); return; } } sortedTraits.Sort(); var traitsToAdd = new List(); foreach (var traitPrototype in sortedTraits) { if (!traitPrototype.Enable || // WD EDIT !_characterRequirements.CheckRequirementsValid( traitPrototype.Requirements, jobPrototypeToUse, profile, playTimes, whitelisted, traitPrototype, EntityManager, _prototype, _configuration, out _)) continue; // To check for cheaters. :FaridaBirb.png: pointsTotal += traitPrototype.Points; traitSelections -= traitPrototype.Slots; traitsToAdd.Add(traitPrototype); } if (pointsTotal < 0 || traitSelections < 0) { _adminLogManager.Add(LogType.AdminMessage, LogImpact.Extreme, $"{ToPrettyString(uid):player} attempted to spawn with illegal trait selection total {profile.TraitPreferences.Count}, and {pointsTotal} net trait points"); if (punishCheater) PunishCheater(uid); if (_playerManager.TryGetSessionByEntity(uid, out var targetPlayer)) { var feedbackMessage = "You have attempted to spawn with an illegal trait list. None of your traits will be applied. If you think this is in error, please return to the lobby and correct your trait selections."; _chatManager.ChatMessageToOne( ChatChannel.Emotes, feedbackMessage, feedbackMessage, EntityUid.Invalid, false, targetPlayer.Channel); } return; } foreach (var trait in traitsToAdd) AddTrait(uid, trait); } /// /// Adds a single Trait Prototype to an Entity. /// public void AddTrait(EntityUid uid, TraitPrototype traitPrototype) { _adminLogManager.Add(LogType.Trait, LogImpact.Low, $"Trait {traitPrototype.ID} was added to {ToPrettyString(uid)}"); // WWDP ADD 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) { 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.UserId); 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); } }