Ghost roles create new minds, better tracking of roles at round end screen (#5175)

* Ghost roles now get new Minds

* Some round start/end button stuff

* Mind tracking for better round end reports

* Make traitor kill objectives use mind CharacterName rather than actual occupied entity ("kill brain" prevention)

* Transition over to EntityUid for mind stuff because that's the only way to do it

* BrainSystem fix for PR rebase
This commit is contained in:
20kdc
2021-11-15 18:14:34 +00:00
committed by GitHub
parent c3a7548545
commit 4cce40bd9f
26 changed files with 229 additions and 68 deletions

View File

@@ -4,9 +4,10 @@
Margin="4" Margin="4"
MinSize="50 50"> MinSize="50 50">
<GridContainer <GridContainer
Columns="4"> Columns="3">
<cc:CommandButton Command="startround" Text="{Loc Start Round}" /> <cc:CommandButton Command="startround" Text="{Loc Start Round}" />
<cc:CommandButton Command="endround" Text="{Loc End Round}" /> <cc:CommandButton Command="endround" Text="{Loc End Round}" />
<cc:CommandButton Command="restartround" Text="{Loc Restart Round}" /> <cc:CommandButton Command="restartround" Text="{Loc Restart Round}" />
<cc:CommandButton Command="restartroundnow" Text="{Loc administration-ui-round-tab-restart-round-now}" />
</GridContainer> </GridContainer>
</Control> </Control>

View File

@@ -37,9 +37,9 @@ namespace Content.IntegrationTests.Tests
visitEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); visitEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace);
mind = new Mind(player.UserId); mind = new Mind(player.UserId);
player.ContentData().Mind = mind; mind.ChangeOwningPlayer(player.UserId);
mind.TransferTo(playerEnt); mind.TransferTo(playerEnt.Uid);
mind.Visit(visitEnt); mind.Visit(visitEnt);
Assert.That(player.AttachedEntity, Is.EqualTo(visitEnt)); Assert.That(player.AttachedEntity, Is.EqualTo(visitEnt));
@@ -81,9 +81,9 @@ namespace Content.IntegrationTests.Tests
playerEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); playerEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace);
mind = new Mind(player.UserId); mind = new Mind(player.UserId);
player.ContentData().Mind = mind; mind.ChangeOwningPlayer(player.UserId);
mind.TransferTo(playerEnt); mind.TransferTo(playerEnt.Uid);
Assert.That(mind.CurrentEntity, Is.EqualTo(playerEnt)); Assert.That(mind.CurrentEntity, Is.EqualTo(playerEnt));
}); });
@@ -130,9 +130,9 @@ namespace Content.IntegrationTests.Tests
playerEnt = entMgr.SpawnEntity(null, grid.ToCoordinates()); playerEnt = entMgr.SpawnEntity(null, grid.ToCoordinates());
mind = new Mind(player.UserId); mind = new Mind(player.UserId);
player.ContentData().Mind = mind; mind.ChangeOwningPlayer(player.UserId);
mind.TransferTo(playerEnt); mind.TransferTo(playerEnt.Uid);
Assert.That(mind.CurrentEntity, Is.EqualTo(playerEnt)); Assert.That(mind.CurrentEntity, Is.EqualTo(playerEnt));
}); });

View File

@@ -93,7 +93,7 @@ namespace Content.Server.Administration
// TODO VERB ICON control mob icon // TODO VERB ICON control mob icon
verb.Act = () => verb.Act = () =>
{ {
player.ContentData()?.Mind?.TransferTo(args.Target, ghostCheckOverride: true); player.ContentData()?.Mind?.TransferTo(args.Target.Uid, ghostCheckOverride: true);
}; };
args.Verbs.Add(verb); args.Verbs.Add(verb);
} }

View File

