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:
@@ -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>
|
||||||
|
|||||||
@@ -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));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace Content.Server.Administration.Commands
|
|||||||
|
|
||||||
DebugTools.AssertNotNull(mind);
|
DebugTools.AssertNotNull(mind);
|
||||||
|
|
||||||
mind!.TransferTo(target);
|
mind!.TransferTo(target.Uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
Content.Server/Ghost/ObserverRole.cs
Normal file
18
Content.Server/Ghost/ObserverRole.cs
Normal 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
21
Content.Server/Ghost/Roles/GhostRoleMarkerRole.cs
Normal file
21
Content.Server/Ghost/Roles/GhostRoleMarkerRole.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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];
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
46
Content.Server/Mind/MindTrackerSystem.cs
Normal file
46
Content.Server/Mind/MindTrackerSystem.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
administration-ui-round-tab-restart-round-now = Restart NOW
|
||||||
|
|
||||||
2
Resources/Locale/en-US/ghost/observer-role.ftl
Normal file
2
Resources/Locale/en-US/ghost/observer-role.ftl
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
observer-role-name = Observer
|
||||||
|
|
||||||
Reference in New Issue
Block a user