From 9146374e390386162d0a913d383c323a6ce40461 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Wed, 12 Apr 2023 06:32:14 -0700 Subject: [PATCH] Remove ghost role component references (#15262) --- .../Botany/Systems/PlantHolderSystem.cs | 7 +- .../Chemistry/ReagentEffects/MakeSentient.cs | 13 ++- .../GameTicking/Rules/NukeopsRuleSystem.cs | 9 +- .../Roles/Components/GhostRoleComponent.cs | 8 +- .../GhostRoleMobSpawnerComponent.cs | 55 ++------- .../GhostTakeoverAvailableComponent.cs | 31 +---- .../Roles/Components/TakeGhostRoleEvent.cs | 9 ++ Content.Server/Ghost/Roles/GhostRoleSystem.cs | 107 ++++++++++++++++-- .../Ghost/Roles/MakeGhostRoleCommand.cs | 15 ++- Content.Server/PAI/PAISystem.cs | 20 ++-- .../StationEvents/Events/RandomSentience.cs | 10 +- .../Zombies/ZombifyOnDeathSystem.cs | 9 +- .../Entities/Markers/Spawners/ghost_roles.yml | 17 +-- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 9 +- .../Prototypes/Entities/Mobs/NPCs/bear.yml | 6 +- .../Prototypes/Entities/Mobs/NPCs/carp.yml | 6 +- .../Prototypes/Entities/Mobs/NPCs/pets.yml | 3 +- .../Entities/Mobs/NPCs/regalrat.yml | 6 +- .../Entities/Mobs/NPCs/revenant.yml | 3 +- .../Prototypes/Entities/Mobs/NPCs/silicon.yml | 3 +- .../Entities/Mobs/NPCs/spacetick.yml | 6 +- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 3 +- .../Entities/Mobs/Player/dragon.yml | 3 +- .../Entities/Mobs/Player/familiars.yml | 6 +- .../Entities/Mobs/Player/guardian.yml | 9 +- .../Entities/Mobs/Player/humanoid.yml | 39 ++++--- .../Entities/Mobs/Player/silicon.yml | 6 +- .../Entities/Mobs/Player/skeleton.yml | 6 +- .../reinforcement_teleporter.yml | 5 +- .../XenoArch/Effects/utility_effects.yml | 3 +- 30 files changed, 254 insertions(+), 178 deletions(-) create mode 100644 Content.Server/Ghost/Roles/Components/TakeGhostRoleEvent.cs diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 29c2d964d3..04ec24e9fc 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -609,9 +609,10 @@ namespace Content.Server.Botany.Systems if (component.Seed.Sentient) { - var comp = EnsureComp(uid); - comp.RoleName = MetaData(uid).EntityName; - comp.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", comp.RoleName)); + var ghostRole = EnsureComp(uid); + EnsureComp(uid); + ghostRole.RoleName = MetaData(uid).EntityName; + ghostRole.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", ghostRole.RoleName)); } if (component.UpdateSpriteAfterUpdate) diff --git a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs index c762ba8a83..93684df7d7 100644 --- a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs +++ b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs @@ -1,8 +1,7 @@ +using Content.Server.Ghost.Roles.Components; using Content.Server.Mind.Components; using Content.Server.Speech.Components; - using Content.Shared.Chemistry.Reagent; -using Content.Server.Ghost.Roles.Components; namespace Content.Server.Chemistry.ReagentEffects; @@ -26,16 +25,18 @@ public sealed class MakeSentient : ReagentEffect } // No idea what anything past this point does - if (entityManager.TryGetComponent(uid, out GhostTakeoverAvailableComponent? takeOver)) + if (entityManager.TryGetComponent(uid, out GhostRoleComponent? ghostRole) || + entityManager.TryGetComponent(uid, out GhostTakeoverAvailableComponent? takeOver)) { return; } - takeOver = entityManager.AddComponent(uid); + ghostRole = entityManager.AddComponent(uid); + entityManager.AddComponent(uid); var entityData = entityManager.GetComponent(uid); - takeOver.RoleName = entityData.EntityName; - takeOver.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description"); + ghostRole.RoleName = entityData.EntityName; + ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description"); } } diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index bf2c7946db..18dd27b859 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -18,6 +18,7 @@ using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Server.Traitor; using Content.Shared.Dataset; +using Content.Shared.Humanoid.Prototypes; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Nuke; @@ -31,7 +32,6 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; -using Content.Shared.Humanoid.Prototypes; namespace Content.Server.GameTicking.Rules; @@ -770,9 +770,10 @@ public sealed class NukeopsRuleSystem : GameRuleSystem else if (addSpawnPoints) { var spawnPoint = EntityManager.SpawnEntity(_nukeopsRuleConfig.GhostSpawnPointProto, _random.Pick(spawns)); - var spawner = EnsureComp(spawnPoint); - spawner.RoleName = Loc.GetString(nukeOpsAntag.Name); - spawner.RoleDescription = Loc.GetString(nukeOpsAntag.Objective); + var ghostRole = EnsureComp(spawnPoint); + EnsureComp(spawnPoint); + ghostRole.RoleName = Loc.GetString(nukeOpsAntag.Name); + ghostRole.RoleDescription = Loc.GetString(nukeOpsAntag.Objective); var nukeOpSpawner = EnsureComp(spawnPoint); nukeOpSpawner.OperativeName = spawnDetails.Name; diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs index 558366e786..0b5a90ff50 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs @@ -1,10 +1,10 @@ using Content.Server.Mind.Commands; -using Robust.Server.Player; namespace Content.Server.Ghost.Roles.Components { + [RegisterComponent] [Access(typeof(GhostRoleSystem))] - public abstract class GhostRoleComponent : Component + public sealed class GhostRoleComponent : Component { [DataField("name")] public string _roleName = "Unknown"; @@ -16,7 +16,7 @@ namespace Content.Server.Ghost.Roles.Components /// Whether the should run on the mob. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("makeSentient")] - protected bool MakeSentient = true; + public bool MakeSentient = true; /// /// The probability that this ghost role will be available after init. @@ -83,7 +83,5 @@ namespace Content.Server.Ghost.Roles.Components [ViewVariables(VVAccess.ReadWrite)] [DataField("reregister")] public bool ReregisterOnGhost { get; set; } = true; - - public abstract bool Take(IPlayerSession session); } } diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs index 720bd5387e..39e22d96c1 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs @@ -1,67 +1,26 @@ -using Content.Server.Mind.Commands; -using Content.Server.Mind.Components; -using JetBrains.Annotations; -using Robust.Server.Player; -using Robust.Shared.Prototypes; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Content.Server.Ghost.Roles.Events; namespace Content.Server.Ghost.Roles.Components { /// /// Allows a ghost to take this role, spawning a new entity. /// - [RegisterComponent, ComponentReference(typeof(GhostRoleComponent))] - public sealed class GhostRoleMobSpawnerComponent : GhostRoleComponent + [RegisterComponent] + [Access(typeof(GhostRoleSystem))] + public sealed class GhostRoleMobSpawnerComponent : Component { - [Dependency] private readonly IEntityManager _entMan = default!; - [ViewVariables(VVAccess.ReadWrite)] [DataField("deleteOnSpawn")] - private bool _deleteOnSpawn = true; + public bool DeleteOnSpawn = true; [ViewVariables(VVAccess.ReadWrite)] [DataField("availableTakeovers")] - private int _availableTakeovers = 1; + public int AvailableTakeovers = 1; [ViewVariables] - private int _currentTakeovers = 0; + public int CurrentTakeovers = 0; - [CanBeNull] [ViewVariables(VVAccess.ReadWrite)] [DataField("prototype", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? Prototype { get; private set; } - - public override bool Take(IPlayerSession session) - { - if (Taken) - return false; - - if (string.IsNullOrEmpty(Prototype)) - throw new NullReferenceException("Prototype string cannot be null or empty!"); - - var mob = _entMan.SpawnEntity(Prototype, _entMan.GetComponent(Owner).Coordinates); - var xform = _entMan.GetComponent(mob); - xform.AttachToGridOrMap(); - - var spawnedEvent = new GhostRoleSpawnerUsedEvent(Owner, mob); - _entMan.EventBus.RaiseLocalEvent(mob, spawnedEvent, false); - - if (MakeSentient) - MakeSentientCommand.MakeSentient(mob, _entMan, AllowMovement, AllowSpeech); - - mob.EnsureComponent(); - - var ghostRoleSystem = EntitySystem.Get(); - ghostRoleSystem.GhostRoleInternalCreateMindAndTransfer(session, Owner, mob, this); - - if (++_currentTakeovers < _availableTakeovers) - return true; - - Taken = true; - - if (_deleteOnSpawn) - _entMan.QueueDeleteEntity(Owner); - - return true; - } } } diff --git a/Content.Server/Ghost/Roles/Components/GhostTakeoverAvailableComponent.cs b/Content.Server/Ghost/Roles/Components/GhostTakeoverAvailableComponent.cs index 0a9a9435f9..44c09f3a67 100644 --- a/Content.Server/Ghost/Roles/Components/GhostTakeoverAvailableComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostTakeoverAvailableComponent.cs @@ -1,36 +1,11 @@ -using Content.Server.Mind.Commands; -using Content.Server.Mind.Components; -using Robust.Server.Player; - namespace Content.Server.Ghost.Roles.Components { /// /// Allows a ghost to take over the Owner entity. /// - [RegisterComponent, ComponentReference(typeof(GhostRoleComponent))] - public sealed class GhostTakeoverAvailableComponent : GhostRoleComponent + [RegisterComponent] + [Access(typeof(GhostRoleSystem))] + public sealed class GhostTakeoverAvailableComponent : Component { - public override bool Take(IPlayerSession session) - { - if (Taken) - return false; - - Taken = true; - - var mind = Owner.EnsureComponent(); - - if (mind.HasMind) - return false; - - if (MakeSentient) - MakeSentientCommand.MakeSentient(Owner, IoCManager.Resolve(), AllowMovement, AllowSpeech); - - var ghostRoleSystem = EntitySystem.Get(); - ghostRoleSystem.GhostRoleInternalCreateMindAndTransfer(session, Owner, Owner, this); - - ghostRoleSystem.UnregisterGhostRole(this); - - return true; - } } } diff --git a/Content.Server/Ghost/Roles/Components/TakeGhostRoleEvent.cs b/Content.Server/Ghost/Roles/Components/TakeGhostRoleEvent.cs new file mode 100644 index 0000000000..a76ea95579 --- /dev/null +++ b/Content.Server/Ghost/Roles/Components/TakeGhostRoleEvent.cs @@ -0,0 +1,9 @@ +using Robust.Server.Player; + +namespace Content.Server.Ghost.Roles.Components; + +[ByRefEvent] +public record struct TakeGhostRoleEvent(IPlayerSession Player) +{ + public bool TookRole { get; set; } +} diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index 00f53c27fa..6cbb51b009 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -2,7 +2,9 @@ using Content.Server.Administration.Logs; using Content.Server.EUI; using Content.Server.Ghost.Components; using Content.Server.Ghost.Roles.Components; +using Content.Server.Ghost.Roles.Events; using Content.Server.Ghost.Roles.UI; +using Content.Server.Mind.Commands; using Content.Server.Mind.Components; using Content.Server.Players; using Content.Shared.Administration; @@ -30,6 +32,7 @@ namespace Content.Server.Ghost.Roles [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly FollowerSystem _followerSystem = default!; + [Dependency] private readonly TransformSystem _transform = default!; private uint _nextRoleIdentifier; private bool _needsUpdateGhostRoleCount = true; @@ -51,22 +54,27 @@ namespace Content.Server.Ghost.Roles SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnSpawnerTakeRole); + SubscribeLocalEvent(OnTakeoverTakeRole); _playerManager.PlayerStatusChanged += PlayerStatusChanged; } - private void OnMobStateChanged(EntityUid uid, GhostRoleComponent component, MobStateChangedEvent args) + private void OnMobStateChanged(EntityUid uid, GhostTakeoverAvailableComponent component, MobStateChangedEvent args) { + if (!TryComp(uid, out GhostRoleComponent? ghostRole)) + return; + switch (args.NewMobState) { case MobState.Alive: { - if (!component.Taken) - RegisterGhostRole(component); + if (!ghostRole.Taken) + RegisterGhostRole(ghostRole); break; } case MobState.Critical: case MobState.Dead: - UnregisterGhostRole(component); + UnregisterGhostRole(ghostRole); break; } } @@ -180,7 +188,11 @@ namespace Content.Server.Ghost.Roles public void Takeover(IPlayerSession player, uint identifier) { if (!_ghostRoles.TryGetValue(identifier, out var role)) return; - if (!role.Take(player)) return; + + var ev = new TakeGhostRoleEvent(player); + RaiseLocalEvent(role.Owner, ref ev); + + if (!ev.TookRole) return; if (player.AttachedEntity != null) _adminLogger.Add(LogType.GhostRoleTaken, LogImpact.Low, $"{player:player} took the {role.RoleName:roleName} ghost role {ToPrettyString(player.AttachedEntity.Value):entity}"); @@ -239,18 +251,24 @@ namespace Content.Server.Ghost.Roles private void OnMindAdded(EntityUid uid, GhostTakeoverAvailableComponent component, MindAddedMessage args) { - component.Taken = true; - UnregisterGhostRole(component); + if (!TryComp(uid, out GhostRoleComponent? ghostRole)) + return; + + ghostRole.Taken = true; + UnregisterGhostRole(ghostRole); } - private void OnMindRemoved(EntityUid uid, GhostRoleComponent component, MindRemovedMessage args) + private void OnMindRemoved(EntityUid uid, GhostTakeoverAvailableComponent component, MindRemovedMessage args) { + if (!TryComp(uid, out GhostRoleComponent? ghostRole)) + return; + // Avoid re-registering it for duplicate entries and potential exceptions. - if (!component.ReregisterOnGhost || component.LifeStage > ComponentLifeStage.Running) + if (!ghostRole.ReregisterOnGhost || component.LifeStage > ComponentLifeStage.Running) return; - component.Taken = false; - RegisterGhostRole(component); + ghostRole.Taken = false; + RegisterGhostRole(ghostRole); } public void Reset(RoundRestartCleanupEvent ev) @@ -282,6 +300,73 @@ namespace Content.Server.Ghost.Roles { UnregisterGhostRole(role); } + + private void OnSpawnerTakeRole(EntityUid uid, GhostRoleMobSpawnerComponent component, ref TakeGhostRoleEvent args) + { + if (!TryComp(uid, out GhostRoleComponent? ghostRole) || + ghostRole.Taken) + { + args.TookRole = false; + return; + } + + if (string.IsNullOrEmpty(component.Prototype)) + throw new NullReferenceException("Prototype string cannot be null or empty!"); + + var mob = Spawn(component.Prototype, Transform(uid).Coordinates); + _transform.AttachToGridOrMap(mob); + + var spawnedEvent = new GhostRoleSpawnerUsedEvent(uid, mob); + RaiseLocalEvent(mob, spawnedEvent); + + if (ghostRole.MakeSentient) + MakeSentientCommand.MakeSentient(mob, EntityManager, ghostRole.AllowMovement, ghostRole.AllowSpeech); + + mob.EnsureComponent(); + + GhostRoleInternalCreateMindAndTransfer(args.Player, uid, mob, ghostRole); + + if (++component.CurrentTakeovers < component.AvailableTakeovers) + { + args.TookRole = true; + return; + } + + ghostRole.Taken = true; + + if (component.DeleteOnSpawn) + QueueDel(uid); + + args.TookRole = true; + } + + private void OnTakeoverTakeRole(EntityUid uid, GhostTakeoverAvailableComponent component, ref TakeGhostRoleEvent args) + { + if (!TryComp(uid, out GhostRoleComponent? ghostRole) || + ghostRole.Taken) + { + args.TookRole = false; + return; + } + + ghostRole.Taken = true; + + var mind = EnsureComp(uid); + + if (mind.HasMind) + { + args.TookRole = false; + return; + } + + if (ghostRole.MakeSentient) + MakeSentientCommand.MakeSentient(uid, EntityManager, ghostRole.AllowMovement, ghostRole.AllowSpeech); + + GhostRoleInternalCreateMindAndTransfer(args.Player, uid, uid, ghostRole); + UnregisterGhostRole(ghostRole); + + args.TookRole = true; + } } [AnyCommand] diff --git a/Content.Server/Ghost/Roles/MakeGhostRoleCommand.cs b/Content.Server/Ghost/Roles/MakeGhostRoleCommand.cs index 128afc20bc..e6ed131550 100644 --- a/Content.Server/Ghost/Roles/MakeGhostRoleCommand.cs +++ b/Content.Server/Ghost/Roles/MakeGhostRoleCommand.cs @@ -46,16 +46,23 @@ namespace Content.Server.Ghost.Roles var description = args[2]; var rules = args.Length >= 4 ? args[3] : Loc.GetString("ghost-role-component-default-rules"); + if (entityManager.TryGetComponent(uid, out GhostRoleComponent? ghostRole)) + { + shell.WriteLine($"Entity {metaData.EntityName} with id {uid} already has a {nameof(GhostRoleComponent)}"); + return; + } + if (entityManager.TryGetComponent(uid, out GhostTakeoverAvailableComponent? takeOver)) { shell.WriteLine($"Entity {metaData.EntityName} with id {uid} already has a {nameof(GhostTakeoverAvailableComponent)}"); return; } - takeOver = entityManager.AddComponent(uid); - takeOver.RoleName = name; - takeOver.RoleDescription = description; - takeOver.RoleRules = rules; + ghostRole = entityManager.AddComponent(uid); + entityManager.AddComponent(uid); + ghostRole.RoleName = name; + ghostRole.RoleDescription = description; + ghostRole.RoleRules = rules; shell.WriteLine($"Made entity {metaData.EntityName} a ghost role."); } diff --git a/Content.Server/PAI/PAISystem.cs b/Content.Server/PAI/PAISystem.cs index 2939ae58c0..5f89c45213 100644 --- a/Content.Server/PAI/PAISystem.cs +++ b/Content.Server/PAI/PAISystem.cs @@ -1,14 +1,13 @@ -using Content.Shared.Examine; -using Content.Shared.PAI; -using Content.Shared.Verbs; -using Content.Server.Popups; -using Content.Server.Instruments; using Content.Server.Ghost.Roles.Components; +using Content.Server.Instruments; using Content.Server.Mind.Components; -using Robust.Server.GameObjects; -using Robust.Shared.Player; +using Content.Server.Popups; +using Content.Shared.Examine; using Content.Shared.Interaction.Events; +using Content.Shared.PAI; using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; namespace Content.Server.PAI { @@ -79,10 +78,11 @@ namespace Content.Server.PAI EntityManager.GetComponent(component.Owner).EntityName = val; - var ghostFinder = EntityManager.EnsureComponent(uid); + var ghostRole = AddComp(uid); + EnsureComp(uid); - ghostFinder.RoleName = Loc.GetString("pai-system-role-name"); - ghostFinder.RoleDescription = Loc.GetString("pai-system-role-description"); + ghostRole.RoleName = Loc.GetString("pai-system-role-name"); + ghostRole.RoleDescription = Loc.GetString("pai-system-role-description"); _popupSystem.PopupEntity(Loc.GetString("pai-system-searching"), uid, args.User); UpdatePAIAppearance(uid, PAIStatus.Searching); diff --git a/Content.Server/StationEvents/Events/RandomSentience.cs b/Content.Server/StationEvents/Events/RandomSentience.cs index 6f0cbf586b..8181e11c0d 100644 --- a/Content.Server/StationEvents/Events/RandomSentience.cs +++ b/Content.Server/StationEvents/Events/RandomSentience.cs @@ -1,11 +1,8 @@ using System.Linq; -using Content.Server.Chat; using Content.Server.Chat.Systems; using Content.Server.Ghost.Roles.Components; -using Content.Server.Mind.Commands; using Content.Server.Station.Systems; using Content.Server.StationEvents.Components; -using Robust.Shared.Random; namespace Content.Server.StationEvents.Events; @@ -31,9 +28,10 @@ public sealed class RandomSentience : StationEventSystem break; EntityManager.RemoveComponent(target.Owner); - var comp = EntityManager.AddComponent(target.Owner); - comp.RoleName = EntityManager.GetComponent(target.Owner).EntityName; - comp.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", comp.RoleName)); + var ghostRole = AddComp(target.Owner); + AddComp(target.Owner); + ghostRole.RoleName = EntityManager.GetComponent(target.Owner).EntityName; + ghostRole.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", ghostRole.RoleName)); groups.Add(Loc.GetString(target.FlavorKind)); } diff --git a/Content.Server/Zombies/ZombifyOnDeathSystem.cs b/Content.Server/Zombies/ZombifyOnDeathSystem.cs index 97c6a251b4..ef7203eba6 100644 --- a/Content.Server/Zombies/ZombifyOnDeathSystem.cs +++ b/Content.Server/Zombies/ZombifyOnDeathSystem.cs @@ -210,10 +210,11 @@ namespace Content.Server.Zombies if (!HasComp(target) && !mindcomp.HasMind) //this specific component gives build test trouble so pop off, ig { //yet more hardcoding. Visit zombie.ftl for more information. - EntityManager.EnsureComponent(target, out var ghostcomp); - ghostcomp.RoleName = Loc.GetString("zombie-generic"); - ghostcomp.RoleDescription = Loc.GetString("zombie-role-desc"); - ghostcomp.RoleRules = Loc.GetString("zombie-role-rules"); + var ghostRole = EnsureComp(target); + EnsureComp(target); + ghostRole.RoleName = Loc.GetString("zombie-generic"); + ghostRole.RoleDescription = Loc.GetString("zombie-role-desc"); + ghostRole.RoleRules = Loc.GetString("zombie-role-rules"); } //Goes through every hand, drops the items in it, then removes the hand diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index 6c0f6ffaaa..bfe3d7219f 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -4,7 +4,7 @@ suffix: rat king parent: MarkerBase components: - - type: GhostRoleMobSpawner + - type: GhostRole prototype: MobRatKing name: Rat King description: You are the Rat King, scavenge food in order to produce rat minions to do your bidding. @@ -22,11 +22,12 @@ suffix: Remilia parent: MarkerBase components: - - type: GhostRoleMobSpawner - prototype: MobBatRemilia + - type: GhostRole name: Remilia, the chaplain's familiar description: Obey your master. Eat fruit. rules: You are an intelligent fruit bat. Follow the chaplain around. Don't cause any trouble unless the chaplain tells you to. + - type: GhostRoleMobSpawner + prototype: MobBatRemilia - type: Sprite sprite: Markers/jobs.rsi layers: @@ -40,11 +41,12 @@ suffix: cerberus parent: MarkerBase components: - - type: GhostRoleMobSpawner - prototype: MobCorgiCerberus + - type: GhostRole name: Cerberus, Evil Familiar description: Obey your master. Spread chaos. rules: You are an intelligent, demonic dog. Try to help the chaplain and any of his flock. As an antagonist, you're otherwise unrestrained. + - type: GhostRoleMobSpawner + prototype: MobCorgiCerberus - type: Sprite sprite: Markers/jobs.rsi layers: @@ -59,9 +61,10 @@ suffix: nukeops parent: MarkerBase components: + - type: GhostRole + rules: You are a syndicate operative tasked with the destruction of the station. As an antagonist, do whatever is required to complete this task. - type: GhostRoleMobSpawner prototype: MobHumanNukeOp - rules: You are a syndicate operative tasked with the destruction of the station. As an antagonist, do whatever is required to complete this task. - type: NukeOperativeSpawner - type: Sprite sprite: Markers/jobs.rsi @@ -69,5 +72,3 @@ - state: green - sprite: Structures/Wallmounts/signs.rsi state: radiation - - diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index afbb9f001d..c9c98b949e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -751,12 +751,13 @@ id: MobMouse description: Squeak! components: - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true allowSpeech: true allowMovement: true name: ghost-role-information-mouse-name description: ghost-role-information-mouse-description + - type: GhostTakeoverAvailable - type: Speech speechSounds: Squeak - type: Sprite @@ -1353,10 +1354,11 @@ - type: MobMover - type: HTN rootTask: SimpleHostileCompound - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true name: ghost-role-information-giant-spider-name description: ghost-role-information-giant-spider-description + - type: GhostTakeoverAvailable - type: entity name: possum @@ -1937,12 +1939,13 @@ id: MobHamster description: A cute, fluffy, robust hamster. components: - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true allowSpeech: true allowMovement: true name: ghost-role-information-hamster-name description: ghost-role-information-hamster-description + - type: GhostTakeoverAvailable - type: Speech speechSounds: Squeak - type: Sprite diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml b/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml index 69a2a99670..7af585c69a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml @@ -90,20 +90,22 @@ radius: 1.2 energy: 2 color: "#4faffb" - - type: GhostTakeoverAvailable + - type: GhostRole prob: 0.25 name: space bear description: | You're a bear! Do bear things. + - type: GhostTakeoverAvailable - type: entity id: MobBearSpaceSalvage parent: MobBearSpace suffix: "Salvage Ruleset" components: - - type: GhostTakeoverAvailable + - type: GhostRole prob: 0.25 name: space bear on salvage wreck description: | Defend the loot inside the salvage wreck! + - type: GhostTakeoverAvailable - type: SalvageMobRestrictions diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 66087f4d7b..768688bec9 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -129,13 +129,14 @@ parent: MobCarp suffix: "Salvage Ruleset" components: - - type: GhostTakeoverAvailable + - type: GhostRole prob: 0.33 name: space carp on salvage wreck allowMovement: true allowSpeech: true description: | Defend the loot inside the salvage wreck! + - type: GhostTakeoverAvailable - type: SalvageMobRestrictions - type: entity @@ -144,11 +145,12 @@ suffix: DragonBrood parent: BaseMobCarp components: - - type: GhostTakeoverAvailable + - type: GhostRole allowMovement: true allowSpeech: true makeSentient: true name: Sentient Carp description: Help the dragon flood the station with carps! + - type: GhostTakeoverAvailable - type: HTN rootTask: DragonCarpCompound diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 4e69648088..4a83c24fd0 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -445,12 +445,13 @@ id: MobHamsterHamlet description: A grumpy, cute and fluffy hamster. components: - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true allowSpeech: true allowMovement: true name: ghost-role-information-hamlet-name description: ghost-role-information-hamlet-description + - type: GhostTakeoverAvailable - type: InteractionPopup successChance: 1 - type: Butcherable diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index 6ddc4ed21c..c1c675f86a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -82,11 +82,12 @@ Base: dead Dead: Base: dead - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true name: Rat King description: You are the Rat King, scavenge food in order to produce rat minions to do your bidding. rules: You are an antagonist, scavenge, attack, and grow your hoard! + - type: GhostTakeoverAvailable - type: Tag tags: - CannotSuicide @@ -283,11 +284,12 @@ Female: Mouse Unsexed: Mouse wilhelmProbability: 0.001 - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true name: Rat Servant description: You are a Rat Servant. You must follow your king's orders. rules: You are an antagonist, scavenge, attack, and serve your king! + - type: GhostTakeoverAvailable - type: Tag tags: - CannotSuicide diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index ace5fd81ec..46b3086566 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -48,11 +48,12 @@ - type: Alerts - type: NameIdentifier group: GenericNumber - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true name: Revenant description: You are a Revenant. Use your powers to harvest souls and unleash chaos upon the crew. Unlock new abilities with the essence you harvest. rules: You are an antagonist, harvest, defile, and drive the crew insane. + - type: GhostTakeoverAvailable - type: Revenant - type: PointLight color: MediumPurple diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 5dcc5ec98a..9b698ef25f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -132,10 +132,11 @@ - type: Construction graph: HonkBot node: bot - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true name: honkbot description: An artificial being of pure evil. + - type: GhostTakeoverAvailable - type: InteractionPopup interactSuccessString: petting-success-honkbot interactFailureString: petting-failure-honkbot diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml index b5029cedb8..a4d5ab7257 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml @@ -74,12 +74,13 @@ Quantity: 5 - type: MeleeChemicalInjector solution: melee - - type: GhostTakeoverAvailable + - type: GhostRole prob: 0.33 makeSentient: true name: space tick description: | Wreak havoc on the station! + - type: GhostTakeoverAvailable - type: ReplacementAccent accent: genericAggressive @@ -88,9 +89,10 @@ parent: MobTick suffix: "Salvage Ruleset" components: - - type: GhostTakeoverAvailable + - type: GhostRole name: space tick on salvage wreck description: | Defend the loot inside the salvage wreck! + - type: GhostTakeoverAvailable - type: SalvageMobRestrictions diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index e8a22f7045..9203155c6c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -89,13 +89,14 @@ spawned: - id: FoodMeatXeno amount: 5 - - type: GhostTakeoverAvailable + - type: GhostRole allowMovement: true allowSpeech: true makeSentient: true name: xeno description: You are a xeno, co-operate with your hive to kill all crewmembers! rules: You are an antagonist, smack, slash, and wack! + - type: GhostTakeoverAvailable - type: TypingIndicator proto: alien - type: Temperature diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index 67524ec40f..fc96a5f66f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -5,12 +5,13 @@ suffix: "" description: A flying leviathan, loosely related to space carps. components: - - type: GhostTakeoverAvailable + - type: GhostRole allowMovement: true allowSpeech: true makeSentient: true name: Space dragon description: Call in 3 carp rifts and take over this quadrant! You have only 5 minutes in between each rift before you will disappear. + - type: GhostTakeoverAvailable - type: HTN rootTask: XenoCompound - type: Faction diff --git a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml index d8477281ed..d4e7c5f47c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml @@ -4,13 +4,14 @@ id: MobBatRemilia description: The chaplain's familiar. Likes fruit. components: - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true allowMovement: true allowSpeech: true name: Remilia, the chaplain's familiar description: Obey your master. Eat fruit. rules: You are an intelligent fruit bat. Follow the chaplain around. Don't cause any trouble unless the chaplain tells you to. + - type: GhostTakeoverAvailable - type: Grammar attributes: gender: female @@ -32,13 +33,14 @@ id: MobCorgiCerberus description: This pupper is not wholesome. components: - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true allowMovement: true allowSpeech: true name: Cerberus, Evil Familiar description: Obey your master. Spread chaos. rules: You are an intelligent, demonic dog. Try to help the chaplain and any of his flock. As an antagonist, you're otherwise unrestrained. + - type: GhostTakeoverAvailable - type: MeleeWeapon hidden: true angle: 0 diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index 34303467f4..7d2da5ba66 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -7,12 +7,13 @@ save: false components: - type: LagCompensation - - type: GhostTakeoverAvailable + - type: GhostRole allowMovement: true allowSpeech: true makeSentient: true name: Guardian description: Listen to your owner. Don't tank damage. Punch people hard. + - type: GhostTakeoverAvailable - type: Input context: "human" - type: MobMover @@ -104,12 +105,13 @@ parent: MobGuardianBase description: A mesmerising whirl of hard-light patterns weaves a marvelous, yet oddly familiar visage. It stands proud, tuning into its owner's life to sustain itself. components: - - type: GhostTakeoverAvailable + - type: GhostRole allowMovement: true allowSpeech: true makeSentient: true name: Holoparasite description: Listen to your owner. Don't tank damage. Punch people hard. + - type: GhostTakeoverAvailable - type: NameIdentifier group: Holoparasite - type: TypingIndicator @@ -130,12 +132,13 @@ id: MobIfritGuardian description: A corrupted jinn, ripped from fitra to serve the wizard's petty needs. It stands wicked, tuning into it's owner's life to sustain itself. components: - - type: GhostTakeoverAvailable + - type: GhostRole allowMovement: true allowSpeech: true makeSentient: true name: Ifrit description: Listen to your owner. Don't tank damage. Punch people hard. + - type: GhostTakeoverAvailable - type: RandomSprite available: - enum.DamageStateVisualLayers.BaseUnshaded: diff --git a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml index f985e4dfbc..a24a69d111 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml @@ -21,9 +21,10 @@ id: ERTLeader randomizeName: false components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Leader description: Lead a team of specialists to resolve the stations issues. + - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTLeaderGear ] - type: RandomMetadata @@ -48,9 +49,10 @@ id: ERTLeaderEVA parent: ERTLeader components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Leader description: Lead a team of specialists to resolve the stations issues. + - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTLeaderGearEVA ] @@ -76,9 +78,10 @@ id: ERTJanitor parent: ERTLeader components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Janitor description: Assist with custodial efforts to resolve the stations issues. + - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: - NamesFirstMilitary @@ -103,9 +106,10 @@ id: ERTJanitorEVA parent: ERTJanitor components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Janitor description: Assist with custodial efforts to resolve the stations issues. + - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTJanitorGearEVA ] @@ -131,9 +135,10 @@ id: ERTEngineer parent: ERTLeader components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Engineer description: Assist with engineering efforts to resolve the stations issues. + - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: - NamesFirstMilitary @@ -158,9 +163,10 @@ id: ERTEngineerEVA parent: ERTEngineer components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Engineer description: Assist with engineering efforts to resolve the stations issues. + - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTEngineerGearEVA ] @@ -186,9 +192,10 @@ id: ERTSecurity parent: ERTLeader components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Security description: Assist with security efforts to resolve the stations issues. + - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: - NamesFirstMilitary @@ -213,9 +220,10 @@ id: ERTSecurityEVA parent: ERTSecurity components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Security description: Assist with security efforts to resolve the stations issues. + - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTSecurityGearEVA ] @@ -241,9 +249,10 @@ id: ERTMedical parent: ERTLeader components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Medical description: Assist with medicaling efforts to resolve the stations issues. + - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: - NamesFirstMilitary @@ -268,9 +277,10 @@ id: ERTMedicalEVA parent: ERTMedical components: - - type: GhostTakeoverAvailable + - type: GhostRole name: ERT Medical description: Assist with medicaling efforts to resolve the stations issues. + - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTMedicalGearEVA ] @@ -292,9 +302,10 @@ components: - type: Loadout prototypes: [CBURNGear] - - type: GhostTakeoverAvailable + - type: GhostRole name: CBURN Agent description: A highly trained CentCom agent, capable of dealing with various threats. + - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: - NamesFirstMilitary @@ -315,9 +326,10 @@ - type: randomHumanoidSettings id: CentcomOfficial components: - - type: GhostTakeoverAvailable + - type: GhostRole name: CentCom official description: Inspect the station, jot down performance reviews for heads of staff, bug the Captain. + - type: GhostTakeoverAvailable - type: Loadout prototypes: [ CentcomGear ] @@ -379,7 +391,8 @@ id: Cluwne randomizeName: false components: - - type: GhostTakeoverAvailable + - type: GhostRole name: Cluwne description: Become a pitiful cluwne, your only goal in life is to find a sweet release from your suffering (usually by being beaten to death). A cluwne is not an antagonist but may defend itself. Crewmembers may murder cluwnes freely. + - type: GhostTakeoverAvailable - type: Cluwne diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index c5e911ff0a..750be45c06 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -84,7 +84,7 @@ interfaces: - key: enum.StrippingUiKey.Key type: StrippableBoundUserInterface - #- type: GhostTakeoverAvailable + #- type: GhostRole # makeSentient: true # name: Maintenance Drone # description: Maintain the station. Ignore other beings except drones. @@ -93,6 +93,7 @@ # 1. You may not involve yourself in the matters of another being, even if such matters conflict with Law Two or Law Three, unless the other being is another Drone. # 2. You may not harm any being, regardless of intent or circumstance. # 3. Your goals are to build, maintain, repair, improve, and power to the best of your abilities, You must never actively work against these goals. + #- type: GhostTakeoverAvailable - type: MovementSpeedModifier baseWalkSpeed : 5 baseSprintSpeed : 5 @@ -186,11 +187,12 @@ interfaces: - key: enum.StrippingUiKey.Key type: StrippableBoundUserInterface - - type: GhostTakeoverAvailable + - type: GhostRole makeSentient: true name: Onestar Mecha description: You are an experimental mecha created by who-knows-what, all you know is that you have weapons and you detect fleshy moving targets nearby... rules: Use your weapons to cause havoc. You are an antagonist. + - type: GhostTakeoverAvailable - type: MovementSpeedModifier baseWalkSpeed : 3 baseSprintSpeed : 2 diff --git a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml index 054e50b921..a9ef010abf 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml @@ -29,9 +29,10 @@ parent: MobSkeletonPerson id: MobSkeletonPirate components: - - type: GhostTakeoverAvailable + - type: GhostRole name: Skeleton Pirate description: Cause chaos and loot the station for treasure. + - type: GhostTakeoverAvailable - type: Loadout prototypes: [PirateGear] - type: RandomHumanoidAppearance @@ -42,9 +43,10 @@ parent: MobSkeletonPerson id: MobSkeletonBiker components: - - type: GhostTakeoverAvailable + - type: GhostRole name: Skeleton Biker description: Ride around on your sweet ride. + - type: GhostTakeoverAvailable - type: Loadout prototypes: [SkeletonBiker] - type: RandomHumanoidAppearance diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml index d9649cef51..5d3084d353 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml @@ -9,11 +9,12 @@ layers: - state: old-radio netsync: false - - type: GhostRoleMobSpawner - prototype: MobHumanSyndicateAgent + - type: GhostRole name: Syndicate Agent description: Someone needs reinforcements. You, the first person the syndicate could find, will help them. rules: Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them. + - type: GhostRoleMobSpawner + prototype: MobHumanSyndicateAgent - type: EmitSoundOnUse sound: /Audio/Misc/emergency_meeting.ogg - type: ItemCooldown diff --git a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml index af2d84a7b9..035c4334a5 100644 --- a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml +++ b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml @@ -172,7 +172,7 @@ targetDepth: 3 effectHint: artifact-effect-hint-sentience permanentComponents: - - type: GhostTakeoverAvailable + - type: GhostRole allowMovement: true allowSpeech: true makeSentient: true @@ -180,6 +180,7 @@ description: | Enact your eldritch whims. Forcibly activate your nodes for good or for evil. + - type: GhostTakeoverAvailable - type: MovementSpeedModifier baseWalkSpeed: 0.25 baseSprintSpeed: 0.5 -- 2.51.2