@@ -58,7 +58,7 @@ namespace Content.Server.Administration.Commands
else else
{ {
ghost.Name = player.Name; ghost.Name = player.Name;
mind.TransferTo(ghost); mind.TransferTo(ghost.Uid);
} }
var comp = ghost.GetComponent<GhostComponent>(); var comp = ghost.GetComponent<GhostComponent>();

View File

@@ -60,7 +60,7 @@ namespace Content.Server.Administration.Commands
DebugTools.AssertNotNull(mind); DebugTools.AssertNotNull(mind);
mind!.TransferTo(target); mind!.TransferTo(target.Uid);
} }
} }
} }

View File

@@ -71,9 +71,9 @@ namespace Content.Server.Administration.Commands
{ {
CharacterName = target.Name CharacterName = target.Name
}; };
playerCData.Mind = mind; mind.ChangeOwningPlayer(session.UserId);
} }
mind.TransferTo(target); mind.TransferTo(target.Uid);
} }
} }
} }

View File

@@ -43,7 +43,7 @@ namespace Content.Server.Body.Systems
if (!EntityManager.HasComponent<IMoverComponent>(newEntity)) if (!EntityManager.HasComponent<IMoverComponent>(newEntity))
EntityManager.AddComponent<SharedDummyInputMoverComponent>(newEntity); EntityManager.AddComponent<SharedDummyInputMoverComponent>(newEntity);
oldMind.Mind?.TransferTo(EntityManager.GetEntity(newEntity)); oldMind.Mind?.TransferTo(newEntity);
} }
} }
} }

View File

@@ -40,7 +40,7 @@ namespace Content.Server.Cloning
mindComp.Mind != null) mindComp.Mind != null)
return; return;
mind.TransferTo(entity, ghostCheckOverride: true); mind.TransferTo(entity.Uid, ghostCheckOverride: true);
mind.UnVisit(); mind.UnVisit();
ClonesWaitingForMind.Remove(mind); ClonesWaitingForMind.Remove(mind);
} }

View File

@@ -17,6 +17,14 @@ namespace Content.Server.GameTicking.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args) public void Execute(IConsoleShell shell, string argStr, string[] args)
{ {
var ticker = EntitySystem.Get<GameTicker>();
if (ticker.RunLevel != GameRunLevel.InRound)
{
shell.WriteLine("This can only be executed while the game is in a round - try restartroundnow");
return;
}
EntitySystem.Get<RoundEndSystem>().EndRound(); EntitySystem.Get<RoundEndSystem>().EndRound();
} }
} }

View File

@@ -37,7 +37,7 @@ namespace Content.Server.GameTicking
{ {
// Always make sure the client has player data. Mind gets assigned on spawn. // Always make sure the client has player data. Mind gets assigned on spawn.
if (session.Data.ContentDataUncast == null) if (session.Data.ContentDataUncast == null)
session.Data.ContentDataUncast = new PlayerData(session.UserId); session.Data.ContentDataUncast = new PlayerData(session.UserId, args.Session.Name);
// Make the player actually join the game. // Make the player actually join the game.
// timer time must be > tick length // timer time must be > tick length

View File

@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.Players; using Content.Server.Players;
using Content.Server.Mind;
using Content.Server.Ghost;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Coordinates; using Content.Shared.Coordinates;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
@@ -210,29 +212,50 @@ namespace Content.Server.GameTicking
//Generate a list of basic player info to display in the end round summary. //Generate a list of basic player info to display in the end round summary.
var listOfPlayerInfo = new List<RoundEndMessageEvent.RoundEndPlayerInfo>(); var listOfPlayerInfo = new List<RoundEndMessageEvent.RoundEndPlayerInfo>();
foreach (var ply in _playerManager.GetAllPlayers().OrderBy(p => p.Name)) // Grab the great big book of all the Minds, we'll need them for this.
var allMinds = EntitySystem.Get<MindTrackerSystem>().AllMinds;
foreach (var mind in allMinds)
{ {
var mind = ply.ContentData()?.Mind;
if (mind != null) if (mind != null)
{ {
_playersInLobby.TryGetValue(ply, out var status); // Some basics assuming things fail
var userId = mind.OriginalOwnerUserId;
var playerOOCName = userId.ToString();
var connected = false;
var observer = mind.AllRoles.Any(role => role is ObserverRole);
// Continuing
if (_playerManager.TryGetSessionById(userId, out var ply))
{
connected = true;
}
PlayerData? contentPlayerData = null;
if (_playerManager.TryGetPlayerData(userId, out var playerData))
{
contentPlayerData = playerData.ContentData();
}
// Finish
var antag = mind.AllRoles.Any(role => role.Antagonist); var antag = mind.AllRoles.Any(role => role.Antagonist);
var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo() var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo()
{ {
PlayerOOCName = ply.Name, // Note that contentPlayerData?.Name sticks around after the player is disconnected.
PlayerICName = mind.CurrentEntity?.Name, // This is as opposed to ply?.Name which doesn't.
PlayerOOCName = contentPlayerData?.Name ?? "(IMPOSSIBLE: REGISTERED MIND WITH NO OWNER)",
// Character name takes precedence over current entity name
PlayerICName = mind.CharacterName ?? mind.CurrentEntity?.Name,
Role = antag Role = antag
? mind.AllRoles.First(role => role.Antagonist).Name ? mind.AllRoles.First(role => role.Antagonist).Name
: mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("game-ticker-unknown-role"), : mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("game-ticker-unknown-role"),
Antag = antag, Antag = antag,
Observer = status == LobbyPlayerStatus.Observer, Observer = observer,
Connected = connected
}; };
listOfPlayerInfo.Add(playerEndRoundInfo); listOfPlayerInfo.Add(playerEndRoundInfo);
} }
} }
// This ordering mechanism isn't great (no ordering of minds) but functions
var listOfPlayerInfoFinal = listOfPlayerInfo.OrderBy(pi => pi.PlayerOOCName).ToArray();
RaiseNetworkEvent(new RoundEndMessageEvent(gamemodeTitle, roundEndText, roundDuration, listOfPlayerInfo.Count, listOfPlayerInfo.ToArray())); RaiseNetworkEvent(new RoundEndMessageEvent(gamemodeTitle, roundEndText, roundDuration, listOfPlayerInfoFinal.Length, listOfPlayerInfoFinal));
} }
public void RestartRound() public void RestartRound()

View File

