From 7780b867acd6fe34ff066aa48660c1e5dc60ffde Mon Sep 17 00:00:00 2001 From: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:18:15 -0600 Subject: [PATCH] Holopads (#32711) * Initial resources commit * Initial code commit * Added additional resources * Continuing to build holopad and telephone systems * Added hologram shader * Added hologram system and entity * Holo calls now have a hologram of the user appear on them * Initial implementation of holopads transmitting nearby chatter * Added support for linking across multiple telephones/holopads/entities * Fixed a bunch of bugs * Tried simplifying holopad entity dependence, added support for mid-call user switching * Replaced PVS expansion with manually networked sprite states * Adjusted volume of ring tone * Added machine board * Minor features and tweaks * Resolving merge conflict * Recommit audio attributions * Telephone chat adjustments * Added support for AI interactions with holopads * Building the holopad UI * Holopad UI finished * Further UI tweaks * Station AI can hear local chatter when being projected from a holopad * Minor bug fixes * Added wire panels to holopads * Basic broadcasting * Start of emergency broadcasting code * Fixing issues with broadcasting * More work on emergency broadcasting * Updated holopad visuals * Added cooldown text to emergency broadcast and control lock out screen * Code clean up * Fixed issue with timing * Broadcasting now requires command access * Fixed some bugs * Added multiple holopad prototypes with different ranges * The AI no longer requires power to interact with holopads * Fixed some additional issues * Addressing more issues * Added emote support for holograms * Changed the broadcast lockout durations to their proper values * Added AI vision wire to holopads * Bug fixes * AI vision and interaction wires can be added to the same wire panel * Fixed error * More bug fixes * Fixed test fail * Embellished the emergency call lock out window * Holopads play borg sounds when speaking * Borg and AI names are listed as the caller ID on the holopad * Borg chassis can now be seen on holopad holograms * Holopad returns to a machine frame when badly damaged * Clarified some text * Fix merge conflict * Fixed merge conflict * Fixing merge conflict * Fixing merge conflict * Fixing merge conflict * Offset menu on open * AI can alt click on holopads to activate the projector * Bug fixes for intellicard interactions * Fixed speech issue with intellicards * The UI automatically opens for the AI when it alt-clicks on the holopad * Simplified shader math * Telephones will auto hang up 60 seconds after the last person on a call stops speaking * Added better support for AI requests when multiple AI cores are on the station * The call controls pop up for the AI when they accept a summons from a holopad * Compatibility mode fix for the hologram shader * Further shader fixes for compatibility mode * File clean up * More cleaning up * Removed access requirements from quantum holopads so they can used by nukies * The title of the holopad window now reflects the name of the device * Linked telephones will lose their connection if both move out of range of each other --- Content.Client/Chat/UI/SpeechBubble.cs | 8 +- .../Holopad/HolopadBoundUserInterface.cs | 101 +++ Content.Client/Holopad/HolopadSystem.cs | 172 ++++ Content.Client/Holopad/HolopadWindow.xaml | 107 +++ Content.Client/Holopad/HolopadWindow.xaml.cs | 338 ++++++++ Content.Client/Stylesheets/StyleNano.cs | 15 + Content.Client/Telephone/TelephoneSystem.cs | 8 + Content.Server/Holopad/HolopadSystem.cs | 761 ++++++++++++++++++ .../Silicons/StationAi/AiVisionWireAction.cs | 4 +- .../Silicons/StationAi/StationAiSystem.cs | 41 + Content.Server/Telephone/TelephoneSystem.cs | 468 +++++++++++ Content.Shared/Doors/AirlockWireStatus.cs | 3 +- .../Holopad/HolographicAvatarComponent.cs | 13 + Content.Shared/Holopad/HolopadComponent.cs | 133 +++ .../Holopad/HolopadHologramComponent.cs | 71 ++ .../Holopad/HolopadUserComponent.cs | 104 +++ Content.Shared/Holopad/SharedHolopadSystem.cs | 43 + .../StationAi/SharedStationAiSystem.cs | 88 +- .../StationAi/StationAiCoreComponent.cs | 10 + Content.Shared/Speech/SpeechComponent.cs | 6 + .../Telephone/SharedTelephoneSystem.cs | 39 + .../Telephone/TelephoneComponent.cs | 203 +++++ Resources/Audio/Machines/attributions.yml | 5 + Resources/Audio/Machines/double_ring.ogg | Bin 0 -> 16754 bytes Resources/Locale/en-US/holopad/holopad.ftl | 40 + .../Locale/en-US/telephone/telephone.ftl | 8 + Resources/Locale/en-US/wires/wire-names.ftl | 1 + .../Entities/Mobs/Player/silicon.yml | 39 + .../Devices/Circuitboards/Machine/holopad.yml | 12 + .../Entities/Structures/Machines/holopad.yml | 158 ++++ Resources/Prototypes/Shaders/shaders.yml | 5 + Resources/Prototypes/Wires/layouts.yml | 10 +- Resources/Textures/Shaders/hologram.swsl | 23 + .../Structures/Machines/holopad.rsi/base.png | Bin 0 -> 457 bytes .../Structures/Machines/holopad.rsi/blank.png | Bin 0 -> 83 bytes .../Machines/holopad.rsi/icon_in_call.png | Bin 0 -> 414 bytes .../Machines/holopad.rsi/lights_calling.png | Bin 0 -> 425 bytes .../holopad.rsi/lights_hanging_up.png | Bin 0 -> 432 bytes .../Machines/holopad.rsi/lights_in_call.png | Bin 0 -> 326 bytes .../Machines/holopad.rsi/lights_ringing.png | Bin 0 -> 4429 bytes .../Structures/Machines/holopad.rsi/meta.json | 100 +++ .../Machines/holopad.rsi/panel_open.png | Bin 0 -> 221 bytes .../Machines/holopad.rsi/unpowered.png | Bin 0 -> 441 bytes 43 files changed, 3129 insertions(+), 8 deletions(-) create mode 100644 Content.Client/Holopad/HolopadBoundUserInterface.cs create mode 100644 Content.Client/Holopad/HolopadSystem.cs create mode 100644 Content.Client/Holopad/HolopadWindow.xaml create mode 100644 Content.Client/Holopad/HolopadWindow.xaml.cs create mode 100644 Content.Client/Telephone/TelephoneSystem.cs create mode 100644 Content.Server/Holopad/HolopadSystem.cs create mode 100644 Content.Server/Telephone/TelephoneSystem.cs create mode 100644 Content.Shared/Holopad/HolographicAvatarComponent.cs create mode 100644 Content.Shared/Holopad/HolopadComponent.cs create mode 100644 Content.Shared/Holopad/HolopadHologramComponent.cs create mode 100644 Content.Shared/Holopad/HolopadUserComponent.cs create mode 100644 Content.Shared/Holopad/SharedHolopadSystem.cs create mode 100644 Content.Shared/Telephone/SharedTelephoneSystem.cs create mode 100644 Content.Shared/Telephone/TelephoneComponent.cs create mode 100644 Resources/Audio/Machines/double_ring.ogg create mode 100644 Resources/Locale/en-US/holopad/holopad.ftl create mode 100644 Resources/Locale/en-US/telephone/telephone.ftl create mode 100644 Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/holopad.yml create mode 100644 Resources/Prototypes/Entities/Structures/Machines/holopad.yml create mode 100644 Resources/Textures/Shaders/hologram.swsl create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/base.png create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/blank.png create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/icon_in_call.png create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/lights_calling.png create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/lights_hanging_up.png create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/lights_in_call.png create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/lights_ringing.png create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/meta.json create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/panel_open.png create mode 100644 Resources/Textures/Structures/Machines/holopad.rsi/unpowered.png diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs index 32e9f4ae9b..aa61e73e31 100644 --- a/Content.Client/Chat/UI/SpeechBubble.cs +++ b/Content.Client/Chat/UI/SpeechBubble.cs @@ -2,6 +2,7 @@ using System.Numerics; using Content.Client.Chat.Managers; using Content.Shared.CCVar; using Content.Shared.Chat; +using Content.Shared.Speech; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -141,7 +142,12 @@ namespace Content.Client.Chat.UI Modulate = Color.White; } - var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset; + var baseOffset = 0f; + + if (_entityManager.TryGetComponent(_senderEntity, out var speech)) + baseOffset = speech.SpeechBubbleOffset; + + var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -(EntityVerticalOffset + baseOffset); var worldPos = _transformSystem.GetWorldPosition(xform) + offset; var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale; diff --git a/Content.Client/Holopad/HolopadBoundUserInterface.cs b/Content.Client/Holopad/HolopadBoundUserInterface.cs new file mode 100644 index 0000000000..20b55ea8c7 --- /dev/null +++ b/Content.Client/Holopad/HolopadBoundUserInterface.cs @@ -0,0 +1,101 @@ +using Content.Shared.Holopad; +using Content.Shared.Silicons.StationAi; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Shared.Player; +using System.Numerics; + +namespace Content.Client.Holopad; + +public sealed class HolopadBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; + [Dependency] private readonly IClyde _displayManager = default!; + + [ViewVariables] + private HolopadWindow? _window; + + public HolopadBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + + _window = this.CreateWindow(); + _window.Title = Loc.GetString("holopad-window-title", ("title", EntMan.GetComponent(Owner).EntityName)); + + if (this.UiKey is not HolopadUiKey) + { + Close(); + return; + } + + var uiKey = (HolopadUiKey)this.UiKey; + + // AIs will see a different holopad interface to crew when interacting with them in the world + if (uiKey == HolopadUiKey.InteractionWindow && EntMan.HasComponent(_playerManager.LocalEntity)) + uiKey = HolopadUiKey.InteractionWindowForAi; + + _window.SetState(Owner, uiKey); + _window.UpdateState(new Dictionary()); + + // Set message actions + _window.SendHolopadStartNewCallMessageAction += SendHolopadStartNewCallMessage; + _window.SendHolopadAnswerCallMessageAction += SendHolopadAnswerCallMessage; + _window.SendHolopadEndCallMessageAction += SendHolopadEndCallMessage; + _window.SendHolopadStartBroadcastMessageAction += SendHolopadStartBroadcastMessage; + _window.SendHolopadActivateProjectorMessageAction += SendHolopadActivateProjectorMessage; + _window.SendHolopadRequestStationAiMessageAction += SendHolopadRequestStationAiMessage; + + // If this call is addressed to an AI, open the window in the bottom right hand corner of the screen + if (uiKey == HolopadUiKey.AiRequestWindow) + _window.OpenCenteredAt(new Vector2(1f, 1f)); + + // Otherwise offset to the left so the holopad can still be seen + else + _window.OpenCenteredAt(new Vector2(0.3333f, 0.50f)); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + var castState = (HolopadBoundInterfaceState)state; + EntMan.TryGetComponent(Owner, out var xform); + + _window?.UpdateState(castState.Holopads); + } + + public void SendHolopadStartNewCallMessage(NetEntity receiver) + { + SendMessage(new HolopadStartNewCallMessage(receiver)); + } + + public void SendHolopadAnswerCallMessage() + { + SendMessage(new HolopadAnswerCallMessage()); + } + + public void SendHolopadEndCallMessage() + { + SendMessage(new HolopadEndCallMessage()); + } + + public void SendHolopadStartBroadcastMessage() + { + SendMessage(new HolopadStartBroadcastMessage()); + } + + public void SendHolopadActivateProjectorMessage() + { + SendMessage(new HolopadActivateProjectorMessage()); + } + + public void SendHolopadRequestStationAiMessage() + { + SendMessage(new HolopadStationAiRequestMessage()); + } +} diff --git a/Content.Client/Holopad/HolopadSystem.cs b/Content.Client/Holopad/HolopadSystem.cs new file mode 100644 index 0000000000..3bd556f1fc --- /dev/null +++ b/Content.Client/Holopad/HolopadSystem.cs @@ -0,0 +1,172 @@ +using Content.Shared.Chat.TypingIndicator; +using Content.Shared.Holopad; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using System.Linq; + +namespace Content.Client.Holopad; + +public sealed class HolopadSystem : SharedHolopadSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnShaderRender); + SubscribeAllEvent(OnTypingChanged); + + SubscribeNetworkEvent(OnPlayerSpriteStateRequest); + SubscribeNetworkEvent(OnPlayerSpriteStateMessage); + } + + private void OnComponentInit(EntityUid uid, HolopadHologramComponent component, ComponentInit ev) + { + if (!TryComp(uid, out var sprite)) + return; + + UpdateHologramSprite(uid); + } + + private void OnShaderRender(EntityUid uid, HolopadHologramComponent component, BeforePostShaderRenderEvent ev) + { + if (ev.Sprite.PostShader == null) + return; + + ev.Sprite.PostShader.SetParameter("t", (float)_timing.CurTime.TotalSeconds * component.ScrollRate); + } + + private void OnTypingChanged(TypingChangedEvent ev, EntitySessionEventArgs args) + { + var uid = args.SenderSession.AttachedEntity; + + if (!Exists(uid)) + return; + + if (!HasComp(uid)) + return; + + var netEv = new HolopadUserTypingChangedEvent(GetNetEntity(uid.Value), ev.IsTyping); + RaiseNetworkEvent(netEv); + } + + private void OnPlayerSpriteStateRequest(PlayerSpriteStateRequest ev) + { + var targetPlayer = GetEntity(ev.TargetPlayer); + var player = _playerManager.LocalSession?.AttachedEntity; + + // Ignore the request if received by a player who isn't the target + if (targetPlayer != player) + return; + + if (!TryComp(player, out var playerSprite)) + return; + + var spriteLayerData = new List(); + + if (playerSprite.Visible) + { + // Record the RSI paths, state names and shader paramaters of all visible layers + for (int i = 0; i < playerSprite.AllLayers.Count(); i++) + { + if (!playerSprite.TryGetLayer(i, out var layer)) + continue; + + if (!layer.Visible || + string.IsNullOrEmpty(layer.ActualRsi?.Path.ToString()) || + string.IsNullOrEmpty(layer.State.Name)) + continue; + + var layerDatum = new PrototypeLayerData(); + layerDatum.RsiPath = layer.ActualRsi.Path.ToString(); + layerDatum.State = layer.State.Name; + + if (layer.CopyToShaderParameters != null) + { + var key = (string)layer.CopyToShaderParameters.LayerKey; + + if (playerSprite.LayerMapTryGet(key, out var otherLayerIdx) && + playerSprite.TryGetLayer(otherLayerIdx, out var otherLayer) && + otherLayer.Visible) + { + layerDatum.MapKeys = new() { key }; + + layerDatum.CopyToShaderParameters = new PrototypeCopyToShaderParameters() + { + LayerKey = key, + ParameterTexture = layer.CopyToShaderParameters.ParameterTexture, + ParameterUV = layer.CopyToShaderParameters.ParameterUV + }; + } + } + + spriteLayerData.Add(layerDatum); + } + } + + // Return the recorded data to the server + var evResponse = new PlayerSpriteStateMessage(ev.TargetPlayer, spriteLayerData.ToArray()); + RaiseNetworkEvent(evResponse); + } + + private void OnPlayerSpriteStateMessage(PlayerSpriteStateMessage ev) + { + UpdateHologramSprite(GetEntity(ev.SpriteEntity), ev.SpriteLayerData); + } + + private void UpdateHologramSprite(EntityUid uid, PrototypeLayerData[]? layerData = null) + { + if (!TryComp(uid, out var hologramSprite)) + return; + + if (!TryComp(uid, out var holopadhologram)) + return; + + for (int i = hologramSprite.AllLayers.Count() - 1; i >= 0; i--) + hologramSprite.RemoveLayer(i); + + if (layerData == null || layerData.Length == 0) + { + layerData = new PrototypeLayerData[1]; + layerData[0] = new PrototypeLayerData() + { + RsiPath = holopadhologram.RsiPath, + State = holopadhologram.RsiState + }; + } + + for (int i = 0; i < layerData.Length; i++) + { + var layer = layerData[i]; + layer.Shader = "unshaded"; + + hologramSprite.AddLayer(layerData[i], i); + } + + UpdateHologramShader(uid, hologramSprite, holopadhologram); + } + + private void UpdateHologramShader(EntityUid uid, SpriteComponent sprite, HolopadHologramComponent holopadHologram) + { + // Find the texture height of the largest layer + float texHeight = sprite.AllLayers.Max(x => x.PixelSize.Y); + + var instance = _prototypeManager.Index(holopadHologram.ShaderName).InstanceUnique(); + instance.SetParameter("color1", new Vector3(holopadHologram.Color1.R, holopadHologram.Color1.G, holopadHologram.Color1.B)); + instance.SetParameter("color2", new Vector3(holopadHologram.Color2.R, holopadHologram.Color2.G, holopadHologram.Color2.B)); + instance.SetParameter("alpha", holopadHologram.Alpha); + instance.SetParameter("intensity", holopadHologram.Intensity); + instance.SetParameter("texHeight", texHeight); + instance.SetParameter("t", (float)_timing.CurTime.TotalSeconds * holopadHologram.ScrollRate); + + sprite.PostShader = instance; + sprite.RaiseShaderEvent = true; + } +} diff --git a/Content.Client/Holopad/HolopadWindow.xaml b/Content.Client/Holopad/HolopadWindow.xaml new file mode 100644 index 0000000000..9c3dfab1ea --- /dev/null +++ b/Content.Client/Holopad/HolopadWindow.xaml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +