From 31b39823ad4356cd1cdafbfc63813d5bbadeccd7 Mon Sep 17 00:00:00 2001 From: 20kdc Date: Fri, 11 Dec 2020 01:10:55 +0000 Subject: [PATCH] Player post-spawn hook and player ghosting controlled by preset (#2734) * GamePreset: Add a hook to catch all player spawns for modification. Unless someone makes the entire job system unpluggable, this is what you get... * GamePreset: Is now in control of voluntary ghosting * Clean up ghost code to only rely on a player's Mind --- Content.IntegrationTests/DummyGameTicker.cs | 7 ++ Content.Server/Commands/Observer/Ghost.cs | 61 ++------------ Content.Server/GameTicking/GamePreset.cs | 82 ++++++++++++++++++- Content.Server/GameTicking/GameTicker.cs | 8 ++ .../Interfaces/GameTicking/IGameTicker.cs | 5 ++ 5 files changed, 107 insertions(+), 56 deletions(-) diff --git a/Content.IntegrationTests/DummyGameTicker.cs b/Content.IntegrationTests/DummyGameTicker.cs index 55643ed677..0f1a706a4d 100644 --- a/Content.IntegrationTests/DummyGameTicker.cs +++ b/Content.IntegrationTests/DummyGameTicker.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; using Content.Shared.Roles; +using Content.Server.Mobs; using Robust.Server.Interfaces.Player; +using Robust.Server.Interfaces.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; using Robust.Shared.Timing; @@ -49,6 +51,11 @@ namespace Content.IntegrationTests { } + public bool OnGhostAttempt(Mind mind, bool canReturnGlobal) + { + return false; + } + public void MakeObserve(IPlayerSession player) { } diff --git a/Content.Server/Commands/Observer/Ghost.cs b/Content.Server/Commands/Observer/Ghost.cs index 6e0e510b37..2e1dcd027c 100644 --- a/Content.Server/Commands/Observer/Ghost.cs +++ b/Content.Server/Commands/Observer/Ghost.cs @@ -27,71 +27,22 @@ namespace Content.Server.Commands.Observer { if (player == null) { - shell.SendText(player, "Nah"); + shell?.SendText(player, "You have no session, you can't ghost."); return; } - var mind = player.ContentData()?.Mind; - + var mind = player!.ContentData()?.Mind; if (mind == null) { - shell.SendText(player, "You can't ghost here!"); + shell?.SendText(player, "You have no Mind, you can't ghost."); return; } - var playerEntity = player.AttachedEntity; - - if (playerEntity != null && playerEntity.HasComponent()) + if (!IoCManager.Resolve().OnGhostAttempt(mind, CanReturn)) + { + shell?.SendText(player, "You can't ghost right now."); return; - - if (mind.VisitingEntity != null) - { - mind.UnVisit(); - mind.VisitingEntity.Delete(); } - - var position = playerEntity?.Transform.Coordinates ?? IoCManager.Resolve().GetObserverSpawnPoint(); - var canReturn = false; - - if (playerEntity != null && CanReturn && playerEntity.TryGetComponent(out IMobStateComponent? mobState)) - { - if (mobState.IsDead()) - { - canReturn = true; - } - else if (mobState.IsCritical()) - { - canReturn = true; - - if (playerEntity.TryGetComponent(out IDamageableComponent? damageable)) - { - //todo: what if they dont breathe lol - damageable.ChangeDamage(DamageType.Asphyxiation, 100, true); - } - } - else - { - canReturn = false; - } - } - - var entityManager = IoCManager.Resolve(); - var ghost = entityManager.SpawnEntity("MobObserver", position); - ghost.Name = mind.CharacterName; - - var ghostComponent = ghost.GetComponent(); - ghostComponent.CanReturnToBody = canReturn; - - if (playerEntity != null && - playerEntity.TryGetComponent(out ServerOverlayEffectsComponent? overlayComponent)) - { - overlayComponent.RemoveOverlay(SharedOverlayID.CircleMaskOverlay); - } - - if (canReturn) - mind.Visit(ghost); - else - mind.TransferTo(ghost); } } } diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs index 0e9001ebba..e70cab4547 100644 --- a/Content.Server/GameTicking/GamePreset.cs +++ b/Content.Server/GameTicking/GamePreset.cs @@ -1,7 +1,21 @@ +#nullable enable annotations using System.Collections.Generic; using Content.Shared.Preferences; -using Robust.Server.Interfaces.Player; +using Content.Server.Administration; +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.Components.Observer; +using Content.Server.Interfaces.GameTicking; +using Content.Server.Players; +using Content.Server.Mobs; +using Content.Shared.Damage; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs.State; using Robust.Shared.Network; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Shared.IoC; namespace Content.Server.GameTicking { @@ -18,6 +32,72 @@ namespace Content.Server.GameTicking public virtual void OnGameStarted() { } + /// + /// Called when a player is spawned in (this includes, but is not limited to, before Start) + /// + public virtual void OnSpawnPlayerCompleted(IPlayerSession session, IEntity mob, bool lateJoin) { } + + /// + /// Called when a player attempts to ghost. + /// + public virtual bool OnGhostAttempt(Mind mind, bool canReturnGlobal) + { + var playerEntity = mind.OwnedEntity; + + if (playerEntity != null && playerEntity.HasComponent()) + return false; + + if (mind.VisitingEntity != null) + { + mind.UnVisit(); + mind.VisitingEntity.Delete(); + } + + var position = playerEntity?.Transform.Coordinates ?? IoCManager.Resolve().GetObserverSpawnPoint(); + var canReturn = false; + + if (playerEntity != null && canReturnGlobal && playerEntity.TryGetComponent(out IMobStateComponent? mobState)) + { + if (mobState.IsDead()) + { + canReturn = true; + } + else if (mobState.IsCritical()) + { + canReturn = true; + + if (playerEntity.TryGetComponent(out IDamageableComponent? damageable)) + { + //todo: what if they dont breathe lol + damageable.ChangeDamage(DamageType.Asphyxiation, 100, true); + } + } + else + { + canReturn = false; + } + } + + var entityManager = IoCManager.Resolve(); + var ghost = entityManager.SpawnEntity("MobObserver", position); + ghost.Name = mind.CharacterName; + + var ghostComponent = ghost.GetComponent(); + ghostComponent.CanReturnToBody = canReturn; + + if (playerEntity != null && + playerEntity.TryGetComponent(out ServerOverlayEffectsComponent? overlayComponent)) + { + overlayComponent.RemoveOverlay(SharedOverlayID.CircleMaskOverlay); + } + + if (canReturn) + mind.Visit(ghost); + else + mind.TransferTo(ghost); + return true; + } + public virtual string GetRoundEndDescription() => ""; } } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 319f2efb13..c49120c272 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -31,6 +31,7 @@ using Robust.Server.Interfaces.Maps; using Robust.Server.Interfaces.Player; using Robust.Server.Player; using Robust.Server.ServerStatus; +using Robust.Server.Interfaces.Console; using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.Configuration; @@ -440,6 +441,11 @@ namespace Content.Server.GameTicking UpdateJobsAvailable(); } + public bool OnGhostAttempt(Mind mind, bool canReturnGlobal) + { + return Preset.OnGhostAttempt(mind, canReturnGlobal); + } + public T AddGameRule() where T : GameRule, new() { var instance = _dynamicTypeFactory.CreateInstance(); @@ -887,6 +893,8 @@ namespace Content.Server.GameTicking AddSpawnedPosition(jobId); EquipIdCard(mob, character.Name, jobPrototype); jobPrototype.Special?.AfterEquip(mob); + + Preset.OnSpawnPlayerCompleted(session, mob, lateJoin); } private void EquipIdCard(IEntity mob, string characterName, JobPrototype jobPrototype) diff --git a/Content.Server/Interfaces/GameTicking/IGameTicker.cs b/Content.Server/Interfaces/GameTicking/IGameTicker.cs index 559c6a6497..5651a26583 100644 --- a/Content.Server/Interfaces/GameTicking/IGameTicker.cs +++ b/Content.Server/Interfaces/GameTicking/IGameTicker.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using Content.Server.GameTicking; +using Content.Server.Mobs; using Content.Shared.Roles; using Robust.Server.Interfaces.Player; +using Robust.Server.Interfaces.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; using Robust.Shared.Timing; @@ -42,6 +44,9 @@ namespace Content.Server.Interfaces.GameTicking void ToggleReady(IPlayerSession player, bool ready); void ToggleDisallowLateJoin(bool disallowLateJoin); + /// proxy to GamePreset (actual handler) + bool OnGhostAttempt(Mind mind, bool canReturnGlobal); + EntityCoordinates GetLateJoinSpawnPoint(); EntityCoordinates GetJobSpawnPoint(string jobId); EntityCoordinates GetObserverSpawnPoint();