diff --git a/Content.Server/Holopad/HolopadSystem.cs b/Content.Server/Holopad/HolopadSystem.cs index bd36d38bee..549bacc1a8 100644 --- a/Content.Server/Holopad/HolopadSystem.cs +++ b/Content.Server/Holopad/HolopadSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Systems; +using Content.Server.Popups; using Content.Server.Power.EntitySystems; using Content.Server.Speech.Components; using Content.Server.Telephone; @@ -31,6 +32,7 @@ public sealed class HolopadSystem : SharedHolopadSystem [Dependency] private readonly SharedStationAiSystem _stationAiSystem = default!; [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly IGameTiming _timing = default!; private float _updateTimer = 1.0f; @@ -117,7 +119,22 @@ public sealed class HolopadSystem : SharedHolopadSystem var source = GetLinkedHolopads(receiver).FirstOrNull(); if (source != null) + { + // Close any AI request windows + if (_stationAiSystem.TryGetStationAiCore(args.Actor, out var stationAiCore) && stationAiCore != null) + _userInterfaceSystem.CloseUi(receiver.Owner, HolopadUiKey.AiRequestWindow, args.Actor); + + // Try to warn the AI if the source of the call is out of its range + if (TryComp(stationAiCore, out var stationAiTelephone) && + TryComp(source, out var sourceTelephone) && + !_telephoneSystem.IsSourceInRangeOfReceiver((stationAiCore.Value.Owner, stationAiTelephone), (source.Value.Owner, sourceTelephone))) + { + _popupSystem.PopupEntity(Loc.GetString("holopad-ai-is-unable-to-reach-holopad"), receiver, args.Actor); + return; + } + ActivateProjector(source.Value, args.Actor); + } return; } @@ -134,7 +151,8 @@ public sealed class HolopadSystem : SharedHolopadSystem if (IsHolopadControlLocked(entity, args.Actor)) return; - _telephoneSystem.EndTelephoneCalls((entity, entityTelephone)); + if (entityTelephone.CurrentState != TelephoneState.EndingCall && entityTelephone.CurrentState != TelephoneState.Idle) + _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 @@ -142,7 +160,8 @@ public sealed class HolopadSystem : SharedHolopadSystem !_stationAiSystem.TryGetStationAiCore((args.Actor, stationAiHeld), out var stationAiCore)) return; - if (TryComp(stationAiCore, out var telephone)) + if (TryComp(stationAiCore, out var telephone) && + telephone.CurrentState != TelephoneState.EndingCall && telephone.CurrentState != TelephoneState.Idle) _telephoneSystem.EndTelephoneCalls((stationAiCore.Value, telephone)); } @@ -414,7 +433,7 @@ public sealed class HolopadSystem : SharedHolopadSystem AlternativeVerb verb = new() { Act = () => ActivateProjector(entity, user), - Text = Loc.GetString("activate-holopad-projector-verb"), + Text = Loc.GetString("holopad-activate-projector-verb"), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")), }; @@ -594,7 +613,8 @@ public sealed class HolopadSystem : SharedHolopadSystem { _stationAiSystem.SwitchRemoteEntityMode((entity.Owner, stationAiCore), true); - if (TryComp(entity, out var stationAiCoreTelphone)) + if (TryComp(entity, out var stationAiCoreTelphone) && + stationAiCoreTelphone.CurrentState != TelephoneState.EndingCall && stationAiCoreTelphone.CurrentState != TelephoneState.Idle) _telephoneSystem.EndTelephoneCalls((entity, stationAiCoreTelphone)); } @@ -648,6 +668,9 @@ public sealed class HolopadSystem : SharedHolopadSystem var source = new Entity(stationAiCore.Value, stationAiTelephone); + if (!_telephoneSystem.IsSourceInRangeOfReceiver(source, receiver)) + return; + // Terminate any calls that the core is hosting and immediately connect to the receiver _telephoneSystem.TerminateTelephoneCalls(source); diff --git a/Content.Server/Telephone/TelephoneSystem.cs b/Content.Server/Telephone/TelephoneSystem.cs index 8507f4d507..b81a72ad23 100644 --- a/Content.Server/Telephone/TelephoneSystem.cs +++ b/Content.Server/Telephone/TelephoneSystem.cs @@ -277,6 +277,10 @@ public sealed class TelephoneSystem : SharedTelephoneSystem public void EndTelephoneCalls(Entity entity) { + // No need to end any calls if the telephone is already ending a call + if (entity.Comp.CurrentState == TelephoneState.EndingCall) + return; + HandleEndingTelephoneCalls(entity, TelephoneState.EndingCall); var ev = new TelephoneCallEndedEvent(); @@ -285,14 +289,15 @@ public sealed class TelephoneSystem : SharedTelephoneSystem public void TerminateTelephoneCalls(Entity entity) { + // No need to terminate any calls if the telephone is idle + if (entity.Comp.CurrentState == TelephoneState.Idle) + return; + HandleEndingTelephoneCalls(entity, TelephoneState.Idle); } private void HandleEndingTelephoneCalls(Entity entity, TelephoneState newState) { - if (entity.Comp.CurrentState == newState) - return; - foreach (var linkedTelephone in entity.Comp.LinkedTelephones) { if (!linkedTelephone.Comp.LinkedTelephones.Remove(entity)) @@ -431,23 +436,26 @@ public sealed class TelephoneSystem : SharedTelephoneSystem public bool IsSourceInRangeOfReceiver(Entity source, Entity receiver) { + // Check if the source and receiver have compatible transmision / reception bandwidths + if (!source.Comp.CompatibleRanges.Contains(receiver.Comp.TransmissionRange)) + return false; + var sourceXform = Transform(source); var receiverXform = Transform(receiver); + // Check if we should ignore a device thats on the same grid + if (source.Comp.IgnoreTelephonesOnSameGrid && + source.Comp.TransmissionRange != TelephoneRange.Grid && + receiverXform.GridUid == sourceXform.GridUid) + return false; + switch (source.Comp.TransmissionRange) { case TelephoneRange.Grid: - return sourceXform.GridUid != null && - receiverXform.GridUid == sourceXform.GridUid && - receiver.Comp.TransmissionRange != TelephoneRange.Long; + return sourceXform.GridUid == receiverXform.GridUid; case TelephoneRange.Map: - return sourceXform.MapID == receiverXform.MapID && - receiver.Comp.TransmissionRange != TelephoneRange.Long; - - case TelephoneRange.Long: - return sourceXform.MapID != receiverXform.MapID && - receiver.Comp.TransmissionRange == TelephoneRange.Long; + return sourceXform.MapID == receiverXform.MapID; case TelephoneRange.Unlimited: return true; diff --git a/Content.Shared/Telephone/TelephoneComponent.cs b/Content.Shared/Telephone/TelephoneComponent.cs index 7eacdb0aee..733ddfbdce 100644 --- a/Content.Shared/Telephone/TelephoneComponent.cs +++ b/Content.Shared/Telephone/TelephoneComponent.cs @@ -52,11 +52,28 @@ public sealed partial class TelephoneComponent : Component public TelephoneVolume SpeakerVolume = TelephoneVolume.Whisper; /// - /// The range at which the telephone can connect to another + /// The maximum range at which the telephone initiate a call with another /// [DataField] public TelephoneRange TransmissionRange = TelephoneRange.Grid; + /// + /// This telephone will ignore devices that share the same grid as it + /// + /// + /// This bool will be ignored if the is + /// set to + /// + [DataField] + public bool IgnoreTelephonesOnSameGrid = false; + + /// + /// The telephone can only connect with other telephones which have a + /// present in this list + /// + [DataField] + public List CompatibleRanges = new List() { TelephoneRange.Grid }; + /// /// The range at which the telephone picks up voices /// @@ -70,7 +87,7 @@ public sealed partial class TelephoneComponent : Component public bool RequiresPower = true; /// - /// This telephone does not appear on public telephone directories + /// This telephone should not appear on public telephone directories /// [DataField] public bool UnlistedNumber = false; @@ -196,8 +213,7 @@ public enum TelephoneVolume : byte [Serializable, NetSerializable] public enum TelephoneRange : byte { - Grid, // Can call grid/map range telephones that are on the same grid - Map, // Can call grid/map range telephones that are on the same map - Long, // Can only long range telephones that are on a different map - Unlimited // Can call any telephone + Grid, // Can only reach telephones that are on the same grid + Map, // Can reach any telephone that is on the same map + Unlimited, // Can reach any telephone, across any distance } diff --git a/Resources/Locale/en-US/holopad/holopad.ftl b/Resources/Locale/en-US/holopad/holopad.ftl index 4c2d92e253..47c5fbe25c 100644 --- a/Resources/Locale/en-US/holopad/holopad.ftl +++ b/Resources/Locale/en-US/holopad/holopad.ftl @@ -37,7 +37,8 @@ holopad-window-flavor-right = v3.0.9 holopad-hologram-name = hologram of {THE($name)} # Holopad actions -activate-holopad-projector-verb = Activate holopad projector +holopad-activate-projector-verb = Activate holopad projector +holopad-ai-is-unable-to-reach-holopad = You are unable to interface with the source of the call, it is too far from your core. # Mapping prototypes # General diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 9a9d66c42d..272e28e91c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -369,6 +369,10 @@ Empty: { state: ai_empty } Occupied: { state: ai } - type: Telephone + compatibleRanges: + - Grid + - Map + - Unlimited listeningRange: 0 speakerVolume: Speak unlistedNumber: true diff --git a/Resources/Prototypes/Entities/Structures/Machines/holopad.yml b/Resources/Prototypes/Entities/Structures/Machines/holopad.yml index a5ac0d5208..d3b02fcae0 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/holopad.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/holopad.yml @@ -22,6 +22,7 @@ - type: StationAiVision - type: Sprite sprite: Structures/Machines/holopad.rsi + drawdepth: FloorObjects snapCardinals: true layers: - state: base @@ -71,9 +72,8 @@ speechSounds: Borg speechBubbleOffset: 0.45 - type: Telephone - transmissionRange: Map ringTone: /Audio/Machines/double_ring.ogg - listeningRange: 4 + listeningRange: 2.5 speakerVolume: Speak - type: AccessReader access: [[ "Command" ]] @@ -104,28 +104,47 @@ node: machineFrame - !type:DoActsBehavior acts: ["Destruction"] - + - type: entity name: long-range holopad - description: "A floor-mounted device for projecting holographic images to other devices that are far away." + description: "A floor-mounted device for projecting holographic images to similar devices that are far away." parent: Holopad id: HolopadLongRange - suffix: For calls between maps components: - type: Telephone - transmissionRange: Long - + transmissionRange: Map + compatibleRanges: + - Map + - Unlimited + ignoreTelephonesOnSameGrid: true + - type: entity name: quantum entangling holopad - description: "An experimental floor-mounted device for projecting holographic images at extreme distances." + description: "An floor-mounted device for projecting holographic images to similar devices at extreme distances." parent: Holopad id: HolopadUnlimitedRange - suffix: Unlimited range components: - type: Telephone transmissionRange: Unlimited - - type: AccessReader - access: [[]] + compatibleRanges: + - Map + - Unlimited + ignoreTelephonesOnSameGrid: true + +- type: entity + name: bluespace holopad + description: "An experimental floor-mounted device for projecting holographic images via bluespace." + parent: Holopad + id: HolopadBluespace + suffix: Unrestricted range + components: + - type: Telephone + unlistedNumber: true + transmissionRange: Unlimited + compatibleRanges: + - Grid + - Map + - Unlimited # These are spawned by holopads - type: entity