Save round information into replay_final.yml (#23013)

* Save round information into the replay

* Add round end text too

* This is way better

* Get actual job

* oop

* OK THERE

* Fake line endings to make life easier

* I was told this yaml is legal

* I just realised this will make my life easier

* REVIEWS BABY IM A PROGRAMMER MOMMY

* Live pjb reaction

* Live pjb reaction 2

* Reviews 2

* Dont need this

* Please no more have mercy on my soul

* Oh frick
This commit is contained in:
Vasilis
2024-02-17 21:30:54 +01:00
committed by GitHub
parent 5b485fedbd
commit cb999d23f4
5 changed files with 63 additions and 7 deletions

View File

@@ -2,6 +2,10 @@
using Robust.Shared; using Robust.Shared;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.Replays; using Robust.Shared.Replays;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.GameTicking; namespace Content.Server.GameTicking;
@@ -10,12 +14,15 @@ public sealed partial class GameTicker
{ {
[Dependency] private readonly IReplayRecordingManager _replays = default!; [Dependency] private readonly IReplayRecordingManager _replays = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly ISerializationManager _serialman = default!;
private ISawmill _sawmillReplays = default!; private ISawmill _sawmillReplays = default!;
private void InitializeReplays() private void InitializeReplays()
{ {
_replays.RecordingFinished += ReplaysOnRecordingFinished; _replays.RecordingFinished += ReplaysOnRecordingFinished;
_replays.RecordingStopped += ReplaysOnRecordingStopped;
} }
/// <summary> /// <summary>
@@ -108,6 +115,20 @@ public sealed partial class GameTicker
data.Directory.Rename(data.Path, state.MoveToPath.Value); data.Directory.Rename(data.Path, state.MoveToPath.Value);
} }
private void ReplaysOnRecordingStopped(MappingDataNode metadata)
{
// Write round info like map and round end summery into the replay_final.yml file. Useful for external parsers.
metadata["map"] = new ValueDataNode(_gameMapManager.GetSelectedMap()?.MapName);
metadata["gamemode"] = new ValueDataNode(CurrentPreset != null ? Loc.GetString(CurrentPreset.ModeTitle) : string.Empty);
metadata["roundEndPlayers"] = _serialman.WriteValue(_replayRoundPlayerInfo);
metadata["roundEndText"] = new ValueDataNode(_replayRoundText);
metadata["server_id"] = new ValueDataNode(_configurationManager.GetCVar(CCVars.ServerId));
// These should be set to null to prepare them for the next round.
_replayRoundPlayerInfo = null;
_replayRoundText = null;
}
private ResPath GetAutoReplayPath() private ResPath GetAutoReplayPath()
{ {
var cfgValue = _cfg.GetCVar(CCVars.ReplayAutoRecordName); var cfgValue = _cfg.GetCVar(CCVars.ReplayAutoRecordName);

View File

@@ -46,6 +46,10 @@ namespace Content.Server.GameTicking
[ViewVariables] [ViewVariables]
private GameRunLevel _runLevel; private GameRunLevel _runLevel;
private RoundEndMessageEvent.RoundEndPlayerInfo[]? _replayRoundPlayerInfo;
private string? _replayRoundText;
[ViewVariables] [ViewVariables]
public GameRunLevel RunLevel public GameRunLevel RunLevel
{ {
@@ -372,11 +376,14 @@ namespace Content.Server.GameTicking
PlayerOOCName = contentPlayerData?.Name ?? "(IMPOSSIBLE: REGISTERED MIND WITH NO OWNER)", PlayerOOCName = contentPlayerData?.Name ?? "(IMPOSSIBLE: REGISTERED MIND WITH NO OWNER)",
// Character name takes precedence over current entity name // Character name takes precedence over current entity name
PlayerICName = playerIcName, PlayerICName = playerIcName,
PlayerGuid = userId,
PlayerNetEntity = GetNetEntity(entity), PlayerNetEntity = GetNetEntity(entity),
Role = antag Role = antag
? roles.First(role => role.Antagonist).Name ? roles.First(role => role.Antagonist).Name
: roles.FirstOrDefault().Name ?? Loc.GetString("game-ticker-unknown-role"), : roles.FirstOrDefault().Name ?? Loc.GetString("game-ticker-unknown-role"),
Antag = antag, Antag = antag,
JobPrototypes = roles.Where(role => !role.Antagonist).Select(role => role.Prototype).ToArray(),
AntagPrototypes = roles.Where(role => role.Antagonist).Select(role => role.Prototype).ToArray(),
Observer = observer, Observer = observer,
Connected = connected Connected = connected
}; };
@@ -389,6 +396,9 @@ namespace Content.Server.GameTicking
RaiseNetworkEvent(new RoundEndMessageEvent(gamemodeTitle, roundEndText, roundDuration, RoundId, RaiseNetworkEvent(new RoundEndMessageEvent(gamemodeTitle, roundEndText, roundDuration, RoundId,
listOfPlayerInfoFinal.Length, listOfPlayerInfoFinal, LobbySong, sound)); listOfPlayerInfoFinal.Length, listOfPlayerInfoFinal, LobbySong, sound));
_replayRoundPlayerInfo = listOfPlayerInfoFinal;
_replayRoundText = roundEndText;
} }
private async void SendRoundEndDiscordMessage() private async void SendRoundEndDiscordMessage()

View File

@@ -1,4 +1,5 @@
using Content.Shared.Roles; using Content.Shared.Roles;
using Robust.Shared.Network;
using Robust.Shared.Replays; using Robust.Shared.Replays;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.Markdown.Mapping;
@@ -144,18 +145,37 @@ namespace Content.Shared.GameTicking
} }
} }
[Serializable, NetSerializable] [Serializable, NetSerializable, DataDefinition]
public sealed class RoundEndMessageEvent : EntityEventArgs public sealed partial class RoundEndMessageEvent : EntityEventArgs
{ {
[Serializable, NetSerializable] [Serializable, NetSerializable, DataDefinition]
public struct RoundEndPlayerInfo public partial struct RoundEndPlayerInfo
{ {
[DataField]
public string PlayerOOCName; public string PlayerOOCName;
[DataField]
public string? PlayerICName; public string? PlayerICName;
[DataField, NonSerialized]
public NetUserId? PlayerGuid;
public string Role; public string Role;
[DataField, NonSerialized]
public string[] JobPrototypes;
[DataField, NonSerialized]
public string[] AntagPrototypes;
public NetEntity? PlayerNetEntity; public NetEntity? PlayerNetEntity;
[DataField]
public bool Antag; public bool Antag;
[DataField]
public bool Observer; public bool Observer;
public bool Connected; public bool Connected;
} }

View File

@@ -16,4 +16,5 @@ public readonly record struct MindGetAllRolesEvent(List<RoleInfo> Roles);
/// <param name="Name">Name of the role.</param> /// <param name="Name">Name of the role.</param>
/// <param name="Antagonist">Whether or not this role makes this player an antagonist.</param> /// <param name="Antagonist">Whether or not this role makes this player an antagonist.</param>
/// <param name="PlayTimeTrackerId">The <see cref="PlayTimeTrackerPrototype"/> id associated with the role.</param> /// <param name="PlayTimeTrackerId">The <see cref="PlayTimeTrackerPrototype"/> id associated with the role.</param>
public readonly record struct RoleInfo(Component Component, string Name, bool Antagonist, string? PlayTimeTrackerId); /// <param name="Prototype">The prototype ID of the role</param>
public readonly record struct RoleInfo(Component Component, string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype);

View File

@@ -27,16 +27,18 @@ public abstract class SharedRoleSystem : EntitySystem
private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args) private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args)
{ {
var name = "game-ticker-unknown-role"; var name = "game-ticker-unknown-role";
var prototype = "";
string? playTimeTracker = null; string? playTimeTracker = null;
if (component.Prototype != null && _prototypes.TryIndex(component.Prototype, out JobPrototype? job)) if (component.Prototype != null && _prototypes.TryIndex(component.Prototype, out JobPrototype? job))
{ {
name = job.Name; name = job.Name;
prototype = job.ID;
playTimeTracker = job.PlayTimeTracker; playTimeTracker = job.PlayTimeTracker;
} }
name = Loc.GetString(name); name = Loc.GetString(name);
args.Roles.Add(new RoleInfo(component, name, false, playTimeTracker)); args.Roles.Add(new RoleInfo(component, name, false, playTimeTracker, prototype));
} }
protected void SubscribeAntagEvents<T>() where T : AntagonistRoleComponent protected void SubscribeAntagEvents<T>() where T : AntagonistRoleComponent
@@ -44,13 +46,15 @@ public abstract class SharedRoleSystem : EntitySystem
SubscribeLocalEvent((EntityUid _, T component, ref MindGetAllRolesEvent args) => SubscribeLocalEvent((EntityUid _, T component, ref MindGetAllRolesEvent args) =>
{ {
var name = "game-ticker-unknown-role"; var name = "game-ticker-unknown-role";
var prototype = "";
if (component.PrototypeId != null && _prototypes.TryIndex(component.PrototypeId, out AntagPrototype? antag)) if (component.PrototypeId != null && _prototypes.TryIndex(component.PrototypeId, out AntagPrototype? antag))
{ {
name = antag.Name; name = antag.Name;
prototype = antag.ID;
} }
name = Loc.GetString(name); name = Loc.GetString(name);
args.Roles.Add(new RoleInfo(component, name, true, null)); args.Roles.Add(new RoleInfo(component, name, true, null, prototype));
}); });
SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => args.IsAntagonist = true); SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => args.IsAntagonist = true);