mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-15 00:54:51 +01:00
* 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
762 lines
28 KiB
C#
762 lines
28 KiB
C#
using Content.Server.Chat.Systems;
|
|
using Content.Server.Power.EntitySystems;
|
|
using Content.Server.Speech.Components;
|
|
using Content.Server.Telephone;
|
|
using Content.Shared.Access.Systems;
|
|
using Content.Shared.Audio;
|
|
using Content.Shared.Chat.TypingIndicator;
|
|
using Content.Shared.Holopad;
|
|
using Content.Shared.IdentityManagement;
|
|
using Content.Shared.Labels.Components;
|
|
using Content.Shared.Silicons.StationAi;
|
|
using Content.Shared.Telephone;
|
|
using Content.Shared.UserInterface;
|
|
using Content.Shared.Verbs;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
using System.Linq;
|
|
|
|
namespace Content.Server.Holopad;
|
|
|
|
public sealed class HolopadSystem : SharedHolopadSystem
|
|
{
|
|
[Dependency] private readonly TelephoneSystem _telephoneSystem = default!;
|
|
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
|
[Dependency] private readonly TransformSystem _xformSystem = default!;
|
|
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
|
[Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
|
|
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
|
|
[Dependency] private readonly SharedStationAiSystem _stationAiSystem = default!;
|
|
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
|
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
|
|
private float _updateTimer = 1.0f;
|
|
|
|
private const float UpdateTime = 1.0f;
|
|
private const float MinTimeBetweenSyncRequests = 0.5f;
|
|
private TimeSpan _minTimeSpanBetweenSyncRequests;
|
|
|
|
private HashSet<EntityUid> _pendingRequestsForSpriteState = new();
|
|
private HashSet<EntityUid> _recentlyUpdatedHolograms = new();
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
_minTimeSpanBetweenSyncRequests = TimeSpan.FromSeconds(MinTimeBetweenSyncRequests);
|
|
|
|
// Holopad UI and bound user interface messages
|
|
SubscribeLocalEvent<HolopadComponent, BeforeActivatableUIOpenEvent>(OnUIOpen);
|
|
SubscribeLocalEvent<HolopadComponent, HolopadStartNewCallMessage>(OnHolopadStartNewCall);
|
|
SubscribeLocalEvent<HolopadComponent, HolopadAnswerCallMessage>(OnHolopadAnswerCall);
|
|
SubscribeLocalEvent<HolopadComponent, HolopadEndCallMessage>(OnHolopadEndCall);
|
|
SubscribeLocalEvent<HolopadComponent, HolopadActivateProjectorMessage>(OnHolopadActivateProjector);
|
|
SubscribeLocalEvent<HolopadComponent, HolopadStartBroadcastMessage>(OnHolopadStartBroadcast);
|
|
SubscribeLocalEvent<HolopadComponent, HolopadStationAiRequestMessage>(OnHolopadStationAiRequest);
|
|
|
|
// Holopad telephone events
|
|
SubscribeLocalEvent<HolopadComponent, TelephoneStateChangeEvent>(OnTelephoneStateChange);
|
|
SubscribeLocalEvent<HolopadComponent, TelephoneCallCommencedEvent>(OnHoloCallCommenced);
|
|
SubscribeLocalEvent<HolopadComponent, TelephoneCallEndedEvent>(OnHoloCallEnded);
|
|
SubscribeLocalEvent<HolopadComponent, TelephoneMessageSentEvent>(OnTelephoneMessageSent);
|
|
|
|
// Networked events
|
|
SubscribeNetworkEvent<HolopadUserTypingChangedEvent>(OnTypingChanged);
|
|
SubscribeNetworkEvent<PlayerSpriteStateMessage>(OnPlayerSpriteStateMessage);
|
|
|
|
// Component start/shutdown events
|
|
SubscribeLocalEvent<HolopadComponent, ComponentInit>(OnHolopadInit);
|
|
SubscribeLocalEvent<HolopadComponent, ComponentShutdown>(OnHolopadShutdown);
|
|
SubscribeLocalEvent<HolopadUserComponent, ComponentInit>(OnHolopadUserInit);
|
|
SubscribeLocalEvent<HolopadUserComponent, ComponentShutdown>(OnHolopadUserShutdown);
|
|
|
|
// Misc events
|
|
SubscribeLocalEvent<HolopadUserComponent, EmoteEvent>(OnEmote);
|
|
SubscribeLocalEvent<HolopadUserComponent, JumpToCoreEvent>(OnJumpToCore);
|
|
SubscribeLocalEvent<HolopadComponent, GetVerbsEvent<AlternativeVerb>>(AddToggleProjectorVerb);
|
|
SubscribeLocalEvent<HolopadComponent, EntRemovedFromContainerMessage>(OnAiRemove);
|
|
}
|
|
|
|
#region: Holopad UI bound user interface messages
|
|
|
|
private void OnUIOpen(Entity<HolopadComponent> entity, ref BeforeActivatableUIOpenEvent args)
|
|
{
|
|
UpdateUIState(entity);
|
|
}
|
|
|
|
private void OnHolopadStartNewCall(Entity<HolopadComponent> source, ref HolopadStartNewCallMessage args)
|
|
{
|
|
if (IsHolopadControlLocked(source, args.Actor))
|
|
return;
|
|
|
|
if (!TryComp<TelephoneComponent>(source, out var sourceTelephone))
|
|
return;
|
|
|
|
var receiver = GetEntity(args.Receiver);
|
|
|
|
if (!TryComp<TelephoneComponent>(receiver, out var receiverTelephone))
|
|
return;
|
|
|
|
LinkHolopadToUser(source, args.Actor);
|
|
_telephoneSystem.CallTelephone((source, sourceTelephone), (receiver, receiverTelephone), args.Actor);
|
|
}
|
|
|
|
private void OnHolopadAnswerCall(Entity<HolopadComponent> receiver, ref HolopadAnswerCallMessage args)
|
|
{
|
|
if (IsHolopadControlLocked(receiver, args.Actor))
|
|
return;
|
|
|
|
if (!TryComp<TelephoneComponent>(receiver, out var receiverTelephone))
|
|
return;
|
|
|
|
if (TryComp<StationAiHeldComponent>(args.Actor, out var userAiHeld))
|
|
{
|
|
var source = GetLinkedHolopads(receiver).FirstOrNull();
|
|
|
|
if (source != null)
|
|
ActivateProjector(source.Value, args.Actor);
|
|
|
|
return;
|
|
}
|
|
|
|
LinkHolopadToUser(receiver, args.Actor);
|
|
_telephoneSystem.AnswerTelephone((receiver, receiverTelephone), args.Actor);
|
|
}
|
|
|
|
private void OnHolopadEndCall(Entity<HolopadComponent> entity, ref HolopadEndCallMessage args)
|
|
{
|
|
if (!TryComp<TelephoneComponent>(entity, out var entityTelephone))
|
|
return;
|
|
|
|
if (IsHolopadControlLocked(entity, args.Actor))
|
|
return;
|
|
|
|
_telephoneSystem.EndTelephoneCalls((entity, entityTelephone));
|
|
|
|
// If the user is an AI, end all calls originating from its
|
|
// associated core to ensure that any broadcasts will end
|
|
if (!TryComp<StationAiHeldComponent>(args.Actor, out var stationAiHeld) ||
|
|
!_stationAiSystem.TryGetStationAiCore((args.Actor, stationAiHeld), out var stationAiCore))
|
|
return;
|
|
|
|
if (TryComp<TelephoneComponent>(stationAiCore, out var telephone))
|
|
_telephoneSystem.EndTelephoneCalls((stationAiCore.Value, telephone));
|
|
}
|
|
|
|
private void OnHolopadActivateProjector(Entity<HolopadComponent> entity, ref HolopadActivateProjectorMessage args)
|
|
{
|
|
ActivateProjector(entity, args.Actor);
|
|
}
|
|
|
|
private void OnHolopadStartBroadcast(Entity<HolopadComponent> source, ref HolopadStartBroadcastMessage args)
|
|
{
|
|
if (IsHolopadControlLocked(source, args.Actor) || IsHolopadBroadcastOnCoolDown(source))
|
|
return;
|
|
|
|
if (!_accessReaderSystem.IsAllowed(args.Actor, source))
|
|
return;
|
|
|
|
// AI broadcasting
|
|
if (TryComp<StationAiHeldComponent>(args.Actor, out var stationAiHeld))
|
|
{
|
|
if (!_stationAiSystem.TryGetStationAiCore((args.Actor, stationAiHeld), out var stationAiCore) ||
|
|
stationAiCore.Value.Comp.RemoteEntity == null ||
|
|
!TryComp<HolopadComponent>(stationAiCore, out var stationAiCoreHolopad))
|
|
return;
|
|
|
|
ExecuteBroadcast((stationAiCore.Value, stationAiCoreHolopad), args.Actor);
|
|
|
|
// Switch the AI's perspective from free roaming to the target holopad
|
|
_xformSystem.SetCoordinates(stationAiCore.Value.Comp.RemoteEntity.Value, Transform(source).Coordinates);
|
|
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore.Value, false);
|
|
|
|
return;
|
|
}
|
|
|
|
// Crew broadcasting
|
|
ExecuteBroadcast(source, args.Actor);
|
|
}
|
|
|
|
private void OnHolopadStationAiRequest(Entity<HolopadComponent> entity, ref HolopadStationAiRequestMessage args)
|
|
{
|
|
if (IsHolopadControlLocked(entity, args.Actor))
|
|
return;
|
|
|
|
if (!TryComp<TelephoneComponent>(entity, out var telephone))
|
|
return;
|
|
|
|
var source = new Entity<TelephoneComponent>(entity, telephone);
|
|
var query = AllEntityQuery<StationAiCoreComponent, TelephoneComponent>();
|
|
var reachableAiCores = new HashSet<Entity<TelephoneComponent>>();
|
|
|
|
while (query.MoveNext(out var receiverUid, out var receiverStationAiCore, out var receiverTelephone))
|
|
{
|
|
var receiver = new Entity<TelephoneComponent>(receiverUid, receiverTelephone);
|
|
|
|
if (!_telephoneSystem.IsSourceAbleToReachReceiver(source, receiver))
|
|
continue;
|
|
|
|
if (_telephoneSystem.IsTelephoneEngaged(receiver))
|
|
continue;
|
|
|
|
reachableAiCores.Add((receiverUid, receiverTelephone));
|
|
|
|
if (!_stationAiSystem.TryGetInsertedAI((receiver, receiverStationAiCore), out var insertedAi))
|
|
continue;
|
|
|
|
if (_userInterfaceSystem.TryOpenUi(receiverUid, HolopadUiKey.AiRequestWindow, insertedAi.Value.Owner))
|
|
LinkHolopadToUser(entity, args.Actor);
|
|
}
|
|
|
|
if (!reachableAiCores.Any())
|
|
return;
|
|
|
|
_telephoneSystem.BroadcastCallToTelephones(source, reachableAiCores, args.Actor);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region: Holopad telephone events
|
|
|
|
private void OnTelephoneStateChange(Entity<HolopadComponent> holopad, ref TelephoneStateChangeEvent args)
|
|
{
|
|
// Update holopad visual and ambient states
|
|
switch (args.NewState)
|
|
{
|
|
case TelephoneState.Idle:
|
|
ShutDownHolopad(holopad);
|
|
SetHolopadAmbientState(holopad, false);
|
|
break;
|
|
|
|
case TelephoneState.EndingCall:
|
|
ShutDownHolopad(holopad);
|
|
break;
|
|
|
|
default:
|
|
SetHolopadAmbientState(holopad, this.IsPowered(holopad, EntityManager));
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void OnHoloCallCommenced(Entity<HolopadComponent> source, ref TelephoneCallCommencedEvent args)
|
|
{
|
|
if (source.Comp.Hologram == null)
|
|
GenerateHologram(source);
|
|
|
|
// Receiver holopad holograms have to be generated now instead of waiting for their own event
|
|
// to fire because holographic avatars get synced immediately
|
|
if (TryComp<HolopadComponent>(args.Receiver, out var receivingHolopad) && receivingHolopad.Hologram == null)
|
|
GenerateHologram((args.Receiver, receivingHolopad));
|
|
|
|
if (source.Comp.User != null)
|
|
{
|
|
// Re-link the user to refresh the sprite data
|
|
LinkHolopadToUser(source, source.Comp.User.Value);
|
|
}
|
|
}
|
|
|
|
private void OnHoloCallEnded(Entity<HolopadComponent> entity, ref TelephoneCallEndedEvent args)
|
|
{
|
|
if (!TryComp<StationAiCoreComponent>(entity, out var stationAiCore))
|
|
return;
|
|
|
|
// Auto-close the AI request window
|
|
if (_stationAiSystem.TryGetInsertedAI((entity, stationAiCore), out var insertedAi))
|
|
_userInterfaceSystem.CloseUi(entity.Owner, HolopadUiKey.AiRequestWindow, insertedAi.Value.Owner);
|
|
}
|
|
|
|
private void OnTelephoneMessageSent(Entity<HolopadComponent> holopad, ref TelephoneMessageSentEvent args)
|
|
{
|
|
LinkHolopadToUser(holopad, args.MessageSource);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region: Networked events
|
|
|
|
private void OnTypingChanged(HolopadUserTypingChangedEvent ev, EntitySessionEventArgs args)
|
|
{
|
|
var uid = args.SenderSession.AttachedEntity;
|
|
|
|
if (!Exists(uid))
|
|
return;
|
|
|
|
if (!TryComp<HolopadUserComponent>(uid, out var holopadUser))
|
|
return;
|
|
|
|
foreach (var linkedHolopad in holopadUser.LinkedHolopads)
|
|
{
|
|
var receiverHolopads = GetLinkedHolopads(linkedHolopad);
|
|
|
|
foreach (var receiverHolopad in receiverHolopads)
|
|
{
|
|
if (receiverHolopad.Comp.Hologram == null)
|
|
continue;
|
|
|
|
_appearanceSystem.SetData(receiverHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, ev.IsTyping);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnPlayerSpriteStateMessage(PlayerSpriteStateMessage ev, EntitySessionEventArgs args)
|
|
{
|
|
var uid = args.SenderSession.AttachedEntity;
|
|
|
|
if (!Exists(uid))
|
|
return;
|
|
|
|
if (!_pendingRequestsForSpriteState.Remove(uid.Value))
|
|
return;
|
|
|
|
if (!TryComp<HolopadUserComponent>(uid, out var holopadUser))
|
|
return;
|
|
|
|
SyncHolopadUserWithLinkedHolograms((uid.Value, holopadUser), ev.SpriteLayerData);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region: Component start/shutdown events
|
|
|
|
private void OnHolopadInit(Entity<HolopadComponent> entity, ref ComponentInit args)
|
|
{
|
|
if (entity.Comp.User != null)
|
|
LinkHolopadToUser(entity, entity.Comp.User.Value);
|
|
}
|
|
|
|
private void OnHolopadUserInit(Entity<HolopadUserComponent> entity, ref ComponentInit args)
|
|
{
|
|
foreach (var linkedHolopad in entity.Comp.LinkedHolopads)
|
|
LinkHolopadToUser(linkedHolopad, entity);
|
|
}
|
|
|
|
private void OnHolopadShutdown(Entity<HolopadComponent> entity, ref ComponentShutdown args)
|
|
{
|
|
ShutDownHolopad(entity);
|
|
SetHolopadAmbientState(entity, false);
|
|
}
|
|
|
|
private void OnHolopadUserShutdown(Entity<HolopadUserComponent> entity, ref ComponentShutdown args)
|
|
{
|
|
foreach (var linkedHolopad in entity.Comp.LinkedHolopads)
|
|
UnlinkHolopadFromUser(linkedHolopad, entity);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region: Misc events
|
|
|
|
private void OnEmote(Entity<HolopadUserComponent> entity, ref EmoteEvent args)
|
|
{
|
|
foreach (var linkedHolopad in entity.Comp.LinkedHolopads)
|
|
{
|
|
// Treat the ability to hear speech as the ability to also perceive emotes
|
|
// (these are almost always going to be linked)
|
|
if (!HasComp<ActiveListenerComponent>(linkedHolopad))
|
|
continue;
|
|
|
|
if (TryComp<TelephoneComponent>(linkedHolopad, out var linkedHolopadTelephone) && linkedHolopadTelephone.Muted)
|
|
continue;
|
|
|
|
foreach (var receiver in GetLinkedHolopads(linkedHolopad))
|
|
{
|
|
if (receiver.Comp.Hologram == null)
|
|
continue;
|
|
|
|
// Name is based on the physical identity of the user
|
|
var ent = Identity.Entity(entity, EntityManager);
|
|
var name = Loc.GetString("holopad-hologram-name", ("name", ent));
|
|
|
|
// Force the emote, because if the user can do it, the hologram can too
|
|
_chatSystem.TryEmoteWithChat(receiver.Comp.Hologram.Value, args.Emote, ChatTransmitRange.Normal, false, name, true, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnJumpToCore(Entity<HolopadUserComponent> entity, ref JumpToCoreEvent args)
|
|
{
|
|
if (!TryComp<StationAiHeldComponent>(entity, out var entityStationAiHeld))
|
|
return;
|
|
|
|
if (!_stationAiSystem.TryGetStationAiCore((entity, entityStationAiHeld), out var stationAiCore))
|
|
return;
|
|
|
|
if (!TryComp<TelephoneComponent>(stationAiCore, out var stationAiCoreTelephone))
|
|
return;
|
|
|
|
_telephoneSystem.EndTelephoneCalls((stationAiCore.Value, stationAiCoreTelephone));
|
|
}
|
|
|
|
private void AddToggleProjectorVerb(Entity<HolopadComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
|
|
{
|
|
if (!args.CanAccess || !args.CanInteract)
|
|
return;
|
|
|
|
if (!this.IsPowered(entity, EntityManager))
|
|
return;
|
|
|
|
if (!TryComp<TelephoneComponent>(entity, out var entityTelephone) ||
|
|
_telephoneSystem.IsTelephoneEngaged((entity, entityTelephone)))
|
|
return;
|
|
|
|
var user = args.User;
|
|
|
|
if (!TryComp<StationAiHeldComponent>(user, out var userAiHeld))
|
|
return;
|
|
|
|
if (!_stationAiSystem.TryGetStationAiCore((user, userAiHeld), out var stationAiCore) ||
|
|
stationAiCore.Value.Comp.RemoteEntity == null)
|
|
return;
|
|
|
|
AlternativeVerb verb = new()
|
|
{
|
|
Act = () => ActivateProjector(entity, user),
|
|
Text = Loc.GetString("activate-holopad-projector-verb"),
|
|
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")),
|
|
};
|
|
|
|
args.Verbs.Add(verb);
|
|
}
|
|
|
|
private void OnAiRemove(Entity<HolopadComponent> entity, ref EntRemovedFromContainerMessage args)
|
|
{
|
|
if (!HasComp<StationAiCoreComponent>(entity))
|
|
return;
|
|
|
|
if (!TryComp<TelephoneComponent>(entity, out var entityTelephone))
|
|
return;
|
|
|
|
_telephoneSystem.EndTelephoneCalls((entity, entityTelephone));
|
|
}
|
|
|
|
#endregion
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
|
|
_updateTimer += frameTime;
|
|
|
|
if (_updateTimer >= UpdateTime)
|
|
{
|
|
_updateTimer -= UpdateTime;
|
|
|
|
var query = AllEntityQuery<HolopadComponent, TelephoneComponent, TransformComponent>();
|
|
while (query.MoveNext(out var uid, out var holopad, out var telephone, out var xform))
|
|
{
|
|
UpdateUIState((uid, holopad), telephone);
|
|
|
|
if (holopad.User != null &&
|
|
!HasComp<IgnoreUIRangeComponent>(holopad.User) &&
|
|
!_xformSystem.InRange((holopad.User.Value, Transform(holopad.User.Value)), (uid, xform), telephone.ListeningRange))
|
|
{
|
|
UnlinkHolopadFromUser((uid, holopad), holopad.User.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
_recentlyUpdatedHolograms.Clear();
|
|
}
|
|
|
|
public void UpdateUIState(Entity<HolopadComponent> entity, TelephoneComponent? telephone = null)
|
|
{
|
|
if (!Resolve(entity.Owner, ref telephone, false))
|
|
return;
|
|
|
|
var source = new Entity<TelephoneComponent>(entity, telephone);
|
|
var holopads = new Dictionary<NetEntity, string>();
|
|
|
|
var query = AllEntityQuery<HolopadComponent, TelephoneComponent>();
|
|
while (query.MoveNext(out var receiverUid, out var _, out var receiverTelephone))
|
|
{
|
|
var receiver = new Entity<TelephoneComponent>(receiverUid, receiverTelephone);
|
|
|
|
if (receiverTelephone.UnlistedNumber)
|
|
continue;
|
|
|
|
if (source == receiver)
|
|
continue;
|
|
|
|
if (!_telephoneSystem.IsSourceInRangeOfReceiver(source, receiver))
|
|
continue;
|
|
|
|
var name = MetaData(receiverUid).EntityName;
|
|
|
|
if (TryComp<LabelComponent>(receiverUid, out var label) && !string.IsNullOrEmpty(label.CurrentLabel))
|
|
name = label.CurrentLabel;
|
|
|
|
holopads.Add(GetNetEntity(receiverUid), name);
|
|
}
|
|
|
|
var uiKey = HasComp<StationAiCoreComponent>(entity) ? HolopadUiKey.AiActionWindow : HolopadUiKey.InteractionWindow;
|
|
_userInterfaceSystem.SetUiState(entity.Owner, uiKey, new HolopadBoundInterfaceState(holopads));
|
|
}
|
|
|
|
private void GenerateHologram(Entity<HolopadComponent> entity)
|
|
{
|
|
if (entity.Comp.Hologram != null ||
|
|
entity.Comp.HologramProtoId == null)
|
|
return;
|
|
|
|
var uid = Spawn(entity.Comp.HologramProtoId, Transform(entity).Coordinates);
|
|
|
|
// Safeguard - spawned holograms must have this component
|
|
if (!TryComp<HolopadHologramComponent>(uid, out var component))
|
|
{
|
|
Del(uid);
|
|
return;
|
|
}
|
|
|
|
entity.Comp.Hologram = new Entity<HolopadHologramComponent>(uid, component);
|
|
}
|
|
|
|
private void DeleteHologram(Entity<HolopadHologramComponent> hologram, Entity<HolopadComponent> attachedHolopad)
|
|
{
|
|
attachedHolopad.Comp.Hologram = null;
|
|
|
|
QueueDel(hologram);
|
|
}
|
|
|
|
private void LinkHolopadToUser(Entity<HolopadComponent> entity, EntityUid user)
|
|
{
|
|
if (!TryComp<HolopadUserComponent>(user, out var holopadUser))
|
|
holopadUser = AddComp<HolopadUserComponent>(user);
|
|
|
|
if (user != entity.Comp.User?.Owner)
|
|
{
|
|
// Removes the old user from the holopad
|
|
UnlinkHolopadFromUser(entity, entity.Comp.User);
|
|
|
|
// Assigns the new user in their place
|
|
holopadUser.LinkedHolopads.Add(entity);
|
|
entity.Comp.User = (user, holopadUser);
|
|
}
|
|
|
|
if (TryComp<HolographicAvatarComponent>(user, out var avatar))
|
|
{
|
|
SyncHolopadUserWithLinkedHolograms((user, holopadUser), avatar.LayerData);
|
|
return;
|
|
}
|
|
|
|
// We have no apriori sprite data for the hologram, request
|
|
// the current appearance of the user from the client
|
|
RequestHolopadUserSpriteUpdate((user, holopadUser));
|
|
}
|
|
|
|
private void UnlinkHolopadFromUser(Entity<HolopadComponent> entity, Entity<HolopadUserComponent>? user)
|
|
{
|
|
if (user == null)
|
|
return;
|
|
|
|
entity.Comp.User = null;
|
|
|
|
foreach (var linkedHolopad in GetLinkedHolopads(entity))
|
|
{
|
|
if (linkedHolopad.Comp.Hologram != null)
|
|
{
|
|
_appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, false);
|
|
|
|
// Send message with no sprite data to the client
|
|
// This will set the holgram sprite to a generic icon
|
|
var ev = new PlayerSpriteStateMessage(GetNetEntity(linkedHolopad.Comp.Hologram.Value));
|
|
RaiseNetworkEvent(ev);
|
|
}
|
|
}
|
|
|
|
if (!HasComp<HolopadUserComponent>(user))
|
|
return;
|
|
|
|
user.Value.Comp.LinkedHolopads.Remove(entity);
|
|
|
|
if (!user.Value.Comp.LinkedHolopads.Any())
|
|
{
|
|
_pendingRequestsForSpriteState.Remove(user.Value);
|
|
|
|
if (user.Value.Comp.LifeStage < ComponentLifeStage.Stopping)
|
|
RemComp<HolopadUserComponent>(user.Value);
|
|
}
|
|
}
|
|
|
|
private void ShutDownHolopad(Entity<HolopadComponent> entity)
|
|
{
|
|
entity.Comp.ControlLockoutOwner = null;
|
|
|
|
if (entity.Comp.Hologram != null)
|
|
DeleteHologram(entity.Comp.Hologram.Value, entity);
|
|
|
|
if (entity.Comp.User != null)
|
|
UnlinkHolopadFromUser(entity, entity.Comp.User.Value);
|
|
|
|
if (TryComp<StationAiCoreComponent>(entity, out var stationAiCore))
|
|
{
|
|
_stationAiSystem.SwitchRemoteEntityMode((entity.Owner, stationAiCore), true);
|
|
|
|
if (TryComp<TelephoneComponent>(entity, out var stationAiCoreTelphone))
|
|
_telephoneSystem.EndTelephoneCalls((entity, stationAiCoreTelphone));
|
|
}
|
|
|
|
Dirty(entity);
|
|
}
|
|
|
|
private void RequestHolopadUserSpriteUpdate(Entity<HolopadUserComponent> user)
|
|
{
|
|
if (!_pendingRequestsForSpriteState.Add(user))
|
|
return;
|
|
|
|
var ev = new PlayerSpriteStateRequest(GetNetEntity(user));
|
|
RaiseNetworkEvent(ev);
|
|
}
|
|
|
|
private void SyncHolopadUserWithLinkedHolograms(Entity<HolopadUserComponent> entity, PrototypeLayerData[]? spriteLayerData)
|
|
{
|
|
foreach (var linkedHolopad in entity.Comp.LinkedHolopads)
|
|
{
|
|
foreach (var receivingHolopad in GetLinkedHolopads(linkedHolopad))
|
|
{
|
|
if (receivingHolopad.Comp.Hologram == null || !_recentlyUpdatedHolograms.Add(receivingHolopad.Comp.Hologram.Value))
|
|
continue;
|
|
|
|
var netHologram = GetNetEntity(receivingHolopad.Comp.Hologram.Value);
|
|
var ev = new PlayerSpriteStateMessage(netHologram, spriteLayerData);
|
|
RaiseNetworkEvent(ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ActivateProjector(Entity<HolopadComponent> entity, EntityUid user)
|
|
{
|
|
if (!TryComp<TelephoneComponent>(entity, out var receiverTelephone))
|
|
return;
|
|
|
|
var receiver = new Entity<TelephoneComponent>(entity, receiverTelephone);
|
|
|
|
if (!TryComp<StationAiHeldComponent>(user, out var userAiHeld))
|
|
return;
|
|
|
|
if (!_stationAiSystem.TryGetStationAiCore((user, userAiHeld), out var stationAiCore) ||
|
|
stationAiCore.Value.Comp.RemoteEntity == null)
|
|
return;
|
|
|
|
if (!TryComp<TelephoneComponent>(stationAiCore, out var stationAiTelephone))
|
|
return;
|
|
|
|
if (!TryComp<HolopadComponent>(stationAiCore, out var stationAiHolopad))
|
|
return;
|
|
|
|
var source = new Entity<TelephoneComponent>(stationAiCore.Value, stationAiTelephone);
|
|
|
|
// Terminate any calls that the core is hosting and immediately connect to the receiver
|
|
_telephoneSystem.TerminateTelephoneCalls(source);
|
|
|
|
var callOptions = new TelephoneCallOptions()
|
|
{
|
|
ForceConnect = true,
|
|
MuteReceiver = true
|
|
};
|
|
|
|
_telephoneSystem.CallTelephone(source, receiver, user, callOptions);
|
|
|
|
if (!_telephoneSystem.IsSourceConnectedToReceiver(source, receiver))
|
|
return;
|
|
|
|
LinkHolopadToUser((stationAiCore.Value, stationAiHolopad), user);
|
|
|
|
// Switch the AI's perspective from free roaming to the target holopad
|
|
_xformSystem.SetCoordinates(stationAiCore.Value.Comp.RemoteEntity.Value, Transform(entity).Coordinates);
|
|
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore.Value, false);
|
|
|
|
// Open the holopad UI if it hasn't been opened yet
|
|
if (TryComp<UserInterfaceComponent>(entity, out var entityUserInterfaceComponent))
|
|
_userInterfaceSystem.OpenUi((entity, entityUserInterfaceComponent), HolopadUiKey.InteractionWindow, user);
|
|
}
|
|
|
|
private void ExecuteBroadcast(Entity<HolopadComponent> source, EntityUid user)
|
|
{
|
|
if (!TryComp<TelephoneComponent>(source, out var sourceTelephone))
|
|
return;
|
|
|
|
var sourceTelephoneEntity = new Entity<TelephoneComponent>(source, sourceTelephone);
|
|
_telephoneSystem.TerminateTelephoneCalls(sourceTelephoneEntity);
|
|
|
|
// Find all holopads in range of the source
|
|
var sourceXform = Transform(source);
|
|
var receivers = new HashSet<Entity<TelephoneComponent>>();
|
|
|
|
var query = AllEntityQuery<HolopadComponent, TelephoneComponent, TransformComponent>();
|
|
while (query.MoveNext(out var receiver, out var receiverHolopad, out var receiverTelephone, out var receiverXform))
|
|
{
|
|
var receiverTelephoneEntity = new Entity<TelephoneComponent>(receiver, receiverTelephone);
|
|
|
|
if (sourceTelephoneEntity == receiverTelephoneEntity ||
|
|
receiverTelephone.UnlistedNumber ||
|
|
!_telephoneSystem.IsSourceAbleToReachReceiver(sourceTelephoneEntity, receiverTelephoneEntity))
|
|
continue;
|
|
|
|
// If any holopads in range are on broadcast cooldown, exit
|
|
if (IsHolopadBroadcastOnCoolDown((receiver, receiverHolopad)))
|
|
return;
|
|
|
|
receivers.Add(receiverTelephoneEntity);
|
|
}
|
|
|
|
var options = new TelephoneCallOptions()
|
|
{
|
|
ForceConnect = true,
|
|
MuteReceiver = true,
|
|
};
|
|
|
|
_telephoneSystem.BroadcastCallToTelephones(sourceTelephoneEntity, receivers, user, options);
|
|
|
|
if (!_telephoneSystem.IsTelephoneEngaged(sourceTelephoneEntity))
|
|
return;
|
|
|
|
// Link to the user after all the calls have been placed,
|
|
// so we only need to sync all the holograms once
|
|
LinkHolopadToUser(source, user);
|
|
|
|
// Lock out the controls of all involved holopads for a set duration
|
|
source.Comp.ControlLockoutOwner = user;
|
|
source.Comp.ControlLockoutStartTime = _timing.CurTime;
|
|
|
|
Dirty(source);
|
|
|
|
foreach (var receiver in GetLinkedHolopads(source))
|
|
{
|
|
receiver.Comp.ControlLockoutOwner = user;
|
|
receiver.Comp.ControlLockoutStartTime = _timing.CurTime;
|
|
|
|
Dirty(receiver);
|
|
}
|
|
}
|
|
|
|
private HashSet<Entity<HolopadComponent>> GetLinkedHolopads(Entity<HolopadComponent> entity)
|
|
{
|
|
var linkedHolopads = new HashSet<Entity<HolopadComponent>>();
|
|
|
|
if (!TryComp<TelephoneComponent>(entity, out var holopadTelephone))
|
|
return linkedHolopads;
|
|
|
|
foreach (var linkedEnt in holopadTelephone.LinkedTelephones)
|
|
{
|
|
if (!TryComp<HolopadComponent>(linkedEnt, out var linkedHolopad))
|
|
continue;
|
|
|
|
linkedHolopads.Add((linkedEnt, linkedHolopad));
|
|
}
|
|
|
|
return linkedHolopads;
|
|
}
|
|
|
|
private void SetHolopadAmbientState(Entity<HolopadComponent> entity, bool isEnabled)
|
|
{
|
|
if (TryComp<PointLightComponent>(entity, out var pointLight))
|
|
_pointLightSystem.SetEnabled(entity, isEnabled, pointLight);
|
|
|
|
if (TryComp<AmbientSoundComponent>(entity, out var ambientSound))
|
|
_ambientSoundSystem.SetAmbience(entity, isEnabled, ambientSound);
|
|
}
|
|
}
|