@@ -4,6 +4,7 @@ using System.Globalization;
using Content.Server.Access.Components; using Content.Server.Access.Components;
using Content.Server.Access.Systems; using Content.Server.Access.Systems;
using Content.Server.CharacterAppearance.Components; using Content.Server.CharacterAppearance.Components;
using Content.Server.Ghost;
using Content.Server.Ghost.Components; using Content.Server.Ghost.Components;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Inventory.Components; using Content.Server.Inventory.Components;
@@ -70,17 +71,18 @@ namespace Content.Server.GameTicking
DebugTools.AssertNotNull(data); DebugTools.AssertNotNull(data);
data!.WipeMind(); data!.WipeMind();
data.Mind = new Mind.Mind(player.UserId) var newMind = new Mind.Mind(data.UserId)
{ {
CharacterName = character.Name CharacterName = character.Name
}; };
newMind.ChangeOwningPlayer(data.UserId);
// Pick best job best on prefs. // Pick best job best on prefs.
jobId ??= PickBestAvailableJob(character); jobId ??= PickBestAvailableJob(character);
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId); var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
var job = new Job(data.Mind, jobPrototype); var job = new Job(newMind, jobPrototype);
data.Mind.AddRole(job); newMind.AddRole(job);
if (lateJoin) if (lateJoin)
{ {
@@ -92,7 +94,7 @@ namespace Content.Server.GameTicking
} }
var mob = SpawnPlayerMob(job, character, lateJoin); var mob = SpawnPlayerMob(job, character, lateJoin);
data.Mind.TransferTo(mob); newMind.TransferTo(mob.Uid);
if (player.UserId == new Guid("{e887eb93-f503-4b65-95b6-2f282c014192}")) if (player.UserId == new Guid("{e887eb93-f503-4b65-95b6-2f282c014192}"))
{ {
@@ -150,13 +152,15 @@ namespace Content.Server.GameTicking
DebugTools.AssertNotNull(data); DebugTools.AssertNotNull(data);
data!.WipeMind(); data!.WipeMind();
data.Mind = new Mind.Mind(player.UserId); var newMind = new Mind.Mind(data.UserId);
newMind.ChangeOwningPlayer(data.UserId);
newMind.AddRole(new ObserverRole(newMind));
var mob = SpawnObserverMob(); var mob = SpawnObserverMob();
mob.Name = name; mob.Name = name;
var ghost = mob.GetComponent<GhostComponent>(); var ghost = mob.GetComponent<GhostComponent>();
EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(ghost, false); EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(ghost, false);
data.Mind.TransferTo(mob); newMind.TransferTo(mob.Uid);
_playersInLobby[player] = LobbyPlayerStatus.Observer; _playersInLobby[player] = LobbyPlayerStatus.Observer;
RaiseNetworkEvent(GetStatusSingle(player, LobbyPlayerStatus.Observer)); RaiseNetworkEvent(GetStatusSingle(player, LobbyPlayerStatus.Observer));

View File

@@ -94,7 +94,7 @@ namespace Content.Server.GameTicking.Presets
if (canReturn) if (canReturn)
mind.Visit(ghost); mind.Visit(ghost);
else else
mind.TransferTo(ghost); mind.TransferTo(ghost.Uid);
return true; return true;
} }

View File

@@ -0,0 +1,18 @@
using Content.Server.Roles;
using Robust.Shared.Localization;
namespace Content.Server.Ghost
{
/// <summary>
/// This is used to mark Observers properly, as they get Minds
/// </summary>
public class ObserverRole : Role
{
public override string Name => Loc.GetString("observer-role-name");
public override bool Antagonist => false;
public ObserverRole(Mind.Mind mind) : base(mind)
{
}
}
}

View File

@@ -51,11 +51,8 @@ namespace Content.Server.Ghost.Roles.Components
mob.EnsureComponent<MindComponent>(); mob.EnsureComponent<MindComponent>();
var mind = session.ContentData()?.Mind; var ghostRoleSystem = EntitySystem.Get<GhostRoleSystem>();
ghostRoleSystem.GhostRoleInternalCreateMindAndTransfer(session, OwnerUid, mob.Uid, this);
DebugTools.AssertNotNull(mind);
mind!.TransferTo(mob);
if (++_currentTakeovers < _availableTakeovers) if (++_currentTakeovers < _availableTakeovers)
return true; return true;

View File

@@ -27,13 +27,10 @@ namespace Content.Server.Ghost.Roles.Components
if (mind.HasMind) if (mind.HasMind)
return false; return false;
var sessionMind = session.ContentData()?.Mind; var ghostRoleSystem = EntitySystem.Get<GhostRoleSystem>();
ghostRoleSystem.GhostRoleInternalCreateMindAndTransfer(session, OwnerUid, OwnerUid, this);
DebugTools.AssertNotNull(sessionMind); ghostRoleSystem.UnregisterGhostRole(this);
sessionMind!.TransferTo(Owner);
EntitySystem.Get<GhostRoleSystem>().UnregisterGhostRole(this);
return true; return true;
} }

View File

@@ -0,0 +1,21 @@
using Content.Server.Roles;
using Robust.Shared.Localization;
namespace Content.Server.Ghost.Roles
{
/// <summary>
/// This is used for round end display of ghost roles.
/// It may also be used to ensure some ghost roles count as antagonists in future.
/// </summary>
public class GhostRoleMarkerRole : Role
{
private readonly string _name;
public override string Name => _name;
public override bool Antagonist => false;
public GhostRoleMarkerRole(Mind.Mind mind, string name) : base(mind)
{
_name = name;
}
}
}

