stats (#103)
* OH YES * хули мне хедкодеру # Conflicts: # Content.Server/Instruments/InstrumentComponent.cs # Content.Server/Instruments/InstrumentSystem.cs # Content.Shared/Cuffs/SharedCuffableSystem.cs # Content.Shared/White/WhiteCVars.cs # Resources/Prototypes/tags.yml
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/*
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
@@ -57,4 +58,5 @@ public sealed class SlippingTest : MovementTest
|
||||
AssertComp<KnockedDownComponent>(true, Player);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.HealthExaminable;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.White.EndOfRoundStats.BloodLost;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
@@ -92,6 +93,8 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var totalBloodLost = 0f; // White-EndOfRoundStats
|
||||
|
||||
var query = EntityQueryEnumerator<BloodstreamComponent>();
|
||||
while (query.MoveNext(out var uid, out var bloodstream))
|
||||
{
|
||||
@@ -125,6 +128,7 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
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; // White - EndOfRoundStats
|
||||
}
|
||||
|
||||
// deal bloodloss damage if their blood level is below a threshold.
|
||||
@@ -157,6 +161,7 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
bloodstream.StatusTime = 0;
|
||||
}
|
||||
}
|
||||
RaiseLocalEvent(new BloodLostStatEvent(totalBloodLost)); // White-EndOfRoundStats
|
||||
}
|
||||
|
||||
private void OnComponentInit(Entity<BloodstreamComponent> entity, ref ComponentInit args)
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed partial class InstrumentComponent : SharedInstrumentComponent
|
||||
public ICommonSession? InstrumentPlayer =>
|
||||
_entMan.GetComponentOrNull<ActivatableUIComponent>(Owner)?.CurrentSingleUser
|
||||
?? _entMan.GetComponentOrNull<ActorComponent>(Owner)?.PlayerSession;
|
||||
|
||||
public TimeSpan? TimeStartedPlaying { get; set; }
|
||||
}
|
||||
|
||||
[RegisterComponent]
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Interaction;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Server.White.EndOfRoundStats.InstrumentPlayed;
|
||||
using Content.Shared.Instruments;
|
||||
using Content.Shared.Instruments.UI;
|
||||
using Content.Shared.Physics;
|
||||
@@ -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;
|
||||
|
||||
@@ -113,6 +115,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
|
||||
instrument.Playing = true;
|
||||
Dirty(uid, instrument);
|
||||
instrument.TimeStartedPlaying = _gameTiming.CurTime;
|
||||
}
|
||||
|
||||
private void OnMidiStop(InstrumentStopMidiEvent msg, EntitySessionEventArgs args)
|
||||
@@ -235,7 +238,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
{
|
||||
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
if (Deleted(uid, metadataQuery))
|
||||
if (Deleted(uid))
|
||||
return Array.Empty<(NetEntity, string)>();
|
||||
|
||||
var list = new ValueList<(NetEntity, string)>();
|
||||
@@ -291,6 +294,17 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
RaiseNetworkEvent(new InstrumentStopMidiEvent(netUid));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
instrument.Playing = false;
|
||||
instrument.Master = null;
|
||||
instrument.FilteredChannels.SetAll(false);
|
||||
@@ -392,7 +406,6 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
}
|
||||
|
||||
var activeQuery = EntityManager.GetEntityQuery<ActiveInstrumentComponent>();
|
||||
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
var transformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
var query = AllEntityQuery<ActiveInstrumentComponent, InstrumentComponent>();
|
||||
@@ -400,7 +413,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
{
|
||||
if (instrument.Master is {} master)
|
||||
{
|
||||
if (Deleted(master, metadataQuery))
|
||||
if (Deleted(master))
|
||||
{
|
||||
Clean(uid, instrument);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Content.Server.White.EndOfRoundStats.BloodLost;
|
||||
|
||||
public sealed class BloodLostStatEvent : EntityEventArgs
|
||||
{
|
||||
public float BloodLost;
|
||||
|
||||
public BloodLostStatEvent(float bloodLost)
|
||||
{
|
||||
BloodLost = bloodLost;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.Configuration;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.White;
|
||||
|
||||
namespace Content.Server.White.EndOfRoundStats.BloodLost;
|
||||
|
||||
public sealed class BloodLostStatSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
FixedPoint2 totalBloodLost = 0;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BloodLostStatEvent>(OnBloodLost);
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEnd);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
}
|
||||
|
||||
private void OnBloodLost(BloodLostStatEvent args)
|
||||
{
|
||||
totalBloodLost += args.BloodLost;
|
||||
}
|
||||
|
||||
private void OnRoundEnd(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
var line = String.Empty;
|
||||
|
||||
if (totalBloodLost < _config.GetCVar<float>(WhiteCVars.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.GameTicking;
|
||||
|
||||
namespace Content.Server.White.EndOfRoundStats.Command;
|
||||
|
||||
public sealed class CommandStatSystem : EntitySystem
|
||||
{
|
||||
public List<(string, string)> eorStats = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEnd);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(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();
|
||||
}
|
||||
}
|
||||
@@ -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.White.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} <stat> <color?>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var _stats = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<CommandStatSystem>();
|
||||
|
||||
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("<Stat>");
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
var options = Color.GetAllDefaultColors().Select(o => new CompletionOption(o.Key));
|
||||
|
||||
return CompletionResult.FromHintOptions(options, "<Color?>");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.White.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<IEntitySystemManager>().GetEntitySystem<CommandStatSystem>();
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.White.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} <stat index?>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var _stats = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<CommandStatSystem>();
|
||||
|
||||
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<IEntitySystemManager>().GetEntitySystem<CommandStatSystem>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Text;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Cuffs.Components;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.White;
|
||||
using Content.Shared.White.EndOfRoundStats.CuffedTime;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.White.EndOfRoundStats.CuffedTime;
|
||||
|
||||
public sealed class CuffedTimeStatSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
private readonly Dictionary<PlayerData, TimeSpan> _userPlayStats = new();
|
||||
|
||||
private struct PlayerData
|
||||
{
|
||||
public string Name;
|
||||
public string? Username;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CuffableComponent, CuffedTimeStatEvent>(OnUncuffed);
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEnd);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
}
|
||||
|
||||
private void OnUncuffed(EntityUid uid, CuffableComponent component, CuffedTimeStatEvent args)
|
||||
{
|
||||
string? username = null;
|
||||
|
||||
if (EntityManager.TryGetComponent<MindComponent>(uid, out var mindComponent) &&
|
||||
mindComponent.Session != null)
|
||||
username = mindComponent.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<CuffableComponent>();
|
||||
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 sb = new StringBuilder("\n[color=cadetblue]");
|
||||
|
||||
(PlayerData Player, TimeSpan TimePlayed) topPlayer = (new PlayerData(), TimeSpan.Zero);
|
||||
|
||||
foreach (var (player, timePlayed) in _userPlayStats)
|
||||
{
|
||||
if (timePlayed >= topPlayer.TimePlayed)
|
||||
topPlayer = (player, timePlayed);
|
||||
}
|
||||
|
||||
if (topPlayer.TimePlayed < TimeSpan.FromMinutes(_config.GetCVar(WhiteCVars.CuffedTimeThreshold)))
|
||||
return;
|
||||
|
||||
sb.Append(GenerateTopPlayer(topPlayer.Item1, topPlayer.Item2));
|
||||
sb.Append("[/color]");
|
||||
ev.AddLine(sb.ToString());
|
||||
}
|
||||
|
||||
private string GenerateTopPlayer(PlayerData data, TimeSpan timeCuffed)
|
||||
{
|
||||
if (data.Username != null)
|
||||
{
|
||||
return Loc.GetString
|
||||
(
|
||||
"eorstats-cuffedtime-hasusername",
|
||||
("username", data.Username),
|
||||
("name", data.Name),
|
||||
("timeCuffedMinutes", Math.Round(timeCuffed.TotalMinutes))
|
||||
);
|
||||
}
|
||||
|
||||
return Loc.GetString
|
||||
(
|
||||
"eorstats-cuffedtime-nousername",
|
||||
("name", data.Name),
|
||||
("timeCuffedMinutes", Math.Round(timeCuffed.TotalMinutes))
|
||||
);
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_userPlayStats.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.White;
|
||||
using Content.Shared.White.EndOfRoundStats.EmitSoundStatSystem;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Server.White.EndOfRoundStats.EmitSound;
|
||||
|
||||
public sealed class EmitSoundStatSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
|
||||
Dictionary<SoundSources, int> 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-<Enum> (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<EmitSoundStatEvent>(OnSoundEmitted);
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEnd);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
}
|
||||
|
||||
private void OnSoundEmitted(EmitSoundStatEvent ev)
|
||||
{
|
||||
SoundSources? source = null;
|
||||
|
||||
foreach (var enumSource in Enum.GetValues<SoundSources>())
|
||||
{
|
||||
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<int>(WhiteCVars.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-<enum>)");
|
||||
|
||||
throw new ArgumentException("Unknown messageId: " + preLocalString);
|
||||
}
|
||||
|
||||
line = localString;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
soundsEmitted.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Content.Server.White.EndOfRoundStats.InstrumentPlayed;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Instruments;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.White;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.White.EndOfRoundStats.InstrumentPlayed;
|
||||
|
||||
public sealed class InstrumentPlayedStatSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
Dictionary<PlayerData, TimeSpan> userPlayStats = new();
|
||||
|
||||
private struct PlayerData
|
||||
{
|
||||
public String Name;
|
||||
public String? Username;
|
||||
}
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InstrumentPlayedStatEvent>(OnInstrumentPlayed);
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEnd);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(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<InstrumentComponent>().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(WhiteCVars.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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.White;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Server.White.EndOfRoundStats.ShotsFired;
|
||||
|
||||
public sealed class ShotsFiredStatSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
|
||||
int shotsFired = 0;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GunComponent, AmmoShotEvent>(OnShotFired);
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEnd);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(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<bool>(WhiteCVars.ShotsFiredDisplayNone))
|
||||
return Loc.GetString("eorstats-shotsfired-noshotsfired");
|
||||
|
||||
if (shotsFired == 0 || shotsFired < _config.GetCVar<int>(WhiteCVars.ShotsFiredThreshold))
|
||||
return string.Empty;
|
||||
|
||||
return Loc.GetString("eorstats-shotsfired-amount", ("shotsFired", shotsFired));
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
shotsFired = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.White;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Server.White.EndOfRoundStats.SlippedCount;
|
||||
|
||||
public sealed class SlippedCountStatSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
private readonly Dictionary<PlayerData, int> _userSlipStats = new();
|
||||
|
||||
private struct PlayerData
|
||||
{
|
||||
public string Name;
|
||||
public string? Username;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SlipperyComponent, SlipEvent>(OnSlip);
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEnd);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
}
|
||||
|
||||
private void OnSlip(EntityUid uid, SlipperyComponent slipComp, ref SlipEvent args)
|
||||
{
|
||||
string? username = null;
|
||||
|
||||
var entity = args.Slipped;
|
||||
|
||||
if (EntityManager.TryGetComponent<MindComponent>(entity, out var mindComp))
|
||||
{
|
||||
username = mindComp.CharacterName;
|
||||
}
|
||||
|
||||
var playerData = new PlayerData
|
||||
{
|
||||
Name = MetaData(entity).EntityName,
|
||||
Username = username
|
||||
};
|
||||
|
||||
if (!_userSlipStats.TryAdd(playerData, 1))
|
||||
{
|
||||
_userSlipStats[playerData]++;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRoundEnd(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
if (_config.GetCVar(WhiteCVars.SlippedCountTopSlipper) == false)
|
||||
return;
|
||||
|
||||
var sortedSlippers = _userSlipStats.OrderByDescending(m => m.Value).ToList();
|
||||
|
||||
var totalTimesSlipped = sortedSlippers.Sum(m => m.Value);
|
||||
|
||||
var sb = new StringBuilder("\n[color=springGreen]");
|
||||
|
||||
if (totalTimesSlipped < _config.GetCVar(WhiteCVars.SlippedCountThreshold))
|
||||
{
|
||||
if (totalTimesSlipped == 0 && _config.GetCVar(WhiteCVars.SlippedCountDisplayNone))
|
||||
{
|
||||
sb.Append(Loc.GetString("eorstats-slippedcount-none"));
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine(Loc.GetString("eorstats-slippedcount-totalslips", ("timesSlipped", totalTimesSlipped)));
|
||||
sb.Append(GenerateTopSlipper(sortedSlippers.First().Key, sortedSlippers.First().Value));
|
||||
}
|
||||
|
||||
sb.Append("[/color]");
|
||||
ev.AddLine(sb.ToString());
|
||||
}
|
||||
|
||||
private string GenerateTopSlipper(PlayerData data, int amountSlipped)
|
||||
{
|
||||
if (data.Username != null)
|
||||
{
|
||||
return Loc.GetString
|
||||
(
|
||||
"eorstats-slippedcount-topslipper-hasusername",
|
||||
("username", data.Username),
|
||||
("name", data.Name),
|
||||
("slipcount", amountSlipped)
|
||||
);
|
||||
}
|
||||
|
||||
return Loc.GetString
|
||||
(
|
||||
"eorstats-slippedcount-topslipper-hasnousername",
|
||||
("name", data.Name),
|
||||
("slipcount", amountSlipped)
|
||||
);
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_userSlipStats.Clear();
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@ public sealed partial class CuffableComponent : Component
|
||||
/// </summary>
|
||||
[DataField("canStillInteract"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CanStillInteract = true;
|
||||
|
||||
[DataField("cuffedTime")]
|
||||
public TimeSpan? CuffedTime { get; set; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -3,7 +3,6 @@ using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Components;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Cuffs.Components;
|
||||
using Content.Shared.Damage;
|
||||
@@ -29,12 +28,13 @@ using Content.Shared.Rejuvenate;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.White.EndOfRoundStats.CuffedTime;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Cuffs
|
||||
{
|
||||
@@ -57,6 +57,7 @@ namespace Content.Shared.Cuffs
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!; // Parkstation-EndOfRoundStats
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -450,6 +451,10 @@ namespace Content.Shared.Cuffs
|
||||
|
||||
_container.Insert(handcuff, component.Container);
|
||||
UpdateHeldItems(target, handcuff, component);
|
||||
|
||||
if (_net.IsServer)
|
||||
component.CuffedTime = _gameTiming.CurTime;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -637,6 +642,12 @@ namespace Content.Shared.Cuffs
|
||||
|
||||
_container.Remove(cuffsToRemove, cuffable.Container);
|
||||
|
||||
if (_net.IsServer && cuffable.CuffedTime != null)
|
||||
{
|
||||
RaiseLocalEvent(target, new CuffedTimeStatEvent(_gameTiming.CurTime - cuffable.CuffedTime.Value));
|
||||
cuffable.CuffedTime = null;
|
||||
}
|
||||
|
||||
if (_net.IsServer)
|
||||
{
|
||||
// Handles spawning broken cuffs on server to avoid client misprediction
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Maps;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Sound.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.White.EndOfRoundStats.EmitSoundStatSystem;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -117,6 +118,9 @@ public abstract class SharedEmitSoundSystem : EntitySystem
|
||||
// don't predict sounds that client couldn't have played already
|
||||
_audioSystem.PlayPvs(component.Sound, uid);
|
||||
}
|
||||
|
||||
if (_netMan.IsServer)
|
||||
RaiseLocalEvent(new EmitSoundStatEvent(component.Owner, component.Sound));
|
||||
}
|
||||
|
||||
private void OnEmitSoundUnpaused(EntityUid uid, EmitSoundOnCollideComponent component, ref EntityUnpausedEvent args)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Content.Shared.White.EndOfRoundStats.CuffedTime;
|
||||
|
||||
public sealed class CuffedTimeStatEvent : EntityEventArgs
|
||||
{
|
||||
public TimeSpan Duration;
|
||||
|
||||
public CuffedTimeStatEvent(TimeSpan duration)
|
||||
{
|
||||
Duration = duration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Shared.White.EndOfRoundStats.EmitSoundStatSystem;
|
||||
|
||||
public sealed class EmitSoundStatEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Emitter;
|
||||
public SoundSpecifier Sound;
|
||||
|
||||
public EmitSoundStatEvent(EntityUid emitter, SoundSpecifier sound)
|
||||
{
|
||||
Emitter = emitter;
|
||||
Sound = sound;
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,6 @@ public sealed class WhiteCVars
|
||||
public static readonly CVarDef<float> JukeboxVolume =
|
||||
CVarDef.Create("white.jukebox_volume", 0f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
|
||||
/*
|
||||
* Chat
|
||||
*/
|
||||
@@ -181,4 +180,80 @@ public sealed class WhiteCVars
|
||||
|
||||
public static readonly CVarDef<string> DefaultChatSize =
|
||||
CVarDef.Create("white.chat_size_default", "300;500", CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* End of round stats
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// The amount of blood lost required to trigger the BloodLost end of round stat.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting this to 0 will disable the BloodLost end of round stat.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> BloodLostThreshold =
|
||||
CVarDef.Create("eorstats.bloodlost_threshold", 100f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time required to trigger the CuffedTime end of round stat, in minutes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting this to 0 will disable the CuffedTime end of round stat.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> CuffedTimeThreshold =
|
||||
CVarDef.Create("eorstats.cuffedtime_threshold", 5, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of sounds required to trigger the EmitSound end of round stat.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting this to 0 will disable the EmitSound end of round stat.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> EmitSoundThreshold =
|
||||
CVarDef.Create("eorstats.emitsound_threshold", 10, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of instruments required to trigger the InstrumentPlayed end of round stat, in minutes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting this to 0 will disable the InstrumentPlayed end of round stat.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> InstrumentPlayedThreshold =
|
||||
CVarDef.Create("eorstats.instrumentplayed_threshold", 4, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of shots fired required to trigger the ShotsFired end of round stat.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting this to 0 will disable the ShotsFired end of round stat.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> ShotsFiredThreshold =
|
||||
CVarDef.Create("eorstats.shotsfired_threshold", 40, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Should a stat be displayed specifically when no shots were fired?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> ShotsFiredDisplayNone =
|
||||
CVarDef.Create("eorstats.shotsfired_displaynone", true, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of times slipped required to trigger the SlippedCount end of round stat.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting this to 0 will disable the SlippedCount end of round stat.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> SlippedCountThreshold =
|
||||
CVarDef.Create("eorstats.slippedcount_threshold", 30, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Should a stat be displayed specifically when nobody was done?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> SlippedCountDisplayNone =
|
||||
CVarDef.Create("eorstats.slippedcount_displaynone", true, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Should the top slipper be displayed in the end of round stats?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> SlippedCountTopSlipper =
|
||||
CVarDef.Create("eorstats.slippedcount_topslipper", true, CVar.SERVERONLY);
|
||||
}
|
||||
|
||||
@@ -4,3 +4,16 @@ carry-verb = Тащить на руках
|
||||
|
||||
chat-manager-entity-say-god-wrap-message = {$entityName} командует, "[color={$color}]{$message}[/color]"
|
||||
|
||||
eorstats-bloodlost-total = {$bloodLost} единиц крови было потеряно в этом раунде!
|
||||
eorstats-cuffedtime-hasusername = {$username} под именем {$name} провел(а) {$timeCuffedMinutes} минут в наручниках в этом раунде! Что он натворил?
|
||||
eorstats-cuffedtime-hasnousername = {$name} провел(а) {$timeCuffedMinutes} минут в наручниках в этом раунде! Что он натворил?
|
||||
eorstats-emitsound-BikeHorn = Количество honk`а в этом раунде было: {$times}
|
||||
eorstats-emitsound-Plushie = Мягкие игрушки были сжаты {$times} раз за эту смену!
|
||||
eorstats-instrumentplayed-topplayer-hasusername = Мастер атмосферы в этом раунде - {$username} под именем {$name}, который играл свои мелодии в течение {$amountPlayedMinutes} минут!
|
||||
eorstats-instrumentplayed-topplayer-hasnousername = Мастер атмосферы в этом раунде - {$name}, который играл свои мелодии в течение {$amountPlayedMinutes} минут!
|
||||
eorstats-shotsfired-noshotsfired = В этом раунде не было произведено ни одного выстрела!
|
||||
eorstats-shotsfired-amount = В этом раунде было произведено {$shotsFired} выстрелов.
|
||||
eorstats-slippedcount-totalslips = Экипаж поскользнулся {$timesSlipped} раз за эту смену!
|
||||
eorstats-slippedcount-none = Экипаж не поскользнулся ни разу за эту смену!
|
||||
eorstats-slippedcount-topslipper-hasusername = {$username} под именем {$name} был неуклюжим в эту смену и поскользнулся {$slipcount} раз!
|
||||
eorstats-slippedcount-topslipper-hasnousername = {$name} был неуклюжим в эту смену и поскользнулся {$slipcount} раз!
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
Cloth: 100
|
||||
- type: StaticPrice
|
||||
price: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Plushie
|
||||
|
||||
- type: entity
|
||||
parent: BasePlushie
|
||||
|
||||
Reference in New Issue
Block a user