using System.Linq; using Content.Server.Cargo.Components; using Content.Server.GameTicking; using Content.Server.Mind; using Content.Server.Station.Systems; using Content.Server.Store.Systems; using Content.Shared._Sunrise.StatsBoard; using Content.Shared.Bed.Sleep; using Content.Shared.Construction; using Content.Shared.Cuffs.Components; using Content.Shared.Damage; using Content.Shared.Doors.Systems; using Content.Shared.Electrocution; using Content.Shared.Fluids; using Content.Shared.Ghost; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Interaction.Components; using Content.Shared.Interaction.Events; using Content.Shared.Item; using Content.Shared.Mind.Components; using Content.Shared.Mobs; using Content.Shared.Slippery; using Content.Shared.Tag; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Localization; namespace Content.Server.StatsBoard; public sealed class StatsBoardSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly GameTicker _gameTicker = default!; private (EntityUid? killer, EntityUid? victim, TimeSpan time) _firstMurder = (null, null, TimeSpan.Zero); private EntityUid? _hamsterKiller; private int _jointCreated; private (EntityUid? clown, TimeSpan? time) _clownCuffed = (null, null); private readonly Dictionary _statisticEntries = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnDamageModify); SubscribeLocalEvent(OnSlippedEvent); SubscribeLocalEvent(OnCreamedEvent); SubscribeLocalEvent(OnInteractionAttempt); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnDoorEmagged); SubscribeLocalEvent(OnElectrocuted); SubscribeLocalEvent(OnItemPurchasedEvent); SubscribeLocalEvent(OnCuffedEvent); SubscribeLocalEvent(OnCraftedEvent); SubscribeLocalEvent(OnAbsorbedPuddleEvent); SubscribeLocalEvent(OnMindAdded); } private void OnMindAdded(EntityUid uid, ActorComponent comp, MindAddedMessage ev) { if (_statisticEntries.ContainsKey(uid) || ev.Mind.Comp.Session == null || HasComp(uid)) return; var value = new StatisticEntry(MetaData(uid).EntityName, ev.Mind.Comp.Session.UserId); _statisticEntries.Add(uid, value); } public void CleanEntries() { _firstMurder = (null, null, TimeSpan.Zero); _hamsterKiller = null; _jointCreated = 0; _clownCuffed = (null, TimeSpan.Zero); _statisticEntries.Clear(); } private void OnAbsorbedPuddleEvent(EntityUid uid, ActorComponent comp, ref AbsorberPudleEvent ev) { if (!_mindSystem.TryGetMind(comp.PlayerSession, out var mindId, out var mind)) return; if (_statisticEntries.TryGetValue(uid, out var value)) { value.AbsorbedPuddleCount += 1; } } private void OnCraftedEvent(EntityUid uid, ActorComponent comp, ref ItemConstructionCreated ev) { if (!_mindSystem.TryGetMind(comp.PlayerSession, out var mindId, out var mind)) return; if (!_statisticEntries.TryGetValue(uid, out var value)) return; if (!TryComp(ev.Item, out var metaDataComponent)) return; if (metaDataComponent.EntityPrototype == null) return; switch (metaDataComponent.EntityPrototype.ID) { case "Blunt": case "Joint": _jointCreated += 1; break; } } private void OnCuffedEvent(EntityUid uid, ActorComponent comp, ref CuffedEvent ev) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; value.CuffedCount += 1; if (_clownCuffed.clown != null) return; if (!HasComp(uid)) return; _clownCuffed.clown = uid; _clownCuffed.time = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); } private void OnItemPurchasedEvent(EntityUid uid, ActorComponent comp, ref SubtractCashEvent ev) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; if (ev.Currency != "Telecrystal") return; if (value.SpentTk == null) { value.SpentTk = ev.Cost.Int(); } else { value.SpentTk += ev.Cost.Int(); } } private void OnElectrocuted(EntityUid uid, ActorComponent comp, ElectrocutedEvent ev) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; value.ElectrocutedCount += 1; } private void OnDoorEmagged(EntityUid uid, ActorComponent comp, ref DoorEmaggedEvent ev) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; value.DoorEmagedCount += 1; } private void OnInteractionAttempt(EntityUid uid, ActorComponent comp, InteractionAttemptEvent args) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; if (!HasComp(args.Target)) return; if (MetaData(args.Target.Value).EntityPrototype == null) return; var entityPrototype = MetaData(args.Target.Value).EntityPrototype; if (entityPrototype is not { ID: "CaptainIDCard" }) return; if (value.IsInteractedCaptainCard) return; value.IsInteractedCaptainCard = true; } private void OnCreamedEvent(EntityUid uid, ActorComponent comp, ref CreamedEvent ev) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; value.CreamedCount += 1; } private void OnMobStateChanged(EntityUid uid, ActorComponent comp, MobStateChangedEvent args) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; switch (args.NewMobState) { case MobState.Dead: { value.DeadCount += 1; EntityUid? origin = null; if (args.Origin != null) { origin = args.Origin.Value; } if (_firstMurder.victim == null && HasComp(uid)) { _firstMurder.victim = uid; _firstMurder.killer = origin; _firstMurder.time = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); Logger.Info($"First Murder. CurTime: {_gameTiming.CurTime}, RoundStartTimeSpan: {_gameTicker.RoundStartTimeSpan}, Substract: {_gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan)}"); } if (origin != null) { if (_hamsterKiller == null && _tagSystem.HasTag(uid, "Hamster")) { _hamsterKiller = origin.Value; } if (!_statisticEntries.TryGetValue(origin.Value, out var originEntry)) return; if (_tagSystem.HasTag(uid, "Mouse")) { originEntry.KilledMouseCount += 1; } if (HasComp(uid)) originEntry.HumanoidKillCount += 1; } break; } } } private void OnDamageModify(EntityUid uid, ActorComponent comp, DamageChangedEvent ev) { DamageGetModify(uid, ev); if (ev.Origin != null) DamageTakeModify(ev.Origin.Value, ev); } private void DamageTakeModify(EntityUid uid, DamageChangedEvent ev) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; if (ev.DamageDelta == null) return; if (ev.DamageIncreased) { value.TotalInflictedDamage += ev.DamageDelta.GetTotal().Int(); } else { value.TotalInflictedHeal += Math.Abs(ev.DamageDelta.GetTotal().Int()); } } private void DamageGetModify(EntityUid uid, DamageChangedEvent ev) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; if (ev.DamageDelta == null) return; if (ev.DamageIncreased) { value.TotalTakeDamage += ev.DamageDelta.GetTotal().Int(); } else { value.TotalTakeHeal += Math.Abs(ev.DamageDelta.GetTotal().Int()); } } private void OnSlippedEvent(EntityUid uid, ActorComponent comp, ref SlippedEvent ev) { if (!_statisticEntries.TryGetValue(uid, out var value)) return; if (HasComp(uid)) value.SlippedCount += 1; } private StationBankAccountComponent? GetBankAccount(EntityUid? uid) { if (uid != null && TryComp(uid, out var bankAccount)) { return bankAccount; } return null; } public override void Update(float frameTime) { base.Update(frameTime); var statsQuery = EntityQueryEnumerator(); while (statsQuery.MoveNext(out var ent, out var comp)) { if (!_statisticEntries.TryGetValue(ent, out var value)) return; if (TryComp(ent, out var transformComponent) && transformComponent.GridUid == null && HasComp(ent)) value.SpaceTime += TimeSpan.FromSeconds(frameTime); if (TryComp(ent, out var cuffableComponent) && !cuffableComponent.CanStillInteract) value.CuffedTime += TimeSpan.FromSeconds(frameTime); if (HasComp(ent)) value.SleepTime += TimeSpan.FromSeconds(frameTime); } } public StatisticEntry[] GetStatisticEntries() { return _statisticEntries.Values.ToArray(); } public SharedStatisticEntry ConvertToSharedStatisticEntry(StatisticEntry entry) { return new SharedStatisticEntry(entry.Name, entry.FirstActor) { TotalTakeDamage = entry.TotalTakeDamage, TotalTakeHeal = entry.TotalTakeHeal, TotalInflictedDamage = entry.TotalInflictedDamage, TotalInflictedHeal = entry.TotalInflictedHeal, SlippedCount = entry.SlippedCount, CreamedCount = entry.CreamedCount, DoorEmagedCount = entry.DoorEmagedCount, ElectrocutedCount = entry.ElectrocutedCount, CuffedCount = entry.CuffedCount, AbsorbedPuddleCount = entry.AbsorbedPuddleCount, SpentTk = entry.SpentTk, DeadCount = entry.DeadCount, HumanoidKillCount = entry.HumanoidKillCount, KilledMouseCount = entry.KilledMouseCount, CuffedTime = entry.CuffedTime, SpaceTime = entry.SpaceTime, SleepTime = entry.SleepTime, IsInteractedCaptainCard = entry.IsInteractedCaptainCard, }; } public string GetRoundStats() { var result = ""; var totalSlipped = 0; var totalCreampied = 0; var totalDamage = 0; var totalHeal = 0; var totalDoorEmaged = 0; var maxSlippedCount = 0; var maxDeadCount = 0; var maxSpeciesCount = 0; var maxDoorEmagedCount = 0; var totalKilledMice = 0; var totalAbsorbedPuddle = 0; var maxKillsMice = 0; var totalCaptainCardInteracted = 0; var totalElectrocutedCount = 0; var totalSleepTime = TimeSpan.Zero; var minSpentTk = int.MaxValue; var maxHumKillCount = 0; var totalCuffedCount = 0; var maxTakeDamage = 0; var maxInflictedHeal = 0; var maxInflictedDamage = 0; var maxPuddleAbsorb = 0; var maxCuffedTime = TimeSpan.Zero; var maxSpaceTime = TimeSpan.Zero; var maxSleepTime = TimeSpan.Zero; string? mostPopularSpecies = null; Dictionary roundSpecies = new(); EntityUid? mostSlippedCharacter = null; EntityUid? mostDeadCharacter = null; EntityUid? mostDoorEmagedCharacter = null; EntityUid? mostKillsMiceCharacter = null; EntityUid? playerWithMinSpentTk = null; EntityUid? playerWithMaxHumKills = null; EntityUid? playerWithMaxDamage = null; EntityUid? playerWithLongestCuffedTime = null; EntityUid? playerWithLongestSpaceTime = null; EntityUid? playerWithLongestSleepTime = null; EntityUid? playerWithMostInflictedHeal = null; EntityUid? playerWithMostInflictedDamage = null; EntityUid? playerWithMostPuddleAbsorb = null; foreach (var (uid, data) in _statisticEntries) { if (TryComp(uid, out var humanoidAppearanceComponent)) { var speciesProto = _prototypeManager.Index(humanoidAppearanceComponent.Species); if (roundSpecies.TryGetValue(speciesProto.Name, out var count)) { roundSpecies[speciesProto.Name] = count + 1; } else { roundSpecies.Add(speciesProto.Name, 1); } } totalDoorEmaged += data.DoorEmagedCount; totalSlipped += data.SlippedCount; totalCreampied += data.CreamedCount; totalDamage += data.TotalTakeDamage; totalHeal += data.TotalTakeHeal; totalCuffedCount += data.CuffedCount; totalKilledMice += data.KilledMouseCount; totalSleepTime += data.SleepTime; totalAbsorbedPuddle += data.AbsorbedPuddleCount; totalElectrocutedCount += data.ElectrocutedCount; if (data.SlippedCount > maxSlippedCount) { maxSlippedCount = data.SlippedCount; mostSlippedCharacter = uid; } if (data.DoorEmagedCount > maxDoorEmagedCount) { maxDoorEmagedCount = data.DoorEmagedCount; mostDoorEmagedCharacter = uid; } if (data.DeadCount > maxDeadCount) { maxDeadCount = data.DeadCount; mostDeadCharacter = uid; } if (data.KilledMouseCount > maxKillsMice) { maxKillsMice = data.KilledMouseCount; mostKillsMiceCharacter = uid; } if (data.IsInteractedCaptainCard) { totalCaptainCardInteracted += 1; } if (data.SpentTk != null && data.SpentTk < minSpentTk) { minSpentTk = data.SpentTk.Value; playerWithMinSpentTk = uid; } if (data.HumanoidKillCount > maxHumKillCount) { maxHumKillCount = data.HumanoidKillCount; playerWithMaxHumKills = uid; } if (data.TotalTakeDamage > maxTakeDamage) { maxTakeDamage = data.TotalTakeDamage; playerWithMaxDamage = uid; } if (data.CuffedTime > maxCuffedTime) { maxCuffedTime = data.CuffedTime; playerWithLongestCuffedTime = uid; } if (data.SleepTime > maxSleepTime) { maxSleepTime = data.SleepTime; playerWithLongestSleepTime = uid; } if (data.SpaceTime > maxSpaceTime) { maxSpaceTime = data.SpaceTime; playerWithLongestSpaceTime = uid; } if (data.TotalInflictedHeal > maxInflictedHeal) { maxInflictedHeal = data.TotalInflictedHeal; playerWithMostInflictedHeal = uid; } if (data.TotalInflictedDamage > maxInflictedDamage) { maxInflictedDamage = data.TotalInflictedDamage; playerWithMostInflictedDamage = uid; } if (data.AbsorbedPuddleCount > maxPuddleAbsorb) { maxPuddleAbsorb = data.AbsorbedPuddleCount; playerWithMostPuddleAbsorb = uid; } } result += Loc.GetString("statsentry-species-entry-name") + "\n"; foreach (var speciesEntry in roundSpecies) { var species = speciesEntry.Key; var count = speciesEntry.Value; if (count > maxSpeciesCount) { maxSpeciesCount = count; mostPopularSpecies = species; } result += Loc.GetString("statsentry-species-entry", ("name", Loc.GetString(species)), ("count", count)) + "\n"; } if (mostPopularSpecies != null) { result += Loc.GetString("statsentry-mst-pop-species", ("name", Loc.GetString(mostPopularSpecies))) + "\n"; } var station = _station.GetStations().FirstOrDefault(); var bank = GetBankAccount(station); if (bank != null) result += Loc.GetString("statsentry-bank-balance", ("balance", bank.Balance)) + "\n"; if (_firstMurder.victim != null) { var victimUsername = TryGetUsername(_firstMurder.victim.Value); var victimName = TryGetName(_firstMurder.victim.Value); var victimUsernameColor = victimUsername != null ? $" ([color=gray]{victimUsername}[/color])" : ""; result += Loc.GetString("statsentry-firth-murder", ("name", victimName), ("username", victimUsernameColor)) + "\n"; result += Loc.GetString("statsentry-firth-murder-time", ("time", _firstMurder.time.ToString("hh\\:mm\\:ss"))) + "\n"; if (_firstMurder.killer != null) { var killerUsername = TryGetUsername(_firstMurder.killer.Value); var killerName = TryGetName(_firstMurder.killer.Value); var killerUsernameColor = killerUsername != null ? $" ([color=gray]{killerUsername}[/color])" : ""; result += Loc.GetString("statsentry-firth-murder-killer", ("name", killerName), ("username", killerUsernameColor)) + "\n"; } else { result += Loc.GetString("statsentry-firth-murder-killer-none") + "\n"; } } if (totalSlipped >= 1) { result += Loc.GetString("statsentry-total-slipped", ("count", totalSlipped)) + "\n"; } if (mostSlippedCharacter != null && maxSlippedCount > 1) { var username = TryGetUsername(mostSlippedCharacter.Value); var name = TryGetName(mostSlippedCharacter.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-most-slipped", ("name", name), ("username", usernameColor), ("count", maxSlippedCount)) + "\n"; } if (totalCreampied >= 1) { result += Loc.GetString("statsentry-total-creampied", ("total", totalCreampied)) + "\n"; } if (mostDeadCharacter != null && maxDeadCount > 1) { var username = TryGetUsername(mostDeadCharacter.Value); var name = TryGetName(mostDeadCharacter.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-most-dead", ("name", name), ("username", usernameColor), ("count", maxDeadCount)) + "\n"; } if (totalDoorEmaged >= 1) { result += Loc.GetString("statsentry-total-door-emaged", ("count", totalDoorEmaged)) + "\n"; } if (mostDoorEmagedCharacter != null) { var username = TryGetUsername(mostDoorEmagedCharacter.Value); var name = TryGetName(mostDoorEmagedCharacter.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-most-door-emaged-character", ("name", name), ("username", usernameColor), ("count", maxDoorEmagedCount)) + "\n"; } if (_jointCreated >= 1) { result += Loc.GetString("statsentry-joint-created", ("count", _jointCreated)) + "\n"; } if (totalKilledMice >= 1) { result += Loc.GetString("statsentry-total-killed-mice", ("count", totalKilledMice)) + "\n"; } if (mostKillsMiceCharacter != null && maxKillsMice > 1) { var username = TryGetUsername(mostKillsMiceCharacter.Value); var name = TryGetName(mostKillsMiceCharacter.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-most-kills-mice-character", ("name", name), ("username", usernameColor), ("count", maxKillsMice)) + "\n"; } if (_hamsterKiller != null) { var username = TryGetUsername(_hamsterKiller.Value); var name = TryGetName(_hamsterKiller.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-hamster-killer", ("name", name), ("username", usernameColor)) + "\n"; } if (totalCuffedCount >= 1) { result += Loc.GetString("statsentry-total-cuffed-count", ("count", totalCuffedCount)) + "\n"; } if (playerWithLongestCuffedTime != null) { var username = TryGetUsername(playerWithLongestCuffedTime.Value); var name = TryGetName(playerWithLongestCuffedTime.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-player-with-longest-cuffed-time", ("name", name), ("username", usernameColor), ("time", maxCuffedTime.ToString("hh\\:mm\\:ss"))) + "\n"; } if (totalSleepTime > TimeSpan.Zero) { result += Loc.GetString("statsentry-total-sleep-time", ("time", totalSleepTime.ToString("hh\\:mm\\:ss"))) + "\n"; } if (playerWithLongestSleepTime != null) { var username = TryGetUsername(playerWithLongestSleepTime.Value); var name = TryGetName(playerWithLongestSleepTime.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-player-with-longest-sleep-time", ("name", name), ("username", usernameColor)) + "\n"; result += Loc.GetString("statsentry-player-with-longest-sleep-time-time", ("time", maxSleepTime.ToString("hh\\:mm\\:ss"))) + "\n"; } if (playerWithLongestSpaceTime != null) { var username = TryGetUsername(playerWithLongestSpaceTime.Value); var name = TryGetName(playerWithLongestSpaceTime.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-player-with-longest-space-time", ("name", name), ("username", usernameColor), ("time", maxSpaceTime.ToString("hh\\:mm\\:ss"))) + "\n"; } if (_clownCuffed.clown != null && _clownCuffed.time != null) { var username = TryGetUsername(_clownCuffed.clown.Value); var name = TryGetName(_clownCuffed.clown.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-clown-cuffed", ("name", name), ("username", usernameColor), ("time", _clownCuffed.time.Value.ToString("hh\\:mm\\:ss"))) + "\n"; } if (totalHeal >= 1) { result += Loc.GetString("statsentry-total-heal", ("count", totalHeal)) + "\n"; } if (playerWithMostInflictedHeal != null) { var username = TryGetUsername(playerWithMostInflictedHeal.Value); var name = TryGetName(playerWithMostInflictedHeal.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-player-with-most-infected-heal", ("name", name), ("username", usernameColor), ("count", maxInflictedHeal)) + "\n"; } if (totalDamage >= 1) { result += Loc.GetString("statsentry-total-damage", ("count", totalDamage)) + "\n"; } if (playerWithMostInflictedDamage != null) { var username = TryGetUsername(playerWithMostInflictedDamage.Value); var name = TryGetName(playerWithMostInflictedDamage.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-player-with-most-infected-damage", ("name", name), ("username", usernameColor), ("count", maxInflictedDamage)) + "\n"; } if (playerWithMinSpentTk != null) { var username = TryGetUsername(playerWithMinSpentTk.Value); var name = TryGetName(playerWithMinSpentTk.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-player-with-min-spent-tk", ("name", name), ("username", usernameColor), ("count", minSpentTk)) + "\n"; } if (playerWithMaxHumKills != null && maxHumKillCount > 1) { var username = TryGetUsername(playerWithMaxHumKills.Value); var name = TryGetName(playerWithMaxHumKills.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-player-with-max-hum-kills", ("name", name), ("username", usernameColor)) + "\n"; result += Loc.GetString("statsentry-player-with-max-hum-kills-count", ("count", maxHumKillCount)) + "\n"; } if (playerWithMaxDamage != null) { var username = TryGetUsername(playerWithMaxDamage.Value); var name = TryGetName(playerWithMaxDamage.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-player-with-max-damage", ("name", name), ("username", usernameColor), ("count", maxTakeDamage)) + "\n"; } if (totalAbsorbedPuddle >= 1) { result += Loc.GetString("statsentry-total-absorbed-puddle", ("count", totalAbsorbedPuddle)) + "\n"; } if (playerWithMostPuddleAbsorb != null && maxPuddleAbsorb > 1) { var username = TryGetUsername(playerWithMostPuddleAbsorb.Value); var name = TryGetName(playerWithMostPuddleAbsorb.Value); var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; result += Loc.GetString("statsentry-player-with-most-puddle-absorb", ("name", name), ("username", usernameColor), ("count", maxPuddleAbsorb)) + "\n"; } if (totalCaptainCardInteracted >= 1) { result += Loc.GetString("statsentry-total-captain-card-interacted", ("count", totalCaptainCardInteracted)) + "\n"; } if (totalElectrocutedCount >= 1) { result += Loc.GetString("statsentry-total-electrocuted-count", ("count", totalElectrocutedCount)) + "\n"; } //убрал пробельчик, так как всё равно он есть при добавлении ласт строчки return result; } private string? TryGetUsername(EntityUid uid) { string? username = null; if (_mindSystem.TryGetMind(uid, out var mindId, out var mind)) { username = mind.Session?.Name; } return username; } private string TryGetName(EntityUid uid) { if (_statisticEntries.TryGetValue(uid, out var value)) return value.Name; if (TryComp(uid, out var metaDataComponent)) return metaDataComponent.EntityName; return "Кто это блядь?"; } } [Serializable] public sealed partial class StatisticEntry(string name, NetUserId userId) { public string Name { get; set; } = name; public NetUserId FirstActor { get; set; } = userId; public int TotalTakeDamage { get; set; } = 0; public int TotalTakeHeal { get; set; } = 0; public int TotalInflictedDamage { get; set; } = 0; public int TotalInflictedHeal { get; set; } = 0; public int SlippedCount { get; set; } = 0; public int CreamedCount { get; set; } = 0; public int DoorEmagedCount { get; set; } = 0; public int ElectrocutedCount { get; set; } = 0; public int CuffedCount { get; set; } = 0; public int AbsorbedPuddleCount { get; set; } = 0; public int? SpentTk { get; set; } = null; public int DeadCount { get; set; } = 0; public int HumanoidKillCount { get; set; } = 0; public int KilledMouseCount { get; set; } = 0; public TimeSpan CuffedTime { get; set; } = TimeSpan.Zero; public TimeSpan SpaceTime { get; set; } = TimeSpan.Zero; public TimeSpan SleepTime { get; set; } = TimeSpan.Zero; public bool IsInteractedCaptainCard { get; set; } = false; }