Fix ghost respawn bug (#17511)
This commit is contained in:
@@ -18,6 +18,38 @@ namespace Content.IntegrationTests.Tests.Minds;
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public sealed partial class MindTests
|
public sealed partial class MindTests
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a server-client pair and ensures that the client is attached to a simple mind test entity.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<PairTracker> SetupPair()
|
||||||
|
{
|
||||||
|
var pairTracker = await PoolManager.GetServerClient(new PoolSettings{ ExtraPrototypes = Prototypes });
|
||||||
|
var pair = pairTracker.Pair;
|
||||||
|
|
||||||
|
var entMan = pair.Server.ResolveDependency<IServerEntityManager>();
|
||||||
|
var playerMan = pair.Server.ResolveDependency<IPlayerManager>();
|
||||||
|
var mindSys = entMan.System<MindSystem>();
|
||||||
|
|
||||||
|
var player = playerMan.ServerSessions.Single();
|
||||||
|
|
||||||
|
EntityUid entity = default;
|
||||||
|
await pair.Server.WaitPost(() =>
|
||||||
|
{
|
||||||
|
entity = entMan.SpawnEntity("MindTestEntity", MapCoordinates.Nullspace);
|
||||||
|
mindSys.TransferTo(mindSys.CreateMind(player.UserId), entity);
|
||||||
|
});
|
||||||
|
|
||||||
|
await PoolManager.RunTicksSync(pair, 5);
|
||||||
|
|
||||||
|
var mind = player.ContentData()?.Mind;
|
||||||
|
Assert.NotNull(mind);
|
||||||
|
Assert.That(player.AttachedEntity, Is.EqualTo(entity));
|
||||||
|
Assert.That(player.AttachedEntity, Is.EqualTo(mind.CurrentEntity), "Player is not attached to the mind's current entity.");
|
||||||
|
Assert.That(entMan.EntityExists(mind.OwnedEntity), "The mind's current entity does not exist");
|
||||||
|
Assert.That(mind.VisitingEntity == null || entMan.EntityExists(mind.VisitingEntity), "The minds visited entity does not exist.");
|
||||||
|
return pairTracker;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<EntityUid> BecomeGhost(Pair pair, bool visit = false)
|
public async Task<EntityUid> BecomeGhost(Pair pair, bool visit = false)
|
||||||
{
|
{
|
||||||
var entMan = pair.Server.ResolveDependency<IServerEntityManager>();
|
var entMan = pair.Server.ResolveDependency<IServerEntityManager>();
|
||||||
@@ -74,9 +106,9 @@ public sealed partial class MindTests
|
|||||||
|
|
||||||
var mind = player.ContentData()!.Mind;
|
var mind = player.ContentData()!.Mind;
|
||||||
Assert.NotNull(mind);
|
Assert.NotNull(mind);
|
||||||
Assert.That(player.AttachedEntity, Is.EqualTo(mind.CurrentEntity));
|
Assert.That(player.AttachedEntity, Is.EqualTo(mind.CurrentEntity), "Player is not attached to the mind's current entity.");
|
||||||
Assert.That(entMan.EntityExists(mind.OwnedEntity));
|
Assert.That(entMan.EntityExists(mind.OwnedEntity), "The mind's current entity does not exist");
|
||||||
Assert.That(entMan.EntityExists(mind.CurrentEntity));
|
Assert.That(mind.VisitingEntity == null || entMan.EntityExists(mind.VisitingEntity), "The minds visited entity does not exist.");
|
||||||
|
|
||||||
return mind;
|
return mind;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,21 +20,19 @@ public sealed partial class MindTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestGhostsCanReconnect()
|
public async Task TestGhostsCanReconnect()
|
||||||
{
|
{
|
||||||
await using var pairTracker = await PoolManager.GetServerClient();
|
await using var pairTracker = await SetupPair();
|
||||||
var pair = pairTracker.Pair;
|
var pair = pairTracker.Pair;
|
||||||
|
|
||||||
var entMan = pair.Server.ResolveDependency<IEntityManager>();
|
var entMan = pair.Server.ResolveDependency<IEntityManager>();
|
||||||
await PoolManager.RunTicksSync(pair, 5);
|
|
||||||
var mind = GetMind(pair);
|
var mind = GetMind(pair);
|
||||||
|
|
||||||
var ghost = await BecomeGhost(pair);
|
var ghost = await BecomeGhost(pair);
|
||||||
await DisconnectReconnect(pair);
|
await DisconnectReconnect(pair);
|
||||||
|
|
||||||
// Player in control of a NEW entity
|
// Player in control of a new ghost, but with the same mind
|
||||||
var newMind = GetMind(pair);
|
Assert.That(GetMind(pair) == mind);
|
||||||
Assert.That(newMind != mind);
|
|
||||||
Assert.That(entMan.Deleted(ghost));
|
Assert.That(entMan.Deleted(ghost));
|
||||||
Assert.Null(newMind.VisitingEntity);
|
Assert.That(entMan.HasComponent<GhostComponent>(mind.OwnedEntity));
|
||||||
|
Assert.Null(mind.VisitingEntity);
|
||||||
|
|
||||||
await pairTracker.CleanReturnAsync();
|
await pairTracker.CleanReturnAsync();
|
||||||
}
|
}
|
||||||
@@ -47,11 +45,9 @@ public sealed partial class MindTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestDeletedCanReconnect()
|
public async Task TestDeletedCanReconnect()
|
||||||
{
|
{
|
||||||
await using var pairTracker = await PoolManager.GetServerClient();
|
await using var pairTracker = await SetupPair();
|
||||||
var pair = pairTracker.Pair;
|
var pair = pairTracker.Pair;
|
||||||
|
|
||||||
var entMan = pair.Server.ResolveDependency<IEntityManager>();
|
var entMan = pair.Server.ResolveDependency<IEntityManager>();
|
||||||
await PoolManager.RunTicksSync(pair, 5);
|
|
||||||
var mind = GetMind(pair);
|
var mind = GetMind(pair);
|
||||||
|
|
||||||
var playerMan = pair.Server.ResolveDependency<IPlayerManager>();
|
var playerMan = pair.Server.ResolveDependency<IPlayerManager>();
|
||||||
@@ -76,15 +72,12 @@ public sealed partial class MindTests
|
|||||||
// Reconnect
|
// Reconnect
|
||||||
await Connect(pair, name);
|
await Connect(pair, name);
|
||||||
player = playerMan.ServerSessions.Single();
|
player = playerMan.ServerSessions.Single();
|
||||||
Assert.That(user == player.UserId);
|
Assert.That(user, Is.EqualTo(player.UserId));
|
||||||
|
|
||||||
// Player is now a new entity
|
// Player is now a new ghost entity
|
||||||
var newMind = GetMind(pair);
|
Assert.That(GetMind(pair), Is.EqualTo(mind));
|
||||||
Assert.That(newMind != mind);
|
Assert.That(mind.OwnedEntity, Is.Not.EqualTo(entity));
|
||||||
Assert.Null(mind.UserId);
|
Assert.That(entMan.HasComponent<GhostComponent>(mind.OwnedEntity));
|
||||||
Assert.Null(mind.CurrentEntity);
|
|
||||||
Assert.NotNull(newMind.OwnedEntity);
|
|
||||||
Assert.That(entMan.EntityExists(newMind.OwnedEntity));
|
|
||||||
|
|
||||||
await pairTracker.CleanReturnAsync();
|
await pairTracker.CleanReturnAsync();
|
||||||
}
|
}
|
||||||
@@ -97,11 +90,10 @@ public sealed partial class MindTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestVisitingGhostReconnect()
|
public async Task TestVisitingGhostReconnect()
|
||||||
{
|
{
|
||||||
await using var pairTracker = await PoolManager.GetServerClient();
|
await using var pairTracker = await SetupPair();
|
||||||
var pair = pairTracker.Pair;
|
var pair = pairTracker.Pair;
|
||||||
|
|
||||||
var entMan = pair.Server.ResolveDependency<IEntityManager>();
|
var entMan = pair.Server.ResolveDependency<IEntityManager>();
|
||||||
await PoolManager.RunTicksSync(pair, 5);
|
|
||||||
var mind = GetMind(pair);
|
var mind = GetMind(pair);
|
||||||
|
|
||||||
var original = mind.CurrentEntity;
|
var original = mind.CurrentEntity;
|
||||||
@@ -109,8 +101,8 @@ public sealed partial class MindTests
|
|||||||
await DisconnectReconnect(pair);
|
await DisconnectReconnect(pair);
|
||||||
|
|
||||||
// Player now controls their original mob, mind was preserved
|
// Player now controls their original mob, mind was preserved
|
||||||
Assert.That(mind == GetMind(pair));
|
Assert.That(mind, Is.EqualTo(GetMind(pair)));
|
||||||
Assert.That(mind.CurrentEntity == original);
|
Assert.That(mind.CurrentEntity, Is.EqualTo(original));
|
||||||
Assert.That(!entMan.Deleted(original));
|
Assert.That(!entMan.Deleted(original));
|
||||||
Assert.That(entMan.Deleted(ghost));
|
Assert.That(entMan.Deleted(ghost));
|
||||||
|
|
||||||
@@ -125,7 +117,7 @@ public sealed partial class MindTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestVisitingReconnect()
|
public async Task TestVisitingReconnect()
|
||||||
{
|
{
|
||||||
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{ ExtraPrototypes = Prototypes });
|
await using var pairTracker = await SetupPair();
|
||||||
var pair = pairTracker.Pair;
|
var pair = pairTracker.Pair;
|
||||||
|
|
||||||
var entMan = pair.Server.ResolveDependency<IEntityManager>();
|
var entMan = pair.Server.ResolveDependency<IEntityManager>();
|
||||||
|
|||||||
@@ -61,26 +61,6 @@ public sealed partial class MindTests
|
|||||||
- !type:GibBehavior { }
|
- !type:GibBehavior { }
|
||||||
";
|
";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exception handling for PlayerData and NetUserId invalid due to testing.
|
|
||||||
/// Can be removed when Players can be mocked.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="func"></param>
|
|
||||||
private void CatchPlayerDataException(Action func)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
func();
|
|
||||||
}
|
|
||||||
catch (ArgumentException e)
|
|
||||||
{
|
|
||||||
// Prevent exiting due to PlayerData not being initialized.
|
|
||||||
if (e.Message == "New owner must have previously logged into the server. (Parameter 'newOwner')")
|
|
||||||
return;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestCreateAndTransferMindToNewEntity()
|
public async Task TestCreateAndTransferMindToNewEntity()
|
||||||
{
|
{
|
||||||
@@ -382,11 +362,11 @@ public sealed partial class MindTests
|
|||||||
await pairTracker.CleanReturnAsync();
|
await pairTracker.CleanReturnAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
// TODO Implement
|
||||||
|
/*[Test]
|
||||||
public async Task TestPlayerCanReturnFromGhostWhenDead()
|
public async Task TestPlayerCanReturnFromGhostWhenDead()
|
||||||
{
|
{
|
||||||
// TODO Implement
|
}*/
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestGhostDoesNotInfiniteLoop()
|
public async Task TestGhostDoesNotInfiniteLoop()
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace Content.Server.GameTicking.Commands
|
|||||||
if (ticker.PlayerGameStatuses.TryGetValue(player.UserId, out var status) &&
|
if (ticker.PlayerGameStatuses.TryGetValue(player.UserId, out var status) &&
|
||||||
status != PlayerGameStatus.JoinedGame)
|
status != PlayerGameStatus.JoinedGame)
|
||||||
{
|
{
|
||||||
ticker.MakeObserve(player);
|
ticker.JoinAsObserver(player);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Content.Server.GameTicking.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
var playerMgr = IoCManager.Resolve<IPlayerManager>();
|
var playerMgr = IoCManager.Resolve<IPlayerManager>();
|
||||||
var sysMan = IoCManager.Resolve<EntitySystemManager>();
|
var sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||||
var ticker = sysMan.GetEntitySystem<GameTicker>();
|
var ticker = sysMan.GetEntitySystem<GameTicker>();
|
||||||
var mind = sysMan.GetEntitySystem<MindSystem>();
|
var mind = sysMan.GetEntitySystem<MindSystem>();
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.GameTicking.Presets;
|
using Content.Server.GameTicking.Presets;
|
||||||
using Content.Server.Ghost.Components;
|
using Content.Server.Ghost.Components;
|
||||||
using Content.Server.Mind;
|
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Damage.Prototypes;
|
using Content.Shared.Damage.Prototypes;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Mobs.Systems;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
|
|
||||||
@@ -19,9 +17,6 @@ namespace Content.Server.GameTicking
|
|||||||
{
|
{
|
||||||
public const float PresetFailedCooldownIncrease = 30f;
|
public const float PresetFailedCooldownIncrease = 30f;
|
||||||
|
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
|
||||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
|
||||||
|
|
||||||
public GamePresetPrototype? Preset { get; private set; }
|
public GamePresetPrototype? Preset { get; private set; }
|
||||||
|
|
||||||
private bool StartPreset(IPlayerSession[] origReadyPlayers, bool force)
|
private bool StartPreset(IPlayerSession[] origReadyPlayers, bool force)
|
||||||
@@ -190,7 +185,7 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
if (mind.VisitingEntity != default)
|
if (mind.VisitingEntity != default)
|
||||||
{
|
{
|
||||||
_mindSystem.UnVisit(mind);
|
_mind.UnVisit(mind);
|
||||||
}
|
}
|
||||||
|
|
||||||
var position = Exists(playerEntity)
|
var position = Exists(playerEntity)
|
||||||
@@ -208,11 +203,11 @@ namespace Content.Server.GameTicking
|
|||||||
// + If we're in a mob that is critical, and we're supposed to be able to return if possible,
|
// + If we're in a mob that is critical, and we're supposed to be able to return if possible,
|
||||||
// we're succumbing - the mob is killed. Therefore, character is dead. Ghosting OK.
|
// we're succumbing - the mob is killed. Therefore, character is dead. Ghosting OK.
|
||||||
// (If the mob survives, that's a bug. Ghosting is kept regardless.)
|
// (If the mob survives, that's a bug. Ghosting is kept regardless.)
|
||||||
var canReturn = canReturnGlobal && _mindSystem.IsCharacterDeadPhysically(mind);
|
var canReturn = canReturnGlobal && _mind.IsCharacterDeadPhysically(mind);
|
||||||
|
|
||||||
if (canReturnGlobal && TryComp(playerEntity, out MobStateComponent? mobState))
|
if (canReturnGlobal && TryComp(playerEntity, out MobStateComponent? mobState))
|
||||||
{
|
{
|
||||||
if (_mobStateSystem.IsCritical(playerEntity.Value, mobState))
|
if (_mobState.IsCritical(playerEntity.Value, mobState))
|
||||||
{
|
{
|
||||||
canReturn = true;
|
canReturn = true;
|
||||||
|
|
||||||
@@ -250,9 +245,9 @@ namespace Content.Server.GameTicking
|
|||||||
_ghosts.SetCanReturnToBody(ghostComponent, canReturn);
|
_ghosts.SetCanReturnToBody(ghostComponent, canReturn);
|
||||||
|
|
||||||
if (canReturn)
|
if (canReturn)
|
||||||
_mindSystem.Visit(mind, ghost);
|
_mind.Visit(mind, ghost);
|
||||||
else
|
else
|
||||||
_mindSystem.TransferTo(mind, ghost);
|
_mind.TransferTo(mind, ghost);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,15 +27,16 @@ namespace Content.Server.GameTicking
|
|||||||
{
|
{
|
||||||
var session = args.Session;
|
var session = args.Session;
|
||||||
|
|
||||||
if (_mindSystem.TryGetMind(session.UserId, out var mind))
|
if (_mind.TryGetMind(session.UserId, out var mind))
|
||||||
{
|
{
|
||||||
if (args.OldStatus == SessionStatus.Connecting && args.NewStatus == SessionStatus.Connected)
|
if (args.OldStatus == SessionStatus.Connecting && args.NewStatus == SessionStatus.Connected)
|
||||||
mind.Session = session;
|
mind.Session = session;
|
||||||
|
|
||||||
DebugTools.Assert(mind.Session == session);
|
DebugTools.Assert(mind.Session == session);
|
||||||
DebugTools.Assert(session.Data.ContentData()?.Mind is not {} dataMind || dataMind == mind);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DebugTools.Assert(session.GetMind() == mind);
|
||||||
|
|
||||||
switch (args.NewStatus)
|
switch (args.NewStatus)
|
||||||
{
|
{
|
||||||
case SessionStatus.Connected:
|
case SessionStatus.Connected:
|
||||||
@@ -75,32 +76,32 @@ namespace Content.Server.GameTicking
|
|||||||
{
|
{
|
||||||
_userDb.ClientConnected(session);
|
_userDb.ClientConnected(session);
|
||||||
|
|
||||||
var data = session.ContentData();
|
if (mind == null)
|
||||||
|
|
||||||
DebugTools.AssertNotNull(data);
|
|
||||||
|
|
||||||
if (data!.Mind == null)
|
|
||||||
{
|
{
|
||||||
if (LobbyEnabled)
|
if (LobbyEnabled)
|
||||||
{
|
|
||||||
PlayerJoinLobby(session);
|
PlayerJoinLobby(session);
|
||||||
return;
|
else
|
||||||
}
|
SpawnWaitDb();
|
||||||
|
|
||||||
SpawnWaitDb();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.Mind.CurrentEntity == null || Deleted(data.Mind.CurrentEntity))
|
if (mind.CurrentEntity == null || Deleted(mind.CurrentEntity))
|
||||||
{
|
{
|
||||||
DebugTools.Assert(data.Mind.CurrentEntity == null, "a mind's current entity has been deleted");
|
DebugTools.Assert(mind.CurrentEntity == null, "a mind's current entity was deleted without updating the mind");
|
||||||
SpawnWaitDb();
|
|
||||||
|
// This player is joining the game with an existing mind, but the mind has no entity.
|
||||||
|
// Their entity was probably deleted sometime while they were disconnected, or they were an observer.
|
||||||
|
// Instead of allowing them to spawn in, we will dump and their existing mind in an observer ghost.
|
||||||
|
SpawnObserverWaitDb();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
session.AttachToEntity(data.Mind.CurrentEntity);
|
// Simply re-attach to existing entity.
|
||||||
|
session.AttachToEntity(mind.CurrentEntity);
|
||||||
PlayerJoinGame(session);
|
PlayerJoinGame(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +124,12 @@ namespace Content.Server.GameTicking
|
|||||||
SpawnPlayer(session, EntityUid.Invalid);
|
SpawnPlayer(session, EntityUid.Invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async void SpawnObserverWaitDb()
|
||||||
|
{
|
||||||
|
await _userDb.WaitLoadComplete(session);
|
||||||
|
JoinAsObserver(session);
|
||||||
|
}
|
||||||
|
|
||||||
async void AddPlayerToDb(Guid id)
|
async void AddPlayerToDb(Guid id)
|
||||||
{
|
{
|
||||||
if (RoundId != 0 && _runLevel != GameRunLevel.PreRoundLobby)
|
if (RoundId != 0 && _runLevel != GameRunLevel.PreRoundLobby)
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Ghost;
|
using Content.Server.Ghost;
|
||||||
using Content.Server.Ghost.Components;
|
|
||||||
using Content.Server.Players;
|
using Content.Server.Players;
|
||||||
using Content.Server.Shuttles.Systems;
|
|
||||||
using Content.Server.Spawners.Components;
|
using Content.Server.Spawners.Components;
|
||||||
using Content.Server.Speech.Components;
|
using Content.Server.Speech.Components;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.Ghost;
|
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@@ -131,7 +128,7 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
if (lateJoin && DisallowLateJoin)
|
if (lateJoin && DisallowLateJoin)
|
||||||
{
|
{
|
||||||
MakeObserve(player);
|
JoinAsObserver(player);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +160,7 @@ namespace Content.Server.GameTicking
|
|||||||
{
|
{
|
||||||
if (!LobbyEnabled)
|
if (!LobbyEnabled)
|
||||||
{
|
{
|
||||||
MakeObserve(player);
|
JoinAsObserver(player);
|
||||||
}
|
}
|
||||||
_chatManager.DispatchServerMessage(player, Loc.GetString("game-ticker-player-no-jobs-available-when-joining"));
|
_chatManager.DispatchServerMessage(player, Loc.GetString("game-ticker-player-no-jobs-available-when-joining"));
|
||||||
return;
|
return;
|
||||||
@@ -175,12 +172,12 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
DebugTools.AssertNotNull(data);
|
DebugTools.AssertNotNull(data);
|
||||||
|
|
||||||
var newMind = _mindSystem.CreateMind(data!.UserId, character.Name);
|
var newMind = _mind.CreateMind(data!.UserId, character.Name);
|
||||||
_mindSystem.SetUserId(newMind, data.UserId);
|
_mind.SetUserId(newMind, data.UserId);
|
||||||
|
|
||||||
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
|
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
|
||||||
var job = new Job(newMind, jobPrototype);
|
var job = new Job(newMind, jobPrototype);
|
||||||
_mindSystem.AddRole(newMind, job);
|
_mind.AddRole(newMind, job);
|
||||||
|
|
||||||
_playTimeTrackings.PlayerRolesChanged(player);
|
_playTimeTrackings.PlayerRolesChanged(player);
|
||||||
|
|
||||||
@@ -189,7 +186,7 @@ namespace Content.Server.GameTicking
|
|||||||
DebugTools.AssertNotNull(mobMaybe);
|
DebugTools.AssertNotNull(mobMaybe);
|
||||||
var mob = mobMaybe!.Value;
|
var mob = mobMaybe!.Value;
|
||||||
|
|
||||||
_mindSystem.TransferTo(newMind, mob);
|
_mind.TransferTo(newMind, mob);
|
||||||
|
|
||||||
if (lateJoin)
|
if (lateJoin)
|
||||||
{
|
{
|
||||||
@@ -243,7 +240,7 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
public void Respawn(IPlayerSession player)
|
public void Respawn(IPlayerSession player)
|
||||||
{
|
{
|
||||||
_mindSystem.WipeMind(player);
|
_mind.WipeMind(player);
|
||||||
_adminLogger.Add(LogType.Respawn, LogImpact.Medium, $"Player {player} was respawned.");
|
_adminLogger.Add(LogType.Respawn, LogImpact.Medium, $"Player {player} was respawned.");
|
||||||
|
|
||||||
if (LobbyEnabled)
|
if (LobbyEnabled)
|
||||||
@@ -263,32 +260,42 @@ namespace Content.Server.GameTicking
|
|||||||
SpawnPlayer(player, station, jobId);
|
SpawnPlayer(player, station, jobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MakeObserve(IPlayerSession player)
|
/// <summary>
|
||||||
|
/// Causes the given player to join the current game as observer ghost. See also <see cref="SpawnObserver"/>
|
||||||
|
/// </summary>
|
||||||
|
public void JoinAsObserver(IPlayerSession player)
|
||||||
{
|
{
|
||||||
// Can't spawn players with a dummy ticker!
|
// Can't spawn players with a dummy ticker!
|
||||||
if (DummyTicker)
|
if (DummyTicker)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PlayerJoinGame(player);
|
PlayerJoinGame(player);
|
||||||
|
SpawnObserver(player);
|
||||||
|
RaiseNetworkEvent(GetStatusSingle(player, PlayerGameStatus.JoinedGame));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawns an observer ghost and attaches the given player to it. If the player does not yet have a mind, the
|
||||||
|
/// player is given a new mind with the observer role. Otherwise, the current mind is transferred to the ghost.
|
||||||
|
/// </summary>
|
||||||
|
public void SpawnObserver(IPlayerSession player)
|
||||||
|
{
|
||||||
|
if (DummyTicker)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var mind = player.GetMind();
|
||||||
|
if (mind == null)
|
||||||
|
{
|
||||||
|
mind = _mind.CreateMind(player.UserId);
|
||||||
|
_mind.SetUserId(mind, player.UserId);
|
||||||
|
_mind.AddRole(mind, new ObserverRole(mind));
|
||||||
|
}
|
||||||
|
|
||||||
var name = GetPlayerProfile(player).Name;
|
var name = GetPlayerProfile(player).Name;
|
||||||
|
var ghost = SpawnObserverMob();
|
||||||
var data = player.ContentData();
|
MetaData(ghost).EntityName = name;
|
||||||
|
_ghost.SetCanReturnToBody(ghost, false);
|
||||||
DebugTools.AssertNotNull(data);
|
_mind.TransferTo(mind, ghost);
|
||||||
|
|
||||||
var newMind = _mindSystem.CreateMind(data!.UserId);
|
|
||||||
_mindSystem.SetUserId(newMind, data.UserId);
|
|
||||||
_mindSystem.AddRole(newMind, new ObserverRole(newMind));
|
|
||||||
|
|
||||||
var mob = SpawnObserverMob();
|
|
||||||
EntityManager.GetComponent<MetaDataComponent>(mob).EntityName = name;
|
|
||||||
var ghost = EntityManager.GetComponent<GhostComponent>(mob);
|
|
||||||
EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(ghost, false);
|
|
||||||
_mindSystem.TransferTo(newMind, mob);
|
|
||||||
|
|
||||||
_playerGameStatuses[player.UserId] = PlayerGameStatus.JoinedGame;
|
|
||||||
RaiseNetworkEvent(GetStatusSingle(player, PlayerGameStatus.JoinedGame));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Mob Spawning Helpers
|
#region Mob Spawning Helpers
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Server.Chat.Systems;
|
|||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.Ghost;
|
using Content.Server.Ghost;
|
||||||
using Content.Server.Maps;
|
using Content.Server.Maps;
|
||||||
|
using Content.Server.Mind;
|
||||||
using Content.Server.Players.PlayTimeTracking;
|
using Content.Server.Players.PlayTimeTracking;
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
using Content.Server.ServerUpdates;
|
using Content.Server.ServerUpdates;
|
||||||
@@ -13,6 +14,8 @@ using Content.Server.Station.Systems;
|
|||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
|
using Content.Shared.Ghost;
|
||||||
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Server;
|
using Robust.Server;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
@@ -34,6 +37,9 @@ namespace Content.Server.GameTicking
|
|||||||
[Dependency] private readonly ArrivalsSystem _arrivals = default!;
|
[Dependency] private readonly ArrivalsSystem _arrivals = default!;
|
||||||
[Dependency] private readonly MapLoaderSystem _map = default!;
|
[Dependency] private readonly MapLoaderSystem _map = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||||
|
[Dependency] private readonly MindSystem _mind = default!;
|
||||||
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
|
|
||||||
[ViewVariables] private bool _initialized;
|
[ViewVariables] private bool _initialized;
|
||||||
[ViewVariables] private bool _postInitialized;
|
[ViewVariables] private bool _postInitialized;
|
||||||
|
|||||||
@@ -61,5 +61,13 @@ namespace Content.Server.Players
|
|||||||
{
|
{
|
||||||
return session.Data.ContentData();
|
return session.Data.ContentData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the mind that is associated with this player.
|
||||||
|
/// </summary>
|
||||||
|
public static Mind.Mind? GetMind(this IPlayerSession session)
|
||||||
|
{
|
||||||
|
return session.Data.ContentData()?.Mind;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ namespace Content.Shared.Ghost
|
|||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetCanReturnToBody(EntityUid uid, bool value, SharedGhostComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.CanReturnToBody = value;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetCanReturnToBody(SharedGhostComponent component, bool value)
|
public void SetCanReturnToBody(SharedGhostComponent component, bool value)
|
||||||
{
|
{
|
||||||
component.CanReturnToBody = value;
|
component.CanReturnToBody = value;
|
||||||
|
|||||||
Reference in New Issue
Block a user