View File

@@ -4,6 +4,7 @@ using Content.Server.EUI;
using Content.Server.Ghost.Components; using Content.Server.Ghost.Components;
using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.Components;
using Content.Server.Ghost.Roles.UI; using Content.Server.Ghost.Roles.UI;
using Content.Server.Players;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Ghost.Roles; using Content.Shared.Ghost.Roles;
using Content.Shared.Ghost; using Content.Shared.Ghost;
@@ -14,6 +15,7 @@ using Robust.Shared.Console;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using Robust.Shared.Utility;
using Robust.Shared.Enums; using Robust.Shared.Enums;
namespace Content.Server.Ghost.Roles namespace Content.Server.Ghost.Roles
@@ -153,6 +155,24 @@ namespace Content.Server.Ghost.Roles
CloseEui(player); CloseEui(player);
} }
public void GhostRoleInternalCreateMindAndTransfer(IPlayerSession player, EntityUid roleUid, EntityUid mob, GhostRoleComponent? role = null)
{
if (!Resolve(roleUid, ref role)) return;
var contentData = player.ContentData();
DebugTools.AssertNotNull(contentData);
var newMind = new Mind.Mind(player.UserId)
{
CharacterName = EntityManager.GetComponent<MetaDataComponent>(mob).EntityName
};
newMind.AddRole(new GhostRoleMarkerRole(newMind, role.RoleName));
newMind.ChangeOwningPlayer(player.UserId);
newMind.TransferTo(mob);
}
public GhostRoleInfo[] GetGhostRolesInfo() public GhostRoleInfo[] GetGhostRolesInfo()
{ {
var roles = new GhostRoleInfo[_ghostRoles.Count]; var roles = new GhostRoleInfo[_ghostRoles.Count];

View File

@@ -92,7 +92,7 @@ namespace Content.Server.Mind.Components
EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(ghost, false); EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(ghost, false);
} }
Mind!.TransferTo(visiting); Mind!.TransferTo(visiting.Uid);
} }
else if (GhostOnShutdown) else if (GhostOnShutdown)
{ {
@@ -116,7 +116,7 @@ namespace Content.Server.Mind.Components
if (Mind != null) if (Mind != null)
{ {
ghost.Name = Mind.CharacterName ?? string.Empty; ghost.Name = Mind.CharacterName ?? string.Empty;
Mind.TransferTo(ghost); Mind.TransferTo(ghost.Uid);
} }
}); });
} }

View File

