using Content.Server.Chat.Systems;
+using Content.Server.Popups;
using Content.Server.Power.EntitySystems;
using Content.Server.Speech.Components;
using Content.Server.Telephone;
[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;
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<TelephoneComponent>(stationAiCore, out var stationAiTelephone) &&
+ TryComp<TelephoneComponent>(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;
}
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
!_stationAiSystem.TryGetStationAiCore((args.Actor, stationAiHeld), out var stationAiCore))
return;
- if (TryComp<TelephoneComponent>(stationAiCore, out var telephone))
+ if (TryComp<TelephoneComponent>(stationAiCore, out var telephone) &&
+ telephone.CurrentState != TelephoneState.EndingCall && telephone.CurrentState != TelephoneState.Idle)
_telephoneSystem.EndTelephoneCalls((stationAiCore.Value, telephone));
}
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")),
};
{
_stationAiSystem.SwitchRemoteEntityMode((entity.Owner, stationAiCore), true);
- if (TryComp<TelephoneComponent>(entity, out var stationAiCoreTelphone))
+ if (TryComp<TelephoneComponent>(entity, out var stationAiCoreTelphone) &&
+ stationAiCoreTelphone.CurrentState != TelephoneState.EndingCall && stationAiCoreTelphone.CurrentState != TelephoneState.Idle)
_telephoneSystem.EndTelephoneCalls((entity, stationAiCoreTelphone));
}
var source = new Entity<TelephoneComponent>(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);
public void EndTelephoneCalls(Entity<TelephoneComponent> 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();
public void TerminateTelephoneCalls(Entity<TelephoneComponent> 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<TelephoneComponent> entity, TelephoneState newState)
{
- if (entity.Comp.CurrentState == newState)
- return;
-
foreach (var linkedTelephone in entity.Comp.LinkedTelephones)
{
if (!linkedTelephone.Comp.LinkedTelephones.Remove(entity))
public bool IsSourceInRangeOfReceiver(Entity<TelephoneComponent> source, Entity<TelephoneComponent> 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;
public TelephoneVolume SpeakerVolume = TelephoneVolume.Whisper;
/// <summary>
- /// The range at which the telephone can connect to another
+ /// The maximum range at which the telephone initiate a call with another
/// </summary>
[DataField]
public TelephoneRange TransmissionRange = TelephoneRange.Grid;
+ /// <summary>
+ /// This telephone will ignore devices that share the same grid as it
+ /// </summary>
+ /// <remarks>
+ /// This bool will be ignored if the <see cref="TransmissionRange"/> is
+ /// set to <see cref="TelephoneRange.Grid"/>
+ /// </remarks>
+ [DataField]
+ public bool IgnoreTelephonesOnSameGrid = false;
+
+ /// <summary>
+ /// The telephone can only connect with other telephones which have a
+ /// <see cref="TransmissionRange"/> present in this list
+ /// </summary>
+ [DataField]
+ public List<TelephoneRange> CompatibleRanges = new List<TelephoneRange>() { TelephoneRange.Grid };
+
/// <summary>
/// The range at which the telephone picks up voices
/// </summary>
public bool RequiresPower = true;
/// <summary>
- /// This telephone does not appear on public telephone directories
+ /// This telephone should not appear on public telephone directories
/// </summary>
[DataField]
public bool UnlistedNumber = false;
[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
}
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
Empty: { state: ai_empty }
Occupied: { state: ai }
- type: Telephone
+ compatibleRanges:
+ - Grid
+ - Map
+ - Unlimited
listeningRange: 0
speakerVolume: Speak
unlistedNumber: true
- type: StationAiVision
- type: Sprite
sprite: Structures/Machines/holopad.rsi
+ drawdepth: FloorObjects
snapCardinals: true
layers:
- state: base
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" ]]
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