diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index f6fdcfedff..400c675af4 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Forensics; using Content.Server.HealthExaminable; using Content.Server.Popups; +using Content.Server.SimpleStation14.EndOfRoundStats.BloodLost; using Content.Shared.Alert; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; @@ -92,6 +93,8 @@ public override void Update(float frameTime) { base.Update(frameTime); + var totalBloodLost = 0f; // Parkstation-EndOfRoundStats + var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var bloodstream)) { @@ -119,6 +122,8 @@ public override void Update(float frameTime) TryModifyBloodLevel(uid, (-bloodstream.BleedAmount), bloodstream); // Bleed rate is reduced by the bleed reduction amount in the bloodstream component. TryModifyBleedAmount(uid, -bloodstream.BleedReductionAmount, bloodstream); + + totalBloodLost += bloodstream.BleedAmount / 20; // Parkstation-EndOfRoundStats } // deal bloodloss damage if their blood level is below a threshold. @@ -151,6 +156,8 @@ public override void Update(float frameTime) bloodstream.StatusTime = 0; } } + + RaiseLocalEvent(new BloodLostStatEvent(totalBloodLost)); // Parkstation-EndOfRoundStats } private void OnComponentInit(Entity entity, ref ComponentInit args) diff --git a/Content.Server/Instruments/InstrumentComponent.cs b/Content.Server/Instruments/InstrumentComponent.cs index 4302ab6791..cda365a0e6 100644 --- a/Content.Server/Instruments/InstrumentComponent.cs +++ b/Content.Server/Instruments/InstrumentComponent.cs @@ -20,6 +20,8 @@ public sealed partial class InstrumentComponent : SharedInstrumentComponent public ICommonSession? InstrumentPlayer => _entMan.GetComponentOrNull(Owner)?.CurrentSingleUser ?? _entMan.GetComponentOrNull(Owner)?.PlayerSession; + + public TimeSpan? TimeStartedPlaying { get; set; } // Parkstation-EndOfRoundStats } [RegisterComponent] diff --git a/Content.Server/Instruments/InstrumentSystem.cs b/Content.Server/Instruments/InstrumentSystem.cs index 17899b7232..8fc9b4195f 100644 --- a/Content.Server/Instruments/InstrumentSystem.cs +++ b/Content.Server/Instruments/InstrumentSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Administration; using Content.Server.Interaction; using Content.Server.Popups; +using Content.Server.SimpleStation14.EndOfRoundStats.Instruments; using Content.Server.Stunnable; using Content.Shared.Administration; using Content.Shared.Instruments; @@ -30,6 +31,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly InteractionSystem _interactions = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; private const float MaxInstrumentBandRange = 10f; @@ -116,6 +118,8 @@ private void OnMidiStart(InstrumentStartMidiEvent msg, EntitySessionEventArgs ar instrument.Playing = true; Dirty(uid, instrument); + + instrument.TimeStartedPlaying = _gameTiming.CurTime; // Parkstation-EndOfRoundStats } private void OnMidiStop(InstrumentStopMidiEvent msg, EntitySessionEventArgs args) @@ -286,6 +290,18 @@ public void Clean(EntityUid uid, InstrumentComponent? instrument = null) RaiseNetworkEvent(new InstrumentMidiEventEvent(netUid, new[]{RobustMidiEvent.SystemReset(0)})); RaiseNetworkEvent(new InstrumentStopMidiEvent(netUid)); + + // Parkstation-EndOfRoundStats-Start + if (instrument.TimeStartedPlaying != null && instrument.InstrumentPlayer != null) + { + var username = instrument.InstrumentPlayer.Name; + var entity = instrument.InstrumentPlayer.AttachedEntity; + var name = entity != null ? MetaData((EntityUid) entity).EntityName : "Unknown"; + + RaiseLocalEvent(new InstrumentPlayedStatEvent(name, (TimeSpan) (_gameTiming.CurTime - instrument.TimeStartedPlaying), username)); + } + instrument.TimeStartedPlaying = null; + // Parkstation-EndOfRoundStats-End } instrument.Playing = false; diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/BloodLost/BloodLostStatEvent.cs b/Content.Server/SimpleStation14/EndOfRoundStats/BloodLost/BloodLostStatEvent.cs new file mode 100644 index 0000000000..73558e7a17 --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/BloodLost/BloodLostStatEvent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.SimpleStation14.EndOfRoundStats.BloodLost; + +public sealed class BloodLostStatEvent : EntityEventArgs +{ + public float BloodLost; + + public BloodLostStatEvent(float bloodLost) + { + BloodLost = bloodLost; + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/BloodLost/BloodLostStatSystem.cs b/Content.Server/SimpleStation14/EndOfRoundStats/BloodLost/BloodLostStatSystem.cs new file mode 100644 index 0000000000..f5684d5e60 --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/BloodLost/BloodLostStatSystem.cs @@ -0,0 +1,46 @@ +using Content.Server.GameTicking; +using Content.Shared.GameTicking; +using Robust.Shared.Configuration; +using Content.Shared.SimpleStation14.CCVar; +using Content.Shared.FixedPoint; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.BloodLost; + +public sealed class BloodLostStatSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _config = default!; + + FixedPoint2 totalBloodLost = 0; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBloodLost); + + SubscribeLocalEvent(OnRoundEnd); + SubscribeLocalEvent(OnRoundRestart); + } + + private void OnBloodLost(BloodLostStatEvent args) + { + totalBloodLost += args.BloodLost; + } + + private void OnRoundEnd(RoundEndTextAppendEvent ev) + { + var line = String.Empty; + + if (totalBloodLost < _config.GetCVar(SimpleStationCCVars.BloodLostThreshold)) + return; + + line += $"[color=maroon]{Loc.GetString("eorstats-bloodlost-total", ("bloodLost", totalBloodLost.Int()))}[/color]"; + + ev.AddLine("\n" + line); + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + totalBloodLost = 0; + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/Command/CommandStatSystem.cs b/Content.Server/SimpleStation14/EndOfRoundStats/Command/CommandStatSystem.cs new file mode 100644 index 0000000000..f2f4c288ab --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/Command/CommandStatSystem.cs @@ -0,0 +1,30 @@ +using Content.Server.GameTicking; +using Content.Shared.GameTicking; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.Command; + +public sealed class CommandStatSystem : EntitySystem +{ + public List<(string, string)> eorStats = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRoundEnd); + SubscribeLocalEvent(OnRoundRestart); + } + + private void OnRoundEnd(RoundEndTextAppendEvent ev) + { + foreach (var (stat, color) in eorStats) + { + ev.AddLine($"[color={color}]{stat}[/color]"); + } + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + eorStats.Clear(); + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/Command/EORStatsAddCommand.cs b/Content.Server/SimpleStation14/EndOfRoundStats/Command/EORStatsAddCommand.cs new file mode 100644 index 0000000000..6c275d1520 --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/Command/EORStatsAddCommand.cs @@ -0,0 +1,59 @@ +using System.Linq; +using Content.Server.Administration; +using Content.Shared.Administration; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Robust.Shared.Console; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.Command; + +[AdminCommand(AdminFlags.Admin)] +public sealed class EORStatsAddCommmand : IConsoleCommand +{ + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + + public string Command => "eorstatsadd"; + public string Description => "Adds an end of round stat to be displayed."; + public string Help => $"Usage: {Command} "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var _stats = IoCManager.Resolve().GetEntitySystem(); + + if (args.Length < 1 || args.Length > 2) + { + shell.WriteError("Invalid amount of arguments."); + return; + } + + if (args.Length == 2 && !Color.TryFromName(args[1], out _)) + { + shell.WriteError("Invalid color."); + return; + } + + _stats.eorStats.Add((args[0], args.Length == 2 ? args[1] : "Green")); + + shell.WriteLine($"Added {args[0]} to end of round stats."); + + _adminLogger.Add(LogType.AdminMessage, LogImpact.Low, + $"{shell.Player!.Name} added '{args[0]}' to end of round stats."); + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHint(""); + } + + if (args.Length == 2) + { + var options = Color.GetAllDefaultColors().Select(o => new CompletionOption(o.Key)); + + return CompletionResult.FromHintOptions(options, ""); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/Command/EORStatsListCommand.cs b/Content.Server/SimpleStation14/EndOfRoundStats/Command/EORStatsListCommand.cs new file mode 100644 index 0000000000..a44876d89e --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/Command/EORStatsListCommand.cs @@ -0,0 +1,37 @@ +using Content.Server.Administration; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.Command; + +[AdminCommand(AdminFlags.Admin)] +public sealed class EORStatsCommand : IConsoleCommand +{ + public string Command => "eorstatslist"; + public string Description => "Lists the current command-added end of round stats to be displayed."; + public string Help => $"Usage: {Command}"; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var _stats = IoCManager.Resolve().GetEntitySystem(); + + if (args.Length != 0) + { + shell.WriteError("Invalid amount of arguments."); + return; + } + + if (_stats.eorStats.Count == 0) + { + shell.WriteLine("No command-added end of round stats to display."); + return; + } + + shell.WriteLine("End of round stats:"); + + foreach (var (stat, color) in _stats.eorStats) + { + shell.WriteLine($"'{stat}' - {color}"); + } + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/Command/EROStatsRemoveCommand.cs b/Content.Server/SimpleStation14/EndOfRoundStats/Command/EROStatsRemoveCommand.cs new file mode 100644 index 0000000000..efdea17b2a --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/Command/EROStatsRemoveCommand.cs @@ -0,0 +1,72 @@ +using System.Linq; +using Content.Server.Administration; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.Command; + +[AdminCommand(AdminFlags.Admin)] +public sealed class EORStatsRemoveCommand : IConsoleCommand +{ + public string Command => "eorstatsremove"; + public string Description => "Removes a previously added end of round stat. Defaults to last added stat."; + public string Help => $"Usage: {Command} "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var _stats = IoCManager.Resolve().GetEntitySystem(); + + if (args.Length > 1) + { + shell.WriteError("Invalid amount of arguments."); + return; + } + + if (_stats.eorStats.Count == 0) + { + shell.WriteError("No stats to remove."); + return; + } + + int index = _stats.eorStats.Count; + + if (args.Length == 1) + { + if (!int.TryParse(args[0], out index)) + { + shell.WriteError("Invalid index."); + return; + } + + if (index < 0 || index > _stats.eorStats.Count) + { + shell.WriteError("Index out of range."); + return; + } + } + + index--; + + shell.WriteLine($"Removed '{_stats.eorStats[index].Item1}' from end of round stats."); + + _stats.eorStats.RemoveAt(index); + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + var _stats = IoCManager.Resolve().GetEntitySystem(); + + if (args.Length == 1) + { + var options = _stats.eorStats.Select(o => new CompletionOption + ((_stats.eorStats.LastIndexOf((o.Item1, o.Item2)) + 1).ToString(), o.Item1)); + + if (options.Count() == 0) + return CompletionResult.FromHint("No stats to remove."); + + return CompletionResult.FromOptions(options); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/CuffedTime/CuffedTimeStatSystem.cs b/Content.Server/SimpleStation14/EndOfRoundStats/CuffedTime/CuffedTimeStatSystem.cs new file mode 100644 index 0000000000..a97b6b99df --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/CuffedTime/CuffedTimeStatSystem.cs @@ -0,0 +1,120 @@ +using Content.Server.GameTicking; +using Content.Server.Mind; +using Content.Shared.Cuffs.Components; +using Content.Shared.GameTicking; +using Content.Shared.Mind.Components; +using Content.Shared.SimpleStation14.CCVar; +using Content.Shared.SimpleStation14.EndOfRoundStats.CuffedTime; +using Robust.Shared.Configuration; +using Robust.Shared.Timing; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.CuffedTime; + +public sealed class CuffedTimeStatSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly MindSystem _mind = default!; + + + Dictionary userPlayStats = new(); + + private struct PlayerData + { + public String Name; + public String? Username; + } + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUncuffed); + + SubscribeLocalEvent(OnRoundEnd); + SubscribeLocalEvent(OnRoundRestart); + } + + private void OnUncuffed(EntityUid uid, CuffableComponent component, CuffedTimeStatEvent args) + { + string? username = null; + + if (EntityManager.TryGetComponent(uid, out var mindComponent) && + mindComponent.Mind != null && + _mind.TryGetSession(mindComponent.Mind.Value, out var session)) + username = session.Name; + + var playerData = new PlayerData + { + Name = MetaData(uid).EntityName, + Username = username + }; + + if (userPlayStats.ContainsKey(playerData)) + { + userPlayStats[playerData] += args.Duration; + return; + } + + userPlayStats.Add(playerData, args.Duration); + } + + private void OnRoundEnd(RoundEndTextAppendEvent ev) + { + // Gather any people currently cuffed. + // Otherwise people cuffed on the evac shuttle will not be counted. + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var component)) + { + if (component.CuffedTime != null) + RaiseLocalEvent(uid, new CuffedTimeStatEvent(_gameTiming.CurTime - component.CuffedTime.Value)); + } + + // Continue with normal logic. + var line = "[color=cadetblue]"; + + (PlayerData, TimeSpan) topPlayer = (new PlayerData(), TimeSpan.Zero); + + foreach (var (player, amountPlayed) in userPlayStats) + { + if (amountPlayed >= topPlayer.Item2) + topPlayer = (player, amountPlayed); + } + + if (topPlayer.Item2 < TimeSpan.FromMinutes(_config.GetCVar(SimpleStationCCVars.CuffedTimeThreshold))) + return; + else + line += GenerateTopPlayer(topPlayer.Item1, topPlayer.Item2); + + ev.AddLine("\n" + line + "[/color]"); + } + + private String GenerateTopPlayer(PlayerData data, TimeSpan timeCuffed) + { + var line = String.Empty; + + if (data.Username != null) + line += Loc.GetString + ( + "eorstats-cuffedtime-hasusername", + ("username", data.Username), + ("name", data.Name), + ("timeCuffedMinutes", Math.Round(timeCuffed.TotalMinutes)) + ); + else + line += Loc.GetString + ( + "eorstats-cuffedtime-nousername", + ("name", data.Name), + ("timeCuffedMinutes", Math.Round(timeCuffed.TotalMinutes)) + ); + + return line; + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + userPlayStats.Clear(); + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/EmitSound/EmitSoundStatSystem.cs b/Content.Server/SimpleStation14/EndOfRoundStats/EmitSound/EmitSoundStatSystem.cs new file mode 100644 index 0000000000..ca0b1e24f1 --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/EmitSound/EmitSoundStatSystem.cs @@ -0,0 +1,108 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Server.GameTicking; +using Content.Shared.GameTicking; +using Content.Shared.SimpleStation14.CCVar; +using Content.Shared.SimpleStation14.EndOfRoundStats.EmitSound; +using Content.Shared.Tag; +using Robust.Shared.Configuration; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.EmitSound; + +public sealed class EmitSoundStatSystem : EntitySystem +{ + [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + + + Dictionary soundsEmitted = new(); + + // This Enum must match the exact tag you're searching for. + // Adding a new tag to this Enum, and ensuring the localisation is set will automatically add it to the end of round stats. + // Local string should be in the format: eorstats-emitsound- (e.g. eorstats-emitsound-BikeHorn) + // and should have a parameter of "times" (e.g. Horns were honked a total of {$times} times!) + private enum SoundSources + { + BikeHorn, + Plushie + } + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSoundEmitted); + + SubscribeLocalEvent(OnRoundEnd); + SubscribeLocalEvent(OnRoundRestart); + } + + private void OnSoundEmitted(EmitSoundStatEvent ev) + { + SoundSources? source = null; + + foreach (var enumSource in Enum.GetValues()) + { + if (_tag.HasTag(ev.Emitter, enumSource.ToString())) + { + source = enumSource; + break; + } + } + + if (source == null) + return; + + if (soundsEmitted.ContainsKey(source.Value)) + { + soundsEmitted[source.Value]++; + return; + } + + soundsEmitted.Add(source.Value, 1); + } + + private void OnRoundEnd(RoundEndTextAppendEvent ev) + { + var minCount = _config.GetCVar(SimpleStationCCVars.EmitSoundThreshold); + + var line = string.Empty; + var entry = false; + + if (minCount == 0) + return; + + foreach (var source in soundsEmitted.Keys) + { + if (soundsEmitted[source] > minCount && TryGenerateSoundsEmitted(source, soundsEmitted[source], out var lineTemp)) + { + line += "\n" + lineTemp; + + entry = true; + } + } + + if (entry) + ev.AddLine("[color=springGreen]" + line + "[/color]"); + } + + private bool TryGenerateSoundsEmitted(SoundSources source, int soundsEmitted, [NotNullWhen(true)] out string? line) + { + string preLocalString = "eorstats-emitsound-" + source.ToString(); + + if (!Loc.TryGetString(preLocalString, out var localString, ("times", soundsEmitted))) + { + Logger.DebugS("eorstats", "Unknown messageId: {0}", preLocalString); + Logger.Debug("Make sure the string is following the correct format, and matches the enum! (eorstats-emitsound-)"); + + throw new ArgumentException("Unknown messageId: " + preLocalString); + } + + line = localString; + return true; + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + soundsEmitted.Clear(); + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/InstrumentPlayed/InstrumentPlayedStatEvent.cs b/Content.Server/SimpleStation14/EndOfRoundStats/InstrumentPlayed/InstrumentPlayedStatEvent.cs new file mode 100644 index 0000000000..923030ff5f --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/InstrumentPlayed/InstrumentPlayedStatEvent.cs @@ -0,0 +1,15 @@ +namespace Content.Server.SimpleStation14.EndOfRoundStats.Instruments; + +public sealed class InstrumentPlayedStatEvent : EntityEventArgs +{ + public String Player; + public TimeSpan Duration; + public String? Username; + + public InstrumentPlayedStatEvent(String player, TimeSpan duration, String? username) + { + Player = player; + Duration = duration; + Username = username; + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/InstrumentPlayed/InstrumentPlayedStatSystem.cs b/Content.Server/SimpleStation14/EndOfRoundStats/InstrumentPlayed/InstrumentPlayedStatSystem.cs new file mode 100644 index 0000000000..1b59aa4990 --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/InstrumentPlayed/InstrumentPlayedStatSystem.cs @@ -0,0 +1,115 @@ +using System.Linq; +using Content.Server.GameTicking; +using Content.Server.Instruments; +using Content.Shared.GameTicking; +using Content.Shared.SimpleStation14.CCVar; +using Robust.Shared.Configuration; +using Robust.Shared.Timing; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.Instruments; + +public sealed class InstrumentPlayedStatSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + + Dictionary userPlayStats = new(); + + private struct PlayerData + { + public String Name; + public String? Username; + } + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInstrumentPlayed); + + SubscribeLocalEvent(OnRoundEnd); + SubscribeLocalEvent(OnRoundRestart); + } + + private void OnInstrumentPlayed(InstrumentPlayedStatEvent args) + { + var playerData = new PlayerData + { + Name = args.Player, + Username = args.Username + }; + + if (userPlayStats.ContainsKey(playerData)) + { + userPlayStats[playerData] += args.Duration; + return; + } + + userPlayStats.Add(playerData, args.Duration); + } + + private void OnRoundEnd(RoundEndTextAppendEvent ev) + { + // Gather any people currently playing istruments. + // This part is very important :P + // Otherwise people playing their tunes on the evac shuttle will not be counted. + foreach (var instrument in EntityManager.EntityQuery().Where(i => i.InstrumentPlayer != null)) + { + if (instrument.TimeStartedPlaying != null && instrument.InstrumentPlayer != null) + { + var username = instrument.InstrumentPlayer.Name; + var entity = instrument.InstrumentPlayer.AttachedEntity; + var name = entity != null ? MetaData((EntityUid) entity).EntityName : "Unknown"; + + RaiseLocalEvent(new InstrumentPlayedStatEvent(name, (TimeSpan) (_gameTiming.CurTime - instrument.TimeStartedPlaying), username)); + } + } + + // Continue with normal logic. + var line = "[color=springGreen]"; + + (PlayerData, TimeSpan) topPlayer = (new PlayerData(), TimeSpan.Zero); + + foreach (var (player, amountPlayed) in userPlayStats) + { + if (amountPlayed >= topPlayer.Item2) + topPlayer = (player, amountPlayed); + } + + if (topPlayer.Item2 < TimeSpan.FromMinutes(_config.GetCVar(SimpleStationCCVars.InstrumentPlayedThreshold))) + return; + else + line += GenerateTopPlayer(topPlayer.Item1, topPlayer.Item2); + + ev.AddLine("\n" + line + "[/color]"); + } + + private String GenerateTopPlayer(PlayerData data, TimeSpan amountPlayed) + { + var line = String.Empty; + + if (data.Username != null) + line += Loc.GetString + ( + "eorstats-instrumentplayed-topplayer-hasusername", + ("username", data.Username), + ("name", data.Name), + ("amountPlayedMinutes", Math.Round(amountPlayed.TotalMinutes)) + ); + else + line += Loc.GetString + ( + "eorstats-instrumentplayed-topplayer-hasnousername", + ("name", data.Name), + ("amountPlayedMinutes", Math.Round(amountPlayed.TotalMinutes)) + ); + + return line; + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + userPlayStats.Clear(); + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/MopUsed/MopUsedStatEvent.cs b/Content.Server/SimpleStation14/EndOfRoundStats/MopUsed/MopUsedStatEvent.cs new file mode 100644 index 0000000000..c38dd3fa0c --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/MopUsed/MopUsedStatEvent.cs @@ -0,0 +1,17 @@ +using Content.Shared.FixedPoint; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.MopUsed; + +public sealed class MopUsedStatEvent : EntityEventArgs +{ + public String Mopper; + public FixedPoint2 AmountMopped; + public String? Username; + + public MopUsedStatEvent(String mopper, FixedPoint2 amountMopped, String? username) + { + Mopper = mopper; + AmountMopped = amountMopped; + Username = username; + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/MopUsed/MopUsedStatSystem.cs b/Content.Server/SimpleStation14/EndOfRoundStats/MopUsed/MopUsedStatSystem.cs new file mode 100644 index 0000000000..20217c40c8 --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/MopUsed/MopUsedStatSystem.cs @@ -0,0 +1,164 @@ +using System.Linq; +using Content.Server.GameTicking; +using Content.Shared.FixedPoint; +using Content.Shared.GameTicking; +using Content.Shared.SimpleStation14.CCVar; +using Robust.Shared.Configuration; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.MopUsed; + +public sealed class MopUsedStatSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _config = default!; + + + Dictionary userMopStats = new(); + int timesMopped = 0; + + private struct MopperData + { + public String Name; + public String? Username; + } + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMopUsed); + + SubscribeLocalEvent(OnRoundEnd); + SubscribeLocalEvent(OnRoundRestart); + } + + + private void OnMopUsed(MopUsedStatEvent args) + { + timesMopped++; + + var mopperData = new MopperData + { + Name = args.Mopper, + Username = args.Username + }; + + if (userMopStats.ContainsKey(mopperData)) + { + userMopStats[mopperData] += args.AmountMopped; + return; + } + + userMopStats.Add(mopperData, args.AmountMopped); + } + + private void OnRoundEnd(RoundEndTextAppendEvent ev) + { + var line = String.Empty; + + if (userMopStats.Count == 0 && _config.GetCVar(SimpleStationCCVars.MopUsedDisplayNone)) + { + line += "\n[color=red]" + Loc.GetString("eorstats-mop-noamountmopped") + "[/color]"; + } + else if (userMopStats.Count == 0) + { + return; + } + else + { + var sortedMoppers = userMopStats.OrderByDescending(m => m.Value); + + int totalAmountMopped = sortedMoppers.Sum(m => (int) m.Value); + + String impressColor; + + if (totalAmountMopped < _config.GetCVar(SimpleStationCCVars.MopUsedThreshold)) + return; + + switch (totalAmountMopped) + { + case var x when x > 2600: + impressColor = "royalBlue"; + break; + case var x when x > 1400: + impressColor = "gold"; + break; + case var x when x > 600: + impressColor = "slateBlue"; + break; + default: + impressColor = "fireBrick"; + break; + } + + line += "\n" + Loc.GetString("eorstats-mop-amountmopped", ("amountMopped", totalAmountMopped), ("timesMopped", timesMopped), ("impressColor", impressColor)); + + if (_config.GetCVar(SimpleStationCCVars.MopUsedTopMopperCount) > 0) + line += "\n" + Loc.GetString("eorstats-mop-topmopper-header"); + + var currentPlace = 1; + foreach (var mopper in sortedMoppers) + { + if (currentPlace > _config.GetCVar(SimpleStationCCVars.MopUsedTopMopperCount)) + break; + + line += GenerateTopMopper(mopper.Key, mopper.Value, currentPlace); + + currentPlace++; + } + } + + ev.AddLine(line); + } + + private String GenerateTopMopper(MopperData data, FixedPoint2 amountMopped, int place) + { + var line = String.Empty; + + if (amountMopped > 0) + { + String impressColor; + + switch (place) + { + case 1: + impressColor = "gold"; + break; + case 2: + impressColor = "slateBlue"; + break; + default: + impressColor = "fireBrick"; + break; + } + + if (data.Username != null) + line += "\n" + Loc.GetString + ( + "eorstats-mop-topmopper-hasusername", + ("name", data.Name), + ("username", data.Username), + ("amountMopped", (int) amountMopped), + ("impressColor", impressColor), + ("place", place) + ); + else + line += "\n" + Loc.GetString + ( + "eorstats-mop-topmopper-hasnousername", + ("name", data.Name), + ("amountMopped", (int) amountMopped), + ("impressColor", impressColor), + ("place", place) + ); + } + + return line; + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + userMopStats.Clear(); + timesMopped = 0; + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/ShotsFired/ShotsFiredStatSystem.cs b/Content.Server/SimpleStation14/EndOfRoundStats/ShotsFired/ShotsFiredStatSystem.cs new file mode 100644 index 0000000000..117c33e6d7 --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/ShotsFired/ShotsFiredStatSystem.cs @@ -0,0 +1,57 @@ +using Content.Server.GameTicking; +using Content.Shared.GameTicking; +using Content.Shared.SimpleStation14.CCVar; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Events; +using Robust.Shared.Configuration; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.ShotsFired; + +public sealed class ShotsFiredStatSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _config = default!; + + + int shotsFired = 0; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnShotFired); + + SubscribeLocalEvent(OnRoundEnd); + SubscribeLocalEvent(OnRoundRestart); + } + + private void OnShotFired(EntityUid _, GunComponent __, AmmoShotEvent args) + { + shotsFired++; + } + + private void OnRoundEnd(RoundEndTextAppendEvent ev) + { + var line = string.Empty; + + line += GenerateShotsFired(shotsFired); + + if (line != string.Empty) + ev.AddLine("\n[color=cadetblue]" + line + "[/color]"); + } + + private string GenerateShotsFired(int shotsFired) + { + if (shotsFired == 0 && _config.GetCVar(SimpleStationCCVars.ShotsFiredDisplayNone)) + return Loc.GetString("eorstats-shotsfired-noshotsfired"); + + if (shotsFired == 0 || shotsFired < _config.GetCVar(SimpleStationCCVars.ShotsFiredThreshold)) + return string.Empty; + + return Loc.GetString("eorstats-shotsfired-amount", ("shotsFired", shotsFired)); + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + shotsFired = 0; + } +} diff --git a/Content.Server/SimpleStation14/EndOfRoundStats/SlippedCount/SlippedCountStatSystem.cs b/Content.Server/SimpleStation14/EndOfRoundStats/SlippedCount/SlippedCountStatSystem.cs new file mode 100644 index 0000000000..3666a5bb46 --- /dev/null +++ b/Content.Server/SimpleStation14/EndOfRoundStats/SlippedCount/SlippedCountStatSystem.cs @@ -0,0 +1,120 @@ +using System.Linq; +using Content.Server.GameTicking; +using Content.Server.Mind; +using Content.Shared.GameTicking; +using Content.Shared.Mind.Components; +using Content.Shared.SimpleStation14.CCVar; +using Content.Shared.Slippery; +using Robust.Shared.Configuration; + +namespace Content.Server.SimpleStation14.EndOfRoundStats.SlippedCount; + +public sealed class SlippedCountStatSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly MindSystem _mind = default!; + + Dictionary userSlipStats = new(); + + private struct PlayerData + { + public String Name; + public String? Username; + } + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSlip); + + SubscribeLocalEvent(OnRoundEnd); + SubscribeLocalEvent(OnRoundRestart); + } + + + private void OnSlip(EntityUid uid, SlipperyComponent slipComp, ref SlipEvent args) + { + string? username = null; + + var entity = args.Slipped; + + if (EntityManager.TryGetComponent(entity, out var mindComp) && + mindComp.Mind != null && + _mind.TryGetSession(mindComp.Mind, out var session)) + username = session.Name; + + var playerData = new PlayerData + { + Name = MetaData(entity).EntityName, + Username = username + }; + + if (userSlipStats.ContainsKey(playerData)) + { + userSlipStats[playerData]++; + return; + } + + userSlipStats.Add(playerData, 1); + } + + private void OnRoundEnd(RoundEndTextAppendEvent ev) + { + var sortedSlippers = userSlipStats.OrderByDescending(m => m.Value); + + int totalTimesSlipped = sortedSlippers.Sum(m => (int) m.Value); + + var line = "[color=springGreen]"; + + if (totalTimesSlipped < _config.GetCVar(SimpleStationCCVars.SlippedCountThreshold)) + { + if (totalTimesSlipped == 0 && _config.GetCVar(SimpleStationCCVars.SlippedCountDisplayNone)) + { + line += Loc.GetString("eorstats-slippedcount-none"); + } + else + return; + } + else + { + line += Loc.GetString("eorstats-slippedcount-totalslips", ("timesSlipped", totalTimesSlipped)); + + line += GenerateTopSlipper(sortedSlippers.First().Key, sortedSlippers.First().Value); + } + + ev.AddLine("\n" + line + "[/color]"); + } + + private String GenerateTopSlipper(PlayerData data, int amountSlipped) + { + var line = String.Empty; + + if (_config.GetCVar(SimpleStationCCVars.SlippedCountTopSlipper) == false) + return line; + + if (data.Username != null) + line += Loc.GetString + ( + "eorstats-slippedcount-topslipper-hasusername", + ("username", data.Username), + ("name", data.Name), + ("slipcount", amountSlipped) + ); + else + line += Loc.GetString + ( + "eorstats-slippedcount-topslipper-hasnousername", + ("name", data.Name), + ("slipcount", amountSlipped) + ); + + return "\n" + line; + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + userSlipStats.Clear(); + } +} diff --git a/Content.Shared/Cuffs/Components/CuffableComponent.cs b/Content.Shared/Cuffs/Components/CuffableComponent.cs index 5da6fa41a5..95c7acd643 100644 --- a/Content.Shared/Cuffs/Components/CuffableComponent.cs +++ b/Content.Shared/Cuffs/Components/CuffableComponent.cs @@ -39,6 +39,14 @@ public sealed partial class CuffableComponent : Component /// [DataField("canStillInteract"), ViewVariables(VVAccess.ReadWrite)] public bool CanStillInteract = true; + + // Parkstation-EndOfRoundStats-Start + /// + /// When this entity was cuffed, if currently cuffed. + /// + [DataField("cuffedTime")] + public TimeSpan? CuffedTime { get; set; } + // Parkstation-EndOfRoundStats-End } [Serializable, NetSerializable] diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 99657c87aa..35cfec0c99 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -27,6 +27,7 @@ using Content.Shared.Pulling.Components; using Content.Shared.Pulling.Events; using Content.Shared.Rejuvenate; +using Content.Shared.SimpleStation14.EndOfRoundStats.CuffedTime; using Content.Shared.Stunnable; using Content.Shared.Verbs; using Content.Shared.Weapons.Melee.Events; @@ -36,6 +37,7 @@ using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Serialization; +using Robust.Shared.Timing; namespace Content.Shared.Cuffs { @@ -58,6 +60,7 @@ public abstract partial class SharedCuffableSystem : EntitySystem [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; public override void Initialize() { @@ -451,6 +454,10 @@ public bool TryAddNewCuffs(EntityUid target, EntityUid user, EntityUid handcuff, _container.Insert(handcuff, component.Container); UpdateHeldItems(target, handcuff, component); + + if (_net.IsServer) // Parkstation-EndOfRoundStats + component.CuffedTime = _gameTiming.CurTime; + return true; } @@ -689,6 +696,14 @@ public void Uncuff(EntityUid target, EntityUid? user, EntityUid cuffsToRemove, C ("cuffedHandCount", cuffable.CuffedHandCount)), user.Value, user.Value); } } + + // Parkstation-EndOfRoundStats-Start + if (_net.IsServer && cuffable.CuffedTime != null) + { + RaiseLocalEvent(target, new CuffedTimeStatEvent(_gameTiming.CurTime - cuffable.CuffedTime.Value)); + cuffable.CuffedTime = null; + } + // Parkstation-EndOfRoundStats-End } cuff.Removing = false; } diff --git a/Content.Shared/SimpleStation14/CCVar/CCVars.cs b/Content.Shared/SimpleStation14/CCVar/CCVars.cs new file mode 100644 index 0000000000..d5d8613a4e --- /dev/null +++ b/Content.Shared/SimpleStation14/CCVar/CCVars.cs @@ -0,0 +1,122 @@ +using Robust.Shared.Configuration; + +namespace Content.Shared.SimpleStation14.CCVar; + +[CVarDefs] +public sealed class SimpleStationCCVars +{ + /* + * End of round stats + */ + #region EndOfRoundStats + #region BloodLost + /// + /// The amount of blood lost required to trigger the BloodLost end of round stat. + /// + /// + /// Setting this to 0 will disable the BloodLost end of round stat. + /// + public static readonly CVarDef BloodLostThreshold = + CVarDef.Create("eorstats.bloodlost_threshold", 300f, CVar.SERVERONLY); + #endregion BloodLost + + #region CuffedTime + /// + /// The amount of time required to trigger the CuffedTime end of round stat, in minutes. + /// + /// + /// Setting this to 0 will disable the CuffedTime end of round stat. + /// + public static readonly CVarDef CuffedTimeThreshold = + CVarDef.Create("eorstats.cuffedtime_threshold", 8, CVar.SERVERONLY); + #endregion CuffedTime + + #region EmitSound + /// + /// The amount of sounds required to trigger the EmitSound end of round stat. + /// + /// + /// Setting this to 0 will disable the EmitSound end of round stat. + /// + public static readonly CVarDef EmitSoundThreshold = + CVarDef.Create("eorstats.emitsound_threshold", 80, CVar.SERVERONLY); + #endregion EmitSound + + #region InstrumentPlayed + /// + /// The amount of instruments required to trigger the InstrumentPlayed end of round stat, in minutes. + /// + /// + /// Setting this to 0 will disable the InstrumentPlayed end of round stat. + /// + public static readonly CVarDef InstrumentPlayedThreshold = + CVarDef.Create("eorstats.instrumentplayed_threshold", 8, CVar.SERVERONLY); + #endregion InstrumentPlayed + + #region MopUsed + /// + /// The amount of liquid mopped required to trigger the MopUsed end of round stat. + /// + /// + /// Setting this to 0 will disable the MopUsed end of round stat. + /// + public static readonly CVarDef MopUsedThreshold = + CVarDef.Create("eorstats.mopused_threshold", 200, CVar.SERVERONLY); + + /// + /// Should a stat be displayed specifically when no mopping was done? + /// + public static readonly CVarDef MopUsedDisplayNone = + CVarDef.Create("eorstats.mopused_displaynone", true, CVar.SERVERONLY); + + /// + /// The amount of top moppers to show in the end of round stats. + /// + /// + /// Setting this to 0 will disable the top moppers. + /// + public static readonly CVarDef MopUsedTopMopperCount = + CVarDef.Create("eorstats.mopused_topmoppercount", 3, CVar.SERVERONLY); + #endregion MopUsed + + #region ShotsFired + /// + /// The amount of shots fired required to trigger the ShotsFired end of round stat. + /// + /// + /// Setting this to 0 will disable the ShotsFired end of round stat. + /// + public static readonly CVarDef ShotsFiredThreshold = + CVarDef.Create("eorstats.shotsfired_threshold", 40, CVar.SERVERONLY); + + /// + /// Should a stat be displayed specifically when no shots were fired? + /// + public static readonly CVarDef ShotsFiredDisplayNone = + CVarDef.Create("eorstats.shotsfired_displaynone", true, CVar.SERVERONLY); + #endregion ShotsFired + + #region SlippedCount + /// + /// The amount of times slipped required to trigger the SlippedCount end of round stat. + /// + /// + /// Setting this to 0 will disable the SlippedCount end of round stat. + /// + public static readonly CVarDef SlippedCountThreshold = + CVarDef.Create("eorstats.slippedcount_threshold", 30, CVar.SERVERONLY); + + /// + /// Should a stat be displayed specifically when nobody was done? + /// + public static readonly CVarDef SlippedCountDisplayNone = + CVarDef.Create("eorstats.slippedcount_displaynone", true, CVar.SERVERONLY); + + /// + /// Should the top slipper be displayed in the end of round stats? + /// + public static readonly CVarDef SlippedCountTopSlipper = + CVarDef.Create("eorstats.slippedcount_topslipper", true, CVar.SERVERONLY); + #endregion SlippedCount + #endregion EndOfRoundStats +} diff --git a/Content.Shared/SimpleStation14/EndOfRoundStats/CuffedTime/CuffedTimeStatEvent.cs b/Content.Shared/SimpleStation14/EndOfRoundStats/CuffedTime/CuffedTimeStatEvent.cs new file mode 100644 index 0000000000..72a94e01b4 --- /dev/null +++ b/Content.Shared/SimpleStation14/EndOfRoundStats/CuffedTime/CuffedTimeStatEvent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.SimpleStation14.EndOfRoundStats.CuffedTime; // Handcuffs are shared. + +public sealed class CuffedTimeStatEvent : EntityEventArgs +{ + public TimeSpan Duration; + + public CuffedTimeStatEvent(TimeSpan duration) + { + Duration = duration; + } +} diff --git a/Content.Shared/SimpleStation14/EndOfRoundStats/EmitSoundStatSystem.cs/EmitSoundStatEvent.cs b/Content.Shared/SimpleStation14/EndOfRoundStats/EmitSoundStatSystem.cs/EmitSoundStatEvent.cs new file mode 100644 index 0000000000..c4741e2fe8 --- /dev/null +++ b/Content.Shared/SimpleStation14/EndOfRoundStats/EmitSoundStatSystem.cs/EmitSoundStatEvent.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Audio; + +namespace Content.Shared.SimpleStation14.EndOfRoundStats.EmitSound; // Sound emitters are shared. + +public sealed class EmitSoundStatEvent : EntityEventArgs +{ + public EntityUid Emitter; + public SoundSpecifier Sound; + + public EmitSoundStatEvent(EntityUid emitter, SoundSpecifier sound) + { + Emitter = emitter; + Sound = sound; + } +} diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs index 5e131a1355..43ffd5c6d8 100644 --- a/Content.Shared/Sound/SharedEmitSoundSystem.cs +++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Interaction.Events; using Content.Shared.Maps; using Content.Shared.Popups; +using Content.Shared.SimpleStation14.EndOfRoundStats.EmitSound; using Content.Shared.Sound.Components; using Content.Shared.Throwing; using JetBrains.Annotations; @@ -117,6 +118,9 @@ protected void TryEmitSound(EntityUid uid, BaseEmitSoundComponent component, Ent // don't predict sounds that client couldn't have played already _audioSystem.PlayPvs(component.Sound, uid); } + + if (_netMan.IsServer) // Parkstation-EndOfRoundStats + RaiseLocalEvent(new EmitSoundStatEvent(uid, component.Sound)); } private void OnEmitSoundUnpaused(EntityUid uid, EmitSoundOnCollideComponent component, ref EntityUnpausedEvent args) diff --git a/Resources/Locale/en-US/simplestation14/end-of-round-stats/blood-lost.ftl b/Resources/Locale/en-US/simplestation14/end-of-round-stats/blood-lost.ftl new file mode 100644 index 0000000000..f38e547ef9 --- /dev/null +++ b/Resources/Locale/en-US/simplestation14/end-of-round-stats/blood-lost.ftl @@ -0,0 +1 @@ +eorstats-bloodlost-total = {$bloodLost} units of blood were lost this round! diff --git a/Resources/Locale/en-US/simplestation14/end-of-round-stats/cuffed-time.ftl b/Resources/Locale/en-US/simplestation14/end-of-round-stats/cuffed-time.ftl new file mode 100644 index 0000000000..484cb04636 --- /dev/null +++ b/Resources/Locale/en-US/simplestation14/end-of-round-stats/cuffed-time.ftl @@ -0,0 +1,2 @@ +eorstats-cuffedtime-hasusername = {$username} as {$name} spent {$timeCuffedMinutes} minutes in cuffs this round! What'd they do?? +eorstats-cuffedtime-hasnousername = {$name} spent {$timeCuffedMinutes} minutes in cuffs this round! What'd they do?? diff --git a/Resources/Locale/en-US/simplestation14/end-of-round-stats/emitted-sound.ftl b/Resources/Locale/en-US/simplestation14/end-of-round-stats/emitted-sound.ftl new file mode 100644 index 0000000000..b94270580a --- /dev/null +++ b/Resources/Locale/en-US/simplestation14/end-of-round-stats/emitted-sound.ftl @@ -0,0 +1,4 @@ +# Make sure the strings all follow the same format, and end with the respective enum. They're selected automatically. + +eorstats-emitsound-BikeHorn = Horns were honked a total of {$times} times! +eorstats-emitsound-Plushie = Plushies were squeezed {$times} times this shift! diff --git a/Resources/Locale/en-US/simplestation14/end-of-round-stats/instruments-played.ftl b/Resources/Locale/en-US/simplestation14/end-of-round-stats/instruments-played.ftl new file mode 100644 index 0000000000..c2cfe54b16 --- /dev/null +++ b/Resources/Locale/en-US/simplestation14/end-of-round-stats/instruments-played.ftl @@ -0,0 +1,2 @@ +eorstats-instrumentplayed-topplayer-hasusername = The master of vibes this round was {$username} as {$name} having played their tunes for {$amountPlayedMinutes} minutes! +eorstats-instrumentplayed-topplayer-hasnousername = The master of vibes this round was {$name} having played their tunes for {$amountPlayedMinutes} minutes! diff --git a/Resources/Locale/en-US/simplestation14/end-of-round-stats/mop-used.ftl b/Resources/Locale/en-US/simplestation14/end-of-round-stats/mop-used.ftl new file mode 100644 index 0000000000..22463a40c4 --- /dev/null +++ b/Resources/Locale/en-US/simplestation14/end-of-round-stats/mop-used.ftl @@ -0,0 +1,8 @@ +eorstats-mop-amountmopped = A total of [color={$impressColor}]{$amountMopped}[/color] units of liquid was mopped this round across [color={$impressColor}]{$timesMopped}[/color] puddles! + +eorstats-mop-topmopper-header = The top moppers were: + +eorstats-mop-topmopper-hasusername = [color={$impressColor}]{$place}. {$username} as {$name} with {$amountMopped} units mopped![/color] +eorstats-mop-topmopper-hasnousername = [color={$impressColor}]{$place}. {$name} with {$amountMopped} units mopped![/color] + +eorstats-mop-noamountmopped = Not one puddle was mopped this round! diff --git a/Resources/Locale/en-US/simplestation14/end-of-round-stats/shots-fired.ftl b/Resources/Locale/en-US/simplestation14/end-of-round-stats/shots-fired.ftl new file mode 100644 index 0000000000..6471467468 --- /dev/null +++ b/Resources/Locale/en-US/simplestation14/end-of-round-stats/shots-fired.ftl @@ -0,0 +1,2 @@ +eorstats-shotsfired-noshotsfired = Not one shot was fired this round! +eorstats-shotsfired-amount = {$shotsFired} shots were fired this round. diff --git a/Resources/Locale/en-US/simplestation14/end-of-round-stats/slipped-count.ftl b/Resources/Locale/en-US/simplestation14/end-of-round-stats/slipped-count.ftl new file mode 100644 index 0000000000..f5f77cdab6 --- /dev/null +++ b/Resources/Locale/en-US/simplestation14/end-of-round-stats/slipped-count.ftl @@ -0,0 +1,6 @@ +eorstats-slippedcount-totalslips = The crew slipped a total of {$timesSlipped} times this shift! + +eorstats-slippedcount-none = The crew didn't slip at all this shift! + +eorstats-slippedcount-topslipper-hasusername = {$username} as {$name} was a clutz this shift and slipped {$slipcount} times! +eorstats-slippedcount-topslipper-hasnousername = {$name} was a clutz this shift and slipped {$slipcount} times! diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index 84b8b289ef..05ef462b5f 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -32,6 +32,9 @@ Cloth: 100 - type: StaticPrice price: 5 + - type: Tag # Parkstation-EndOfRoundStats + tags: + - Plushie - type: entity parent: BasePlushie @@ -66,6 +69,7 @@ tags: - ForceableFollow - PlushieGhost + - Plushie # Parkstation-EndOfRoundStats - type: RandomWalk accumulatorRatio: 0.5 maxSpeed: 1 @@ -333,6 +337,7 @@ - type: Tag tags: - PlushieSharkBlue + - Plushie # Parkstation-EndOfRoundStats - type: entity parent: PlushieSharkBlue @@ -348,6 +353,7 @@ - type: Tag tags: - PlushieSharkPink + - Plushie # Parkstation-EndOfRoundStats - type: entity parent: PlushieSharkBlue @@ -363,6 +369,7 @@ - type: Tag tags: - PlushieSharkGrey + - Plushie # Parkstation-EndOfRoundStats - type: entity parent: BasePlushie @@ -869,7 +876,7 @@ params: variation: 0.65 volume: -10 - + - type: entity parent: BaseItem id: PonderingOrb @@ -1213,4 +1220,4 @@ path: /Audio/Voice/Human/malescream_3.ogg - type: MeleeWeapon soundHit: - path: /Audio/Voice/Human/malescream_4.ogg \ No newline at end of file + path: /Audio/Voice/Human/malescream_4.ogg diff --git a/Resources/Prototypes/SimpleStation14/tags.yml b/Resources/Prototypes/SimpleStation14/tags.yml index 3b885a5801..2743fee8e5 100644 --- a/Resources/Prototypes/SimpleStation14/tags.yml +++ b/Resources/Prototypes/SimpleStation14/tags.yml @@ -1,2 +1,5 @@ - type: Tag id: GlassesNearsight + +- type: Tag + id: Plushie