From 4718be2674cd3917f1f065527234b2429c9b2b98 Mon Sep 17 00:00:00 2001 From: Pspritechologist Date: Tue, 6 Feb 2024 16:53:12 -0500 Subject: [PATCH] Ton of work refactoring Holograms, generally cleaning it up, and adding new functionality. The AIEye can now function as a camera-bound Hologram (commented out), work still needs to be done on making Hologram disks and servers cool to use, and I need to decide how to determine what a Hologram collides with. --- .../Holograms/AcceptHologramEui.cs | 44 +++ .../Holograms/AcceptHologramWindow.cs | 60 ++++ .../Holograms/HologramSystem.cs | 60 +++- .../Holograms/AcceptHologramEui.cs | 32 ++ ...kComponent.cs => HologramDiskComponent.cs} | 5 +- .../Components/HologramDiskDummyComponent.cs | 18 ++ .../Systems/HologramProjectorSystem.cs | 34 +++ .../Holograms/Systems/HologramServerSystem.cs | 273 +++--------------- .../Holograms/Systems/HologramSystem.cs | 214 +++++++++----- .../StationAI/Systems/AIEyeSystem.cs | 6 + .../Systems/SurveillanceCameraSystem.cs | 7 +- .../Holograms/AcceptHologramEuiMessage.cs | 22 ++ .../Components/HoloServerComponent.cs | 8 - .../Components/HologramProjectedComponent.cs | 26 +- .../HologramProjectorActiveComponent.cs | 10 + .../Components/HologramProjectorComponent.cs | 11 +- .../Components/HologramServerComponent.cs | 14 + .../HologramServerLinkedComponent.cs | 5 +- .../Systems/SharedHologramSystem.Projected.cs | 76 +++-- .../SharedHologramSystem.ServerLinked.cs | 17 ++ .../Holograms/Systems/SharedHologramSystem.cs | 68 ++--- .../Entities/Mobs/Player/admin_ghost.yml | 1 + .../Wallmounts/surveillance_camera.yml | 5 + .../Entities/Mobs/Player/hologram.yml | 4 +- .../Entities/Mobs/Player/stationai.yml | 19 +- .../Machines/hologram_constructor.yml | 1 + 26 files changed, 628 insertions(+), 412 deletions(-) create mode 100644 Content.Client/SimpleStation14/Holograms/AcceptHologramEui.cs create mode 100644 Content.Client/SimpleStation14/Holograms/AcceptHologramWindow.cs create mode 100644 Content.Server/SimpleStation14/Holograms/AcceptHologramEui.cs rename Content.Server/SimpleStation14/Holograms/Components/{HoloDiskComponent.cs => HologramDiskComponent.cs} (65%) create mode 100644 Content.Server/SimpleStation14/Holograms/Components/HologramDiskDummyComponent.cs create mode 100644 Content.Server/SimpleStation14/Holograms/Systems/HologramProjectorSystem.cs create mode 100644 Content.Shared/SimpleStation14/Holograms/AcceptHologramEuiMessage.cs delete mode 100644 Content.Shared/SimpleStation14/Holograms/Components/HoloServerComponent.cs create mode 100644 Content.Shared/SimpleStation14/Holograms/Components/HologramProjectorActiveComponent.cs create mode 100644 Content.Shared/SimpleStation14/Holograms/Components/HologramServerComponent.cs create mode 100644 Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.ServerLinked.cs diff --git a/Content.Client/SimpleStation14/Holograms/AcceptHologramEui.cs b/Content.Client/SimpleStation14/Holograms/AcceptHologramEui.cs new file mode 100644 index 0000000000..232efc37cc --- /dev/null +++ b/Content.Client/SimpleStation14/Holograms/AcceptHologramEui.cs @@ -0,0 +1,44 @@ +using Content.Client.Eui; +using Content.Client.Holograms; +using Content.Shared.SimpleStation14.Holograms; +using JetBrains.Annotations; +using Robust.Client.Graphics; + +namespace Content.Client.SimpleStation14.Holograms; + +[UsedImplicitly] +public sealed class AcceptHologramEui : BaseEui +{ + private readonly AcceptHologramWindow _window; + + public AcceptHologramEui() + { + _window = new AcceptHologramWindow(); + + _window.DenyButton.OnPressed += _ => + { + SendMessage(new AcceptHologramChoiceMessage(AcceptHologramUiButton.Deny)); + _window.Close(); + }; + + _window.OnClose += () => SendMessage(new AcceptHologramChoiceMessage(AcceptHologramUiButton.Deny)); + + _window.AcceptButton.OnPressed += _ => + { + SendMessage(new AcceptHologramChoiceMessage(AcceptHologramUiButton.Accept)); + _window.Close(); + }; + } + + public override void Opened() + { + IoCManager.Resolve().RequestWindowAttention(); + _window.OpenCentered(); + } + + public override void Closed() + { + _window.Close(); + } + +} diff --git a/Content.Client/SimpleStation14/Holograms/AcceptHologramWindow.cs b/Content.Client/SimpleStation14/Holograms/AcceptHologramWindow.cs new file mode 100644 index 0000000000..e4da45d059 --- /dev/null +++ b/Content.Client/SimpleStation14/Holograms/AcceptHologramWindow.cs @@ -0,0 +1,60 @@ +using System.Numerics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using static Robust.Client.UserInterface.Controls.BoxContainer; + +namespace Content.Client.Holograms; + +public sealed class AcceptHologramWindow : DefaultWindow +{ + public readonly Button DenyButton; + public readonly Button AcceptButton; + + public AcceptHologramWindow() + { + + Title = Loc.GetString("accept-hologram-window-title"); + + Contents.AddChild(new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Children = + { + new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Children = + { + new Label() + { + Text = Loc.GetString("accept-hologram-window-prompt-text-part") + }, + new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + Align = AlignMode.Center, + Children = + { + (AcceptButton = new Button + { + Text = Loc.GetString("accept-hologram-window-accept-button"), + }), + + new Control() + { + MinSize = new Vector2(20, 0) + }, + + (DenyButton = new Button + { + Text = Loc.GetString("accept-hologram-window-deny-button"), + }) + } + }, + } + }, + } + }); + } +} diff --git a/Content.Client/SimpleStation14/Holograms/HologramSystem.cs b/Content.Client/SimpleStation14/Holograms/HologramSystem.cs index 0a92b1fdc1..ff88b124dc 100644 --- a/Content.Client/SimpleStation14/Holograms/HologramSystem.cs +++ b/Content.Client/SimpleStation14/Holograms/HologramSystem.cs @@ -10,29 +10,43 @@ namespace Content.Client.SimpleStation14.Holograms; public sealed class HologramSystem : SharedHologramSystem { [Dependency] private readonly IPlayerManager _player = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly TransformSystem _transform = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnHoloProjectedShutdown); + SubscribeLocalEvent(OnProjectedShutdown); } public override void Update(float frameTime) { - var player = _player.LocalPlayer?.ControlledEntity; // This makes it so only the currently controlled entity is predicted, assuming they're a hologram. + var player = _player.LocalPlayer?.ControlledEntity; if (TryComp(player, out var holoProjComp)) - ProjectedUpdate(player.Value, holoProjComp, frameTime); + { + ProjectedUpdate(player.Value, holoProjComp, frameTime); // This makes it so only the currently controlled entity is predicted, assuming they're a hologram. + + // Check if we should be setting the eye target of the hologram. + if (holoProjComp.SetEyeTarget && TryComp(player.Value, out var eyeComp)) + eyeComp.Target = holoProjComp.CurProjector; + } HandleProjectedEffects(EntityQueryEnumerator()); } + private void OnProjectedShutdown(EntityUid hologram, HologramProjectedComponent component, ComponentShutdown args) + { + DeleteEffect(component); + + if (component.SetEyeTarget && TryComp(hologram, out var eyeComp)) + eyeComp.Target = null; // This should be fine? I guess if you're a hologram riding a vehicle when this happens it'd be a bit weird. + } + private void HandleProjectedEffects(EntityQueryEnumerator query) { while (query.MoveNext(out var hologram, out var holoProjectedComp)) { - if (!holoProjectedComp.DoProjectionEffect) + if (holoProjectedComp.EffectPrototype == null) { DeleteEffect(holoProjectedComp); continue; @@ -58,12 +72,30 @@ private void HandleProjectedEffects(EntityQueryEnumerator(projector, out var projComp)) + { + var direction = projXformComp.LocalRotation.GetCardinalDir(); + + var offset = direction switch + { + Direction.North => projComp.EffectOffsets[Direction.South], + Direction.South => projComp.EffectOffsets[Direction.North], + Direction.East => projComp.EffectOffsets[Direction.West], + Direction.West => projComp.EffectOffsets[Direction.East], + _ => Vector2.Zero + }; + + originPos += offset; + } + // Determine a middle point between the hologram and the projector. - var effectPos = (holoCoords.Position + projCoords.Position) / 2; - // Offset the position a quarter tile towards the projector. - effectPos += (projCoords.Position - holoCoords.Position).Normalized() * 0.25f; + var effectPos = (holoCoords.Position + originPos) / 2; + // Determine a rotation that points from the projector to the hologram. - var effectRot = (holoCoords.Position - projCoords.Position).ToAngle() + -MathHelper.PiOver2; + var effectRot = (holoCoords.Position - originPos).ToAngle() - MathHelper.PiOver2; var effectCoords = new EntityCoordinates(holoCoords.EntityId, effectPos); if (!effectCoords.IsValid(EntityManager)) @@ -81,16 +113,12 @@ private void HandleProjectedEffects(EntityQueryEnumerator(holoProjectedComp.EffectEntity.Value).Scale = effectScale.Y > 0.1f ? effectScale : Vector2.One; + var yScale = (holoCoords.Position - originPos).Length(); + var effectScale = new Vector2(1, Math.Max(0.1f, yScale)); // No smaller than 0.1. + Comp(holoProjectedComp.EffectEntity.Value).Scale = effectScale; } } - private void OnHoloProjectedShutdown(EntityUid uid, HologramProjectedComponent component, ComponentShutdown args) - { - DeleteEffect(component); - } - private void DeleteEffect(HologramProjectedComponent component) { if (component.EffectEntity != null && Exists(component.EffectEntity.Value)) diff --git a/Content.Server/SimpleStation14/Holograms/AcceptHologramEui.cs b/Content.Server/SimpleStation14/Holograms/AcceptHologramEui.cs new file mode 100644 index 0000000000..ca0f8d9691 --- /dev/null +++ b/Content.Server/SimpleStation14/Holograms/AcceptHologramEui.cs @@ -0,0 +1,32 @@ +using Content.Server.EUI; +using Content.Shared.Eui; +using Content.Shared.SimpleStation14.Holograms; + +namespace Content.Server.SimpleStation14.Holograms; + +public sealed class AcceptHologramEui : BaseEui +{ + private readonly HologramSystem _hologramSystem; + private readonly Mind.Mind _mind; + + public AcceptHologramEui(Mind.Mind mind, HologramSystem hologramSys) + { + _mind = mind; + _hologramSystem = hologramSys; + } + + public override void HandleMessage(EuiMessageBase msg) + { + base.HandleMessage(msg); + + if (msg is not AcceptHologramChoiceMessage choice || + choice.Button == AcceptHologramUiButton.Deny) + { + Close(); + return; + } + + _hologramSystem.TransferMindToHologram(_mind); + Close(); + } +} diff --git a/Content.Server/SimpleStation14/Holograms/Components/HoloDiskComponent.cs b/Content.Server/SimpleStation14/Holograms/Components/HologramDiskComponent.cs similarity index 65% rename from Content.Server/SimpleStation14/Holograms/Components/HoloDiskComponent.cs rename to Content.Server/SimpleStation14/Holograms/Components/HologramDiskComponent.cs index 709260b942..d2b0b9b589 100644 --- a/Content.Server/SimpleStation14/Holograms/Components/HoloDiskComponent.cs +++ b/Content.Server/SimpleStation14/Holograms/Components/HologramDiskComponent.cs @@ -1,7 +1,8 @@ -// using static Content.Server.SimpleStation14.Hologram.HologramSystem; - namespace Content.Server.SimpleStation14.Holograms; +/// +/// Marks this entity as storing a hologram's data in it, for use in a . +/// [RegisterComponent] public sealed class HologramDiskComponent : Component { diff --git a/Content.Server/SimpleStation14/Holograms/Components/HologramDiskDummyComponent.cs b/Content.Server/SimpleStation14/Holograms/Components/HologramDiskDummyComponent.cs new file mode 100644 index 0000000000..0bf910a951 --- /dev/null +++ b/Content.Server/SimpleStation14/Holograms/Components/HologramDiskDummyComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.SimpleStation14.Holograms.Components; + +/// +/// For any items that should generate a 'dummy' hologram when inserted as a holo disk. +/// Mostly intended for jokes and gaffs, but could be used for useful AI entities as well. +/// +[RegisterComponent] +public sealed class HologramDiskDummyComponent : Component +{ + /// + /// The prototype to spawn when this disk is inserted into a server. + /// + [DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + public string HoloPrototype = default!; +} diff --git a/Content.Server/SimpleStation14/Holograms/Systems/HologramProjectorSystem.cs b/Content.Server/SimpleStation14/Holograms/Systems/HologramProjectorSystem.cs new file mode 100644 index 0000000000..ab853e359f --- /dev/null +++ b/Content.Server/SimpleStation14/Holograms/Systems/HologramProjectorSystem.cs @@ -0,0 +1,34 @@ +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.SurveillanceCamera; +using Content.Shared.SimpleStation14.Holograms; +using Content.Shared.SimpleStation14.Holograms.Components; + +namespace Content.Server.SimpleStation14.Holograms; + +public sealed class HologramProjectorSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent((EntityUid ent, HologramProjectorComponent comp, ref PowerChangedEvent _) => CheckState(ent, comp)); + SubscribeLocalEvent((ent, comp, args) => CheckState(ent, comp)); + SubscribeLocalEvent((ent, comp, args) => CheckState(ent, comp)); + } + + public void CheckState(EntityUid projector, HologramProjectorComponent? projComp = null) + { + if (!Resolve(projector, ref projComp)) + return; + + if (TryComp(projector, out var powerComp) && !powerComp.Powered || + TryComp(projector, out var cameraComp) && !cameraComp.Active) + { + RemComp(projector); + return; + } + + EnsureComp(projector); + } +} diff --git a/Content.Server/SimpleStation14/Holograms/Systems/HologramServerSystem.cs b/Content.Server/SimpleStation14/Holograms/Systems/HologramServerSystem.cs index da1e6c5b87..05ae57b91f 100644 --- a/Content.Server/SimpleStation14/Holograms/Systems/HologramServerSystem.cs +++ b/Content.Server/SimpleStation14/Holograms/Systems/HologramServerSystem.cs @@ -1,65 +1,32 @@ -using System.Linq; -using Content.Server.Cloning; -using Content.Server.Cloning.Components; -using Content.Server.Psionics; -using Content.Server.EUI; using Content.Server.Humanoid; -using Content.Server.Jobs; using Content.Server.Mind; using Content.Server.Preferences.Managers; using Content.Server.Power.Components; -using Content.Server.Administration.Commands; using Content.Shared.Tag; using Content.Shared.Popups; using Content.Shared.SimpleStation14.Holograms; -using Content.Shared.Pulling; using Content.Shared.Administration.Logs; -using Content.Shared.Database; -using Content.Shared.Speech; -using Content.Shared.Preferences; -using Content.Shared.Emoting; -using Content.Shared.Humanoid; using Content.Shared.Mobs.Systems; using Content.Shared.Interaction; -using Content.Shared.Inventory; -using Content.Shared.Interaction.Components; -using Content.Shared.Access.Components; -using Content.Shared.Clothing.Components; using Robust.Server.Player; -using Robust.Shared.Player; using Robust.Shared.Containers; -using Robust.Shared.GameObjects.Components.Localization; using Content.Shared.Movement.Systems; -using System.Threading.Tasks; using Content.Server.Mind.Components; using Content.Shared.SimpleStation14.Holograms.Components; +using System.Diagnostics.CodeAnalysis; +using Content.Server.EUI; +using Robust.Server.GameObjects; namespace Content.Server.SimpleStation14.Holograms; public sealed class HologramServerSystem : EntitySystem { - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly SharedPullingSystem _pulling = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly IPlayerManager _playerManager = null!; - [Dependency] private readonly CloningSystem _cloningSystem = default!; - [Dependency] private readonly EuiManager _euiManager = null!; - [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly MindSystem _mind = default!; - [Dependency] private readonly TagSystem _tag = default!; - [Dependency] private readonly IServerPreferencesManager _prefs = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly SharedMoverController _mover = default!; + [Dependency] private readonly TagSystem _tags = default!; [Dependency] private readonly HologramSystem _hologram = default!; + [Dependency] private readonly TransformSystem _transform = default!; - private const string DiskSlot = "holo_disk"; - public readonly Dictionary ClonesWaitingForMind = new(); + public const string TagHoloDisk = "HoloDisk"; public override void Initialize() { @@ -71,43 +38,38 @@ public override void Initialize() } /// - /// Handles generating a hologram from an inserted disk + /// Handles generating a hologram from an inserted disk /// private void OnEntInserted(EntityUid uid, HologramServerComponent component, EntInsertedIntoContainerMessage args) { - if (args.Container.ID != DiskSlot || !_tagSystem.HasTag(args.Entity, "HoloDisk") || - !_entityManager.TryGetComponent(args.Entity, out var diskComp) || diskComp.HoloMind == null) + if (args.Container.ID != component.DiskSlot || !_tags.HasTag(args.Entity, TagHoloDisk)) return; - if (component.LinkedHologram != null && _entityManager.EntityExists(component.LinkedHologram)) - { - _hologram.DoKillHologram(component.LinkedHologram.Value); - } + if (Exists(component.LinkedHologram)) + if (!_hologram.TryKillHologram(component.LinkedHologram.Value)) + return; // This is a werid situation to encounter, so we'll just stop doin stuff. - if (TryHoloGenerate(uid, diskComp.HoloMind!, component, out var holo)) + if (TryGenerateHologram(uid, args.Entity, out var holo, component)) { - var holoLinkComp = _entityManager.EnsureComponent(holo); component.LinkedHologram = holo; - holoLinkComp.LinkedServer = uid; + EnsureComp(holo.Value).LinkedServer = uid; } } /// - /// Handles killing a hologram when a disk is removed + /// Handles killing a hologram when a disk is removed /// private void OnEntRemoved(EntityUid uid, HologramServerComponent component, EntRemovedFromContainerMessage args) { - if (args.Container.ID != DiskSlot || !_tagSystem.HasTag(args.Entity, "HoloDisk") || - !_entityManager.TryGetComponent(args.Entity, out var diskComp) || diskComp.HoloMind == null) return; + if (args.Container.ID != component.DiskSlot || !_tags.HasTag(args.Entity, "HoloDisk")) + return; - if (_entityManager.EntityExists(component.LinkedHologram)) - { + if (Exists(component.LinkedHologram)) _hologram.DoKillHologram(component.LinkedHologram.Value); - } } /// - /// Called when the server's power state changes + /// Called when the server's power state changes /// /// The entity uid of the server /// The HologramServerComponent @@ -115,7 +77,7 @@ private void OnEntRemoved(EntityUid uid, HologramServerComponent component, EntR private void OnPowerChanged(EntityUid uid, HologramServerComponent component, ref PowerChangedEvent args) { // If the server is no longer powered and the hologram exists - if (!args.Powered && _entityManager.EntityExists(component.LinkedHologram)) + if (!args.Powered && Exists(component.LinkedHologram)) { // Kill the Hologram _hologram.DoKillHologram(component.LinkedHologram.Value); @@ -125,197 +87,37 @@ private void OnPowerChanged(EntityUid uid, HologramServerComponent component, re // If the server is powered else if (args.Powered) { - var serverContainer = _entityManager.GetComponent(uid); - if (serverContainer.GetContainer(DiskSlot).ContainedEntities.Count <= 0) - { + if (component.DiskSlot == null) + return; // No disk slot + + var container = Comp(uid).Containers[component.DiskSlot]; + + if (container.ContainedEntities.Count <= 0) return; // No disk in the server - } - var disk = serverContainer.GetContainer(DiskSlot).ContainedEntities[0]; - var diskData = _entityManager.GetComponent(disk).HoloMind; // If the hologram is generated successfully - if (diskData != null && TryHoloGenerate(uid, diskData, component, out var holo)) + if (TryGenerateHologram(uid, container.ContainedEntities[0], out var holo, component)) { // Set the linked hologram to the generated hologram - var holoLinkComp = _entityManager.EnsureComponent(holo); + var holoLinkComp = EnsureComp(holo.Value); component.LinkedHologram = holo; holoLinkComp.LinkedServer = uid; } } } - public bool TryHoloGenerate(EntityUid uid, Mind.Mind mind, HologramServerComponent? holoServer, out EntityUid holo) + public bool TryGenerateHologram(EntityUid server, EntityUid disk, [NotNullWhen(true)] out EntityUid? hologram, HologramServerComponent? holoServerComp = null) { - CloningSystem cloneSys = new(); - holo = EntityUid.Invalid; - - if (ClonesWaitingForMind.TryGetValue(mind, out var clone)) - { - if (EntityManager.EntityExists(clone) && - !_mobStateSystem.IsDead(clone) && - TryComp(clone, out var cloneMindComp) && - (cloneMindComp.Mind == null || cloneMindComp.Mind == mind)) - return false; // Mind already has clone - - ClonesWaitingForMind.Remove(mind); - } - - if (mind.OwnedEntity != null && (_mobStateSystem.IsAlive(mind.OwnedEntity.Value) || _mobStateSystem.IsCritical(mind.OwnedEntity.Value))) - return false; // Body controlled by mind is not dead - - // Yes, we still need to track down the client because we need to open the Eui - if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client)) - return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad. + hologram = null; - var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(mind.UserId.Value).SelectedCharacter; + // if (TryComp(disk, out var diskDummyComp)) //TODO - var mob = HoloFetchAndSpawn(holoServer!, pref); + if (!TryComp(disk, out var diskComp) || diskComp.HoloMind == null) + return false; - var cloneMindReturn = EntityManager.AddComponent(mob); - cloneMindReturn.Mind = mind; - ClonesWaitingForMind.Add(mind, mob); - TransferMindToClone(mind); - - if (mind.CurrentJob != null) - { - foreach (var special in mind.CurrentJob.Prototype.Special) - { - if (special is AddComponentSpecial) - special.AfterEquip(mob); - } - - // Get each access from the job prototype and add it to the mob - foreach (var access in mind.CurrentJob.Prototype.Access) - { - var accessComp = EntityManager.EnsureComponent(mob); - accessComp.Tags.Add(access); - } - - // Get the loadout from the job prototype and add it to the Hologram - // making each item unremovable. - if (mind.CurrentJob.Prototype.StartingGear != null) - { - SetOutfitCommand.SetOutfit(mob, mind.CurrentJob.Prototype.StartingGear, _entityManager, (_, item) => - { - if (_entityManager.TryGetComponent(item, out var clothing)) - { - if (clothing.InSlot is "back" or "pocket1" or "pocket2" or "belt" or "suitstorage" or "id") - { - _entityManager.DeleteEntity(item); - return; - } - } - EnsureComp(item); - _entityManager.EnsureComponent(item); - }); - // HoloEquip(mob, mind.CurrentJob.Prototype); - } - } - - _adminLogger.Add(LogType.Unknown, LogImpact.Medium, - $"{ToPrettyString(mob):mob} was generated at {ToPrettyString((EntityUid) uid):entity}"); - - holo = mob; - return true; + return _hologram.TryGenerateHologram(diskComp.HoloMind, _transform.GetMoverCoordinates(server), out hologram); } - internal void TransferMindToClone(Mind.Mind mind) - { - if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) || - !EntityManager.EntityExists(entity) || - !TryComp(entity, out var mindComp) || - mindComp.Mind != null) - return; - - _mind.TransferTo(mind, entity, true); - _mind.UnVisit(mind); - - ClonesWaitingForMind.Remove(mind); - } - - /// - /// Handles fetching the mob and any appearance stuff... - /// - private EntityUid HoloFetchAndSpawn(HologramServerComponent holoServer, HumanoidCharacterProfile pref) - { - List sexes = new(); - var name = pref.Name; - var toSpawn = "MobHologramProjected"; - - var mob = Spawn(toSpawn, Transform(holoServer.Owner).MapPosition); - Transform(mob).AttachToGridOrMap(); - - ResetCamera(mob); - - _humanoidSystem.LoadProfile(mob, pref); - - MetaData(mob).EntityName = name; - var mind = EnsureComp(mob); - _mind.SetExamineInfo(mob, true, mind); - - var grammar = EnsureComp(mob); - grammar.ProperNoun = true; - grammar.Gender = Robust.Shared.Enums.Gender.Neuter; - Dirty(grammar); - - var popupAppearOther = Loc.GetString("system-hologram-phasing-appear-others", ("name", MetaData(mob).EntityName)); - var popupAppearSelf = Loc.GetString("system-hologram-phasing-appear-self"); - - _popup.PopupEntity(popupAppearOther, mob, Filter.PvsExcept(mob), false, PopupType.Medium); - _popup.PopupEntity(popupAppearSelf, mob, mob, PopupType.Large); - _audio.PlayPvs("/Audio/SimpleStation14/Effects/Hologram/holo_on.ogg", mob); - - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - RemComp(mob); - - _tag.AddTag(mob, "DoorBumpOpener"); - - return mob; - } - - private async void ResetCamera(EntityUid mob) - { - await Task.Delay(500); - - _mover.ResetCamera(mob); - } - - - /// - /// WiP for equipping unique items based on job. - /// -// private void HoloEquip(EntityUid mob, JobPrototype job) -// { -// // Check what job they are against a list, and output a hardcoded item for each. - - - - -// var mobTransform = EntityManager.GetComponent(mob); -// var mobPos = mobTransform.WorldPosition; - -// if (!_entityManager.TryGetComponent(mob, out var mobInv)) -// return; -// var mobHands = EntityManager.EnsureComponent(mob); - -// foreach (var item in job.StartingGear.Items) -// { -// var itemEnt = Spawn(item.Prototype, mobPos); -// _entityManager.GetComponent(itemEnt).AttachToGridOrMap(); - -// if (item.Slot == null) -// { -// mobInv.TryPutInHandOrAny(itemEnt, out var _); -// } -// else -// { -// mobInv.TryPutInSlot(itemEnt, item.Slot); -// } -// } -// } - private void OnAfterInteract(EntityUid uid, HologramDiskComponent component, AfterInteractEvent args) { if (args.Target == null || !TryComp(args.Target, out var targetMind)) @@ -334,13 +136,4 @@ private void OnAfterInteract(EntityUid uid, HologramDiskComponent component, Aft args.Handled = true; } - - -// // List of jobs with hardcoded items for Holograms, like a Clown's horn. -// static readonly Dictionary HoloJobItems = new Dictionary -// { -// { "Clown", "BikeHorn" }, -// { "value2", "output2" }, -// { "value3", "output3" } -// }; } diff --git a/Content.Server/SimpleStation14/Holograms/Systems/HologramSystem.cs b/Content.Server/SimpleStation14/Holograms/Systems/HologramSystem.cs index 5eb9c1ea30..33359f4b82 100644 --- a/Content.Server/SimpleStation14/Holograms/Systems/HologramSystem.cs +++ b/Content.Server/SimpleStation14/Holograms/Systems/HologramSystem.cs @@ -2,105 +2,189 @@ using Content.Server.Mind.Components; using Content.Shared.Popups; using Content.Shared.SimpleStation14.Holograms; -using Content.Shared.Pulling; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Robust.Shared.Player; -using Content.Shared.Pulling.Components; -using Linguini.Syntax.Ast; -using Robust.Shared.Timing; -using JetBrains.Annotations; +using Content.Server.Cloning.Components; +using Content.Server.Psionics; +using Content.Server.Humanoid; +using Content.Server.Jobs; +using Content.Server.Mind; +using Content.Server.Preferences.Managers; +using Content.Server.Power.Components; +using Content.Server.Administration.Commands; +using Content.Shared.Tag; +using Content.Shared.Speech; +using Content.Shared.Preferences; +using Content.Shared.Emoting; +using Content.Shared.Humanoid; +using Content.Shared.Mobs.Systems; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Components; +using Content.Shared.Access.Components; +using Content.Shared.Clothing.Components; +using Robust.Server.Player; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects.Components.Localization; +using Content.Shared.Movement.Systems; +using System.Threading.Tasks; using Content.Shared.SimpleStation14.Holograms.Components; +using Content.Server.SimpleStation14.Holograms.Components; +using System.Diagnostics.CodeAnalysis; +using Content.Server.EUI; +using Robust.Server.GameObjects; +using Robust.Shared.Enums; +using Robust.Shared.Map; +using Content.Server.Access.Systems; +using Content.Server.Station.Systems; +using Content.Server.Station.Components; namespace Content.Server.SimpleStation14.Holograms; public sealed class HologramSystem : SharedHologramSystem { [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly SharedPullingSystem _pulling = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly IPlayerManager _playerManager = null!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly IServerPreferencesManager _prefs = default!; + [Dependency] private readonly TagSystem _tags = default!; + [Dependency] private readonly SharedMoverController _mover = default!; + [Dependency] private readonly EuiManager _eui = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly MetaDataSystem _meta = default!; + [Dependency] private readonly AccessSystem _access = default!; + [Dependency] private readonly StationSystem _station = default!; + + public readonly Dictionary HologramsWaitingForMind = new(); - public readonly Dictionary ClonesWaitingForMind = new(); - - public override void Initialize() + /// + /// Handles killing a Hologram, with no checks in place. + /// + /// + /// You should generally use instead. + /// + public override void DoKillHologram(EntityUid hologram, HologramComponent? holoComp = null) // TODOPark: HOLO Move this to Shared once Upstream merge. { - base.Initialize(); - // SubscribeLocalEvent(Startup); - // SubscribeLocalEvent(Shutdown); - // SubscribeLocalEvent(HoloTeleport); - } + var meta = MetaData(hologram); + var holoPos = Transform(hologram).Coordinates; - // private void Startup(EntityUid uid, HologramComponent component, ComponentStartup args) - // { - // var action = new WorldTargetAction(_prototypeManager.Index("ShadekinTeleport")); - // _actionsSystem.AddAction(uid, action, uid); - // } + if (TryComp(hologram, out var mindComp) && mindComp.Mind != null) + _gameTicker.OnGhostAttempt(mindComp.Mind, false); - // private void Shutdown(EntityUid uid, HologramComponent component, ComponentShutdown args) - // { - // var action = new WorldTargetAction(_prototypeManager.Index("ShadekinTeleport")); - // _actionsSystem.RemoveAction(uid, action); - // } + _audio.Play(filename: "/Audio/SimpleStation14/Effects/Hologram/holo_off.ogg", playerFilter: Filter.Pvs(hologram), coordinates: holoPos, false); + _popup.PopupCoordinates(Loc.GetString(PopupDisappearOther, ("name", meta.EntityName)), holoPos, Filter.PvsExcept(hologram), false, PopupType.MediumCaution); + _popup.PopupCoordinates(Loc.GetString(PopupDeathSelf), holoPos, hologram, PopupType.LargeCaution); - /// - /// Mind stuff is all server side, so this exists to actually kill the Hologram in reaction to . - /// - public override void DoReturnHologram(EntityUid uid, out bool holoKilled, HologramProjectedComponent? component = null) - { - base.DoReturnHologram(uid, out holoKilled, component); - if (holoKilled) - DoKillHologram(uid); + _entityManager.QueueDeleteEntity(hologram); + + _adminLogger.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(hologram):mob} was killed!"); } - /// - /// Kills a Hologram after playing the visual and auditory effects. - /// - public void DoKillHologram(EntityUid uid, HologramComponent? holoComp = null) + public bool TryGenerateHologram(Mind.Mind mind, EntityCoordinates coords, [NotNullWhen(true)] out EntityUid? holo) { - if (!Resolve(uid, ref holoComp)) - return; + holo = null; - var meta = MetaData(uid); - var holoPos = Transform(uid).Coordinates; + if (HologramsWaitingForMind.TryGetValue(mind, out var clone)) + { + if (EntityManager.EntityExists(clone) && + !_mobState.IsDead(clone) && + TryComp(clone, out var cloneMindComp) && + (cloneMindComp.Mind == null || cloneMindComp.Mind == mind)) + return false; // Mind already has clone - if (TryComp(uid, out var mindComp) && mindComp.Mind != null) - _gameTicker.OnGhostAttempt(mindComp.Mind, false); + HologramsWaitingForMind.Remove(mind); + } - _audio.Play(filename: "/Audio/SimpleStation14/Effects/Hologram/holo_off.ogg", playerFilter: Filter.Pvs(uid), coordinates: holoPos, false); - _popup.PopupCoordinates(Loc.GetString(PopupDisappearOther, ("name", meta.EntityName)), holoPos, Filter.PvsExcept(uid), false, PopupType.MediumCaution); - _popup.PopupCoordinates(Loc.GetString(PopupDeathSelf), holoPos, uid, PopupType.LargeCaution); - if (TryComp(uid, out var holoLinkComp) && holoLinkComp.LinkedServer != null) + if (mind.OwnedEntity != null && (_mobState.IsAlive(mind.OwnedEntity.Value) || _mobState.IsCritical(mind.OwnedEntity.Value))) + return false; // Body controlled by mind is not dead + + // Yes, we still need to track down the client because we need to open the Eui + if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client)) + return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad. + + var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(mind.UserId.Value).SelectedCharacter; + + var mob = HoloFetchAndSpawn(pref, coords, "MobHologramProjected"); + + HologramsWaitingForMind.Add(mind, mob); + _eui.OpenEui(new AcceptHologramEui(mind, this), client); + + if (mind.CurrentJob != null) { - if (TryComp(holoLinkComp.LinkedServer.Value, out var serverComp)) - serverComp.LinkedHologram = null; - holoLinkComp.LinkedServer = null; + foreach (var special in mind.CurrentJob.Prototype.Special) + if (special is AddComponentSpecial) + special.AfterEquip(mob); + + // Get each access from the job prototype and add it to the mob + var extended = _station.GetOwningStation(mob) is { } station && TryComp(station, out var jobComp) && jobComp.ExtendedAccess; + _access.SetAccessToJob(mob, mind.CurrentJob.Prototype, extended, EnsureComp(mob)); + + // Get the loadout from the job prototype and add it to the Hologram making each item unremovable. + if (mind.CurrentJob.Prototype.StartingGear != null) + { + SetOutfitCommand.SetOutfit(mob, mind.CurrentJob.Prototype.StartingGear, EntityManager, (_, item) => + { + if (TryComp(item, out var clothing)) + { + if (clothing.InSlot is "back" or "pocket1" or "pocket2" or "belt" or "suitstorage" or "id") + { + QueueDel(item); + return; + } + } + EnsureComp(item); + EnsureComp(item); + }); + } } - _entityManager.QueueDeleteEntity(uid); + _adminLogger.Add(LogType.Mind, LogImpact.Medium, + $"Hologram {ToPrettyString(mob):mob} was generated at {coords}"); - _adminLogger.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid):mob} was disabled due to lack of projectors"); + holo = mob; + return true; } - // private void HoloTeleport(HoloTeleportEvent args) - // { - // if (args.Handled) return; + internal void TransferMindToHologram(Mind.Mind mind) + { + if (!HologramsWaitingForMind.TryGetValue(mind, out var entity) || + !EntityManager.EntityExists(entity) || + !TryComp(entity, out var mindComp) || + mindComp.Mind != null) + return; + + _mind.TransferTo(mind, entity, true); + _mind.UnVisit(mind); - // if HoloGetProjector(args.Target, args. ) - // var transform = Transform(args.Performer); - // if (transform.MapID != args.Target.GetMapId(EntityManager)) return; + HologramsWaitingForMind.Remove(mind); + } - // _transformSystem.SetCoordinates(args.Performer, args.Target); - // transform.AttachToGridOrMap(); + /// + /// Handles fetching the mob and any appearance stuff... + /// + private EntityUid HoloFetchAndSpawn(HumanoidCharacterProfile pref, EntityCoordinates coords, string mobPrototype) + { + var mob = Spawn(mobPrototype, coords); + _transform.AttachToGridOrMap(mob); + + _humanoid.LoadProfile(mob, pref); + _meta.SetEntityName(mob, pref.Name); - // _audio.PlayPvs(args.BlinkSound, args.Performer, AudioParams.Default.WithVolume(args.BlinkVolume)); + var mind = EnsureComp(mob); + _mind.SetExamineInfo(mob, true, mind); - // _staminaSystem.TakeStaminaDamage(args.Performer, 35); + var grammar = EnsureComp(mob); + grammar.ProperNoun = true; + grammar.Gender = Gender.Neuter; + Dirty(grammar); - // args.Handled = true; - // } - // } + return mob; + } } diff --git a/Content.Server/SimpleStation14/StationAI/Systems/AIEyeSystem.cs b/Content.Server/SimpleStation14/StationAI/Systems/AIEyeSystem.cs index a98d47d7f5..80897240f4 100644 --- a/Content.Server/SimpleStation14/StationAI/Systems/AIEyeSystem.cs +++ b/Content.Server/SimpleStation14/StationAI/Systems/AIEyeSystem.cs @@ -12,6 +12,8 @@ using Content.Server.Borgs; using Robust.Server.GameObjects; using Content.Server.Visible; +using Content.Shared.SimpleStation14.Holograms.Components; +using Content.Shared.SimpleStation14.Holograms; namespace Content.Server.SimpleStation14.StationAI { @@ -70,6 +72,10 @@ private void OnPowerUsed(EntityUid uid, AIEyePowerComponent component, AIEyePowe Transform(projection).AttachToGridOrMap(); _mindSwap.Swap(uid, projection); + // Hologram stuff. + if (TryComp(projection, out var serverLinkedComp)) + serverLinkedComp.LinkedServer = uid; + // Consistent name _entityManager.GetComponent(projection).EntityName = core.EntityName != "" diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs index f159ec52d9..180a29eedf 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs @@ -58,7 +58,7 @@ public override void Initialize() SubscribeLocalEvent(OnSetName); SubscribeLocalEvent(OnSetNetwork); SubscribeLocalEvent>(AddVerbs); - + SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnEmpDisabledRemoved); } @@ -262,6 +262,8 @@ private void Deactivate(EntityUid camera, SurveillanceCameraComponent? component // Send a local event that's broadcasted everywhere afterwards. RaiseLocalEvent(ev); + RaiseLocalEvent(camera, new SurveillanceCameraChangeStateEvent(false)); // Parkstation-Holograms + UpdateVisuals(camera, component); } @@ -279,6 +281,7 @@ public void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponen if (attemptEv.Cancelled) return; component.Active = setting; + RaiseLocalEvent(camera, new SurveillanceCameraChangeStateEvent(setting)); // Parkstation-Holograms } else { @@ -439,3 +442,5 @@ public SurveillanceCameraDeactivateEvent(EntityUid camera) [ByRefEvent] public record struct SurveillanceCameraSetActiveAttemptEvent(bool Cancelled); + +public readonly record struct SurveillanceCameraChangeStateEvent(bool Active); // Parkstation-Holograms diff --git a/Content.Shared/SimpleStation14/Holograms/AcceptHologramEuiMessage.cs b/Content.Shared/SimpleStation14/Holograms/AcceptHologramEuiMessage.cs new file mode 100644 index 0000000000..1b66002a9f --- /dev/null +++ b/Content.Shared/SimpleStation14/Holograms/AcceptHologramEuiMessage.cs @@ -0,0 +1,22 @@ +using Content.Shared.Eui; +using Robust.Shared.Serialization; + +namespace Content.Shared.SimpleStation14.Holograms; + +[Serializable, NetSerializable] +public enum AcceptHologramUiButton +{ + Deny, + Accept, +} + +[Serializable, NetSerializable] +public sealed class AcceptHologramChoiceMessage : EuiMessageBase +{ + public readonly AcceptHologramUiButton Button; + + public AcceptHologramChoiceMessage(AcceptHologramUiButton button) + { + Button = button; + } +} diff --git a/Content.Shared/SimpleStation14/Holograms/Components/HoloServerComponent.cs b/Content.Shared/SimpleStation14/Holograms/Components/HoloServerComponent.cs deleted file mode 100644 index 2447b54f24..0000000000 --- a/Content.Shared/SimpleStation14/Holograms/Components/HoloServerComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Content.Shared.SimpleStation14.Holograms; - -[RegisterComponent] -public sealed class HologramServerComponent : Component -{ - [ViewVariables] - public EntityUid? LinkedHologram; -} diff --git a/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectedComponent.cs b/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectedComponent.cs index 84492bdbf5..039fe38f84 100644 --- a/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectedComponent.cs +++ b/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectedComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Tag; +using Content.Shared.Whitelist; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -14,10 +15,10 @@ namespace Content.Shared.SimpleStation14.Holograms.Components; public sealed partial class HologramProjectedComponent : Component { /// - /// A list of tags to check for on projectors, to determine if they're valid. + /// A whitelist to check for on projectors, to determine if they're valid. /// - [DataField("validProjectorTags", customTypeSerializer: typeof(PrototypeIdListSerializer)), ViewVariables(VVAccess.ReadWrite)] - public List ValidProjectorTags = new(); + [DataField("validProjectorWhitelist"), ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist ValidProjectorWhitelist = new(); /// /// A timer for a grace period before the Holo is returned, to allow for moving through doors. @@ -28,20 +29,27 @@ public sealed partial class HologramProjectedComponent : Component /// /// The maximum range from a projector a Hologram can be before they're returned. /// + /// + /// Note that making this number larger than PVS is highly inadvisable, as the client will be stuck predicting the Hologram returning while the server confirms that they do not. + /// [DataField("projectorRange"), ViewVariables(VVAccess.ReadWrite)] - public float ProjectorRange = 18f; + public float ProjectorRange = 14f; /// - /// The prototype of the effect to spawn for the Hologram's projection, assuming is true. + /// The prototype of the effect to spawn for the Hologram's projection. Leave null to disable the visual projection effect. /// [DataField("effectPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string EffectPrototype = "EffectHologramProjectionBeam"; + public string? EffectPrototype; /// - /// Whether or not the Hologram should have a visual projection effect. + /// Whether or not the Hologram's vision should snap to the projector they're projected from. /// - [DataField("doProjectionEffect"), ViewVariables(VVAccess.ReadWrite)] - public bool DoProjectionEffect = true; + /// + /// This provides a super cool effect of the Hologram only getting the visual information they technically should, but it's also a bit of a pain from a player perspective. + /// Primarily used for the station AI. + /// + [DataField("setEyeTarget"), ViewVariables(VVAccess.ReadWrite)] + public bool SetEyeTarget = false; /// /// The current projector the hologram is connected to. diff --git a/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectorActiveComponent.cs b/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectorActiveComponent.cs new file mode 100644 index 0000000000..aaf726b518 --- /dev/null +++ b/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectorActiveComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.SimpleStation14.Holograms.Components; + +/// +/// Marks a hologram projector as active and working. +/// +[RegisterComponent, NetworkedComponent] +public sealed class HologramProjectorActiveComponent : Component +{ } diff --git a/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectorComponent.cs b/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectorComponent.cs index eb4708891c..a60e9dddb8 100644 --- a/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectorComponent.cs +++ b/Content.Shared/SimpleStation14/Holograms/Components/HologramProjectorComponent.cs @@ -1,7 +1,14 @@ +using System.Numerics; +using Robust.Shared.GameStates; + namespace Content.Shared.SimpleStation14.Holograms; -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed class HologramProjectorComponent : Component { - + /// + /// The tile offset of the projector effect for this projector for each direction. + /// + [DataField("effectOffsets")] + public Dictionary EffectOffsets { get; } = new() { { Direction.North, Vector2.Zero }, { Direction.East, Vector2.Zero }, { Direction.South, Vector2.Zero }, { Direction.West, Vector2.Zero } }; } diff --git a/Content.Shared/SimpleStation14/Holograms/Components/HologramServerComponent.cs b/Content.Shared/SimpleStation14/Holograms/Components/HologramServerComponent.cs new file mode 100644 index 0000000000..c2f83e6299 --- /dev/null +++ b/Content.Shared/SimpleStation14/Holograms/Components/HologramServerComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.SimpleStation14.Holograms; + +/// +/// Marks an entity as being capable of generating a hologram by inserting a into it. +/// +[RegisterComponent] +public sealed class HologramServerComponent : Component +{ + [DataField("diskSlot")] + public string? DiskSlot; + + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? LinkedHologram; +} diff --git a/Content.Shared/SimpleStation14/Holograms/Components/HologramServerLinkedComponent.cs b/Content.Shared/SimpleStation14/Holograms/Components/HologramServerLinkedComponent.cs index 5833be940e..2587d2f610 100644 --- a/Content.Shared/SimpleStation14/Holograms/Components/HologramServerLinkedComponent.cs +++ b/Content.Shared/SimpleStation14/Holograms/Components/HologramServerLinkedComponent.cs @@ -3,12 +3,15 @@ namespace Content.Shared.SimpleStation14.Holograms.Components; /// /// Marks that this Hologram requires a server of some kind to generate it. /// +/// +/// This could be anything from a literal server, to an AICore, to the person a HoloParasite lives in. +/// [RegisterComponent] public sealed class HologramServerLinkedComponent : Component { /// /// Whether this Hologram is bound to the same grid as its server. - /// If false, it will be returned if it leaves the grid. + /// If true, it will be returned if it leaves the grid. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("gridBound")] diff --git a/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.Projected.cs b/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.Projected.cs index 1a3e3f653a..54a219c0aa 100644 --- a/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.Projected.cs +++ b/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.Projected.cs @@ -7,11 +7,21 @@ using Content.Shared.Pulling.Components; using Content.Shared.Database; using Content.Shared.SimpleStation14.Holograms.Components; +using Robust.Shared.Configuration; +using Robust.Shared; +using Content.Shared.Whitelist; namespace Content.Shared.SimpleStation14.Holograms; -public abstract partial class SharedHologramSystem +public partial class SharedHologramSystem { + [Dependency] private readonly IConfigurationManager _config = default!; + + private void InitializeProjected() + { + SubscribeLocalEvent(OnProjectedInit); + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -27,38 +37,30 @@ public override void Update(float frameTime) /// The hologram to return. /// True if the hologram was killed, false if it was returned. /// The hologram's projected component. - public virtual void DoReturnHologram(EntityUid hologram, out bool holoKilled, HologramProjectedComponent? holoProjectedComp = null) + public virtual void DoReturnHologram(EntityUid hologram, HologramProjectedComponent? holoProjectedComp = null) { - holoKilled = false; - if (!Resolve(hologram, ref holoProjectedComp)) return; if (!IsHoloProjectorValid(hologram, holoProjectedComp.CurProjector, 0, false)) // If their last visited Projector is invalid ignoring occlusion... { - if (!TryGetHoloProjector(hologram, holoProjectedComp.ProjectorRange, out holoProjectedComp.CurProjector, holoProjectedComp)) + if (!TryGetHoloProjector(hologram, holoProjectedComp.ProjectorRange, out holoProjectedComp.CurProjector, holoProjectedComp, false)) { - var killedEvent = new HologramKillAttemptEvent(); - RaiseLocalEvent(hologram, ref killedEvent); - if (killedEvent.Cancelled) - return; - - RaiseLocalEvent(hologram, new HologramKilledEvent()); - holoKilled = true; // And if none is found and it doesn't get cancelled, kill the hologram. - return; // The killing is dealt with server-side, due to mind component. + TryKillHologram(hologram); // And if none is found, kill the hologram. + return; } } - // The two if statements above set the current projector, and return if it's null, so we know it's not null moving forward. + // The two if statements above set the current projector, and kill if it's null, so we know it's not null moving forward. var returnedEvent = new HologramReturnAttemptEvent(); RaiseLocalEvent(hologram, ref returnedEvent); if (returnedEvent.Cancelled) return; - RaiseLocalEvent(hologram, new HologramReturnedEvent(holoProjectedComp.CurProjector!.Value)); + RaiseLocalEvent(hologram, new HologramReturnedEvent(holoProjectedComp.CurProjector.Value)); - MoveHologramToProjector(hologram, holoProjectedComp.CurProjector!.Value); + MoveHologramToProjector(hologram, holoProjectedComp.CurProjector.Value); _adminLogger.Add(LogType.Mind, LogImpact.Low, $"{ToPrettyString(hologram):mob} was returned to projector {ToPrettyString(holoProjectedComp.CurProjector.Value):entity}"); @@ -69,18 +71,18 @@ public virtual void DoReturnHologram(EntityUid hologram, out bool holoKilled, Ho /// /// The coords to perform the check from. /// The UID of the projector, or null if no projectors are found. - /// A list of tags to check for on projectors, to determine if they're valid. + /// A list of tags to check for on projectors, to determine if they're valid. /// The range it should check for projectors in, if occlude is true /// Should it check only for unoccluded and in range projectors? /// Returns true if a projector is found, false if not. - public bool TryGetHoloProjector(MapCoordinates coords, float range, [NotNullWhen(true)] out EntityUid? result, List? allowedTags = null, bool occlude = true) + public bool TryGetHoloProjector(MapCoordinates coords, float range, [NotNullWhen(true)] out EntityUid? result, EntityWhitelist? whiteList = null, bool occlude = true) { result = null; // Sort all projectors in distance increasing order. var nearProjList = new SortedList(); - var query = _entityManager.EntityQueryEnumerator(); + var query = _entityManager.EntityQueryEnumerator(); while (query.MoveNext(out var projector, out _)) { var dist = (_transform.GetWorldPosition(projector) - coords.Position).LengthSquared(); @@ -90,7 +92,7 @@ public bool TryGetHoloProjector(MapCoordinates coords, float range, [NotNullWhen // Find the nearest, valid projector. foreach (var nearProj in nearProjList) { - if (!IsHoloProjectorValid(coords, nearProj.Value, range, occlude)) + if (!IsHoloProjectorValid(coords, nearProj.Value, range, occlude, whiteList)) continue; result = nearProj.Value; return true; @@ -101,7 +103,7 @@ public bool TryGetHoloProjector(MapCoordinates coords, float range, [NotNullWhen /// /// This takes into consideration any ProjectorOverride the hologram may have. /// - /// + /// public bool TryGetHoloProjector(EntityUid uid, float range, [NotNullWhen(true)] out EntityUid? result, HologramProjectedComponent? projectedComp = null, bool occlude = true) { result = null; @@ -128,7 +130,7 @@ public bool TryGetHoloProjector(EntityUid uid, float range, [NotNullWhen(true)] } // Otherwise, we simply check for the nearest projector, considering any tags it requires. - return TryGetHoloProjector(Transform(uid).MapPosition, range, out result, projectedComp.ValidProjectorTags, occlude); + return TryGetHoloProjector(Transform(uid).MapPosition, range, out result, projectedComp.ValidProjectorWhitelist, occlude); } /// @@ -138,22 +140,26 @@ public bool TryGetHoloProjector(EntityUid uid, float range, [NotNullWhen(true)] /// The projector to compare on, or its position. /// The max range to allow. Ignored if occlude is false. /// Should it check only for unoccluded and in range projectors? + /// The hologram's component. If provided, the hologram's list of allowed tags will be used. /// True if the projector is within range, and unoccluded to the hologram. Otherwise, false. - public bool IsHoloProjectorValid(EntityUid hologram, EntityUid? projector, float range = 18f, bool occlude = true, HologramProjectedComponent? projectedComp = null) + public bool IsHoloProjectorValid(EntityUid hologram, [NotNullWhen(true)] EntityUid? projector, float range = 18f, bool occlude = true, HologramProjectedComponent? projectedComp = null) { if (!Resolve(hologram, ref projectedComp)) return false; - return IsHoloProjectorValid(Transform(hologram).MapPosition, projector, range, occlude, projectedComp.ValidProjectorTags); + return IsHoloProjectorValid(Transform(hologram).MapPosition, projector, range, occlude, projectedComp.ValidProjectorWhitelist); } - /// - /// A list of tags to check for on projectors, to determine if they're valid. Usually found on the Holo's . - public bool IsHoloProjectorValid(MapCoordinates hologram, EntityUid? projector, float range = 18f, bool occlude = true, List? allowedTags = null) + /// + /// A whitelist to check for on projectors, to determine if they're valid. Usually found on the Holo's . + public bool IsHoloProjectorValid(MapCoordinates hologram, EntityUid? projector, float range = 18f, bool occlude = true, EntityWhitelist? whitelist = null) { if (projector == null || !Exists(projector.Value)) return false; - if (allowedTags != null && !_tags.HasAnyTag(projector.Value, allowedTags)) + if (!HasComp(projector.Value)) + return false; + + if (whitelist != null && !whitelist.IsValid(projector.Value, _entityManager)) return false; if (occlude && !projector.Value.InRangeUnOccluded(hologram, range)) @@ -183,7 +189,7 @@ public void MoveHologram(EntityUid hologram, EntityCoordinates projector) // Plays the vanishing effects. var meta = MetaData(hologram); - if (!_timing.InPrediction) + if (!_timing.InPrediction) // TODOPark: HOLO Change this to run on the first prediction once it predicts reliably. { var holoPos = Transform(hologram).Coordinates; _audio.Play(filename: "/Audio/SimpleStation14/Effects/Hologram/holo_off.ogg", playerFilter: Filter.Pvs(hologram), coordinates: holoPos, false); @@ -203,7 +209,7 @@ public void MoveHologram(EntityUid hologram, EntityCoordinates projector) } } - /// + /// public void MoveHologramToProjector(EntityUid hologram, EntityUid projector) { MoveHologram(hologram, Transform(projector).Coordinates); @@ -233,7 +239,7 @@ protected bool ProjectedUpdate(EntityUid hologram, HologramProjectedComponent ho } // Attempts to return the hologram if their time is up. - DoReturnHologram(hologram, out _); + DoReturnHologram(hologram); Dirty(hologramProjectedComp); return false; } @@ -243,9 +249,15 @@ private void OnStoreInContainerAttempt(EntityUid uid, HologramComponent componen { if (HasComp(uid)) { - DoReturnHologram(uid, out _); + DoReturnHologram(uid); args.Cancelled = true; args.Handled = true; } } + + private void OnProjectedInit(EntityUid uid, HologramProjectedComponent component, ComponentInit args) + { + if (_config.GetCVar(CVars.NetMaxUpdateRange) > component.ProjectorRange) + throw new InvalidOperationException($"Hologram {ToPrettyString(uid):player}'s projector range is higher than PVS range- This will cause mispredicting."); + } } diff --git a/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.ServerLinked.cs b/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.ServerLinked.cs new file mode 100644 index 0000000000..6ae71ea7c8 --- /dev/null +++ b/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.ServerLinked.cs @@ -0,0 +1,17 @@ +using Content.Shared.SimpleStation14.Holograms.Components; + +namespace Content.Shared.SimpleStation14.Holograms; + +public partial class SharedHologramSystem +{ + private void InitalizeServerLinked() + { + SubscribeLocalEvent(OnGridChange); + } + + private void OnGridChange(EntityUid hologram, HologramServerLinkedComponent serverLinkComp, ref ChangedGridEvent args) + { + if (serverLinkComp.GridBound && serverLinkComp.LinkedServer != null && args.NewGrid != Transform(serverLinkComp.LinkedServer.Value).GridUid) + DoReturnHologram(hologram); + } +} diff --git a/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.cs b/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.cs index 855279fc9f..56caa53724 100644 --- a/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.cs +++ b/Content.Shared/SimpleStation14/Holograms/Systems/SharedHologramSystem.cs @@ -1,8 +1,6 @@ using Content.Shared.Interaction.Events; -using Content.Shared.Interaction.Components; using Content.Shared.Tag; using Content.Shared.Popups; -using Robust.Shared.Player; using Content.Shared.Storage.Components; using Content.Shared.Administration.Logs; using Content.Shared.Pulling; @@ -30,7 +28,7 @@ public abstract partial class SharedHologramSystem : EntitySystem protected const string PopupInteractionWithHoloFail = "system-hologram-interaction-with-holo-fail"; public const string TagHardLight = "Hardlight"; - public const string TagHoloMapped = "HoloMapped"; + public const string TagHoloMapped = "HoloMapped"; // TODO: HOLO public override void Initialize() { @@ -38,12 +36,14 @@ public override void Initialize() SubscribeLocalEvent(OnInteractionWithHoloAttempt); SubscribeLocalEvent(OnStoreInContainerAttempt); SubscribeLocalEvent(OnHoloCollide); + + InitalizeServerLinked(); } // Stops the Hologram from interacting with anything they shouldn't. private void OnHoloInteractionAttempt(EntityUid uid, HologramComponent component, InteractionAttemptEvent args) { - if (HoloInteractionAllowed(args.Uid, args.Target) || args.Target == null) + if (HoloInteractionAllowed(args.Uid, args.Target)) return; args.Cancel(); @@ -58,7 +58,7 @@ private void OnHoloInteractionAttempt(EntityUid uid, HologramComponent component private void OnInteractionWithHoloAttempt(EntityUid uid, HologramComponent component, GettingInteractedWithAttemptEvent args) { // Allow the interaction if either of them are hardlight, or if the interactor is a Hologram. - if (HoloInteractionAllowed(uid, args.Uid) || args.Target == null) + if (HoloInteractionAllowed(uid, args.Uid)) return; args.Cancel(); @@ -71,7 +71,7 @@ private void OnInteractionWithHoloAttempt(EntityUid uid, HologramComponent compo private void OnHoloCollide(EntityUid uid, HologramComponent component, ref PreventCollideEvent args) { - if (_tags.HasAnyTag(args.OtherEntity, component.CollideTags) || HoloInteractionAllowed(args.OurEntity, args.OtherEntity, component)) + if (Transform(args.OtherEntity).Anchored || HoloInteractionAllowed(args.OurEntity, args.OtherEntity, component)) return; args.Cancelled = true; @@ -89,32 +89,32 @@ public bool HoloInteractionAllowed(EntityUid hologram, EntityUid? potential, Hol return true; return _tags.HasTag(hologram, TagHardLight) || _tags.HasTag(potential.Value, TagHardLight) || Resolve(hologram, ref holoComp) == HasComp(potential); } + + /// + /// Kills a Hologram after playing the visual and auditory effects. + /// + /// + /// Note that the effects of killing a Hologram are not predicted. + /// + public bool TryKillHologram(EntityUid hologram, HologramComponent? holoComp = null) + { + if (!Resolve(hologram, ref holoComp)) + return false; + + var killedEvent = new HologramKillAttemptEvent(); + RaiseLocalEvent(hologram, ref killedEvent); + if (killedEvent.Cancelled) + return false; + + DoKillHologram(hologram, holoComp); + return true; + } + + /// + /// Kills a Hologram, playing the effects and deleting the entity. + /// + /// + /// This function does nothing if called on the client. + /// + public virtual void DoKillHologram(EntityUid hologram, HologramComponent? holoComp = null) { } // The killing is dealt with server-side, due to mind component. } -// public struct HoloData -// { -// [DataField("type")] -// public HoloType Type { get; set; } - -// [DataField("isHardlight")] -// public bool IsHardlight { get; set; } - -// public HoloData(HoloType type, bool isHardlight = false) -// { -// Type = type; -// IsHardlight = isHardlight; -// } -// } - - -// [Serializable, NetSerializable] -// public sealed class HoloTeleportEvent : EntityEventArgs -// { -// public readonly EntityUid Uid; -// public readonly List Lights; - -// public ShadekinDarkenEvent(EntityUid uid, List lights) -// { -// Uid = uid; -// Lights = lights; -// } -// } diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 4141bae621..18744d5901 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -12,6 +12,7 @@ - InstantDoAfters - CanPilot - BypassInteractionRangeChecks + - Hardlight - type: Input context: "aghost" - type: Ghost diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml index 0d8d17d2b9..8318ea9006 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml @@ -66,6 +66,11 @@ sound: path: /Audio/Effects/metalbreak.ogg - type: HologramProjector + effectOffsets: + North: "0.30, -0.40" + East: "-0.40, -0.30" + South: "0.30, 0.40" + West: "0.40, -0.30" - type: Tag tags: - HoloProjectorCamera diff --git a/Resources/Prototypes/SimpleStation14/Entities/Mobs/Player/hologram.yml b/Resources/Prototypes/SimpleStation14/Entities/Mobs/Player/hologram.yml index 0ec047a5ea..77c8974f20 100644 --- a/Resources/Prototypes/SimpleStation14/Entities/Mobs/Player/hologram.yml +++ b/Resources/Prototypes/SimpleStation14/Entities/Mobs/Player/hologram.yml @@ -151,9 +151,11 @@ - Airlock - type: HologramProjected gracePeriod: 0.08 - validProjectorTags: + validProjectorWhitelist: + tags: - HoloProjectorServer - HoloProjectorCamera + effectPrototype: EffectHologramProjectionBeam - type: Stealth lastVisibility: 0.80 - type: InteractionPopup diff --git a/Resources/Prototypes/SimpleStation14/Entities/Mobs/Player/stationai.yml b/Resources/Prototypes/SimpleStation14/Entities/Mobs/Player/stationai.yml index 83601e93b2..44f3cea610 100644 --- a/Resources/Prototypes/SimpleStation14/Entities/Mobs/Player/stationai.yml +++ b/Resources/Prototypes/SimpleStation14/Entities/Mobs/Player/stationai.yml @@ -227,6 +227,13 @@ - NanoTrasen - type: Laws lawsID: LawsStationAIDefault + # - type: HologramServer + # - type: HologramProjector + # effectOffsets: + # North: "0, 0.40" + # East: "-0.15, 0" + # South: "0, -0.20" + # West: "-0.15, 0" - type: RandomSprite available: - enum.PowerDeviceVisualLayers.Powered: @@ -325,7 +332,7 @@ layer: 4 # Can see through walls for now - type: Eye - drawFov: false + # drawFov: true - type: Input context: "human" - type: MobMover @@ -373,6 +380,16 @@ keywords: ["AI", "console", "interface"] priority: -1 event: !type:ToggleIntrinsicUIEvent + # - type: Hologram + # - type: HologramServerLinked + # - type: HologramProjected + # validProjectorWhitelist: + # components: + # - AICamera + # - AIEyePower + # effectPrototype: EffectHologramProjectionBeam + # setEyeTarget: true + # Mostly works, just not as I wish, so I'm disabling it for now. # Need to split this into two PRs, # There is too many issues this PR has fixed we need live for me to keep delaying it with more things diff --git a/Resources/Prototypes/SimpleStation14/Entities/Structures/Machines/hologram_constructor.yml b/Resources/Prototypes/SimpleStation14/Entities/Structures/Machines/hologram_constructor.yml index 21e502ca9c..4035ebf17b 100644 --- a/Resources/Prototypes/SimpleStation14/Entities/Structures/Machines/hologram_constructor.yml +++ b/Resources/Prototypes/SimpleStation14/Entities/Structures/Machines/hologram_constructor.yml @@ -116,6 +116,7 @@ tags: - HoloProjectorServer - type: HologramServer + diskSlot: holo_disk - type: ItemSlots slots: holo_disk: #this slot name is important