diff --git a/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs new file mode 100644 index 0000000000..75868e08d9 --- /dev/null +++ b/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Medical.SuitSensors; + +namespace Content.Client.Medical.SuitSensors; + +public sealed class SuitSensorSystem : SharedSuitSensorSystem; diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index cb0fbd736c..7af093b178 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -1,65 +1,25 @@ -using System.Numerics; -using Content.Server.Access.Systems; using Content.Server.DeviceNetwork.Systems; using Content.Server.Emp; using Content.Server.Medical.CrewMonitoring; -using Content.Server.Popups; -using Content.Server.Station.Systems; -using Content.Shared.ActionBlocker; -using Content.Shared.Clothing; -using Content.Shared.Damage; -using Content.Shared.DeviceNetwork; -using Content.Shared.DoAfter; -using Content.Shared.Examine; -using Content.Shared.GameTicking; -using Content.Shared.Interaction; -using Content.Shared.Inventory; -using Content.Shared.Medical.SuitSensor; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Content.Shared.Verbs; -using Robust.Shared.Containers; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; using Content.Shared.DeviceNetwork.Components; +using Content.Shared.Medical.SuitSensor; +using Content.Shared.Medical.SuitSensors; +using Robust.Shared.Timing; namespace Content.Server.Medical.SuitSensors; -public sealed class SuitSensorSystem : EntitySystem +public sealed class SuitSensorSystem : SharedSuitSensorSystem { [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; - [Dependency] private readonly IdCardSystem _idCardSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly SingletonDeviceNetServerSystem _singletonServerSystem = default!; - [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; - [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly InventorySystem _inventory = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPlayerSpawn); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnEquipped); - SubscribeLocalEvent(OnUnequipped); - SubscribeLocalEvent(OnExamine); - SubscribeLocalEvent>(OnVerb); - SubscribeLocalEvent(OnInsert); - SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnEmpFinished); - SubscribeLocalEvent(OnSuitSensorDoAfter); } public override void Update(float frameTime) @@ -78,14 +38,13 @@ public sealed class SuitSensorSystem : EntitySystem if (curTime < sensor.NextUpdate) continue; - if (!CheckSensorAssignedStation(uid, sensor)) + if (!CheckSensorAssignedStation((uid, sensor))) continue; - // TODO: This would cause imprecision at different tick rates. - sensor.NextUpdate = curTime + sensor.UpdateRate; + sensor.NextUpdate += sensor.UpdateRate; // get sensor status - var status = GetSensorState(uid, sensor); + var status = GetSensorState((uid, sensor)); if (status == null) continue; @@ -112,399 +71,21 @@ public sealed class SuitSensorSystem : EntitySystem } } - /// - /// Checks whether the sensor is assigned to a station or not - /// and tries to assign an unassigned sensor to a station if it's currently on a grid - /// - /// True if the sensor is assigned to a station or assigning it was successful. False otherwise. - private bool CheckSensorAssignedStation(EntityUid uid, SuitSensorComponent sensor) - { - if (!sensor.StationId.HasValue && Transform(uid).GridUid == null) - return false; - - sensor.StationId = _stationSystem.GetOwningStation(uid); - return sensor.StationId.HasValue; - } - - private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev) - { - // If the player spawns in arrivals then the grid underneath them may not be appropriate. - // in which case we'll just use the station spawn code told us they are attached to and set all of their - // sensors. - var sensorQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - RecursiveSensor(ev.Mob, ev.Station, sensorQuery, xformQuery); - } - - private void RecursiveSensor(EntityUid uid, EntityUid stationUid, EntityQuery sensorQuery, EntityQuery xformQuery) - { - var xform = xformQuery.GetComponent(uid); - var enumerator = xform.ChildEnumerator; - - while (enumerator.MoveNext(out var child)) - { - if (sensorQuery.TryGetComponent(child, out var sensor)) - { - sensor.StationId = stationUid; - } - - RecursiveSensor(child, stationUid, sensorQuery, xformQuery); - } - } - - private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEvent args) - { - // Fallback - component.StationId ??= _stationSystem.GetOwningStation(uid); - - // generate random mode - if (component.RandomMode) - { - //make the sensor mode favor higher levels, except coords. - var modesDist = new[] - { - SuitSensorMode.SensorOff, - SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary, - SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, - SuitSensorMode.SensorCords, SuitSensorMode.SensorCords - }; - component.Mode = _random.Pick(modesDist); - } - } - - private void OnEquipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotEquippedEvent args) - { - component.User = args.Wearer; - } - - private void OnUnequipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotUnequippedEvent args) - { - component.User = null; - } - - private void OnExamine(EntityUid uid, SuitSensorComponent component, ExaminedEvent args) - { - if (!args.IsInDetailsRange) - return; - - string msg; - switch (component.Mode) - { - case SuitSensorMode.SensorOff: - msg = "suit-sensor-examine-off"; - break; - case SuitSensorMode.SensorBinary: - msg = "suit-sensor-examine-binary"; - break; - case SuitSensorMode.SensorVitals: - msg = "suit-sensor-examine-vitals"; - break; - case SuitSensorMode.SensorCords: - msg = "suit-sensor-examine-cords"; - break; - default: - return; - } - - args.PushMarkup(Loc.GetString(msg)); - } - - private void OnVerb(EntityUid uid, SuitSensorComponent component, GetVerbsEvent args) - { - // check if user can change sensor - if (component.ControlsLocked) - return; - - // standard interaction checks - if (!args.CanInteract || args.Hands == null) - return; - - if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) - return; - - // check if target is incapacitated (cuffed, dead, etc) - if (component.User != null && args.User != component.User && _actionBlocker.CanInteract(component.User.Value, null)) - return; - - args.Verbs.UnionWith(new[] - { - CreateVerb(uid, component, args.User, SuitSensorMode.SensorOff), - CreateVerb(uid, component, args.User, SuitSensorMode.SensorBinary), - CreateVerb(uid, component, args.User, SuitSensorMode.SensorVitals), - CreateVerb(uid, component, args.User, SuitSensorMode.SensorCords) - }); - } - - private void OnInsert(EntityUid uid, SuitSensorComponent component, EntGotInsertedIntoContainerMessage args) - { - if (args.Container.ID != component.ActivationContainer) - return; - - component.User = args.Container.Owner; - } - - private void OnRemove(EntityUid uid, SuitSensorComponent component, EntGotRemovedFromContainerMessage args) - { - if (args.Container.ID != component.ActivationContainer) - return; - - component.User = null; - } - - private void OnEmpPulse(EntityUid uid, SuitSensorComponent component, ref EmpPulseEvent args) + private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) { args.Affected = true; args.Disabled = true; - component.PreviousMode = component.Mode; - SetSensor((uid, component), SuitSensorMode.SensorOff, null); + ent.Comp.PreviousMode = ent.Comp.Mode; + SetSensor(ent.AsNullable(), SuitSensorMode.SensorOff, null); - component.PreviousControlsLocked = component.ControlsLocked; - component.ControlsLocked = true; + ent.Comp.PreviousControlsLocked = ent.Comp.ControlsLocked; + ent.Comp.ControlsLocked = true; } - private void OnEmpFinished(EntityUid uid, SuitSensorComponent component, ref EmpDisabledRemoved args) + private void OnEmpFinished(Entity ent, ref EmpDisabledRemoved args) { - SetSensor((uid, component), component.PreviousMode, null); - component.ControlsLocked = component.PreviousControlsLocked; - } - - private Verb CreateVerb(EntityUid uid, SuitSensorComponent component, EntityUid userUid, SuitSensorMode mode) - { - return new Verb() - { - Text = GetModeName(mode), - Disabled = component.Mode == mode, - Priority = -(int) mode, // sort them in descending order - Category = VerbCategory.SetSensor, - Act = () => TrySetSensor((uid, component), mode, userUid) - }; - } - - private string GetModeName(SuitSensorMode mode) - { - string name; - switch (mode) - { - case SuitSensorMode.SensorOff: - name = "suit-sensor-mode-off"; - break; - case SuitSensorMode.SensorBinary: - name = "suit-sensor-mode-binary"; - break; - case SuitSensorMode.SensorVitals: - name = "suit-sensor-mode-vitals"; - break; - case SuitSensorMode.SensorCords: - name = "suit-sensor-mode-cords"; - break; - default: - return ""; - } - - return Loc.GetString(name); - } - - public void TrySetSensor(Entity sensors, SuitSensorMode mode, EntityUid userUid) - { - var comp = sensors.Comp; - - if (!Resolve(sensors, ref comp)) - return; - - if (comp.User == null || userUid == comp.User) - SetSensor(sensors, mode, userUid); - else - { - var doAfterEvent = new SuitSensorChangeDoAfterEvent(mode); - var doAfterArgs = new DoAfterArgs(EntityManager, userUid, comp.SensorsTime, doAfterEvent, sensors) - { - BreakOnMove = true, - BreakOnDamage = true - }; - - _doAfterSystem.TryStartDoAfter(doAfterArgs); - } - } - - private void OnSuitSensorDoAfter(Entity sensors, ref SuitSensorChangeDoAfterEvent args) - { - if (args.Handled || args.Cancelled) - return; - - SetSensor(sensors, args.Mode, args.User); - } - - public void SetSensor(Entity sensors, SuitSensorMode mode, EntityUid? userUid = null) - { - var comp = sensors.Comp; - - comp.Mode = mode; - - if (userUid != null) - { - var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode))); - _popupSystem.PopupEntity(msg, sensors, userUid.Value); - } - } - - /// - /// Set all suit sensors on the equipment someone is wearing to the specified mode. - /// - public void SetAllSensors(EntityUid target, SuitSensorMode mode, SlotFlags slots = SlotFlags.All ) - { - // iterate over all inventory slots - var slotEnumerator = _inventory.GetSlotEnumerator(target, slots); - while (slotEnumerator.NextItem(out var item, out _)) - { - if (TryComp(item, out var sensorComp)) - SetSensor((item, sensorComp), mode); - } - } - - public SuitSensorStatus? GetSensorState(EntityUid uid, SuitSensorComponent? sensor = null, TransformComponent? transform = null) - { - if (!Resolve(uid, ref sensor, ref transform)) - return null; - - // check if sensor is enabled and worn by user - if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp(sensor.User) || transform.GridUid == null) - return null; - - // try to get mobs id from ID slot - var userName = Loc.GetString("suit-sensor-component-unknown-name"); - var userJob = Loc.GetString("suit-sensor-component-unknown-job"); - var userJobIcon = "JobIconNoId"; - var userJobDepartments = new List(); - - if (_idCardSystem.TryFindIdCard(sensor.User.Value, out var card)) - { - if (card.Comp.FullName != null) - userName = card.Comp.FullName; - if (card.Comp.LocalizedJobTitle != null) - userJob = card.Comp.LocalizedJobTitle; - userJobIcon = card.Comp.JobIcon; - - foreach (var department in card.Comp.JobDepartments) - userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name)); - } - - // get health mob state - var isAlive = false; - if (TryComp(sensor.User.Value, out MobStateComponent? mobState)) - isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState); - - // get mob total damage - var totalDamage = 0; - if (TryComp(sensor.User.Value, out var damageable)) - totalDamage = damageable.TotalDamage.Int(); - - // Get mob total damage crit threshold - int? totalDamageThreshold = null; - if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold)) - totalDamageThreshold = critThreshold.Value.Int(); - - // finally, form suit sensor status - var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(uid), userName, userJob, userJobIcon, userJobDepartments); - switch (sensor.Mode) - { - case SuitSensorMode.SensorBinary: - status.IsAlive = isAlive; - break; - case SuitSensorMode.SensorVitals: - status.IsAlive = isAlive; - status.TotalDamage = totalDamage; - status.TotalDamageThreshold = totalDamageThreshold; - break; - case SuitSensorMode.SensorCords: - status.IsAlive = isAlive; - status.TotalDamage = totalDamage; - status.TotalDamageThreshold = totalDamageThreshold; - EntityCoordinates coordinates; - var xformQuery = GetEntityQuery(); - - if (transform.GridUid != null) - { - coordinates = new EntityCoordinates(transform.GridUid.Value, - Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery), - _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery))); - } - else if (transform.MapUid != null) - { - coordinates = new EntityCoordinates(transform.MapUid.Value, - _transform.GetWorldPosition(transform, xformQuery)); - } - else - { - coordinates = EntityCoordinates.Invalid; - } - - status.Coordinates = GetNetCoordinates(coordinates); - break; - } - - return status; - } - - /// - /// Serialize create a device network package from the suit sensors status. - /// - public NetworkPayload SuitSensorToPacket(SuitSensorStatus status) - { - var payload = new NetworkPayload() - { - [DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState, - [SuitSensorConstants.NET_NAME] = status.Name, - [SuitSensorConstants.NET_JOB] = status.Job, - [SuitSensorConstants.NET_JOB_ICON] = status.JobIcon, - [SuitSensorConstants.NET_JOB_DEPARTMENTS] = status.JobDepartments, - [SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive, - [SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid, - [SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid, - }; - - if (status.TotalDamage != null) - payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage); - if (status.TotalDamageThreshold != null) - payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold); - if (status.Coordinates != null) - payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates); - - return payload; - } - - /// - /// Try to create the suit sensors status from the device network message - /// - public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload) - { - // check command - if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command)) - return null; - if (command != DeviceNetworkConstants.CmdUpdatedState) - return null; - - // check name, job and alive - if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_ICON, out string? jobIcon)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_DEPARTMENTS, out List? jobDepartments)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null; - - // try get total damage and cords (optionals) - payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage); - payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold); - payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords); - - var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments) - { - IsAlive = isAlive.Value, - TotalDamage = totalDamage, - TotalDamageThreshold = totalDamageThreshold, - Coordinates = coords, - }; - return status; + SetSensor(ent.AsNullable(), ent.Comp.PreviousMode, null); + ent.Comp.ControlsLocked = ent.Comp.PreviousControlsLocked; } } diff --git a/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs b/Content.Shared/Medical/SuitSensors/SharedSuitSensor.cs similarity index 100% rename from Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs rename to Content.Shared/Medical/SuitSensors/SharedSuitSensor.cs diff --git a/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs new file mode 100644 index 0000000000..2ed1089ba8 --- /dev/null +++ b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs @@ -0,0 +1,468 @@ +using System.Numerics; +using Content.Shared.Access.Systems; +using Content.Shared.ActionBlocker; +using Content.Shared.Clothing; +using Content.Shared.Damage; +using Content.Shared.DeviceNetwork; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.GameTicking; +using Content.Shared.Interaction; +using Content.Shared.Inventory; +using Content.Shared.Medical.SuitSensor; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; +using Content.Shared.Station; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Shared.Medical.SuitSensors; + +public abstract class SharedSuitSensorSystem : EntitySystem +{ + [Dependency] private readonly SharedStationSystem _stationSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly SharedIdCardSystem _idCardSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private EntityQuery _sensorQuery; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnPlayerSpawn); + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnequipped); + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent>(OnVerb); + SubscribeLocalEvent(OnInsert); + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnSuitSensorDoAfter); + + _sensorQuery = GetEntityQuery(); + } + + /// + /// Checks whether the sensor is assigned to a station or not + /// and tries to assign an unassigned sensor to a station if it's currently on a grid. + /// + /// True if the sensor is assigned to a station or assigning it was successful. False otherwise. + public bool CheckSensorAssignedStation(Entity sensor) + { + if (!sensor.Comp.StationId.HasValue && Transform(sensor.Owner).GridUid == null) + return false; + + sensor.Comp.StationId = _stationSystem.GetOwningStation(sensor.Owner); + Dirty(sensor); + return sensor.Comp.StationId.HasValue; + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + // Fallback + ent.Comp.StationId ??= _stationSystem.GetOwningStation(ent.Owner); + + // generate random mode + if (ent.Comp.RandomMode) + { + //make the sensor mode favor higher levels, except coords. + var modesDist = new[] + { + SuitSensorMode.SensorOff, + SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary, + SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, + SuitSensorMode.SensorCords, SuitSensorMode.SensorCords + }; + ent.Comp.Mode = _random.Pick(modesDist); + } + + ent.Comp.NextUpdate = _timing.CurTime; + Dirty(ent); + } + + private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev) + { + // If the player spawns in arrivals then the grid underneath them may not be appropriate. + // in which case we'll just use the station spawn code told us they are attached to and set all of their + // sensors. + RecursiveSensor(ev.Mob, ev.Station); + } + + private void RecursiveSensor(EntityUid uid, EntityUid stationUid) + { + var xform = Transform(uid); + var enumerator = xform.ChildEnumerator; + + while (enumerator.MoveNext(out var child)) + { + if (_sensorQuery.TryComp(child, out var sensor)) + { + sensor.StationId = stationUid; + Dirty(child, sensor); + } + + RecursiveSensor(child, stationUid); + } + } + + private void OnEquipped(Entity ent, ref ClothingGotEquippedEvent args) + { + ent.Comp.User = args.Wearer; + Dirty(ent); + } + + private void OnUnequipped(Entity ent, ref ClothingGotUnequippedEvent args) + { + ent.Comp.User = null; + Dirty(ent); + } + + private void OnExamine(Entity ent, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + string msg; + switch (ent.Comp.Mode) + { + case SuitSensorMode.SensorOff: + msg = "suit-sensor-examine-off"; + break; + case SuitSensorMode.SensorBinary: + msg = "suit-sensor-examine-binary"; + break; + case SuitSensorMode.SensorVitals: + msg = "suit-sensor-examine-vitals"; + break; + case SuitSensorMode.SensorCords: + msg = "suit-sensor-examine-cords"; + break; + default: + return; + } + + args.PushMarkup(Loc.GetString(msg)); + } + + private void OnVerb(Entity ent, ref GetVerbsEvent args) + { + // check if user can change sensor + if (ent.Comp.ControlsLocked) + return; + + // standard interaction checks + if (!args.CanInteract || args.Hands == null) + return; + + if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) + return; + + // check if target is incapacitated (cuffed, dead, etc) + if (ent.Comp.User != null && args.User != ent.Comp.User && _actionBlocker.CanInteract(ent.Comp.User.Value, null)) + return; + + args.Verbs.UnionWith(new[] + { + CreateVerb(ent, args.User, SuitSensorMode.SensorOff), + CreateVerb(ent, args.User, SuitSensorMode.SensorBinary), + CreateVerb(ent, args.User, SuitSensorMode.SensorVitals), + CreateVerb(ent, args.User, SuitSensorMode.SensorCords) + }); + } + + private void OnInsert(Entity ent, ref EntGotInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.ActivationContainer) + return; + + ent.Comp.User = args.Container.Owner; + Dirty(ent); + } + + private void OnRemove(Entity ent, ref EntGotRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.ActivationContainer) + return; + + ent.Comp.User = null; + Dirty(ent); + } + + private Verb CreateVerb(Entity ent, EntityUid userUid, SuitSensorMode mode) + { + return new Verb() + { + Text = GetModeName(mode), + Disabled = ent.Comp.Mode == mode, + Priority = -(int)mode, // sort them in descending order + Category = VerbCategory.SetSensor, + Act = () => TrySetSensor(ent.AsNullable(), mode, userUid) + }; + } + + public string GetModeName(SuitSensorMode mode) + { + string name; + switch (mode) + { + case SuitSensorMode.SensorOff: + name = "suit-sensor-mode-off"; + break; + case SuitSensorMode.SensorBinary: + name = "suit-sensor-mode-binary"; + break; + case SuitSensorMode.SensorVitals: + name = "suit-sensor-mode-vitals"; + break; + case SuitSensorMode.SensorCords: + name = "suit-sensor-mode-cords"; + break; + default: + return ""; + } + + return Loc.GetString(name); + } + + /// + /// Attempts to set mode of the entity to the selected in params. + /// Works instantly if the user is the player wearing the sensors and will start a DoAfter otherwise. + /// + /// Entity and its component that should be changed. + /// Selected mode + /// userUid, when not equal to the , creates doafter + public bool TrySetSensor(Entity sensors, SuitSensorMode mode, EntityUid userUid) + { + if (!Resolve(sensors, ref sensors.Comp, false)) + return false; + + if (sensors.Comp.User == null || userUid == sensors.Comp.User) + SetSensor(sensors, mode, userUid); + else + { + var doAfterEvent = new SuitSensorChangeDoAfterEvent(mode); + var doAfterArgs = new DoAfterArgs(EntityManager, userUid, sensors.Comp.SensorsTime, doAfterEvent, sensors) + { + BreakOnMove = true, + BreakOnDamage = true + }; + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + } + return true; + } + + private void OnSuitSensorDoAfter(Entity sensors, ref SuitSensorChangeDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + SetSensor(sensors.AsNullable(), args.Mode, args.User); + } + + /// + /// Sets mode of the of the chosen entity. + /// Makes popup when not null + /// + /// Entity and it's component that should be changed + /// Selected mode + /// uid, required for the popup + public void SetSensor(Entity sensors, SuitSensorMode mode, EntityUid? userUid = null) + { + if (!Resolve(sensors, ref sensors.Comp, false)) + return; + + sensors.Comp.Mode = mode; + Dirty(sensors); + + if (userUid != null) + { + var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode))); + _popupSystem.PopupClient(msg, sensors, userUid.Value); + } + } + + /// + /// Set all suit sensors on the equipment someone is wearing to the specified mode. + /// + public void SetAllSensors(EntityUid target, SuitSensorMode mode, SlotFlags slots = SlotFlags.All) + { + // iterate over all inventory slots + var slotEnumerator = _inventory.GetSlotEnumerator(target, slots); + while (slotEnumerator.NextItem(out var item, out _)) + { + if (TryComp(item, out var sensorComp)) + SetSensor((item, sensorComp), mode); + } + } + + /// + /// Attempts to get full from the + /// + /// Entity to get status + /// Full of the chosen uid + public SuitSensorStatus? GetSensorState(Entity ent) + { + if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false)) + return null; + + var sensor = ent.Comp1; + var transform = ent.Comp2; + + // check if sensor is enabled and worn by user + if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp(sensor.User) || transform.GridUid == null) + return null; + + // try to get mobs id from ID slot + var userName = Loc.GetString("suit-sensor-component-unknown-name"); + var userJob = Loc.GetString("suit-sensor-component-unknown-job"); + var userJobIcon = "JobIconNoId"; + var userJobDepartments = new List(); + + if (_idCardSystem.TryFindIdCard(sensor.User.Value, out var card)) + { + if (card.Comp.FullName != null) + userName = card.Comp.FullName; + if (card.Comp.LocalizedJobTitle != null) + userJob = card.Comp.LocalizedJobTitle; + userJobIcon = card.Comp.JobIcon; + + foreach (var department in card.Comp.JobDepartments) + userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name)); + } + + // get health mob state + var isAlive = false; + if (TryComp(sensor.User.Value, out MobStateComponent? mobState)) + isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState); + + // get mob total damage + var totalDamage = 0; + if (TryComp(sensor.User.Value, out var damageable)) + totalDamage = damageable.TotalDamage.Int(); + + // Get mob total damage crit threshold + int? totalDamageThreshold = null; + if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold)) + totalDamageThreshold = critThreshold.Value.Int(); + + // finally, form suit sensor status + var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(ent.Owner), userName, userJob, userJobIcon, userJobDepartments); + switch (sensor.Mode) + { + case SuitSensorMode.SensorBinary: + status.IsAlive = isAlive; + break; + case SuitSensorMode.SensorVitals: + status.IsAlive = isAlive; + status.TotalDamage = totalDamage; + status.TotalDamageThreshold = totalDamageThreshold; + break; + case SuitSensorMode.SensorCords: + status.IsAlive = isAlive; + status.TotalDamage = totalDamage; + status.TotalDamageThreshold = totalDamageThreshold; + EntityCoordinates coordinates; + var xformQuery = GetEntityQuery(); + + if (transform.GridUid != null) + { + coordinates = new EntityCoordinates(transform.GridUid.Value, + Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery), + _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery))); + } + else if (transform.MapUid != null) + { + coordinates = new EntityCoordinates(transform.MapUid.Value, + _transform.GetWorldPosition(transform, xformQuery)); + } + else + { + coordinates = EntityCoordinates.Invalid; + } + + status.Coordinates = GetNetCoordinates(coordinates); + break; + } + + return status; + } + + /// + /// Create a device network package from the suit sensors status. + /// + public NetworkPayload SuitSensorToPacket(SuitSensorStatus status) + { + var payload = new NetworkPayload() + { + [DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState, + [SuitSensorConstants.NET_NAME] = status.Name, + [SuitSensorConstants.NET_JOB] = status.Job, + [SuitSensorConstants.NET_JOB_ICON] = status.JobIcon, + [SuitSensorConstants.NET_JOB_DEPARTMENTS] = status.JobDepartments, + [SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive, + [SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid, + [SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid, + }; + + if (status.TotalDamage != null) + payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage); + if (status.TotalDamageThreshold != null) + payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold); + if (status.Coordinates != null) + payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates); + + return payload; + } + + /// + /// Try to create the suit sensors status from the device network message. + /// + public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload) + { + // check command + if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command)) + return null; + if (command != DeviceNetworkConstants.CmdUpdatedState) + return null; + + // check name, job and alive + if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_ICON, out string? jobIcon)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_DEPARTMENTS, out List? jobDepartments)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null; + + // try get total damage and cords (optionals) + payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage); + payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold); + payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords); + + var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments) + { + IsAlive = isAlive.Value, + TotalDamage = totalDamage, + TotalDamageThreshold = totalDamageThreshold, + Coordinates = coords, + }; + return status; + } +} diff --git a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs b/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs similarity index 89% rename from Content.Server/Medical/SuitSensors/SuitSensorComponent.cs rename to Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs index 91039712e5..b20b7af2c9 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs +++ b/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs @@ -1,14 +1,16 @@ using Content.Shared.Medical.SuitSensor; +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.Medical.SuitSensors; +namespace Content.Shared.Medical.SuitSensors; /// /// Tracking device, embedded in almost all uniforms and jumpsuits. /// If enabled, will report to crew monitoring console owners position and status. /// -[RegisterComponent, AutoGenerateComponentPause] -[Access(typeof(SuitSensorSystem))] +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedSuitSensorSystem))] +[AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class SuitSensorComponent : Component { /// @@ -20,7 +22,7 @@ public sealed partial class SuitSensorComponent : Component /// /// If true user can't change suit sensor mode /// - [DataField] + [DataField, AutoNetworkedField] public bool ControlsLocked = false; /// @@ -32,7 +34,7 @@ public sealed partial class SuitSensorComponent : Component /// /// Current sensor mode. Can be switched by user verbs. /// - [DataField] + [DataField, AutoNetworkedField] public SuitSensorMode Mode = SuitSensorMode.SensorOff; /// @@ -56,7 +58,7 @@ public sealed partial class SuitSensorComponent : Component /// /// Current user that wears suit sensor. Null if nobody wearing it. /// - [ViewVariables] + [DataField, AutoNetworkedField] public EntityUid? User = null; /// @@ -69,7 +71,7 @@ public sealed partial class SuitSensorComponent : Component /// /// The station this suit sensor belongs to. If it's null the suit didn't spawn on a station and the sensor doesn't work. /// - [DataField("station")] + [DataField("station"), AutoNetworkedField] public EntityUid? StationId = null; ///