@@ -37,12 +37,14 @@ namespace Content.Server.Mind
private readonly List<Objective> _objectives = new(); private readonly List<Objective> _objectives = new();
/// <summary> /// <summary>
/// Creates the new mind attached to a specific player session. /// Creates the new mind.
/// Note: the Mind is NOT initially attached!
/// The provided UserId is solely for tracking of intended owner.
/// </summary> /// </summary>
/// <param name="userId">The session ID of the owning player.</param> /// <param name="userId">The session ID of the original owner (may get credited).</param>
public Mind(NetUserId userId) public Mind(NetUserId userId)
{ {
UserId = userId; OriginalOwnerUserId = userId;
} }
// TODO: This session should be able to be changed, probably. // TODO: This session should be able to be changed, probably.
@@ -52,6 +54,13 @@ namespace Content.Server.Mind
[ViewVariables] [ViewVariables]
public NetUserId? UserId { get; private set; } public NetUserId? UserId { get; private set; }
/// <summary>
/// The session ID of the original owner, if any.
/// May end up used for round-end information (as the owner may have abandoned Mind since)
/// </summary>
[ViewVariables]
public NetUserId OriginalOwnerUserId { get; }
[ViewVariables] [ViewVariables]
public bool IsVisitingEntity => VisitingEntity != null; public bool IsVisitingEntity => VisitingEntity != null;
@@ -234,12 +243,10 @@ namespace Content.Server.Mind
return true; return true;
} }
/// <summary> /// <summary>
/// Transfer this mind's control over to a new entity. /// Transfer this mind's control over to a new entity.
/// </summary> /// </summary>
/// <param name="entity"> /// <param name="entityUid">
/// The entity to control. /// The entity to control.
/// Can be null, in which case it will simply detach the mind from any entity. /// Can be null, in which case it will simply detach the mind from any entity.
/// </param> /// </param>
@@ -249,28 +256,31 @@ namespace Content.Server.Mind
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// Thrown if <paramref name="entity"/> is already owned by another mind. /// Thrown if <paramref name="entity"/> is already owned by another mind.
/// </exception> /// </exception>
public void TransferTo(IEntity? entity, bool ghostCheckOverride = false) public void TransferTo(EntityUid? entityUid, bool ghostCheckOverride = false)
{ {
var entMan = IoCManager.Resolve<IEntityManager>();
IEntity? entity = (entityUid != null) ? entMan.GetEntity(entityUid.Value) : null;
MindComponent? component = null; MindComponent? component = null;
var alreadyAttached = false; var alreadyAttached = false;
if (entity != null) if (entityUid != null)
{ {
if (!entity.TryGetComponent(out component)) if (!entMan.TryGetComponent<MindComponent>(entityUid.Value, out component))
{ {
component = entity.AddComponent<MindComponent>(); component = entMan.AddComponent<MindComponent>(entityUid.Value);
} }
else if (component.HasMind) else if (component!.HasMind)
{ {
EntitySystem.Get<GameTicker>().OnGhostAttempt(component.Mind!, false); EntitySystem.Get<GameTicker>().OnGhostAttempt(component.Mind!, false);
} }
if (entity.TryGetComponent(out ActorComponent? actor)) if (entMan.TryGetComponent<ActorComponent>(entityUid.Value, out var actor))
{ {
// Happens when transferring to your currently visited entity. // Happens when transferring to your currently visited entity.
if (actor.PlayerSession != Session) if (actor.PlayerSession != Session)
{ {
throw new ArgumentException("Visit target already has a session.", nameof(entity)); throw new ArgumentException("Visit target already has a session.", nameof(entityUid));
} }
alreadyAttached = true; alreadyAttached = true;
@@ -298,11 +308,6 @@ namespace Content.Server.Mind
} }
} }
public void RemoveOwningPlayer()
{
UserId = null;
}
public void ChangeOwningPlayer(NetUserId? newOwner) public void ChangeOwningPlayer(NetUserId? newOwner)
{ {
var playerMgr = IoCManager.Resolve<IPlayerManager>(); var playerMgr = IoCManager.Resolve<IPlayerManager>();
@@ -329,7 +334,7 @@ namespace Content.Server.Mind
{ {
var data = playerMgr.GetPlayerData(UserId.Value).ContentData(); var data = playerMgr.GetPlayerData(UserId.Value).ContentData();
DebugTools.AssertNotNull(data); DebugTools.AssertNotNull(data);
data!.Mind = null; data!.UpdateMindFromMindChangeOwningPlayer(null);
} }
UserId = newOwner; UserId = newOwner;
@@ -342,7 +347,7 @@ namespace Content.Server.Mind
// Can I mention how much I love the word yank? // Can I mention how much I love the word yank?
DebugTools.AssertNotNull(newOwnerData); DebugTools.AssertNotNull(newOwnerData);
newOwnerData!.Mind?.ChangeOwningPlayer(null); newOwnerData!.Mind?.ChangeOwningPlayer(null);
newOwnerData.Mind = this; newOwnerData.UpdateMindFromMindChangeOwningPlayer(this);
} }
public void Visit(IEntity entity) public void Visit(IEntity entity)

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using Content.Server.GameTicking;
using Content.Server.Mind.Components;
using Content.Shared.GameTicking;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
using Robust.Shared.Player;
namespace Content.Server.Mind
{
/// <summary>
/// This is absolutely evil.
/// It tracks all mind changes and logs all the Mind objects.
/// This is so that when round end comes around, there's a coherent list of all Minds that were in play during the round.
/// The Minds themselves contain metadata about their owners.
/// Anyway, this is because disconnected people and ghost roles have been breaking round end statistics for way too long.
/// </summary>
public class MindTrackerSystem : EntitySystem
{
[ViewVariables]
public readonly HashSet<Mind> AllMinds = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<MindComponent, MindAddedMessage>(OnMindAdded);
}
void Reset(RoundRestartCleanupEvent ev)
{
AllMinds.Clear();
}
void OnMindAdded(EntityUid uid, MindComponent mc, MindAddedMessage args)
{
var mind = mc.Mind;
if (mind != null)
AllMinds.Add(mind);
}
}
}

View File

@@ -9,7 +9,7 @@ namespace Content.Server.Objectives.Conditions
protected Mind.Mind? Target; protected Mind.Mind? Target;
public abstract IObjectiveCondition GetAssigned(Mind.Mind mind); public abstract IObjectiveCondition GetAssigned(Mind.Mind mind);
public string Title => Loc.GetString("objective-condition-kill-person-title", ("targetName", Target?.OwnedEntity?.Name ?? string.Empty)); public string Title => Loc.GetString("objective-condition-kill-person-title", ("targetName", Target?.CharacterName ?? Target?.OwnedEntity?.Name ?? string.Empty));
public string Description => Loc.GetString("objective-condition-kill-person-description"); public string Description => Loc.GetString("objective-condition-kill-person-description");

View File

@@ -16,12 +16,19 @@ namespace Content.Server.Players
[ViewVariables] [ViewVariables]
public NetUserId UserId { get; } public NetUserId UserId { get; }
/// <summary>
/// This is a backup copy of the player name stored on connection.
/// This is useful in the event the player disconnects.
/// </summary>
[ViewVariables]
public string Name { get; }
/// <summary> /// <summary>
/// The currently occupied mind of the player owning this data. /// The currently occupied mind of the player owning this data.
/// DO NOT DIRECTLY SET THIS UNLESS YOU KNOW WHAT YOU'RE DOING. /// DO NOT DIRECTLY SET THIS UNLESS YOU KNOW WHAT YOU'RE DOING.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public Mind.Mind? Mind { get; set; } public Mind.Mind? Mind { get; private set; }
/// <summary> /// <summary>
/// If true, the player is an admin and they explicitly de-adminned mid-game, /// If true, the player is an admin and they explicitly de-adminned mid-game,
@@ -32,13 +39,22 @@ namespace Content.Server.Players
public void WipeMind() public void WipeMind()
{ {
Mind?.TransferTo(null); Mind?.TransferTo(null);
Mind?.RemoveOwningPlayer(); // This will ensure Mind == null
Mind = null; Mind?.ChangeOwningPlayer(null);
} }
public PlayerData(NetUserId userId) /// <summary>
/// Called from Mind.ChangeOwningPlayer *and nowhere else.*
/// </summary>
public void UpdateMindFromMindChangeOwningPlayer(Mind.Mind? mind)
{
Mind = mind;
}
public PlayerData(NetUserId userId, string name)
{ {
UserId = userId; UserId = userId;
Name = name;
} }
} }

View File

@@ -128,6 +128,7 @@ namespace Content.Shared.GameTicking
public string Role; public string Role;
public bool Antag; public bool Antag;
public bool Observer; public bool Observer;
public bool Connected;
} }
public string GamemodeTitle { get; } public string GamemodeTitle { get; }

View File

@@ -0,0 +1,2 @@
administration-ui-round-tab-restart-round-now = Restart NOW

View File

@@ -0,0 +1,2 @@
observer-role-name = Observer