From f1aef2edb95352262914034fd888ae420df46729 Mon Sep 17 00:00:00 2001 From: Vigers Ray <60344369+VigersRay@users.noreply.github.com> Date: Thu, 25 Jul 2024 02:01:01 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=BD=D1=86=D0=B0=20=D1=80?= =?UTF-8?q?=D0=B0=D1=83=D0=BD=D0=B4=D0=B0=20(#239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RoundEnd/RoundEndSummaryUIController.cs | 3 +- .../RoundEnd/RoundEndSummaryWindow.cs | 82 +- .../_Sunrise/StatsBoard/StatsEntries.xaml | 10 + .../_Sunrise/StatsBoard/StatsEntries.xaml.cs | 20 + .../_Sunrise/StatsBoard/StatsEntry.xaml | 14 + .../_Sunrise/StatsBoard/StatsEntry.xaml.cs | 61 ++ .../ConstructionSystem.Initial.cs | 5 + .../Fluids/EntitySystems/AbsorbentSystem.cs | 5 + .../GameTicking/GameTicker.RoundFlow.cs | 9 + .../Nutrition/EntitySystems/CreamPieSystem.cs | 10 + Content.Server/StatsBoard/StatsBoardSystem.cs | 756 ++++++++++++++++++ .../Store/Systems/StoreSystem.Ui.cs | 11 + Content.Server/Store/Systems/StoreSystem.cs | 8 + Content.Shared/Construction/Events.cs | 8 + .../Cuffs/Components/HandcuffComponent.cs | 9 + Content.Shared/Cuffs/SharedCuffableSystem.cs | 6 + .../Doors/Systems/SharedDoorSystem.cs | 9 + Content.Shared/Fluids/AbsorbentComponent.cs | 8 + .../GameTicking/SharedGameTicker.cs | 7 + Content.Shared/Slippery/SlipperySystem.cs | 10 + .../_Sunrise/StatsBoard/StatsBoardEntry.cs | 29 + .../round-end/round-end-summary-window.ftl | 2 + 22 files changed, 1079 insertions(+), 3 deletions(-) create mode 100644 Content.Client/_Sunrise/StatsBoard/StatsEntries.xaml create mode 100644 Content.Client/_Sunrise/StatsBoard/StatsEntries.xaml.cs create mode 100644 Content.Client/_Sunrise/StatsBoard/StatsEntry.xaml create mode 100644 Content.Client/_Sunrise/StatsBoard/StatsEntry.xaml.cs create mode 100644 Content.Server/StatsBoard/StatsBoardSystem.cs create mode 100644 Content.Shared/_Sunrise/StatsBoard/StatsBoardEntry.cs diff --git a/Content.Client/RoundEnd/RoundEndSummaryUIController.cs b/Content.Client/RoundEnd/RoundEndSummaryUIController.cs index cf824833efb..9f94c4498b0 100644 --- a/Content.Client/RoundEnd/RoundEndSummaryUIController.cs +++ b/Content.Client/RoundEnd/RoundEndSummaryUIController.cs @@ -14,6 +14,7 @@ public sealed class RoundEndSummaryUIController : UIController, IOnSystemLoaded { [Dependency] private readonly IInputManager _input = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; private RoundEndSummaryWindow? _window; @@ -40,7 +41,7 @@ public void OpenRoundEndSummaryWindow(RoundEndMessageEvent message) return; _window = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, - message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, EntityManager); + message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, message.RoundEndStats, message.StatisticEntries, EntityManager, _playerManager); // Sunrise-Edit } public void OnSystemLoaded(ClientGameTicker system) diff --git a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs index 9c9f83a4275..f34723eb844 100644 --- a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs +++ b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs @@ -1,9 +1,12 @@ using System.Linq; using System.Numerics; +using Content.Client._Sunrise.StatsBoard; using Content.Client.Message; +using Content.Shared._Sunrise.StatsBoard; using Content.Shared.GameTicking; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Player; using Robust.Shared.Utility; using static Robust.Client.UserInterface.Controls.BoxContainer; @@ -12,14 +15,16 @@ namespace Content.Client.RoundEnd public sealed class RoundEndSummaryWindow : DefaultWindow { private readonly IEntityManager _entityManager; + private readonly ISharedPlayerManager _playerManager; public int RoundId; public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, int roundId, - RoundEndMessageEvent.RoundEndPlayerInfo[] info, IEntityManager entityManager) + RoundEndMessageEvent.RoundEndPlayerInfo[] info, string roundEndStats, StatisticEntry[] statisticEntries, IEntityManager entityManager, ISharedPlayerManager playerManager) // Sunrise-Edit { _entityManager = entityManager; + _playerManager = playerManager; // Sunrise-Edit - MinSize = SetSize = new Vector2(520, 580); + MinSize = SetSize = new Vector2(700, 600); // Sunrise-Edit Title = Loc.GetString("round-end-summary-window-title"); @@ -31,6 +36,8 @@ public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, RoundId = roundId; var roundEndTabs = new TabContainer(); + roundEndTabs.AddChild(MakeRoundEndStatsTab(roundEndStats)); // Sunrise-End + roundEndTabs.AddChild(MakeRoundEndMyStatsTab(statisticEntries)); // Sunrise-End roundEndTabs.AddChild(MakeRoundEndSummaryTab(gm, roundEnd, roundTimeSpan, roundId)); roundEndTabs.AddChild(MakePlayerManifestTab(info)); @@ -166,6 +173,77 @@ private BoxContainer MakePlayerManifestTab(RoundEndMessageEvent.RoundEndPlayerIn return playerManifestTab; } + + // Sunrise-Start + private BoxContainer MakeRoundEndStatsTab(string stats) + { + var roundEndSummaryTab = new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Name = Loc.GetString("round-end-summary-window-stats-tab-title") + }; + + var roundEndSummaryContainerScrollbox = new ScrollContainer + { + VerticalExpand = true, + Margin = new Thickness(10) + }; + var roundEndSummaryContainer = new BoxContainer + { + Orientation = LayoutOrientation.Vertical + }; + + //Round end text + if (!string.IsNullOrEmpty(stats)) + { + var statsLabel = new RichTextLabel(); + statsLabel.SetMarkup(stats); + roundEndSummaryContainer.AddChild(statsLabel); + } + + roundEndSummaryContainerScrollbox.AddChild(roundEndSummaryContainer); + roundEndSummaryTab.AddChild(roundEndSummaryContainerScrollbox); + + return roundEndSummaryTab; + } + + private BoxContainer MakeRoundEndMyStatsTab(StatisticEntry[] statisticEntries) + { + var roundEndSummaryTab = new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Name = Loc.GetString("round-end-summary-window-my-stats-tab-title") + }; + + var roundEndSummaryContainerScrollbox = new ScrollContainer + { + VerticalExpand = true, + Margin = new Thickness(10), + }; + + var statsEntries = new StatsEntries(); + foreach (var statisticEntry in statisticEntries) + { + if (statisticEntry.FirstActor != _playerManager.LocalSession!.UserId) + continue; + + var statsEntry = new StatsEntry(statisticEntry.Name, statisticEntry.TotalTakeDamage, + statisticEntry.TotalTakeHeal, statisticEntry.TotalInflictedDamage, + statisticEntry.TotalInflictedHeal, statisticEntry.SlippedCount, + statisticEntry.CreamedCount, statisticEntry.DoorEmagedCount, statisticEntry.ElectrocutedCount, + statisticEntry.CuffedCount, statisticEntry.AbsorbedPuddleCount, statisticEntry.SpentTk ?? 0, + statisticEntry.DeadCount, statisticEntry.HumanoidKillCount, statisticEntry.KilledMouseCount, + statisticEntry.CuffedTime, statisticEntry.SpaceTime, statisticEntry.SleepTime, + statisticEntry.IsInteractedCaptainCard ? "Да" : "Нет"); + statsEntries.AddEntry(statsEntry); + } + + roundEndSummaryContainerScrollbox.AddChild(statsEntries); + roundEndSummaryTab.AddChild(roundEndSummaryContainerScrollbox); + + return roundEndSummaryTab; + } + // Sunrise-End } } diff --git a/Content.Client/_Sunrise/StatsBoard/StatsEntries.xaml b/Content.Client/_Sunrise/StatsBoard/StatsEntries.xaml new file mode 100644 index 00000000000..2640f111005 --- /dev/null +++ b/Content.Client/_Sunrise/StatsBoard/StatsEntries.xaml @@ -0,0 +1,10 @@ + + + diff --git a/Content.Client/_Sunrise/StatsBoard/StatsEntries.xaml.cs b/Content.Client/_Sunrise/StatsBoard/StatsEntries.xaml.cs new file mode 100644 index 00000000000..7a56a1f2d3b --- /dev/null +++ b/Content.Client/_Sunrise/StatsBoard/StatsEntries.xaml.cs @@ -0,0 +1,20 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._Sunrise.StatsBoard; + +[GenerateTypedNameReferences] +public sealed partial class StatsEntries : BoxContainer +{ + public StatsEntries() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + } + + public void AddEntry(StatsEntry entry) + { + EntriesContainer.AddChild(entry); + } +} diff --git a/Content.Client/_Sunrise/StatsBoard/StatsEntry.xaml b/Content.Client/_Sunrise/StatsBoard/StatsEntry.xaml new file mode 100644 index 00000000000..b8a83919edd --- /dev/null +++ b/Content.Client/_Sunrise/StatsBoard/StatsEntry.xaml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/Content.Client/_Sunrise/StatsBoard/StatsEntry.xaml.cs b/Content.Client/_Sunrise/StatsBoard/StatsEntry.xaml.cs new file mode 100644 index 00000000000..756e5ed0ec3 --- /dev/null +++ b/Content.Client/_Sunrise/StatsBoard/StatsEntry.xaml.cs @@ -0,0 +1,61 @@ +using Content.Client.Message; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._Sunrise.StatsBoard; + +[GenerateTypedNameReferences] +public sealed partial class StatsEntry : BoxContainer +{ + public StatsEntry(string entityName, float takeDamage, float takeHeal, float inflictedDamage, float inflictedHeal, + float slippedCount, float creamedCount, float doorEmagedCount, float electrocutedCount, float cuffedCount, + float absorbedPuddleCount, int spentTk, float deadCount, float humanoidKillCount, float killedMouseCount, + TimeSpan cuffedTime, TimeSpan spaceTime, TimeSpan sleepTime, string interactedCaptainCard) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + NameLabel.SetMarkup($"{entityName}"); + var statsText = ""; + + if (takeDamage > 0) + statsText += $"Получил урона: {takeDamage}\n"; + if (takeHeal > 0) + statsText += $"Получил лечения: {takeHeal}\n"; + if (inflictedDamage > 0) + statsText += $"Нанес урона: {inflictedDamage}\n"; + if (inflictedHeal > 0) + statsText += $"Вылечил урона: {inflictedHeal}\n"; + if (slippedCount > 0) + statsText += $"Подскользнулся {slippedCount} раз\n"; + if (creamedCount > 0) + statsText += $"Кремирован {creamedCount} раз\n"; + if (doorEmagedCount > 0) + statsText += $"Емагнул {doorEmagedCount} дверей\n"; + if (electrocutedCount > 0) + statsText += $"Шокирован {electrocutedCount} раз\n"; + if (cuffedCount > 0) + statsText += $"Закован {cuffedCount} раз\n"; + if (absorbedPuddleCount > 0) + statsText += $"Убрано {absorbedPuddleCount} луж\n"; + if (spentTk > 0) + statsText += $"Потрачено {spentTk} ТК\n"; + if (deadCount > 0) + statsText += $"Погиб {deadCount} раз\n"; + if (humanoidKillCount > 0) + statsText += $"Убил {humanoidKillCount} гуманоидов\n"; + if (killedMouseCount > 0) + statsText += $"Убито мышей {killedMouseCount}\n"; + if (cuffedTime > TimeSpan.Zero) + statsText += $"Был в наручниках [color=yellow]{cuffedTime.ToString("hh\\:mm\\:ss")}[/color]\n"; + if (spaceTime > TimeSpan.Zero) + statsText += $"Был в космосе [color=yellow]{spaceTime.ToString("hh\\:mm\\:ss")}[/color]\n"; + if (sleepTime > TimeSpan.Zero) + statsText += $"Проспал [color=yellow]{sleepTime.ToString("hh\\:mm\\:ss")}[/color]\n"; + + statsText += $"Трогал карту капитана: {interactedCaptainCard}"; + + StatsLabel.SetMarkup($"{statsText}"); + } +} diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 6cc430b74f6..45430d86d1a 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -395,6 +395,11 @@ public async Task TryStartItemConstruction(string prototype, EntityUid use Transform(user).Coordinates) is not { Valid: true } item) return false; + // Sunrise-Start + var ev = new ItemConstructionCreated(item); + RaiseLocalEvent(user, ref ev); + // Sunrise-End + // Just in case this is a stack, attempt to merge it. If it isn't a stack, this will just normally pick up // or drop the item as normal. _stackSystem.TryMergeToHands(item, user); diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs index 52afdcf8b49..51a89942375 100644 --- a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs +++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs @@ -323,6 +323,11 @@ private bool TryPuddleInteract(EntityUid user, EntityUid used, EntityUid target, _melee.DoLunge(user, used, Angle.Zero, localPos, null, false); + // Sunrise-Start + var ev = new AbsorberPudleEvent(user); + RaiseLocalEvent(user, ref ev); + // Sunrise-End + return true; } } diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 951523a244b..7dda39fd15c 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -20,6 +20,7 @@ using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Utility; +using Content.Server.StatsBoard; namespace Content.Server.GameTicking { @@ -27,6 +28,7 @@ public sealed partial class GameTicker { [Dependency] private readonly DiscordWebhook _discord = default!; [Dependency] private readonly ITaskManager _taskManager = default!; + [Dependency] private readonly StatsBoardSystem _statsBoardSystem = default!; private static readonly Counter RoundNumberMetric = Metrics.CreateCounter( "ss14_round_number", @@ -428,6 +430,11 @@ public void ShowRoundEndScoreboard(string text = "") var listOfPlayerInfoFinal = listOfPlayerInfo.OrderBy(pi => pi.PlayerOOCName).ToArray(); var sound = RoundEndSoundCollection == null ? null : _audio.GetSound(new SoundCollectionSpecifier(RoundEndSoundCollection)); + // Sunrise-Start + var roundStats = _statsBoardSystem.GetRoundStats(); + var statisticEntries = _statsBoardSystem.GetStatisticEntries(); + // Sunrise-End + var roundEndMessageEvent = new RoundEndMessageEvent( gamemodeTitle, roundEndText, @@ -435,6 +442,8 @@ public void ShowRoundEndScoreboard(string text = "") RoundId, listOfPlayerInfoFinal.Length, listOfPlayerInfoFinal, + roundStats, // Sunrise-Edit + statisticEntries, // Sunrise-Edit sound ); RaiseNetworkEvent(roundEndMessageEvent); diff --git a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs index a28679ddbc9..19ad2c4673a 100644 --- a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs @@ -96,6 +96,12 @@ protected override void CreamedEntity(EntityUid uid, CreamPiedComponent creamPie { otherPlayers.RemovePlayer(actor.PlayerSession); } + + // Sunrise-Start + var ev = new CreamedEvent(args.Target); + RaiseLocalEvent(args.Target, ref ev); + // Sunrise-End + _popup.PopupEntity(Loc.GetString("cream-pied-component-on-hit-by-message-others", ("owner", uid), ("thrower", args.Thrown)), uid, otherPlayers, false); } @@ -105,3 +111,7 @@ private void OnRejuvenate(Entity entity, ref RejuvenateEvent } } } + +// Sunrise-Edit +[ByRefEvent] +public readonly record struct CreamedEvent(EntityUid Target); diff --git a/Content.Server/StatsBoard/StatsBoardSystem.cs b/Content.Server/StatsBoard/StatsBoardSystem.cs new file mode 100644 index 00000000000..227e0981f03 --- /dev/null +++ b/Content.Server/StatsBoard/StatsBoardSystem.cs @@ -0,0 +1,756 @@ +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.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +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; + + _statisticEntries[uid].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 (_statisticEntries[uid].SpentTk == null) + { + _statisticEntries[uid].SpentTk = ev.Cost.Int(); + } + else + { + _statisticEntries[uid].SpentTk += ev.Cost.Int(); + } + } + + private void OnElectrocuted(EntityUid uid, ActorComponent comp, ElectrocutedEvent ev) + { + if (!_statisticEntries.TryGetValue(uid, out var value)) + return; + + _statisticEntries[uid].ElectrocutedCount += 1; + } + + private void OnDoorEmagged(EntityUid uid, ActorComponent comp, ref DoorEmaggedEvent ev) + { + if (!_statisticEntries.TryGetValue(uid, out var value)) + return; + + _statisticEntries[uid].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 (_statisticEntries[uid].IsInteractedCaptainCard) + return; + _statisticEntries[uid].IsInteractedCaptainCard = true; + } + + private void OnCreamedEvent(EntityUid uid, ActorComponent comp, ref CreamedEvent ev) + { + if (!_statisticEntries.TryGetValue(uid, out var value)) + return; + + _statisticEntries[uid].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: + { + _statisticEntries[uid].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 (_tagSystem.HasTag(uid, "Mouse")) + { + _statisticEntries[origin.Value].KilledMouseCount += 1; + } + + if (HasComp(uid)) + _statisticEntries[origin.Value].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)) + _statisticEntries[uid].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)) + _statisticEntries[ent].SpaceTime += TimeSpan.FromSeconds(frameTime); + + if (TryComp(ent, out var cuffableComponent) && + !cuffableComponent.CanStillInteract) + _statisticEntries[ent].CuffedTime += TimeSpan.FromSeconds(frameTime); + + if (HasComp(ent)) + _statisticEntries[ent].SleepTime += TimeSpan.FromSeconds(frameTime); + } + } + + public StatisticEntry[] GetStatisticEntries() + { + return _statisticEntries.Values.ToArray(); + } + + 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 += "На станции были представители таких рас:"; + foreach (var speciesEntry in roundSpecies) + { + var species = speciesEntry.Key; + var count = speciesEntry.Value; + + if (count > maxSpeciesCount) + { + maxSpeciesCount = count; + mostPopularSpecies = species; + } + + result += $"\n[bold][color=white]{Loc.GetString(species)}[/color][/bold] в количестве [color=white]{count}[/color]."; + } + + if (mostPopularSpecies != null) + { + result += $"\nСамой распространённой расой стал [color=white]{Loc.GetString(mostPopularSpecies)}[/color]."; + } + + var station = _station.GetStations().FirstOrDefault(); + var bank = GetBankAccount(station); + + if (bank != null) + result += $"\nПод конец смены баланс карго составил [color=white]{bank.Balance}[/color] кредитов."; + + if (_firstMurder.victim != null) + { + var victimUsername = TryGetUsername(_firstMurder.victim.Value); + var victimName = TryGetName(_firstMurder.victim.Value, + _statisticEntries[_firstMurder.victim.Value].Name); + var victimUsernameColor = victimUsername != null ? $" ([color=gray]{victimUsername}[/color])" : ""; + result += $"\nПервая жертва станции - [color=white]{victimName}[/color]{victimUsernameColor}."; + result += $"\nВремя смерти - [color=yellow]{_firstMurder.time.ToString("hh\\:mm\\:ss")}[/color]."; + if (_firstMurder.killer != null) + { + var killerUsername = TryGetUsername(_firstMurder.killer.Value); + var killerName = TryGetName(_firstMurder.killer.Value, + _statisticEntries[_firstMurder.killer.Value].Name); + var killerUsernameColor = killerUsername != null ? $" ([color=gray]{killerUsername}[/color])" : ""; + result += + $"\nУбийца - [color=white]{killerName}[/color]{killerUsernameColor}."; + } + else + { + result += "\nСмерть наступила при неизвестных обстоятельствах."; + } + } + + if (totalSlipped >= 1) + { + result += $"\nИгроки в этой смене поскользнулись [color=white]{totalSlipped}[/color] раз."; + } + + if (mostSlippedCharacter != null && maxSlippedCount > 1) + { + var username = TryGetUsername(mostSlippedCharacter.Value); + var name = TryGetName(mostSlippedCharacter.Value, + _statisticEntries[mostSlippedCharacter.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nБольше всех раз поскользнулся [color=white]{name}[/color]{usernameColor} - [color=white]{maxSlippedCount}[/color]."; + } + + if (totalCreampied >= 1) + { + result += $"\nВсего кремировано игроков: {totalCreampied}."; + } + + if (mostDeadCharacter != null && maxDeadCount > 1) + { + var username = TryGetUsername(mostDeadCharacter.Value); + var name = TryGetName(mostDeadCharacter.Value, + _statisticEntries[mostDeadCharacter.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nБольше всего раз умирал [color=white]{name}[/color]{usernameColor}, а именно [color=white]{maxDeadCount}[/color] раз."; + } + + if (totalDoorEmaged >= 1) + { + result += $"\nШлюзы были емагнуты [color=white]{totalDoorEmaged}[/color] раз."; + } + + if (mostDoorEmagedCharacter != null) + { + var username = TryGetUsername(mostDoorEmagedCharacter.Value); + var name = TryGetName(mostDoorEmagedCharacter.Value, + _statisticEntries[mostDoorEmagedCharacter.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nБольше всего шлюзов емагнул - [color=white]{name}[/color]{usernameColor} - [color=white]{maxDoorEmagedCount}[/color] раз."; + } + + if (_jointCreated >= 1) + { + result += $"\nБыло скручено [color=white]{_jointCreated}[/color] косяков."; + } + + if (totalKilledMice >= 1) + { + result += $"\nБыло убито [color=white]{totalKilledMice}[/color] мышей."; + } + + if (mostKillsMiceCharacter != null && maxKillsMice > 1) + { + var username = TryGetUsername(mostKillsMiceCharacter.Value); + var name = TryGetName(mostKillsMiceCharacter.Value, + _statisticEntries[mostKillsMiceCharacter.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += $"\n{name}[/color]{usernameColor} устроил геноцид, убив [color=white]{maxKillsMice}[/color] мышей."; + } + + if (_hamsterKiller != null) + { + var username = TryGetUsername(_hamsterKiller.Value); + var name = TryGetName(_hamsterKiller.Value, + _statisticEntries[_hamsterKiller.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nУбийцей гамлета был [color=white]{name}[/color]{usernameColor}."; + } + + if (totalCuffedCount >= 1) + { + result += $"\nИгроки были закованы [color=white]{totalCuffedCount}[/color] раз."; + } + + if (playerWithLongestCuffedTime != null) + { + var username = TryGetUsername(playerWithLongestCuffedTime.Value); + var name = TryGetName(playerWithLongestCuffedTime.Value, + _statisticEntries[playerWithLongestCuffedTime.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nБольше всего времени в наручниках провёл [color=white]{name}[/color]{usernameColor} - [color=yellow]{maxCuffedTime.ToString("hh\\:mm\\:ss")}[/color]."; + } + + if (totalSleepTime > TimeSpan.Zero) + { + result += $"\nОбщее время сна игроков составило [color=yellow]{totalSleepTime.ToString("hh\\:mm\\:ss")}[/color]."; + } + + if (playerWithLongestSleepTime != null) + { + var username = TryGetUsername(playerWithLongestSleepTime.Value); + var name = TryGetName(playerWithLongestSleepTime.Value, + _statisticEntries[playerWithLongestSleepTime.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += $"\nГлавной соней станции оказался [color=white]{name}[/color]{usernameColor}."; + result += $"\nОн спал на протяжении [color=yellow]{maxSleepTime.ToString("hh\\:mm\\:ss")}[/color]."; + } + + if (playerWithLongestSpaceTime != null) + { + var username = TryGetUsername(playerWithLongestSpaceTime.Value); + var name = TryGetName(playerWithLongestSpaceTime.Value, + _statisticEntries[playerWithLongestSpaceTime.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nБольше всего времени в космосе провел [color=white]{name}[/color]{usernameColor} - [color=yellow]{maxSpaceTime.ToString("hh\\:mm\\:ss")}[/color]."; + } + + if (_clownCuffed.clown != null && _clownCuffed.time != null) + { + var username = TryGetUsername(_clownCuffed.clown.Value); + var name = TryGetName(_clownCuffed.clown.Value, + _statisticEntries[_clownCuffed.clown.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nКлоун [color=white]{name}[/color]{usernameColor} был закован всего спустя [color=yellow]{_clownCuffed.time.Value.ToString("hh\\:mm\\:ss")}[/color]."; + } + + if (totalHeal >= 1) + { + result += $"\nВсего игроками было излечено [color=white]{totalHeal}[/color] урона."; + } + + if (playerWithMostInflictedHeal != null) + { + var username = TryGetUsername(playerWithMostInflictedHeal.Value); + var name = TryGetName(playerWithMostInflictedHeal.Value, + _statisticEntries[playerWithMostInflictedHeal.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nБольше всего урона игрокам вылечил [color=white]{name}[/color]{usernameColor} - [color=white]{maxInflictedHeal}[/color]."; + } + + if (totalDamage >= 1) + { + result += $"\nВсего игроками было получено [color=white]{totalDamage}[/color] урона."; + } + + if (playerWithMostInflictedDamage != null) + { + var username = TryGetUsername(playerWithMostInflictedDamage.Value); + var name = TryGetName(playerWithMostInflictedDamage.Value, + _statisticEntries[playerWithMostInflictedDamage.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nБольше всего урона нанес [color=white]{name}[/color]{usernameColor} - [color=white]{maxInflictedDamage}[/color]."; + } + + if (playerWithMinSpentTk != null) + { + var username = TryGetUsername(playerWithMinSpentTk.Value); + var name = TryGetName(playerWithMinSpentTk.Value, + _statisticEntries[playerWithMinSpentTk.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nМеньше всего телекристалов потратил [color=white]{name}[/color]{usernameColor} - [color=white]{minSpentTk}[/color]ТК."; + } + + if (playerWithMaxHumKills != null && maxHumKillCount > 1) + { + var username = TryGetUsername(playerWithMaxHumKills.Value); + var name = TryGetName(playerWithMaxHumKills.Value, + _statisticEntries[playerWithMaxHumKills.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += $"\nНастоящим маньяком в этой смене был [color=white]{name}[/color]{usernameColor}."; + result += $"\nОн убил [color=white]{maxHumKillCount}[/color] гуманоидов."; + } + + if (playerWithMaxDamage != null) + { + var username = TryGetUsername(playerWithMaxDamage.Value); + var name = TryGetName(playerWithMaxDamage.Value, + _statisticEntries[playerWithMaxDamage.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nБольше всего урона получил [color=white]{name}[/color]{usernameColor} - [color=white]{maxTakeDamage}[/color]. Вот бедняга."; + } + + if (totalAbsorbedPuddle >= 1) + { + result += $"\nИгроками было убрано [color=white]{totalAbsorbedPuddle}[/color] луж."; + } + + if (playerWithMostPuddleAbsorb != null && maxPuddleAbsorb > 1) + { + var username = TryGetUsername(playerWithMostPuddleAbsorb.Value); + var name = TryGetName(playerWithMostPuddleAbsorb.Value, + _statisticEntries[playerWithMostPuddleAbsorb.Value].Name); + var usernameColor = username != null ? $" ([color=gray]{username}[/color])" : ""; + result += + $"\nБольше всего луж было убрано благодаря [color=white]{name}[/color]{usernameColor} - [color=white]{maxPuddleAbsorb}[/color]."; + } + + if (totalCaptainCardInteracted >= 1) + { + result += $"\nКарта капитана побывала у [color=white]{totalCaptainCardInteracted}[/color] игроков."; + } + + if (totalElectrocutedCount >= 1) + { + result += $"\nИгроки были шокированы [color=white]{totalElectrocutedCount}[/color] раз."; + } + + result += "\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, string savedName) + { + return TryComp(uid, out var metaDataComponent) ? metaDataComponent.EntityName : savedName; + } +} diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 983d68d0af7..1b0174f1c70 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -172,12 +172,23 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi component.BalanceSpent.TryAdd(currency, FixedPoint2.Zero); component.BalanceSpent[currency] += value; + + // Sunrise-Start + var ev = new SubtractCashEvent(buyer, currency, value); + RaiseLocalEvent(buyer, ref ev); + // Sunrise-End } //spawn entity if (listing.ProductEntity != null) { var product = Spawn(listing.ProductEntity, Transform(buyer).Coordinates); + + // Sunrise-Start + var ev = new ItemPurchasedEvent(buyer); + RaiseLocalEvent(product, ref ev); + // Sunrise-End + _hands.PickupOrDrop(buyer, product); HandleRefundComp(uid, component, product); diff --git a/Content.Server/Store/Systems/StoreSystem.cs b/Content.Server/Store/Systems/StoreSystem.cs index c13a9583beb..1876e38df27 100644 --- a/Content.Server/Store/Systems/StoreSystem.cs +++ b/Content.Server/Store/Systems/StoreSystem.cs @@ -166,6 +166,14 @@ public bool TryAddCurrency(Dictionary currency, EntityUid u } } +// Sunrise-Start +[ByRefEvent] +public readonly record struct ItemPurchasedEvent(EntityUid Purchaser); + +[ByRefEvent] +public readonly record struct SubtractCashEvent(EntityUid Purchaser, string Currency, FixedPoint2 Cost); +// Sunrise-End + public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs { public readonly EntityUid User; diff --git a/Content.Shared/Construction/Events.cs b/Content.Shared/Construction/Events.cs index 12f2c198a3a..804ec0b9e05 100644 --- a/Content.Shared/Construction/Events.cs +++ b/Content.Shared/Construction/Events.cs @@ -132,3 +132,11 @@ public ConstructionInteractDoAfterEvent(IEntityManager entManager, InteractUsing public sealed partial class WelderRefineDoAfterEvent : SimpleDoAfterEvent { } + +// Sunrise-Start +[ByRefEvent] +public record struct ItemConstructionCreated(EntityUid Item) +{ + public readonly EntityUid Item = Item; +} +// Sunrise-End diff --git a/Content.Shared/Cuffs/Components/HandcuffComponent.cs b/Content.Shared/Cuffs/Components/HandcuffComponent.cs index 289f587239b..1b484d4da89 100644 --- a/Content.Shared/Cuffs/Components/HandcuffComponent.cs +++ b/Content.Shared/Cuffs/Components/HandcuffComponent.cs @@ -106,6 +106,15 @@ public record struct UncuffAttemptEvent(EntityUid User, EntityUid Target) public bool Cancelled = false; } +// Sunrise-Start +[ByRefEvent] +public record struct CuffedEvent(EntityUid User, EntityUid Target) +{ + public readonly EntityUid User = User; + public readonly EntityUid Target = Target; +} +// Sunrise-End + /// /// Event raised on an entity being uncuffed to determine any modifiers to the amount of time it takes to uncuff them. /// diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index b9f287f1ce4..a8941c11084 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -466,6 +466,12 @@ public bool TryAddNewCuffs(EntityUid target, EntityUid user, EntityUid handcuff, _container.Insert(handcuff, component.Container); UpdateHeldItems(target, handcuff, component); + + // Sunrise-Start + var ev = new CuffedEvent(user, target); + RaiseLocalEvent(target, ref ev); + // Sunrise-End + return true; } diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index e35b26cfb72..b4345932fa0 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -146,6 +146,10 @@ private void OnEmagged(EntityUid uid, DoorComponent door, ref GotEmaggedEvent ar return; Audio.PlayPredicted(door.SparkSound, uid, args.UserUid, AudioParams.Default.WithVolume(8)); args.Handled = true; + // Sunrise-Start + var emagged = new DoorEmaggedEvent(args.UserUid); + RaiseLocalEvent(args.UserUid, ref emagged); + // Sunrise-End } #region StateManagement @@ -806,3 +810,8 @@ private void NextState(Entity ent, TimeSpan time) } #endregion } + +// Sunrise-Start +[ByRefEvent] +public readonly record struct DoorEmaggedEvent(EntityUid UserUid); +// Sunrise-End diff --git a/Content.Shared/Fluids/AbsorbentComponent.cs b/Content.Shared/Fluids/AbsorbentComponent.cs index 450ecc0df6d..d67dbc58e37 100644 --- a/Content.Shared/Fluids/AbsorbentComponent.cs +++ b/Content.Shared/Fluids/AbsorbentComponent.cs @@ -39,3 +39,11 @@ public sealed partial class AbsorbentComponent : Component Params = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation).WithVolume(-3f), }; } + +// Sunrise-Start +[ByRefEvent] +public record struct AbsorberPudleEvent(EntityUid User) +{ + public readonly EntityUid User = User; +} +// Sunrise-End diff --git a/Content.Shared/GameTicking/SharedGameTicker.cs b/Content.Shared/GameTicking/SharedGameTicker.cs index 6e1ab68feae..ece0d8c1f63 100644 --- a/Content.Shared/GameTicking/SharedGameTicker.cs +++ b/Content.Shared/GameTicking/SharedGameTicker.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Shared._Sunrise.StatsBoard; using Content.Shared.Roles; using Robust.Shared.Network; using Robust.Shared.Prototypes; @@ -198,6 +199,8 @@ public partial struct RoundEndPlayerInfo public int RoundId { get; } public int PlayerCount { get; } public RoundEndPlayerInfo[] AllPlayersEndInfo { get; } + public string RoundEndStats { get; } // Sunrise-Edit + public StatisticEntry[] StatisticEntries { get; } // Sunrise-Edit /// /// Sound gets networked due to how entity lifecycle works between client / server and to avoid clipping. @@ -211,6 +214,8 @@ public RoundEndMessageEvent( int roundId, int playerCount, RoundEndPlayerInfo[] allPlayersEndInfo, + string roundEndStats, // Sunrise-Edit + StatisticEntry[] statisticEntries, // Sunrise-Edit string? restartSound) { GamemodeTitle = gamemodeTitle; @@ -219,6 +224,8 @@ public RoundEndMessageEvent( RoundId = roundId; PlayerCount = playerCount; AllPlayersEndInfo = allPlayersEndInfo; + RoundEndStats = roundEndStats; // Sunrise-Edit + StatisticEntries = statisticEntries; // Sunrise-Edit RestartSound = restartSound; } } diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index 5b2a2dfe452..167ba8df25b 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -101,6 +101,11 @@ public void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other, _stun.TryParalyze(other, TimeSpan.FromSeconds(component.ParalyzeTime), true); + // Sunrise-Start + var evSlipped = new SlippedEvent(other); + RaiseLocalEvent(other, ref evSlipped); + // Sunrise-End + // Preventing from playing the slip sound when you are already knocked down. if (playSound) { @@ -131,3 +136,8 @@ public record struct SlipCausingAttemptEvent (bool Cancelled); /// The entity being slipped [ByRefEvent] public readonly record struct SlipEvent(EntityUid Slipped); + +// Sunrise-Start +[ByRefEvent] +public readonly record struct SlippedEvent(EntityUid Target); +// Sunrise-End diff --git a/Content.Shared/_Sunrise/StatsBoard/StatsBoardEntry.cs b/Content.Shared/_Sunrise/StatsBoard/StatsBoardEntry.cs new file mode 100644 index 00000000000..c5207dc0128 --- /dev/null +++ b/Content.Shared/_Sunrise/StatsBoard/StatsBoardEntry.cs @@ -0,0 +1,29 @@ +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared._Sunrise.StatsBoard; + +[Serializable, NetSerializable] +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; +} diff --git a/Resources/Locale/ru-RU/round-end/round-end-summary-window.ftl b/Resources/Locale/ru-RU/round-end/round-end-summary-window.ftl index b330261930c..19ae46a5d8b 100644 --- a/Resources/Locale/ru-RU/round-end/round-end-summary-window.ftl +++ b/Resources/Locale/ru-RU/round-end/round-end-summary-window.ftl @@ -1,6 +1,8 @@ round-end-summary-window-title = Итоги раунда round-end-summary-window-round-end-summary-tab-title = Информация round-end-summary-window-player-manifest-tab-title = Манифест игроков +round-end-summary-window-stats-tab-title = Статистика +round-end-summary-window-my-stats-tab-title = Моя статистика round-end-summary-window-round-id-label = Раунд [color=white]#{ $roundId }[/color] завершился. round-end-summary-window-gamemode-name-label = Игровой режим был [color=white]{ $gamemode }[/color]. round-end-summary-window-duration-label = Он длился [color=yellow]{ $hours } ч., { $minutes } мин., и { $seconds } сек.