From df8b9a659ecbd412243acdbf02fa96e2a74d5789 Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Wed, 22 May 2024 11:16:20 +0000 Subject: [PATCH 01/21] Firelock improvements part 1 (#26582) * Change prying system and pryunpoweredcomp to allow for custom time modifiers This will be useful if I go the route of making firelocks pryable when unpowered instead of just being able to open and close instantly when unpowered. * Make firelocks properly predicted Shared system made. Since atmos checks can only be done on the server we just have it set relevant bools on the component and then dirty it. Ditched atmos checks on trying to open, they now only happen whenever firelocks are updated. * Make firelocks pryable without a crowbar While this usually would only allow you to do this when a door is unpowered, firelocks do not have the airlock component which actually does that check. As such firelocks will always allow you to pry them open/closed by hand. * Clean up System. Change update interval to be based on ticks. Move as much as possible to shared * Make firelocks unable to emergency close for 2 seconds after being pried open * Clean up * More cleanup * Reorganize SharedFirelockSystem methods to match Initialize order --------- Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> --- Content.Client/Doors/FirelockSystem.cs | 3 +- .../Doors/Systems/FirelockSystem.cs | 134 +++--------------- .../Doors/Components/FirelockComponent.cs | 48 ++++++- .../Doors/Systems/SharedDoorSystem.cs | 3 +- .../Doors/Systems/SharedFirelockSystem.cs | 125 ++++++++++++++++ .../Components/PryUnpoweredComponent.cs | 2 + Content.Shared/Prying/Systems/PryingSystem.cs | 8 +- .../Structures/Doors/Firelocks/firelock.yml | 2 + 8 files changed, 200 insertions(+), 125 deletions(-) create mode 100644 Content.Shared/Doors/Systems/SharedFirelockSystem.cs diff --git a/Content.Client/Doors/FirelockSystem.cs b/Content.Client/Doors/FirelockSystem.cs index cfd84a4713..f64b4c8e52 100644 --- a/Content.Client/Doors/FirelockSystem.cs +++ b/Content.Client/Doors/FirelockSystem.cs @@ -1,9 +1,10 @@ using Content.Shared.Doors.Components; +using Content.Shared.Doors.Systems; using Robust.Client.GameObjects; namespace Content.Client.Doors; -public sealed class FirelockSystem : EntitySystem +public sealed class FirelockSystem : SharedFirelockSystem { [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index 3d4c8a4ec5..5ad86fb20a 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -1,67 +1,56 @@ using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Monitor.Systems; -using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Shuttles.Components; -using Content.Shared.Access.Systems; using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.Doors; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; -using Content.Shared.Popups; -using Content.Shared.Prying.Components; using Robust.Shared.Map.Components; namespace Content.Server.Doors.Systems { - public sealed class FirelockSystem : EntitySystem + public sealed class FirelockSystem : SharedFirelockSystem { - [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedDoorSystem _doorSystem = default!; [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; [Dependency] private readonly AtmosphereSystem _atmosSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; + [Dependency] private readonly SharedMapSystem _mapping = default!; - private static float _visualUpdateInterval = 0.5f; - private float _accumulatedFrameTime; + private const int UpdateInterval = 30; + private int _accumulatedTicks; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnBeforeDoorOpened); - SubscribeLocalEvent(OnDoorGetPryTimeModifier); - SubscribeLocalEvent(OnUpdateState); SubscribeLocalEvent(OnBeforeDoorAutoclose); SubscribeLocalEvent(OnAtmosAlarm); - // Visuals - SubscribeLocalEvent(UpdateVisuals); - SubscribeLocalEvent(UpdateVisuals); SubscribeLocalEvent(PowerChanged); + } private void PowerChanged(EntityUid uid, FirelockComponent component, ref PowerChangedEvent args) { // TODO this should REALLLLY not be door specific appearance thing. _appearance.SetData(uid, DoorVisuals.Powered, args.Powered); + component.Powered = args.Powered; + Dirty(uid, component); } - #region Visuals - private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid, component); - public override void Update(float frameTime) { - _accumulatedFrameTime += frameTime; - if (_accumulatedFrameTime < _visualUpdateInterval) + _accumulatedTicks += 1; + if (_accumulatedTicks < UpdateInterval) return; - _accumulatedFrameTime -= _visualUpdateInterval; + _accumulatedTicks = 0; var airtightQuery = GetEntityQuery(); var appearanceQuery = GetEntityQuery(); @@ -84,94 +73,13 @@ namespace Content.Server.Doors.Systems { var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery); _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance); + firelock.Temperature = fire; + firelock.Pressure = pressure; + Dirty(uid, firelock); } } } - private void UpdateVisuals(EntityUid uid, - FirelockComponent? firelock = null, - DoorComponent? door = null, - AirtightComponent? airtight = null, - AppearanceComponent? appearance = null, - TransformComponent? xform = null) - { - if (!Resolve(uid, ref door, ref appearance, false)) - return; - - // only bother to check pressure on doors that are some variation of closed. - if (door.State != DoorState.Closed - && door.State != DoorState.Welded - && door.State != DoorState.Denying) - { - _appearance.SetData(uid, DoorVisuals.ClosedLights, false, appearance); - return; - } - - var query = GetEntityQuery(); - if (!Resolve(uid, ref firelock, ref airtight, ref appearance, ref xform, false) || !query.Resolve(uid, ref airtight, false)) - return; - - var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, query); - _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance); - } - #endregion - - public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = null, DoorComponent? door = null) - { - if (!Resolve(uid, ref firelock, ref door)) - return false; - - if (door.State == DoorState.Open) - { - if (_doorSystem.TryClose(uid, door)) - { - return _doorSystem.OnPartialClose(uid, door); - } - } - return false; - } - - private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) - { - // Give the Door remote the ability to force a firelock open even if it is holding back dangerous gas - var overrideAccess = (args.User != null) && _accessReaderSystem.IsAllowed(args.User.Value, uid); - - if (!this.IsPowered(uid, EntityManager) || (!overrideAccess && IsHoldingPressureOrFire(uid, component))) - args.Cancel(); - } - - private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args) - { - var state = CheckPressureAndFire(uid, component); - - if (state.Fire) - { - _popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-fire-message"), - uid, args.User, PopupType.MediumCaution); - } - else if (state.Pressure) - { - _popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-pressure-message"), - uid, args.User, PopupType.MediumCaution); - } - - if (state.Fire || state.Pressure) - args.PryTimeModifier *= component.LockedPryTimeModifier; - } - - private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args) - { - var ev = new BeforeDoorAutoCloseEvent(); - RaiseLocalEvent(uid, ev); - UpdateVisuals(uid, component, args); - if (ev.Cancelled) - { - return; - } - - _doorSystem.SetNextStateChange(uid, component.AutocloseDelay); - } - private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args) { if (!this.IsPowered(uid, EntityManager)) @@ -204,12 +112,6 @@ namespace Content.Server.Doors.Systems } } - public bool IsHoldingPressureOrFire(EntityUid uid, FirelockComponent firelock) - { - var result = CheckPressureAndFire(uid, firelock); - return result.Pressure || result.Fire; - } - public (bool Pressure, bool Fire) CheckPressureAndFire(EntityUid uid, FirelockComponent firelock) { var query = GetEntityQuery(); @@ -234,17 +136,17 @@ namespace Content.Server.Doors.Systems return (false, false); } - if (!TryComp(xform.ParentUid, out GridAtmosphereComponent? gridAtmosphere)) + if (!HasComp(xform.ParentUid)) return (false, false); var grid = Comp(xform.ParentUid); - var pos = grid.CoordinatesToTile(xform.Coordinates); + var pos = _mapping.CoordinatesToTile(xform.ParentUid, grid, xform.Coordinates); var minPressure = float.MaxValue; var maxPressure = float.MinValue; var minTemperature = float.MaxValue; var maxTemperature = float.MinValue; - bool holdingFire = false; - bool holdingPressure = false; + var holdingFire = false; + var holdingPressure = false; // We cannot simply use `_atmosSystem.GetAdjacentTileMixtures` because of how the `includeBlocked` option // works, we want to ignore the firelock's blocking, while including blockers on other tiles. @@ -284,7 +186,7 @@ namespace Content.Server.Doors.Systems { // Is there some airtight entity blocking this direction? If yes, don't include this direction in the // pressure differential - if (HasAirtightBlocker(grid.GetAnchoredEntities(adjacentPos), dir.GetOpposite(), airtightQuery)) + if (HasAirtightBlocker(_mapping.GetAnchoredEntities(xform.ParentUid, grid, adjacentPos), dir.GetOpposite(), airtightQuery)) continue; var p = gas.Pressure; diff --git a/Content.Shared/Doors/Components/FirelockComponent.cs b/Content.Shared/Doors/Components/FirelockComponent.cs index 97e57185ca..ca62daaa0f 100644 --- a/Content.Shared/Doors/Components/FirelockComponent.cs +++ b/Content.Shared/Doors/Components/FirelockComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Doors.Components; +using Robust.Shared.GameStates; namespace Content.Shared.Doors.Components { @@ -7,9 +7,11 @@ namespace Content.Shared.Doors.Components /// auto-closing on depressurization, air/fire alarm interactions, and preventing normal door functions when /// retaining pressure.. /// - [RegisterComponent] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class FirelockComponent : Component { + #region Settings + /// /// Pry time modifier to be used when the firelock is currently closed due to fire or pressure. /// @@ -39,5 +41,47 @@ namespace Content.Shared.Doors.Components /// [DataField("alarmAutoClose"), ViewVariables(VVAccess.ReadWrite)] public bool AlarmAutoClose = true; + + /// + /// The cooldown duration before a firelock can automatically close due to a hazardous environment after it has + /// been pried open. Measured in seconds. + /// + [DataField] + public TimeSpan EmergencyCloseCooldownDuration = TimeSpan.FromSeconds(2); + + #endregion + + #region Set by system + + /// + /// When the firelock will be allowed to automatically close again due to a hazardous environment. + /// + [DataField] + public TimeSpan? EmergencyCloseCooldown; + + /// + /// Whether the firelock can open, or is locked due to its environment. + /// + public bool IsLocked => Pressure || Temperature; + + /// + /// Whether the firelock is holding back a hazardous pressure. + /// + [DataField, AutoNetworkedField] + public bool Pressure; + + /// + /// Whether the firelock is holding back extreme temperatures. + /// + [DataField, AutoNetworkedField] + public bool Temperature; + + /// + /// Whether the airlock is powered. + /// + [DataField, AutoNetworkedField] + public bool Powered; + + #endregion } } diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index b58b7b265e..20456c1477 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -6,7 +6,6 @@ using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Doors.Components; using Content.Shared.Emag.Systems; -using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Physics; using Content.Shared.Popups; @@ -466,7 +465,7 @@ public abstract partial class SharedDoorSystem : EntitySystem door.Partial = true; - // Make sure no entity waled into the airlock when it started closing. + // Make sure no entity walked into the airlock when it started closing. if (!CanClose(uid, door)) { door.NextStateChange = GameTiming.CurTime + door.OpenTimeTwo; diff --git a/Content.Shared/Doors/Systems/SharedFirelockSystem.cs b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs new file mode 100644 index 0000000000..7d033efdd4 --- /dev/null +++ b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs @@ -0,0 +1,125 @@ +using Content.Shared.Access.Systems; +using Content.Shared.Doors.Components; +using Content.Shared.Popups; +using Content.Shared.Prying.Components; +using Robust.Shared.Timing; + +namespace Content.Shared.Doors.Systems; + +public abstract class SharedFirelockSystem : EntitySystem +{ + [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedDoorSystem _doorSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUpdateState); + + // Access/Prying + SubscribeLocalEvent(OnBeforeDoorOpened); + SubscribeLocalEvent(OnDoorGetPryTimeModifier); + SubscribeLocalEvent(OnAfterPried); + + // Visuals + SubscribeLocalEvent(UpdateVisuals); + SubscribeLocalEvent(UpdateVisuals); + } + + public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = null, DoorComponent? door = null) + { + if (!Resolve(uid, ref firelock, ref door)) + return false; + + if (door.State != DoorState.Open + || firelock.EmergencyCloseCooldown != null + && _gameTiming.CurTime < firelock.EmergencyCloseCooldown) + return false; + + if (!_doorSystem.TryClose(uid, door)) + return false; + + return _doorSystem.OnPartialClose(uid, door); + } + + private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args) + { + var ev = new BeforeDoorAutoCloseEvent(); + RaiseLocalEvent(uid, ev); + UpdateVisuals(uid, component, args); + if (ev.Cancelled) + { + return; + } + + _doorSystem.SetNextStateChange(uid, component.AutocloseDelay); + } + + #region Access/Prying + + private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) + { + // Give the Door remote the ability to force a firelock open even if it is holding back dangerous gas + var overrideAccess = (args.User != null) && _accessReaderSystem.IsAllowed(args.User.Value, uid); + + if (!component.Powered || (!overrideAccess && component.IsLocked)) + args.Cancel(); + } + + private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args) + { + if (component.Temperature) + { + _popupSystem.PopupClient(Loc.GetString("firelock-component-is-holding-fire-message"), + uid, args.User, PopupType.MediumCaution); + } + else if (component.Pressure) + { + _popupSystem.PopupClient(Loc.GetString("firelock-component-is-holding-pressure-message"), + uid, args.User, PopupType.MediumCaution); + } + + if (component.IsLocked) + args.PryTimeModifier *= component.LockedPryTimeModifier; + } + + private void OnAfterPried(EntityUid uid, FirelockComponent component, ref PriedEvent args) + { + component.EmergencyCloseCooldown = _gameTiming.CurTime + component.EmergencyCloseCooldownDuration; + } + + #endregion + + #region Visuals + + private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid, component); + + private void UpdateVisuals(EntityUid uid, + FirelockComponent? firelock = null, + DoorComponent? door = null, + AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref door, ref appearance, false)) + return; + + // only bother to check pressure on doors that are some variation of closed. + if (door.State != DoorState.Closed + && door.State != DoorState.Welded + && door.State != DoorState.Denying) + { + _appearance.SetData(uid, DoorVisuals.ClosedLights, false, appearance); + return; + } + + if (!Resolve(uid, ref firelock, ref appearance, false)) + return; + + _appearance.SetData(uid, DoorVisuals.ClosedLights, firelock.IsLocked, appearance); + } + + #endregion +} diff --git a/Content.Shared/Prying/Components/PryUnpoweredComponent.cs b/Content.Shared/Prying/Components/PryUnpoweredComponent.cs index f0e61dc968..b6b69a2577 100644 --- a/Content.Shared/Prying/Components/PryUnpoweredComponent.cs +++ b/Content.Shared/Prying/Components/PryUnpoweredComponent.cs @@ -8,4 +8,6 @@ namespace Content.Shared.Prying.Components; [RegisterComponent, NetworkedComponent] public sealed partial class PryUnpoweredComponent : Component { + [DataField] + public float PryModifier = 0.1f; } diff --git a/Content.Shared/Prying/Systems/PryingSystem.cs b/Content.Shared/Prying/Systems/PryingSystem.cs index ab87585c70..372c89c9ae 100644 --- a/Content.Shared/Prying/Systems/PryingSystem.cs +++ b/Content.Shared/Prying/Systems/PryingSystem.cs @@ -93,17 +93,17 @@ public sealed class PryingSystem : EntitySystem id = null; // We don't care about displaying a message if no tool was used. - if (!CanPry(target, user, out _)) + if (!TryComp(target, out var unpoweredComp) || !CanPry(target, user, out _, unpoweredComp: unpoweredComp)) // If we have reached this point we want the event that caused this // to be marked as handled. return true; // hand-prying is much slower - var modifier = CompOrNull(user)?.SpeedModifier ?? 0.1f; + var modifier = CompOrNull(user)?.SpeedModifier ?? unpoweredComp.PryModifier; return StartPry(target, user, null, modifier, out id); } - private bool CanPry(EntityUid target, EntityUid user, out string? message, PryingComponent? comp = null) + private bool CanPry(EntityUid target, EntityUid user, out string? message, PryingComponent? comp = null, PryUnpoweredComponent? unpoweredComp = null) { BeforePryEvent canev; @@ -113,7 +113,7 @@ public sealed class PryingSystem : EntitySystem } else { - if (!TryComp(target, out _)) + if (!Resolve(target, ref unpoweredComp)) { message = null; return false; diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index fcdb432dce..860db862ae 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -108,6 +108,8 @@ - type: DoorBolt - type: AccessReader access: [ [ "Engineering" ] ] + - type: PryUnpowered + pryModifier: 0.5 - type: entity id: Firelock From 456e6f07ec4c98a72a0900bde6aee4051ae7ee4f Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 22 May 2024 11:17:27 +0000 Subject: [PATCH 02/21] Automatic changelog update --- Resources/Changelog/Changelog.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index d20cf44cdb..a8e3a5f155 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: MACMAN2003 - changes: - - message: Diagonal windows no longer space you when in a pressurized environment. - type: Fix - id: 6112 - time: '2024-03-08T07:50:34.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25926 - author: mryikes changes: - message: New Nuke Detonation Song "Clearly Nuclear". @@ -3870,3 +3863,15 @@ id: 6611 time: '2024-05-22T07:52:58.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28200 +- author: nikthechampiongr + changes: + - message: Firelocks can now be pried by hand. + type: Tweak + - message: Firelocks will now not automatically close for 2 seconds after you pry + them open. + type: Tweak + - message: Firelocks are now properly predicted. + type: Fix + id: 6612 + time: '2024-05-22T11:16:20.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/26582 From 687bf8caaa1f3c75224f8cdb367a358d6e1ebfbe Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Wed, 22 May 2024 05:29:19 -0700 Subject: [PATCH 03/21] Revert "Make hotplate and grill anchorable on table" (#28202) Revert "Make hotplate and grill anchorable on table (#28026)" This reverts commit 26747be232c364abcb4e43fb934c85ef0e3e1264. --- .../Prototypes/Entities/Structures/Machines/hotplate.yml | 4 +++- .../Prototypes/Entities/Structures/Machines/microwave.yml | 1 - .../Entities/Structures/Machines/reagent_grinder.yml | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml b/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml index 05cbb60ed3..3764f13591 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml @@ -15,7 +15,9 @@ mask: - TabletopMachineMask layer: - - TabletopMachineLayer + - Impassable + - MidImpassable + - LowImpassable hard: false - type: ApcPowerReceiver powerLoad: 300 diff --git a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml index d78960030d..fe4eb14518 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml @@ -58,7 +58,6 @@ - TabletopMachineMask layer: - TabletopMachineLayer - hard: false - type: Sprite sprite: Structures/Machines/microwave.rsi drawdepth: SmallObjects diff --git a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml index 9894a2691f..d6e7333313 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml @@ -32,7 +32,6 @@ - TabletopMachineMask layer: - TabletopMachineLayer - hard: false - type: Sprite sprite: Structures/Machines/juicer.rsi drawdepth: SmallObjects From 1fa493e1af0e3f31b53c40bda0d7cefe885af343 Mon Sep 17 00:00:00 2001 From: Julian Giebel Date: Wed, 22 May 2024 16:23:55 +0200 Subject: [PATCH 04/21] Implement permissive version of AddMarkup and use it for tips (#28204) * Implement permissive version of ddMarkup Use permissive ddMarkup for news article input Use permissive ddMarkup for tips * Fix doc comment format --- .../MassMedia/Ui/ArticleEditorPanel.xaml.cs | 2 +- Content.Client/Message/RichTextLabelExt.cs | 18 ++++++++++++++++++ Content.Client/Tips/TippyUIController.cs | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs index 7f98e3e0c3..5e068f1e9c 100644 --- a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs +++ b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs @@ -76,7 +76,7 @@ public sealed partial class ArticleEditorPanel : Control TextEditPanel.Visible = !_preview; PreviewPanel.Visible = _preview; - PreviewLabel.SetMarkup(Rope.Collapse(ContentField.TextRope)); + PreviewLabel.SetMarkupPermissive(Rope.Collapse(ContentField.TextRope)); } private void OnCancel(BaseButton.ButtonEventArgs eventArgs) diff --git a/Content.Client/Message/RichTextLabelExt.cs b/Content.Client/Message/RichTextLabelExt.cs index ab6d17bf44..7ff6390764 100644 --- a/Content.Client/Message/RichTextLabelExt.cs +++ b/Content.Client/Message/RichTextLabelExt.cs @@ -5,9 +5,27 @@ namespace Content.Client.Message; public static class RichTextLabelExt { + + + /// + /// Sets the labels markup. + /// + /// + /// Invalid markup will cause exceptions to be thrown. Don't use this for user input! + /// public static RichTextLabel SetMarkup(this RichTextLabel label, string markup) { label.SetMessage(FormattedMessage.FromMarkup(markup)); return label; } + + /// + /// Sets the labels markup.
+ /// Uses FormatedMessage.FromMarkupPermissive which treats invalid markup as text. + ///
+ public static RichTextLabel SetMarkupPermissive(this RichTextLabel label, string markup) + { + label.SetMessage(FormattedMessage.FromMarkupPermissive(markup)); + return label; + } } diff --git a/Content.Client/Tips/TippyUIController.cs b/Content.Client/Tips/TippyUIController.cs index 67c3ee45a7..2cc694d97d 100644 --- a/Content.Client/Tips/TippyUIController.cs +++ b/Content.Client/Tips/TippyUIController.cs @@ -175,7 +175,7 @@ public sealed class TippyUIController : UIController sprite.LayerSetVisible("hiding", false); } sprite.Rotation = 0; - tippy.Label.SetMarkup(_currentMessage.Msg); + tippy.Label.SetMarkupPermissive(_currentMessage.Msg); tippy.Label.Visible = false; tippy.LabelPanel.Visible = false; tippy.Visible = true; From 79d4c0eee0861e8499134a34db0231a189206b0f Mon Sep 17 00:00:00 2001 From: Doctor-Cpu <77215380+Doctor-Cpu@users.noreply.github.com> Date: Wed, 22 May 2024 17:59:33 +0100 Subject: [PATCH 05/21] misc atlas fixes (#28191) misc fixes --- Resources/Maps/atlas.yml | 113 ++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 68 deletions(-) diff --git a/Resources/Maps/atlas.yml b/Resources/Maps/atlas.yml index cfd5f3b5ac..cb7dc8e778 100644 --- a/Resources/Maps/atlas.yml +++ b/Resources/Maps/atlas.yml @@ -89,7 +89,7 @@ entities: version: 6 0,1: ind: 0,1 - tiles: AwAAAAAAAwAAAAAAAwAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAHQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAALAAAAAAAeQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAHQAAAAAAeQAAAAAAeQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAHQAAAAAAeQAAAAAAeQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAPgAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAPgAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAPgAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAALgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAWQAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAWQAAAAAA + tiles: AwAAAAAAAwAAAAAAAwAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAHQAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAHQAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAALAAAAAAAeQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAHQAAAAAAeQAAAAAAeQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAdgAAAAAAHQAAAAAAeQAAAAAAeQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAPgAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAPgAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAPgAAAAAAdgAAAAAAdgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAALgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAWQAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAWQAAAAAA version: 6 1,1: ind: 1,1 @@ -3360,6 +3360,13 @@ entities: - type: Transform pos: -44.5,-15.5 parent: 30 +- proto: AirlockArmoryGlassLocked + entities: + - uid: 1448 + components: + - type: Transform + pos: 9.5,14.5 + parent: 30 - proto: AirlockAtmosphericsGlassLocked entities: - uid: 241 @@ -3940,6 +3947,9 @@ entities: - type: Transform pos: 18.5,-6.5 parent: 30 + - type: DeviceLinkSource + lastSignals: + DoorStatus: True - uid: 2282 components: - type: Transform @@ -4053,12 +4063,6 @@ entities: parent: 30 - proto: AirlockHeadOfSecurityGlassLocked entities: - - uid: 1448 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 9.5,14.5 - parent: 30 - uid: 7378 components: - type: Transform @@ -4239,6 +4243,9 @@ entities: - type: Transform pos: -6.5,17.5 parent: 30 + - type: DeviceLinkSource + lastSignals: + DoorStatus: True - proto: AirlockMedicalGlass entities: - uid: 2408 @@ -7082,7 +7089,7 @@ entities: - uid: 2074 components: - type: Transform - pos: -10.5,8.5 + pos: -7.5,8.5 parent: 30 - uid: 2075 components: @@ -23899,14 +23906,6 @@ entities: rot: 1.5707963267948966 rad pos: -44.5,-16.5 parent: 30 -- proto: EncryptionKeyCargo - entities: - - uid: 8041 - components: - - type: Transform - parent: 8035 - - type: Physics - canCollide: False - proto: EncryptionKeyCommand entities: - uid: 6716 @@ -23931,14 +23930,6 @@ entities: parent: 6972 - type: Physics canCollide: False -- proto: EncryptionKeyMedical - entities: - - uid: 7916 - components: - - type: Transform - parent: 7915 - - type: Physics - canCollide: False - proto: EncryptionKeyScience entities: - uid: 7393 @@ -38622,6 +38613,14 @@ entities: rot: -1.5707963267948966 rad pos: -11.5,-9.5 parent: 30 +- proto: HydroponicsToolClippers + entities: + - uid: 7916 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.368904,17.565664 + parent: 30 - proto: HydroponicsToolHatchet entities: - uid: 1498 @@ -41743,14 +41742,6 @@ entities: parent: 30 - type: ApcPowerReceiver powerLoad: 0 - - uid: 7438 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -30.5,-11.5 - parent: 30 - - type: ApcPowerReceiver - powerLoad: 0 - uid: 7439 components: - type: Transform @@ -41773,6 +41764,12 @@ entities: rot: 3.141592653589793 rad pos: 3.5,-15.5 parent: 30 + - uid: 8041 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -31.5,-11.5 + parent: 30 - uid: 8112 components: - type: Transform @@ -46316,6 +46313,14 @@ entities: - type: Transform pos: 10.5,15.5 parent: 30 +- proto: SpawnMobMonkeyPunpun + entities: + - uid: 8035 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -18.5,3.5 + parent: 30 - proto: SpawnMobMouse entities: - uid: 1570 @@ -48692,48 +48697,20 @@ entities: showEnts: False occludes: True ents: [] -- proto: TelecomServerFilled +- proto: TelecomServerFilledCargo + entities: + - uid: 7438 + components: + - type: Transform + pos: -48.5,6.5 + parent: 30 +- proto: TelecomServerFilledMedical entities: - uid: 7915 components: - type: Transform pos: -47.5,6.5 parent: 30 - - type: ContainerContainer - containers: - key_slots: !type:Container - showEnts: False - occludes: True - ents: - - 7916 - machine_board: !type:Container - showEnts: False - occludes: True - ents: [] - machine_parts: !type:Container - showEnts: False - occludes: True - ents: [] - - uid: 8035 - components: - - type: Transform - pos: -48.5,6.5 - parent: 30 - - type: ContainerContainer - containers: - key_slots: !type:Container - showEnts: False - occludes: True - ents: - - 8041 - machine_board: !type:Container - showEnts: False - occludes: True - ents: [] - machine_parts: !type:Container - showEnts: False - occludes: True - ents: [] - proto: ToiletDirtyWater entities: - uid: 7674 From c082773b5fa50741cd6d35dea95452a940feb36e Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Wed, 22 May 2024 18:59:49 +0200 Subject: [PATCH 06/21] Remove dupe closet janitor bombsuit (#28177) * Remove dupe closet janitor bombsuit * Update Resources/migration.yml Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> --------- Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> --- .../Prototypes/Catalog/Cargo/cargo_service.yml | 2 +- .../Prototypes/Catalog/Fills/Crates/service.yml | 13 ------------- .../Prototypes/Catalog/Fills/Lockers/service.yml | 2 +- Resources/migration.yml | 3 +++ 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml index ebcd9dfc5e..d2ca08e116 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml @@ -173,7 +173,7 @@ icon: sprite: Clothing/Head/Helmets/janitor_bombsuit.rsi state: icon - product: CrateJanitorExplosive + product: ClosetJanitorBombFilled cost: 1000 category: cargoproduct-category-name-service group: market diff --git a/Resources/Prototypes/Catalog/Fills/Crates/service.yml b/Resources/Prototypes/Catalog/Fills/Crates/service.yml index 35e66ac4d3..5b16b91f8b 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/service.yml @@ -321,16 +321,3 @@ prob: 0.1 - id: ShardGlassPlasma prob: 0.1 - -- type: entity - id: CrateJanitorExplosive - parent: ClosetJanitorBomb - name: janitorial bomb suit crate - description: Supplies a bomb suit for cleaning up any explosive compounds, buy one today! - components: - - type: StorageFill - contents: - - id: ClothingOuterSuitJanitorBomb - amount: 1 - - id: ClothingHeadHelmetJanitorBombSuit - amount: 1 diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index 945ae0dd7b..6d0577a16e 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -130,7 +130,7 @@ - type: entity id: ClosetJanitorBombFilled parent: ClosetJanitorBomb - suffix: Filled + suffix: DO NOT MAP, Filled components: - type: StorageFill contents: diff --git a/Resources/migration.yml b/Resources/migration.yml index 28d63be992..c19f140870 100644 --- a/Resources/migration.yml +++ b/Resources/migration.yml @@ -339,3 +339,6 @@ DrinkBottleGoldschlager: DrinkBottleGildlager # 2024-05-14 soda_dispenser: SodaDispenser chem_master: ChemMaster + +# 2024-05-21 +CrateJanitorExplosive: ClosetJanitorBombFilled From 0e0f50f3d1d76c0ba1283d5fa2a92678e3de0466 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Wed, 22 May 2024 21:16:14 -0700 Subject: [PATCH 07/21] Prioritize empty item slots when inserting (#28203) * Prioritize empty item slots when inserting * Revert "Prioritize empty item slots when inserting" This reverts commit 4272a65cba5fc18df801812b0af20123aec08409. * Prioritize empty item slots when inserting * Try drop * Check for any can insert before dropping --- .../Containers/ItemSlot/ItemSlotsSystem.cs | 76 ++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 914b34d3c1..2e3f9ed461 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -197,6 +197,7 @@ namespace Content.Shared.Containers.ItemSlots if (!EntityManager.TryGetComponent(args.User, out HandsComponent? hands)) return; + var slots = new List(); foreach (var slot in itemSlots.Slots.Values) { if (!slot.InsertOnInteract) @@ -205,10 +206,20 @@ namespace Content.Shared.Containers.ItemSlots if (!CanInsert(uid, args.Used, args.User, slot, swap: slot.Swap, popup: args.User)) continue; - // Drop the held item onto the floor. Return if the user cannot drop. - if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands)) - return; + slots.Add(slot); + } + if (slots.Count == 0) + return; + + // Drop the held item onto the floor. Return if the user cannot drop. + if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands)) + return; + + slots.Sort(SortEmpty); + + foreach (var slot in slots) + { if (slot.Item != null) _handsSystem.TryPickupAnyHand(args.User, slot.Item.Value, handsComp: hands); @@ -333,6 +344,65 @@ namespace Content.Shared.Containers.ItemSlots Insert(uid, slot, held, user, excludeUserAudio: excludeUserAudio); return true; } + + /// + /// Tries to insert an item into any empty slot. + /// + /// The entity that has the item slots. + /// The item to be inserted. + /// The entity performing the interaction. + /// + /// If true, will exclude the user when playing sound. Does nothing client-side. + /// Useful for predicted interactions + /// + /// False if failed to insert item + public bool TryInsertEmpty(Entity ent, EntityUid item, EntityUid? user, bool excludeUserAudio = false) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + var slots = new List(); + foreach (var slot in ent.Comp.Slots.Values) + { + if (slot.ContainerSlot?.ContainedEntity != null) + continue; + + if (CanInsert(ent, item, user, slot)) + slots.Add(slot); + } + + if (slots.Count == 0) + return false; + + if (user != null && _handsSystem.IsHolding(user.Value, item)) + { + if (!_handsSystem.TryDrop(user.Value, item)) + return false; + } + + slots.Sort(SortEmpty); + + foreach (var slot in slots) + { + if (TryInsert(ent, slot, item, user, excludeUserAudio: excludeUserAudio)) + return true; + } + + return false; + } + + private static int SortEmpty(ItemSlot a, ItemSlot b) + { + var aEnt = a.ContainerSlot?.ContainedEntity; + var bEnt = b.ContainerSlot?.ContainedEntity; + if (aEnt == null && bEnt == null) + return a.Priority.CompareTo(b.Priority); + + if (aEnt == null) + return -1; + + return 1; + } #endregion #region Eject From 2ccd8e234d9acfb368c51fcf79485deb3d21ad4c Mon Sep 17 00:00:00 2001 From: blueDev2 <89804215+blueDev2@users.noreply.github.com> Date: Thu, 23 May 2024 16:27:45 -0400 Subject: [PATCH 08/21] Add Fruit Tag to watermelon slice (#28226) --- .../Prototypes/Entities/Objects/Consumable/Food/produce.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index 1bd895829b..888e4e4e35 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -1647,6 +1647,9 @@ reagents: - ReagentId: JuiceWatermelon Quantity: 4 + - type: Tag + tags: + - Fruit - type: entity name: grapes From 5cb914e4a62270d07d2c2f87e1d4762d14bc722f Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 23 May 2024 20:28:53 +0000 Subject: [PATCH 09/21] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index a8e3a5f155..9ae89dad4a 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: mryikes - changes: - - message: New Nuke Detonation Song "Clearly Nuclear". - type: Add - id: 6113 - time: '2024-03-09T01:44:54.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25765 - author: Plykiya changes: - message: Pre-filled syringes start in inject mode now. @@ -3875,3 +3868,10 @@ id: 6612 time: '2024-05-22T11:16:20.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/26582 +- author: blueDev2 + changes: + - message: Watermelon slices are now considered fruit + type: Fix + id: 6613 + time: '2024-05-23T20:27:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28226 From 594a8982602ca9e7cfe10dd77745e55b953088fb Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Thu, 23 May 2024 18:23:55 -0700 Subject: [PATCH 10/21] Fix whatever the fuck is going on in storage system slightly (#28236) * Fix whatever the fuck is going on in storage system slightly * Fix inverted check * h * Add silent bool * Silent --- .../BypassInteractionChecksComponent.cs | 6 ++ .../Inventory/InventorySystem.Slots.cs | 2 +- Content.Shared/Lock/LockSystem.cs | 9 +++ .../EntitySystems/SharedStorageSystem.cs | 62 ++++++++++--------- Content.Shared/Storage/StorageComponent.cs | 4 +- .../Entities/Mobs/Player/admin_ghost.yml | 1 + 6 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 Content.Shared/Interaction/Components/BypassInteractionChecksComponent.cs diff --git a/Content.Shared/Interaction/Components/BypassInteractionChecksComponent.cs b/Content.Shared/Interaction/Components/BypassInteractionChecksComponent.cs new file mode 100644 index 0000000000..ca0ff96315 --- /dev/null +++ b/Content.Shared/Interaction/Components/BypassInteractionChecksComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Interaction.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class BypassInteractionChecksComponent : Component; diff --git a/Content.Shared/Inventory/InventorySystem.Slots.cs b/Content.Shared/Inventory/InventorySystem.Slots.cs index 19831278b0..228b788722 100644 --- a/Content.Shared/Inventory/InventorySystem.Slots.cs +++ b/Content.Shared/Inventory/InventorySystem.Slots.cs @@ -75,7 +75,7 @@ public partial class InventorySystem : EntitySystem if (TryGetSlotEntity(uid, ev.Slot, out var entityUid) && TryComp(entityUid, out var storageComponent)) { - _storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent); + _storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent, false); } } diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 54f5d801ea..4115358d9d 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Hands.Components; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Storage; using Content.Shared.Storage.Components; using Content.Shared.Verbs; using Content.Shared.Wires; @@ -42,11 +43,13 @@ public sealed class LockSystem : EntitySystem SubscribeLocalEvent(OnEmagged); SubscribeLocalEvent(OnDoAfterLock); SubscribeLocalEvent(OnDoAfterUnlock); + SubscribeLocalEvent(OnStorageInteractAttempt); SubscribeLocalEvent(OnLockToggleAttempt); SubscribeLocalEvent(OnAttemptChangePanel); SubscribeLocalEvent(OnUnanchorAttempt); } + private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args) { _appearanceSystem.SetData(uid, LockVisuals.Locked, lockComp.Locked); @@ -293,6 +296,12 @@ public sealed class LockSystem : EntitySystem TryUnlock(uid, args.User, skipDoAfter: true); } + private void OnStorageInteractAttempt(Entity ent, ref StorageInteractAttemptEvent args) + { + if (ent.Comp.Locked) + args.Cancelled = true; + } + private void OnLockToggleAttempt(Entity ent, ref LockToggleAttemptEvent args) { if (args.Cancelled) diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 6dd7f9d418..ea0c9632f0 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -2,17 +2,15 @@ using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.ActionBlocker; -using Content.Shared.Administration; -using Content.Shared.Administration.Managers; using Content.Shared.Containers.ItemSlots; using Content.Shared.Destructible; using Content.Shared.DoAfter; -using Content.Shared.Ghost; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Implants.Components; using Content.Shared.Input; using Content.Shared.Interaction; +using Content.Shared.Interaction.Components; using Content.Shared.Inventory; using Content.Shared.Item; using Content.Shared.Lock; @@ -40,7 +38,6 @@ public abstract class SharedStorageSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] protected readonly IRobustRandom Random = default!; - [Dependency] private readonly ISharedAdminManager _admin = default!; [Dependency] protected readonly ActionBlockerSystem ActionBlocker = default!; [Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; @@ -249,17 +246,8 @@ public abstract class SharedStorageSystem : EntitySystem private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent args) { - var silent = false; - if (!args.CanAccess || !args.CanInteract || TryComp(uid, out var lockComponent) && lockComponent.Locked) - { - // we allow admins to open the storage anyways - if (!_admin.HasAdminFlag(args.User, AdminFlags.Admin)) - return; - - silent = true; - } - - silent |= HasComp(args.User); + if (!CanInteract(args.User, (uid, component), args.CanAccess && args.CanInteract)) + return; // Does this player currently have the storage UI open? var uiOpen = _ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User); @@ -274,7 +262,7 @@ public abstract class SharedStorageSystem : EntitySystem } else { - OpenStorageUI(uid, args.User, component, silent); + OpenStorageUI(uid, args.User, component); } } }; @@ -298,13 +286,16 @@ public abstract class SharedStorageSystem : EntitySystem /// Opens the storage UI for an entity /// /// The entity to open the UI for - public void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? storageComp = null, bool silent = false) + public void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? storageComp = null, bool silent = true) { if (!Resolve(uid, ref storageComp, false)) return; // prevent spamming bag open / honkerton honk sound silent |= TryComp(uid, out var useDelay) && UseDelay.IsDelayed((uid, useDelay)); + if (!CanInteract(entity, (uid, storageComp), silent: silent)) + return; + if (!silent) { if (!_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key)) @@ -326,7 +317,7 @@ public abstract class SharedStorageSystem : EntitySystem var entities = component.Container.ContainedEntities; - if (entities.Count == 0 || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) + if (entities.Count == 0 || !CanInteract(args.User, (uid, component))) return; // if the target is storage, add a verb to transfer storage. @@ -337,7 +328,7 @@ public abstract class SharedStorageSystem : EntitySystem { Text = Loc.GetString("storage-component-transfer-verb"), IconEntity = GetNetEntity(args.Using), - Act = () => TransferEntities(uid, args.Target, args.User, component, lockComponent, targetStorage, targetLock) + Act = () => TransferEntities(uid, args.Target, args.User, component, null, targetStorage, targetLock) }; args.Verbs.Add(verb); @@ -350,7 +341,7 @@ public abstract class SharedStorageSystem : EntitySystem /// true if inserted, false otherwise private void OnInteractUsing(EntityUid uid, StorageComponent storageComp, InteractUsingEvent args) { - if (args.Handled || !storageComp.ClickInsert || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) + if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert, false)) return; if (HasComp(uid)) @@ -368,7 +359,7 @@ public abstract class SharedStorageSystem : EntitySystem /// private void OnActivate(EntityUid uid, StorageComponent storageComp, ActivateInWorldEvent args) { - if (args.Handled || TryComp(uid, out var lockComponent) && lockComponent.Locked) + if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert)) return; // Toggle @@ -378,7 +369,7 @@ public abstract class SharedStorageSystem : EntitySystem } else { - OpenStorageUI(uid, args.User, storageComp); + OpenStorageUI(uid, args.User, storageComp, false); } args.Handled = true; @@ -392,7 +383,7 @@ public abstract class SharedStorageSystem : EntitySystem if (args.Handled) return; - OpenStorageUI(uid, args.Performer, storageComp); + OpenStorageUI(uid, args.Performer, storageComp, false); args.Handled = true; } @@ -1403,7 +1394,7 @@ public abstract class SharedStorageSystem : EntitySystem } /// - /// Checks if a storage's UI is open by anyone when locked, and closes it unless they're an admin. + /// Checks if a storage's UI is open by anyone when locked, and closes it. /// private void OnLockToggled(EntityUid uid, StorageComponent component, ref LockToggledEvent args) { @@ -1413,11 +1404,8 @@ public abstract class SharedStorageSystem : EntitySystem // Gets everyone looking at the UI foreach (var actor in _ui.GetActors(uid, StorageComponent.StorageUiKey.Key).ToList()) { - if (_admin.HasAdminFlag(actor, AdminFlags.Admin)) - continue; - - // And closes it unless they're an admin - _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor); + if (!CanInteract(actor, (uid, component))) + _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor); } } @@ -1457,7 +1445,7 @@ public abstract class SharedStorageSystem : EntitySystem if (!_ui.IsUiOpen(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt)) { - OpenStorageUI(storageEnt.Value, playerEnt); + OpenStorageUI(storageEnt.Value, playerEnt, silent: false); } else { @@ -1472,6 +1460,20 @@ public abstract class SharedStorageSystem : EntitySystem #endif } + private bool CanInteract(EntityUid user, Entity storage, bool canInteract = true, bool silent = true) + { + if (HasComp(user)) + return true; + + if (!canInteract) + return false; + + var ev = new StorageInteractAttemptEvent(silent); + RaiseLocalEvent(storage, ref ev); + + return !ev.Cancelled; + } + /// /// Plays a clientside pickup animation for the specified uid. /// diff --git a/Content.Shared/Storage/StorageComponent.cs b/Content.Shared/Storage/StorageComponent.cs index ef682dd4f9..2860f8dacf 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -5,7 +5,6 @@ using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Map; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -228,6 +227,9 @@ namespace Content.Shared.Storage } } + [ByRefEvent] + public record struct StorageInteractAttemptEvent(bool Silent, bool Cancelled = false); + [NetSerializable] [Serializable] public enum StorageVisuals : byte diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 8171ec0053..b294729e07 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -90,6 +90,7 @@ - type: InventorySlots - type: Loadout prototypes: [ MobAghostGear ] + - type: BypassInteractionChecks - type: entity id: ActionAGhostShowSolar From 8a95cb186c922eee64c4c389c5ff247090861210 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Thu, 23 May 2024 22:43:04 -0400 Subject: [PATCH 11/21] Remove AlertType and AlertCategory (#27933) --- Content.Client/Alerts/ClientAlertsSystem.cs | 2 +- Content.Client/Revenant/RevenantSystem.cs | 3 +- .../Systems/Alerts/AlertsUIController.cs | 3 +- .../Systems/Alerts/Widgets/AlertsUI.xaml.cs | 13 +- .../Components/Mobs/AlertsComponentTests.cs | 15 +- .../Tests/Gravity/WeightlessStatusTests.cs | 8 +- .../Abilities/Mime/MimePowersComponent.cs | 8 + .../Abilities/Mime/MimePowersSystem.cs | 11 +- Content.Server/Alert/Commands/ClearAlert.cs | 4 +- Content.Server/Alert/Commands/ShowAlert.cs | 4 +- .../Atmos/Components/BarotraumaComponent.cs | 10 + .../Atmos/Components/FlammableComponent.cs | 5 + .../Atmos/EntitySystems/BarotraumaSystem.cs | 10 +- .../Atmos/EntitySystems/FlammableSystem.cs | 4 +- .../Body/Components/BloodstreamComponent.cs | 4 + .../Body/Components/InternalsComponent.cs | 6 + .../Body/Components/LungComponent.cs | 4 +- .../Body/Systems/BloodstreamSystem.cs | 4 +- .../Body/Systems/InternalsSystem.cs | 14 +- .../Chemistry/ReagentEffects/AdjustAlert.cs | 8 +- Content.Server/Clothing/MagbootsSystem.cs | 4 +- .../Ensnaring/EnsnareableSystem.Ensnaring.cs | 4 +- .../Ninja/Systems/SpaceNinjaSystem.cs | 11 +- .../Revenant/EntitySystems/RevenantSystem.cs | 2 +- .../Shuttles/Systems/ShuttleConsoleSystem.cs | 4 +- Content.Server/Silicons/Borgs/BorgSystem.cs | 18 +- .../Components/TemperatureComponent.cs | 8 + .../Temperature/Systems/TemperatureSystem.cs | 14 +- Content.Shared/Alert/AlertCategory.cs | 20 -- .../Alert/AlertCategoryPrototype.cs | 14 ++ Content.Shared/Alert/AlertKey.cs | 12 +- Content.Shared/Alert/AlertOrderPrototype.cs | 35 +-- Content.Shared/Alert/AlertPrototype.cs | 208 +++++++++--------- Content.Shared/Alert/AlertState.cs | 3 +- Content.Shared/Alert/AlertType.cs | 59 ----- Content.Shared/Alert/AlertsSystem.cs | 26 +-- Content.Shared/Alert/ClickAlertEvent.cs | 7 +- .../Buckle/Components/StrapComponent.cs | 3 +- .../Buckle/SharedBuckleSystem.Buckle.cs | 5 +- Content.Shared/Clothing/MagbootsComponent.cs | 4 + .../Pacification/PacificationSystem.cs | 5 +- .../Pacification/PacifiedComponent.cs | 4 + .../Cuffs/Components/CuffableComponent.cs | 5 + Content.Shared/Cuffs/SharedCuffableSystem.cs | 4 +- .../Damage/Components/StaminaComponent.cs | 5 + .../Damage/Systems/StaminaSystem.cs | 8 +- .../Components/EnsnareableComponent.cs | 5 + Content.Shared/Gravity/SharedGravitySystem.cs | 15 +- .../Mobs/Components/MobThresholdsComponent.cs | 28 ++- .../Mobs/Systems/MobThresholdSystem.cs | 2 +- .../Pulling/Components/PullableComponent.cs | 5 + .../Pulling/Components/PullerComponent.cs | 7 +- .../Movement/Pulling/Systems/PullingSystem.cs | 8 +- .../Ninja/Components/SpaceNinjaComponent.cs | 4 + .../Nutrition/Components/HungerComponent.cs | 14 +- .../Nutrition/Components/ThirstComponent.cs | 12 +- .../Nutrition/EntitySystems/HungerSystem.cs | 4 +- .../Nutrition/EntitySystems/ThirstSystem.cs | 2 +- .../Revenant/Components/RevenantComponent.cs | 4 + .../Shuttles/Components/PilotComponent.cs | 5 + .../Borgs/Components/BorgChassisComponent.cs | 10 +- .../StatusEffect/StatusEffectPrototype.cs | 2 +- .../StatusEffect/StatusEffectsSystem.cs | 2 +- .../Weapons/Reflect/ReflectSystem.cs | 7 +- .../Shared/Alert/AlertManagerTests.cs | 17 +- .../Shared/Alert/AlertOrderPrototypeTests.cs | 26 +-- .../Shared/Alert/AlertPrototypeTests.cs | 6 +- .../Alert/ServerAlertsComponentTests.cs | 17 +- Resources/Prototypes/Alerts/categories.yml | 35 +++ 69 files changed, 483 insertions(+), 386 deletions(-) delete mode 100644 Content.Shared/Alert/AlertCategory.cs create mode 100644 Content.Shared/Alert/AlertCategoryPrototype.cs delete mode 100644 Content.Shared/Alert/AlertType.cs create mode 100644 Resources/Prototypes/Alerts/categories.yml diff --git a/Content.Client/Alerts/ClientAlertsSystem.cs b/Content.Client/Alerts/ClientAlertsSystem.cs index 223bf7876a..359c8957f9 100644 --- a/Content.Client/Alerts/ClientAlertsSystem.cs +++ b/Content.Client/Alerts/ClientAlertsSystem.cs @@ -91,7 +91,7 @@ public sealed class ClientAlertsSystem : AlertsSystem ClearAlerts?.Invoke(this, EventArgs.Empty); } - public void AlertClicked(AlertType alertType) + public void AlertClicked(ProtoId alertType) { RaiseNetworkEvent(new ClickAlertEvent(alertType)); } diff --git a/Content.Client/Revenant/RevenantSystem.cs b/Content.Client/Revenant/RevenantSystem.cs index 49d29d8a5f..e050fe35aa 100644 --- a/Content.Client/Revenant/RevenantSystem.cs +++ b/Content.Client/Revenant/RevenantSystem.cs @@ -1,5 +1,4 @@ using Content.Client.Alerts; -using Content.Shared.Alert; using Content.Shared.Revenant; using Content.Shared.Revenant.Components; using Robust.Client.GameObjects; @@ -42,7 +41,7 @@ public sealed class RevenantSystem : EntitySystem private void OnUpdateAlert(Entity ent, ref UpdateAlertSpriteEvent args) { - if (args.Alert.AlertType != AlertType.Essence) + if (args.Alert.ID != ent.Comp.EssenceAlert) return; var sprite = args.SpriteViewEnt.Comp; diff --git a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs index 3b85972a9b..5c19512038 100644 --- a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs +++ b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs @@ -7,6 +7,7 @@ using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface.Systems.Alerts; @@ -43,7 +44,7 @@ public sealed class AlertsUIController : UIController, IOnStateEntered e) { _alertsSystem?.AlertClicked(e); } diff --git a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs index a1a494c47b..d6a79a81c4 100644 --- a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs +++ b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs @@ -4,6 +4,7 @@ using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Input; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface.Systems.Alerts.Widgets; @@ -21,8 +22,10 @@ public sealed partial class AlertsUI : UIWidget RobustXamlLoader.Load(this); } - public void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype, - IReadOnlyDictionary alertStates) + public void SyncControls(AlertsSystem alertsSystem, + AlertOrderPrototype? alertOrderPrototype, + IReadOnlyDictionary alertStates) { // remove any controls with keys no longer present if (SyncRemoveControls(alertStates)) @@ -46,7 +49,7 @@ public sealed partial class AlertsUI : UIWidget _alertControls.Clear(); } - public event EventHandler? AlertPressed; + public event EventHandler>? AlertPressed; private bool SyncRemoveControls(IReadOnlyDictionary alertStates) { @@ -88,7 +91,7 @@ public sealed partial class AlertsUI : UIWidget } if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) && - existingAlertControl.Alert.AlertType == newAlert.AlertType) + existingAlertControl.Alert.ID == newAlert.ID) { // key is the same, simply update the existing control severity / cooldown existingAlertControl.SetSeverity(alertState.Severity); @@ -155,6 +158,6 @@ public sealed partial class AlertsUI : UIWidget if (args.Event.Function != EngineKeyFunctions.UIClick) return; - AlertPressed?.Invoke(this, control.Alert.AlertType); + AlertPressed?.Invoke(this, control.Alert.ID); } } diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs index 1da77ac558..ef4e6326cd 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs @@ -5,7 +5,6 @@ using Content.Shared.Alert; using Robust.Client.UserInterface; using Robust.Server.Player; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs { @@ -45,8 +44,8 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs Assert.That(alerts, Is.Not.Null); var alertCount = alerts.Count; - alertsSystem.ShowAlert(playerUid, AlertType.Debug1); - alertsSystem.ShowAlert(playerUid, AlertType.Debug2); + alertsSystem.ShowAlert(playerUid, "Debug1"); + alertsSystem.ShowAlert(playerUid, "Debug2"); Assert.That(alerts, Has.Count.EqualTo(alertCount + 2)); }); @@ -87,14 +86,14 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs // we should be seeing 3 alerts - our health, and the 2 debug alerts, in a specific order. Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(3)); var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c); - var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray(); - var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug1, AlertType.Debug2 }; + var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray(); + var expectedIDs = new[] { "HumanHealth", "Debug1", "Debug2" }; Assert.That(alertIDs, Is.SupersetOf(expectedIDs)); }); await server.WaitAssertion(() => { - alertsSystem.ClearAlert(playerUid, AlertType.Debug1); + alertsSystem.ClearAlert(playerUid, "Debug1"); }); await pair.RunTicksSync(5); @@ -104,8 +103,8 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs // we should be seeing 2 alerts now because one was cleared Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(2)); var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c); - var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray(); - var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug2 }; + var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray(); + var expectedIDs = new[] { "HumanHealth", "Debug2" }; Assert.That(alertIDs, Is.SupersetOf(expectedIDs)); }); diff --git a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs index 0ad198d6ef..74641126ae 100644 --- a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs +++ b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs @@ -1,5 +1,6 @@ using Content.Server.Gravity; using Content.Shared.Alert; +using Content.Shared.Gravity; using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.Gravity @@ -38,6 +39,7 @@ namespace Content.IntegrationTests.Tests.Gravity var entityManager = server.ResolveDependency(); var alertsSystem = server.ResolveDependency().GetEntitySystem(); + var weightlessAlert = SharedGravitySystem.WeightlessAlert; EntityUid human = default; @@ -56,7 +58,7 @@ namespace Content.IntegrationTests.Tests.Gravity await server.WaitAssertion(() => { // No gravity without a gravity generator - Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless)); + Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert)); generatorUid = entityManager.SpawnEntity("WeightlessGravityGeneratorDummy", entityManager.GetComponent(human).Coordinates); }); @@ -66,7 +68,7 @@ namespace Content.IntegrationTests.Tests.Gravity await server.WaitAssertion(() => { - Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless), Is.False); + Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert), Is.False); // This should kill gravity entityManager.DeleteEntity(generatorUid); @@ -76,7 +78,7 @@ namespace Content.IntegrationTests.Tests.Gravity await server.WaitAssertion(() => { - Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless)); + Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert)); }); await pair.RunTicksSync(10); diff --git a/Content.Server/Abilities/Mime/MimePowersComponent.cs b/Content.Server/Abilities/Mime/MimePowersComponent.cs index fd4fc2c2af..d56644ed19 100644 --- a/Content.Server/Abilities/Mime/MimePowersComponent.cs +++ b/Content.Server/Abilities/Mime/MimePowersComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Alert; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -47,5 +48,12 @@ namespace Content.Server.Abilities.Mime /// [DataField("vowCooldown")] public TimeSpan VowCooldown = TimeSpan.FromMinutes(5); + + [DataField] + public ProtoId VowAlert = "VowOfSilence"; + + [DataField] + public ProtoId VowBrokenAlert = "VowBroken"; + } } diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index 629fe99344..f3bf6e703f 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Popups; -using Content.Server.Speech.Muting; using Content.Shared.Actions; using Content.Shared.Actions.Events; using Content.Shared.Alert; @@ -54,7 +53,7 @@ namespace Content.Server.Abilities.Mime private void OnComponentInit(EntityUid uid, MimePowersComponent component, ComponentInit args) { EnsureComp(uid); - _alertsSystem.ShowAlert(uid, AlertType.VowOfSilence); + _alertsSystem.ShowAlert(uid, component.VowAlert); _actionsSystem.AddAction(uid, ref component.InvisibleWallActionEntity, component.InvisibleWallAction, uid); } @@ -115,8 +114,8 @@ namespace Content.Server.Abilities.Mime mimePowers.VowBroken = true; mimePowers.VowRepentTime = _timing.CurTime + mimePowers.VowCooldown; RemComp(uid); - _alertsSystem.ClearAlert(uid, AlertType.VowOfSilence); - _alertsSystem.ShowAlert(uid, AlertType.VowBroken); + _alertsSystem.ClearAlert(uid, mimePowers.VowAlert); + _alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert); _actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallActionEntity); } @@ -138,8 +137,8 @@ namespace Content.Server.Abilities.Mime mimePowers.ReadyToRepent = false; mimePowers.VowBroken = false; AddComp(uid); - _alertsSystem.ClearAlert(uid, AlertType.VowBroken); - _alertsSystem.ShowAlert(uid, AlertType.VowOfSilence); + _alertsSystem.ClearAlert(uid, mimePowers.VowAlert); + _alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert); _actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid); } } diff --git a/Content.Server/Alert/Commands/ClearAlert.cs b/Content.Server/Alert/Commands/ClearAlert.cs index 73a6ca52c7..2e317de754 100644 --- a/Content.Server/Alert/Commands/ClearAlert.cs +++ b/Content.Server/Alert/Commands/ClearAlert.cs @@ -40,13 +40,13 @@ namespace Content.Server.Alert.Commands var alertType = args[0]; var alertsSystem = _e.System(); - if (!alertsSystem.TryGet(Enum.Parse(alertType), out var alert)) + if (!alertsSystem.TryGet(alertType, out var alert)) { shell.WriteLine("unrecognized alertType " + alertType); return; } - alertsSystem.ClearAlert(attachedEntity, alert.AlertType); + alertsSystem.ClearAlert(attachedEntity, alert.ID); } } } diff --git a/Content.Server/Alert/Commands/ShowAlert.cs b/Content.Server/Alert/Commands/ShowAlert.cs index f37ab23f2f..cae24ff336 100644 --- a/Content.Server/Alert/Commands/ShowAlert.cs +++ b/Content.Server/Alert/Commands/ShowAlert.cs @@ -41,7 +41,7 @@ namespace Content.Server.Alert.Commands var alertType = args[0]; var severity = args[1]; var alertsSystem = _e.System(); - if (!alertsSystem.TryGet(Enum.Parse(alertType), out var alert)) + if (!alertsSystem.TryGet(alertType, out var alert)) { shell.WriteLine("unrecognized alertType " + alertType); return; @@ -53,7 +53,7 @@ namespace Content.Server.Alert.Commands } short? severity1 = sevint == -1 ? null : sevint; - alertsSystem.ShowAlert(attachedEntity, alert.AlertType, severity1, null); + alertsSystem.ShowAlert(attachedEntity, alert.ID, severity1, null); } } } diff --git a/Content.Server/Atmos/Components/BarotraumaComponent.cs b/Content.Server/Atmos/Components/BarotraumaComponent.cs index 4e29699872..d261c5ab03 100644 --- a/Content.Server/Atmos/Components/BarotraumaComponent.cs +++ b/Content.Server/Atmos/Components/BarotraumaComponent.cs @@ -1,5 +1,7 @@ +using Content.Shared.Alert; using Content.Shared.Damage; using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; namespace Content.Server.Atmos.Components { @@ -46,5 +48,13 @@ namespace Content.Server.Atmos.Components [ViewVariables(VVAccess.ReadWrite)] public bool HasImmunity = false; + [DataField] + public ProtoId HighPressureAlert = "HighPressure"; + + [DataField] + public ProtoId LowPressureAlert = "LowPressure"; + + [DataField] + public ProtoId PressureAlertCategory = "Pressure"; } } diff --git a/Content.Server/Atmos/Components/FlammableComponent.cs b/Content.Server/Atmos/Components/FlammableComponent.cs index e00f5efbdc..9ae99a1513 100644 --- a/Content.Server/Atmos/Components/FlammableComponent.cs +++ b/Content.Server/Atmos/Components/FlammableComponent.cs @@ -1,5 +1,7 @@ +using Content.Shared.Alert; using Content.Shared.Damage; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Prototypes; namespace Content.Server.Atmos.Components { @@ -77,5 +79,8 @@ namespace Content.Server.Atmos.Components /// [DataField, ViewVariables(VVAccess.ReadWrite)] public float FirestackFade = -0.1f; + + [DataField] + public ProtoId FireAlert = "Fire"; } } diff --git a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs index 98a5ffa70a..ec508790ba 100644 --- a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs +++ b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs @@ -245,7 +245,7 @@ namespace Content.Server.Atmos.EntitySystems _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking low pressure damage"); } - _alertsSystem.ShowAlert(uid, AlertType.LowPressure, 2); + _alertsSystem.ShowAlert(uid, barotrauma.LowPressureAlert, 2); } else if (pressure >= Atmospherics.HazardHighPressure) { @@ -260,7 +260,7 @@ namespace Content.Server.Atmos.EntitySystems _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking high pressure damage"); } - _alertsSystem.ShowAlert(uid, AlertType.HighPressure, 2); + _alertsSystem.ShowAlert(uid, barotrauma.HighPressureAlert, 2); } else { @@ -275,13 +275,13 @@ namespace Content.Server.Atmos.EntitySystems switch (pressure) { case <= Atmospherics.WarningLowPressure: - _alertsSystem.ShowAlert(uid, AlertType.LowPressure, 1); + _alertsSystem.ShowAlert(uid, barotrauma.LowPressureAlert, 1); break; case >= Atmospherics.WarningHighPressure: - _alertsSystem.ShowAlert(uid, AlertType.HighPressure, 1); + _alertsSystem.ShowAlert(uid, barotrauma.HighPressureAlert, 1); break; default: - _alertsSystem.ClearAlertCategory(uid, AlertCategory.Pressure); + _alertsSystem.ClearAlertCategory(uid, barotrauma.PressureAlertCategory); break; } } diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index 4a8cbbdc88..e8721920dd 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -415,11 +415,11 @@ namespace Content.Server.Atmos.EntitySystems if (!flammable.OnFire) { - _alertsSystem.ClearAlert(uid, AlertType.Fire); + _alertsSystem.ClearAlert(uid, flammable.FireAlert); continue; } - _alertsSystem.ShowAlert(uid, AlertType.Fire); + _alertsSystem.ShowAlert(uid, flammable.FireAlert); if (flammable.FireStacks > 0) { diff --git a/Content.Server/Body/Components/BloodstreamComponent.cs b/Content.Server/Body/Components/BloodstreamComponent.cs index 1d8aa9ffd3..a6d2afab21 100644 --- a/Content.Server/Body/Components/BloodstreamComponent.cs +++ b/Content.Server/Body/Components/BloodstreamComponent.cs @@ -1,5 +1,6 @@ using Content.Server.Body.Systems; using Content.Server.Chemistry.EntitySystems; +using Content.Shared.Alert; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; @@ -171,5 +172,8 @@ namespace Content.Server.Body.Components /// [ViewVariables(VVAccess.ReadWrite)] public TimeSpan StatusTime; + + [DataField] + public ProtoId BleedingAlert = "Bleed"; } } diff --git a/Content.Server/Body/Components/InternalsComponent.cs b/Content.Server/Body/Components/InternalsComponent.cs index 18caab8dcf..098f178921 100644 --- a/Content.Server/Body/Components/InternalsComponent.cs +++ b/Content.Server/Body/Components/InternalsComponent.cs @@ -1,3 +1,6 @@ +using Content.Shared.Alert; +using Robust.Shared.Prototypes; + namespace Content.Server.Body.Components { /// @@ -18,5 +21,8 @@ namespace Content.Server.Body.Components [ViewVariables(VVAccess.ReadWrite)] [DataField] public TimeSpan Delay = TimeSpan.FromSeconds(3); + + [DataField] + public ProtoId InternalsAlert = "Internals"; } } diff --git a/Content.Server/Body/Components/LungComponent.cs b/Content.Server/Body/Components/LungComponent.cs index 46600b3020..72af4d9e63 100644 --- a/Content.Server/Body/Components/LungComponent.cs +++ b/Content.Server/Body/Components/LungComponent.cs @@ -1,8 +1,8 @@ -using Content.Server.Atmos; using Content.Server.Body.Systems; using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Chemistry.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Body.Components; @@ -33,5 +33,5 @@ public sealed partial class LungComponent : Component /// The type of gas this lung needs. Used only for the breathing alerts, not actual metabolism. /// [DataField] - public AlertType Alert = AlertType.LowOxygen; + public ProtoId Alert = "LowOxygen"; } diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index ddeb154e79..f961307fc6 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -392,11 +392,11 @@ public sealed class BloodstreamSystem : EntitySystem component.BleedAmount = Math.Clamp(component.BleedAmount, 0, component.MaxBleedAmount); if (component.BleedAmount == 0) - _alertsSystem.ClearAlert(uid, AlertType.Bleed); + _alertsSystem.ClearAlert(uid, component.BleedingAlert); else { var severity = (short) Math.Clamp(Math.Round(component.BleedAmount, MidpointRounding.ToZero), 0, 10); - _alertsSystem.ShowAlert(uid, AlertType.Bleed, severity); + _alertsSystem.ShowAlert(uid, component.BleedingAlert, severity); } return true; diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index 8afd1c767f..c1e1de2baa 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -144,12 +144,12 @@ public sealed class InternalsSystem : EntitySystem private void OnInternalsStartup(Entity ent, ref ComponentStartup args) { - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } private void OnInternalsShutdown(Entity ent, ref ComponentShutdown args) { - _alerts.ClearAlert(ent, AlertType.Internals); + _alerts.ClearAlert(ent, ent.Comp.InternalsAlert); } private void OnInhaleLocation(Entity ent, ref InhaleLocationEvent args) @@ -159,7 +159,7 @@ public sealed class InternalsSystem : EntitySystem var gasTank = Comp(ent.Comp.GasTankEntity!.Value); args.Gas = _gasTank.RemoveAirVolume((ent.Comp.GasTankEntity.Value, gasTank), Atmospherics.BreathVolume); // TODO: Should listen to gas tank updates instead I guess? - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } } public void DisconnectBreathTool(Entity ent) @@ -173,7 +173,7 @@ public sealed class InternalsSystem : EntitySystem DisconnectTank(ent); } - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } public void ConnectBreathTool(Entity ent, EntityUid toolEntity) @@ -184,7 +184,7 @@ public sealed class InternalsSystem : EntitySystem } ent.Comp.BreathToolEntity = toolEntity; - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } public void DisconnectTank(InternalsComponent? component) @@ -196,7 +196,7 @@ public sealed class InternalsSystem : EntitySystem _gasTank.DisconnectFromInternals((component.GasTankEntity.Value, tank)); component.GasTankEntity = null; - _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component)); + _alerts.ShowAlert(component.Owner, component.InternalsAlert, GetSeverity(component)); } public bool TryConnectTank(Entity ent, EntityUid tankEntity) @@ -208,7 +208,7 @@ public sealed class InternalsSystem : EntitySystem _gasTank.DisconnectFromInternals((ent.Comp.GasTankEntity.Value, tank)); ent.Comp.GasTankEntity = tankEntity; - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); return true; } diff --git a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs index 8d475570ad..40858176bd 100644 --- a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs +++ b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs @@ -10,8 +10,8 @@ public sealed partial class AdjustAlert : ReagentEffect /// /// The specific Alert that will be adjusted /// - [DataField("alertType", required: true)] - public AlertType Type; + [DataField(required: true)] + public ProtoId AlertType; /// /// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately. @@ -42,7 +42,7 @@ public sealed partial class AdjustAlert : ReagentEffect if (Clear && Time <= 0) { - alertSys.ClearAlert(args.SolutionEntity, Type); + alertSys.ClearAlert(args.SolutionEntity, AlertType); } else { @@ -52,7 +52,7 @@ public sealed partial class AdjustAlert : ReagentEffect if ((ShowCooldown || Clear) && Time > 0) cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time)); - alertSys.ShowAlert(args.SolutionEntity, Type, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown); + alertSys.ShowAlert(args.SolutionEntity, AlertType, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown); } } diff --git a/Content.Server/Clothing/MagbootsSystem.cs b/Content.Server/Clothing/MagbootsSystem.cs index f12558389e..3838ad168d 100644 --- a/Content.Server/Clothing/MagbootsSystem.cs +++ b/Content.Server/Clothing/MagbootsSystem.cs @@ -29,11 +29,11 @@ public sealed class MagbootsSystem : SharedMagbootsSystem if (state) { - _alerts.ShowAlert(parent, AlertType.Magboots); + _alerts.ShowAlert(parent, component.MagbootsAlert); } else { - _alerts.ClearAlert(parent, AlertType.Magboots); + _alerts.ClearAlert(parent, component.MagbootsAlert); } } diff --git a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs index 105a8f9720..51d3242ce4 100644 --- a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs +++ b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs @@ -163,8 +163,8 @@ public sealed partial class EnsnareableSystem public void UpdateAlert(EntityUid target, EnsnareableComponent component) { if (!component.IsEnsnared) - _alerts.ClearAlert(target, AlertType.Ensnared); + _alerts.ClearAlert(target, component.EnsnaredAlert); else - _alerts.ShowAlert(target, AlertType.Ensnared); + _alerts.ShowAlert(target, component.EnsnaredAlert); } } diff --git a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs index 1dfaf4f339..0c1e88653f 100644 --- a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs +++ b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs @@ -102,20 +102,23 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem /// public void SetSuitPowerAlert(EntityUid uid, SpaceNinjaComponent? comp = null) { - if (!Resolve(uid, ref comp, false) || comp.Deleted || comp.Suit == null) + if (!Resolve(uid, ref comp, false)) + return; + + if (comp.Deleted || comp.Suit == null) { - _alerts.ClearAlert(uid, AlertType.SuitPower); + _alerts.ClearAlert(uid, comp.SuitPowerAlert); return; } if (GetNinjaBattery(uid, out _, out var battery)) { var severity = ContentHelpers.RoundToLevels(MathF.Max(0f, battery.CurrentCharge), battery.MaxCharge, 8); - _alerts.ShowAlert(uid, AlertType.SuitPower, (short) severity); + _alerts.ShowAlert(uid, comp.SuitPowerAlert, (short) severity); } else { - _alerts.ClearAlert(uid, AlertType.SuitPower); + _alerts.ClearAlert(uid, comp.SuitPowerAlert); } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index 86be70c41f..c390432f3a 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -141,7 +141,7 @@ public sealed partial class RevenantSystem : EntitySystem if (TryComp(uid, out var store)) _store.UpdateUserInterface(uid, uid, store); - _alerts.ShowAlert(uid, AlertType.Essence); + _alerts.ShowAlert(uid, component.EssenceAlert); if (component.Essence <= 0) { diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index 2b5769881d..7a19fd13b2 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -317,7 +317,7 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem component.SubscribedPilots.Add(entity); - _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle); + _alertsSystem.ShowAlert(entity, pilotComponent.PilotingAlert); pilotComponent.Console = uid; ActionBlockerSystem.UpdateCanMove(entity); @@ -339,7 +339,7 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem if (!helm.SubscribedPilots.Remove(pilotUid)) return; - _alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle); + _alertsSystem.ClearAlert(pilotUid, pilotComponent.PilotingAlert); _popup.PopupEntity(Loc.GetString("shuttle-pilot-end"), pilotUid, pilotUid); diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index ceab044d4c..97adfd00eb 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -84,7 +84,7 @@ public sealed partial class BorgSystem : SharedBorgSystem private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args) { - UpdateBatteryAlert(uid); + UpdateBatteryAlert((uid, component)); _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); } @@ -183,7 +183,7 @@ public sealed partial class BorgSystem : SharedBorgSystem private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, PowerCellChangedEvent args) { - UpdateBatteryAlert(uid); + UpdateBatteryAlert((uid, component)); if (!TryComp(uid, out var draw)) return; @@ -256,12 +256,12 @@ public sealed partial class BorgSystem : SharedBorgSystem args.Cancel(); } - private void UpdateBatteryAlert(EntityUid uid, PowerCellSlotComponent? slotComponent = null) + private void UpdateBatteryAlert(Entity ent, PowerCellSlotComponent? slotComponent = null) { - if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery, slotComponent)) + if (!_powerCell.TryGetBatteryFromSlot(ent, out var battery, slotComponent)) { - _alerts.ClearAlert(uid, AlertType.BorgBattery); - _alerts.ShowAlert(uid, AlertType.BorgBatteryNone); + _alerts.ClearAlert(ent, ent.Comp.BatteryAlert); + _alerts.ShowAlert(ent, ent.Comp.NoBatteryAlert); return; } @@ -269,13 +269,13 @@ public sealed partial class BorgSystem : SharedBorgSystem // we make sure 0 only shows if they have absolutely no battery. // also account for floating point imprecision - if (chargePercent == 0 && _powerCell.HasDrawCharge(uid, cell: slotComponent)) + if (chargePercent == 0 && _powerCell.HasDrawCharge(ent, cell: slotComponent)) { chargePercent = 1; } - _alerts.ClearAlert(uid, AlertType.BorgBatteryNone); - _alerts.ShowAlert(uid, AlertType.BorgBattery, chargePercent); + _alerts.ClearAlert(ent, ent.Comp.NoBatteryAlert); + _alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent); } /// diff --git a/Content.Server/Temperature/Components/TemperatureComponent.cs b/Content.Server/Temperature/Components/TemperatureComponent.cs index ec00a570f9..3bfa12f269 100644 --- a/Content.Server/Temperature/Components/TemperatureComponent.cs +++ b/Content.Server/Temperature/Components/TemperatureComponent.cs @@ -1,7 +1,9 @@ using Content.Server.Temperature.Systems; +using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Damage; using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; namespace Content.Server.Temperature.Components; @@ -78,4 +80,10 @@ public sealed partial class TemperatureComponent : Component /// [DataField] public bool TakingDamage = false; + + [DataField] + public ProtoId HotAlert = "Hot"; + + [DataField] + public ProtoId ColdAlert = "Cold"; } diff --git a/Content.Server/Temperature/Systems/TemperatureSystem.cs b/Content.Server/Temperature/Systems/TemperatureSystem.cs index 6c9e99e5f3..23c8cb6783 100644 --- a/Content.Server/Temperature/Systems/TemperatureSystem.cs +++ b/Content.Server/Temperature/Systems/TemperatureSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Inventory; using Content.Shared.Rejuvenate; using Content.Shared.Temperature; using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Temperature.Systems; @@ -33,6 +34,9 @@ public sealed class TemperatureSystem : EntitySystem private float _accumulatedFrametime; + [ValidatePrototypeId] + public const string TemperatureAlertCategory = "Temperature"; + public override void Initialize() { SubscribeLocalEvent(EnqueueDamage); @@ -180,13 +184,13 @@ public sealed class TemperatureSystem : EntitySystem private void ServerAlert(EntityUid uid, AlertsComponent status, OnTemperatureChangeEvent args) { - AlertType type; + ProtoId type; float threshold; float idealTemp; if (!TryComp(uid, out var temperature)) { - _alerts.ClearAlertCategory(uid, AlertCategory.Temperature); + _alerts.ClearAlertCategory(uid, TemperatureAlertCategory); return; } @@ -203,12 +207,12 @@ public sealed class TemperatureSystem : EntitySystem if (args.CurrentTemperature <= idealTemp) { - type = AlertType.Cold; + type = temperature.ColdAlert; threshold = temperature.ColdDamageThreshold; } else { - type = AlertType.Hot; + type = temperature.HotAlert; threshold = temperature.HeatDamageThreshold; } @@ -230,7 +234,7 @@ public sealed class TemperatureSystem : EntitySystem break; case > 0.66f: - _alerts.ClearAlertCategory(uid, AlertCategory.Temperature); + _alerts.ClearAlertCategory(uid, TemperatureAlertCategory); break; } } diff --git a/Content.Shared/Alert/AlertCategory.cs b/Content.Shared/Alert/AlertCategory.cs deleted file mode 100644 index 7450f585a4..0000000000 --- a/Content.Shared/Alert/AlertCategory.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Content.Shared.Alert; - -/// -/// Every category of alert. Corresponds to category field in alert prototypes defined in YML -/// -public enum AlertCategory -{ - Pressure, - Temperature, - Breathing, - Buckled, - Health, - Internals, - Stamina, - Piloting, - Hunger, - Thirst, - Toxins, - Battery -} diff --git a/Content.Shared/Alert/AlertCategoryPrototype.cs b/Content.Shared/Alert/AlertCategoryPrototype.cs new file mode 100644 index 0000000000..7c7d047521 --- /dev/null +++ b/Content.Shared/Alert/AlertCategoryPrototype.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Alert; + +/// +/// This is a prototype for a category for marking alerts as mutually exclusive. +/// +[Prototype] +public sealed partial class AlertCategoryPrototype : IPrototype +{ + /// + [IdDataField] + public string ID { get; } = default!; +} diff --git a/Content.Shared/Alert/AlertKey.cs b/Content.Shared/Alert/AlertKey.cs index c784af4cd4..c5c3a7643e 100644 --- a/Content.Shared/Alert/AlertKey.cs +++ b/Content.Shared/Alert/AlertKey.cs @@ -1,5 +1,5 @@ -using Robust.Shared.Serialization; -using Robust.Shared.Serialization.Manager; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Alert; @@ -11,13 +11,13 @@ namespace Content.Shared.Alert; [Serializable, NetSerializable] public struct AlertKey { - public AlertType? AlertType { get; private set; } = Alert.AlertType.Error; - public readonly AlertCategory? AlertCategory; + public ProtoId? AlertType { get; private set; } = default!; + public readonly ProtoId? AlertCategory; /// NOTE: if the alert has a category you must pass the category for this to work /// properly as a key. I.e. if the alert has a category and you pass only the alert type, and you /// compare this to another AlertKey that has both the category and the same alert type, it will not consider them equal. - public AlertKey(AlertType? alertType, AlertCategory? alertCategory) + public AlertKey(ProtoId? alertType, ProtoId? alertCategory) { AlertCategory = alertCategory; AlertType = alertType; @@ -49,7 +49,7 @@ public struct AlertKey /// alert category, must not be null /// An alert key for the provided alert category. This must only be used for /// queries and never storage, as it is lacking an alert type. - public static AlertKey ForCategory(AlertCategory category) + public static AlertKey ForCategory(ProtoId category) { return new(null, category); } diff --git a/Content.Shared/Alert/AlertOrderPrototype.cs b/Content.Shared/Alert/AlertOrderPrototype.cs index 8279d592b4..af4241a27e 100644 --- a/Content.Shared/Alert/AlertOrderPrototype.cs +++ b/Content.Shared/Alert/AlertOrderPrototype.cs @@ -7,7 +7,7 @@ namespace Content.Shared.Alert /// /// Defines the order of alerts so they show up in a consistent order. /// - [Prototype("alertOrder")] + [Prototype] [DataDefinition] public sealed partial class AlertOrderPrototype : IPrototype, IComparer { @@ -15,7 +15,7 @@ namespace Content.Shared.Alert [IdDataField] public string ID { get; private set; } = default!; - [DataField("order")] + [DataField] private (string type, string alert)[] Order { // why would paul do this to me. @@ -46,10 +46,10 @@ namespace Content.Shared.Alert switch (type) { case "alertType": - _typeToIdx[Enum.Parse(alert)] = i++; + _typeToIdx[alert] = i++; break; case "category": - _categoryToIdx[Enum.Parse(alert)] = i++; + _categoryToIdx[alert] = i++; break; default: throw new ArgumentException(); @@ -58,17 +58,17 @@ namespace Content.Shared.Alert } } - private readonly Dictionary _typeToIdx = new(); - private readonly Dictionary _categoryToIdx = new(); + private readonly Dictionary, int> _typeToIdx = new(); + private readonly Dictionary, int> _categoryToIdx = new(); private int GetOrderIndex(AlertPrototype alert) { - if (_typeToIdx.TryGetValue(alert.AlertType, out var idx)) + if (_typeToIdx.TryGetValue(alert.ID, out var idx)) { return idx; } if (alert.Category != null && - _categoryToIdx.TryGetValue((AlertCategory) alert.Category, out idx)) + _categoryToIdx.TryGetValue(alert.Category.Value, out idx)) { return idx; } @@ -78,20 +78,25 @@ namespace Content.Shared.Alert public int Compare(AlertPrototype? x, AlertPrototype? y) { - if ((x == null) && (y == null)) return 0; - if (x == null) return 1; - if (y == null) return -1; + if (x == null && y == null) + return 0; + if (x == null) + return 1; + if (y == null) + return -1; var idx = GetOrderIndex(x); var idy = GetOrderIndex(y); if (idx == -1 && idy == -1) { // break ties by type value // Must cast to int to avoid integer overflow when subtracting (enum's unsigned) - return (int)x.AlertType - (int)y.AlertType; + return string.Compare(x.ID, y.ID, StringComparison.InvariantCulture); } - if (idx == -1) return 1; - if (idy == -1) return -1; + if (idx == -1) + return 1; + if (idy == -1) + return -1; var result = idx - idy; // not strictly necessary (we don't care about ones that go at the same index) // but it makes the sort stable @@ -99,7 +104,7 @@ namespace Content.Shared.Alert { // break ties by type value // Must cast to int to avoid integer overflow when subtracting (enum's unsigned) - return (int)x.AlertType - (int)y.AlertType; + return string.Compare(x.ID, y.ID, StringComparison.InvariantCulture); } return result; diff --git a/Content.Shared/Alert/AlertPrototype.cs b/Content.Shared/Alert/AlertPrototype.cs index 248cc00ba4..f53da27c0d 100644 --- a/Content.Shared/Alert/AlertPrototype.cs +++ b/Content.Shared/Alert/AlertPrototype.cs @@ -1,120 +1,116 @@ using Robust.Shared.Prototypes; using Robust.Shared.Utility; -namespace Content.Shared.Alert +namespace Content.Shared.Alert; + +/// +/// An alert popup with associated icon, tooltip, and other data. +/// +[Prototype] +public sealed partial class AlertPrototype : IPrototype { /// - /// An alert popup with associated icon, tooltip, and other data. + /// Type of alert, no 2 alert prototypes should have the same one. /// - [Prototype("alert")] - public sealed partial class AlertPrototype : IPrototype + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// List of icons to use for this alert. Each entry corresponds to a different severity level, starting from the + /// minimum and incrementing upwards. If severities are not supported, the first entry is used. + /// + [DataField(required: true)] + public List Icons = new(); + + /// + /// An entity used for displaying the in the UI control. + /// + [DataField] + public EntProtoId AlertViewEntity = "AlertSpriteView"; + + /// + /// Name to show in tooltip window. Accepts formatting. + /// + [DataField] + public string Name { get; private set; } = string.Empty; + + /// + /// Description to show in tooltip window. Accepts formatting. + /// + [DataField] + public string Description { get; private set; } = string.Empty; + + /// + /// Category the alert belongs to. Only one alert of a given category + /// can be shown at a time. If one is shown while another is already being shown, + /// it will be replaced. This can be useful for categories of alerts which should naturally + /// replace each other and are mutually exclusive, for example lowpressure / highpressure, + /// hot / cold. If left unspecified, the alert will not replace or be replaced by any other alerts. + /// + [DataField] + public ProtoId? Category { get; private set; } + + /// + /// Key which is unique w.r.t category semantics (alerts with same category have equal keys, + /// alerts with no category have different keys). + /// + public AlertKey AlertKey => new(ID, Category); + + /// + /// -1 (no effect) unless MaxSeverity is specified. Defaults to 1. Minimum severity level supported by this state. + /// + public short MinSeverity => MaxSeverity == -1 ? (short) -1 : _minSeverity; + + [DataField("minSeverity")] private short _minSeverity = 1; + + /// + /// Maximum severity level supported by this state. -1 (default) indicates + /// no severity levels are supported by the state. + /// + [DataField] + public short MaxSeverity = -1; + + /// + /// Indicates whether this state support severity levels + /// + public bool SupportsSeverity => MaxSeverity != -1; + + /// + /// Defines what to do when the alert is clicked. + /// This will always be null on clientside. + /// + [DataField(serverOnly: true)] + public IAlertClick? OnClick { get; private set; } + + /// severity level, if supported by this alert + /// the icon path to the texture for the provided severity level + public SpriteSpecifier GetIcon(short? severity = null) { - [ViewVariables] - string IPrototype.ID => AlertType.ToString(); + var minIcons = SupportsSeverity + ? MaxSeverity - MinSeverity + : 1; - /// - /// Type of alert, no 2 alert prototypes should have the same one. - /// - [IdDataField] - public AlertType AlertType { get; private set; } + if (Icons.Count < minIcons) + throw new InvalidOperationException($"Insufficient number of icons given for alert {ID}"); - /// - /// List of icons to use for this alert. Each entry corresponds to a different severity level, starting from the - /// minimum and incrementing upwards. If severities are not supported, the first entry is used. - /// - [DataField("icons", required: true)] - public List Icons = new(); + if (!SupportsSeverity) + return Icons[0]; - /// - /// An entity used for displaying the in the UI control. - /// - [DataField] - public EntProtoId AlertViewEntity = "AlertSpriteView"; - - /// - /// Name to show in tooltip window. Accepts formatting. - /// - [DataField("name")] - public string Name { get; private set; } = ""; - - /// - /// Description to show in tooltip window. Accepts formatting. - /// - [DataField("description")] - public string Description { get; private set; } = ""; - - /// - /// Category the alert belongs to. Only one alert of a given category - /// can be shown at a time. If one is shown while another is already being shown, - /// it will be replaced. This can be useful for categories of alerts which should naturally - /// replace each other and are mutually exclusive, for example lowpressure / highpressure, - /// hot / cold. If left unspecified, the alert will not replace or be replaced by any other alerts. - /// - [DataField("category")] - public AlertCategory? Category { get; private set; } - - /// - /// Key which is unique w.r.t category semantics (alerts with same category have equal keys, - /// alerts with no category have different keys). - /// - public AlertKey AlertKey => new(AlertType, Category); - - /// - /// -1 (no effect) unless MaxSeverity is specified. Defaults to 1. Minimum severity level supported by this state. - /// - public short MinSeverity => MaxSeverity == -1 ? (short) -1 : _minSeverity; - - [DataField("minSeverity")] private short _minSeverity = 1; - - /// - /// Maximum severity level supported by this state. -1 (default) indicates - /// no severity levels are supported by the state. - /// - [DataField("maxSeverity")] - public short MaxSeverity = -1; - - /// - /// Indicates whether this state support severity levels - /// - public bool SupportsSeverity => MaxSeverity != -1; - - /// - /// Defines what to do when the alert is clicked. - /// This will always be null on clientside. - /// - [DataField("onClick", serverOnly: true)] - public IAlertClick? OnClick { get; private set; } - - /// severity level, if supported by this alert - /// the icon path to the texture for the provided severity level - public SpriteSpecifier GetIcon(short? severity = null) + if (severity == null) { - var minIcons = SupportsSeverity - ? MaxSeverity - MinSeverity - : 1; - - if (Icons.Count < minIcons) - throw new InvalidOperationException($"Insufficient number of icons given for alert {AlertType}"); - - if (!SupportsSeverity) - return Icons[0]; - - if (severity == null) - { - throw new ArgumentException($"No severity specified but this alert ({AlertKey}) has severity.", nameof(severity)); - } - - if (severity < MinSeverity) - { - throw new ArgumentOutOfRangeException(nameof(severity), $"Severity below minimum severity in {AlertKey}."); - } - - if (severity > MaxSeverity) - { - throw new ArgumentOutOfRangeException(nameof(severity), $"Severity above maximum severity in {AlertKey}."); - } - - return Icons[severity.Value - _minSeverity]; + throw new ArgumentException($"No severity specified but this alert ({AlertKey}) has severity.", nameof(severity)); } + + if (severity < MinSeverity) + { + throw new ArgumentOutOfRangeException(nameof(severity), $"Severity below minimum severity in {AlertKey}."); + } + + if (severity > MaxSeverity) + { + throw new ArgumentOutOfRangeException(nameof(severity), $"Severity above maximum severity in {AlertKey}."); + } + + return Icons[severity.Value - _minSeverity]; } } diff --git a/Content.Shared/Alert/AlertState.cs b/Content.Shared/Alert/AlertState.cs index effd952203..d6309f6b42 100644 --- a/Content.Shared/Alert/AlertState.cs +++ b/Content.Shared/Alert/AlertState.cs @@ -1,3 +1,4 @@ +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Alert; @@ -9,5 +10,5 @@ public struct AlertState public (TimeSpan, TimeSpan)? Cooldown; public bool AutoRemove; public bool ShowCooldown; - public AlertType Type; + public ProtoId Type; } diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs deleted file mode 100644 index b989b8d4b6..0000000000 --- a/Content.Shared/Alert/AlertType.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace Content.Shared.Alert -{ - /// - /// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML - /// NOTE: Using byte for a compact encoding when sending this in messages, can upgrade - /// to ushort - /// - public enum AlertType : byte - { - Error, - LowOxygen, - LowNitrogen, - LowPressure, - HighPressure, - Fire, - Cold, - Hot, - Weightless, - Stun, - Handcuffed, - Ensnared, - Buckled, - HumanCrit, - HumanDead, - HumanHealth, - BorgBattery, - BorgBatteryNone, - PilotingShuttle, - Peckish, - Starving, - Thirsty, - Parched, - Stamina, - Pulled, - Pulling, - Magboots, - Internals, - Toxins, - Muted, - VowOfSilence, - VowBroken, - Essence, - Corporeal, - Bleed, - Pacified, - Debug1, - Debug2, - Debug3, - Debug4, - Debug5, - Debug6, - SuitPower, - BorgHealth, - BorgCrit, - BorgDead, - Deflecting - } - -} diff --git a/Content.Shared/Alert/AlertsSystem.cs b/Content.Shared/Alert/AlertsSystem.cs index 5b888e30c4..83c6fcd0dd 100644 --- a/Content.Shared/Alert/AlertsSystem.cs +++ b/Content.Shared/Alert/AlertsSystem.cs @@ -11,7 +11,7 @@ public abstract class AlertsSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IGameTiming _timing = default!; - private FrozenDictionary _typeToAlert = default!; + private FrozenDictionary, AlertPrototype> _typeToAlert = default!; public IReadOnlyDictionary? GetActiveAlerts(EntityUid euid) { @@ -20,23 +20,23 @@ public abstract class AlertsSystem : EntitySystem : null; } - public short GetSeverityRange(AlertType alertType) + public short GetSeverityRange(ProtoId alertType) { var minSeverity = _typeToAlert[alertType].MinSeverity; return (short)MathF.Max(minSeverity,_typeToAlert[alertType].MaxSeverity - minSeverity); } - public short GetMaxSeverity(AlertType alertType) + public short GetMaxSeverity(ProtoId alertType) { return _typeToAlert[alertType].MaxSeverity; } - public short GetMinSeverity(AlertType alertType) + public short GetMinSeverity(ProtoId alertType) { return _typeToAlert[alertType].MinSeverity; } - public bool IsShowingAlert(EntityUid euid, AlertType alertType) + public bool IsShowingAlert(EntityUid euid, ProtoId alertType) { if (!EntityManager.TryGetComponent(euid, out AlertsComponent? alertsComponent)) return false; @@ -51,7 +51,7 @@ public abstract class AlertsSystem : EntitySystem } /// true iff an alert of the indicated alert category is currently showing - public bool IsShowingAlertCategory(EntityUid euid, AlertCategory alertCategory) + public bool IsShowingAlertCategory(EntityUid euid, ProtoId alertCategory) { return EntityManager.TryGetComponent(euid, out AlertsComponent? alertsComponent) && alertsComponent.Alerts.ContainsKey(AlertKey.ForCategory(alertCategory)); @@ -78,7 +78,7 @@ public abstract class AlertsSystem : EntitySystem /// be erased if there is currently a cooldown for the alert) /// if true, the alert will be removed at the end of the cooldown /// if true, the cooldown will be visibly shown over the alert icon - public void ShowAlert(EntityUid euid, AlertType alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null, bool autoRemove = false, bool showCooldown = true ) + public void ShowAlert(EntityUid euid, ProtoId alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null, bool autoRemove = false, bool showCooldown = true ) { // This should be handled as part of networking. if (_timing.ApplyingState) @@ -131,7 +131,7 @@ public abstract class AlertsSystem : EntitySystem /// /// Clear the alert with the given category, if one is currently showing. /// - public void ClearAlertCategory(EntityUid euid, AlertCategory category) + public void ClearAlertCategory(EntityUid euid, ProtoId category) { if(!TryComp(euid, out AlertsComponent? alertsComponent)) return; @@ -150,7 +150,7 @@ public abstract class AlertsSystem : EntitySystem /// /// Clear the alert of the given type if it is currently showing. /// - public void ClearAlert(EntityUid euid, AlertType alertType) + public void ClearAlert(EntityUid euid, ProtoId alertType) { if (_timing.ApplyingState) return; @@ -286,13 +286,13 @@ public abstract class AlertsSystem : EntitySystem protected virtual void LoadPrototypes() { - var dict = new Dictionary(); + var dict = new Dictionary, AlertPrototype>(); foreach (var alert in _prototypeManager.EnumeratePrototypes()) { - if (!dict.TryAdd(alert.AlertType, alert)) + if (!dict.TryAdd(alert.ID, alert)) { Log.Error("Found alert with duplicate alertType {0} - all alerts must have" + - " a unique alertType, this one will be skipped", alert.AlertType); + " a unique alertType, this one will be skipped", alert.ID); } } @@ -303,7 +303,7 @@ public abstract class AlertsSystem : EntitySystem /// Tries to get the alert of the indicated type /// /// true if found - public bool TryGet(AlertType alertType, [NotNullWhen(true)] out AlertPrototype? alert) + public bool TryGet(ProtoId alertType, [NotNullWhen(true)] out AlertPrototype? alert) { return _typeToAlert.TryGetValue(alertType, out alert); } diff --git a/Content.Shared/Alert/ClickAlertEvent.cs b/Content.Shared/Alert/ClickAlertEvent.cs index fe7ca97e4c..43dd086b56 100644 --- a/Content.Shared/Alert/ClickAlertEvent.cs +++ b/Content.Shared/Alert/ClickAlertEvent.cs @@ -1,4 +1,5 @@ -using Robust.Shared.Serialization; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Alert; @@ -8,9 +9,9 @@ namespace Content.Shared.Alert; [Serializable, NetSerializable] public sealed class ClickAlertEvent : EntityEventArgs { - public readonly AlertType Type; + public readonly ProtoId Type; - public ClickAlertEvent(AlertType alertType) + public ClickAlertEvent(ProtoId alertType) { Type = alertType; } diff --git a/Content.Shared/Buckle/Components/StrapComponent.cs b/Content.Shared/Buckle/Components/StrapComponent.cs index 72c92ebf84..9a19cea0c9 100644 --- a/Content.Shared/Buckle/Components/StrapComponent.cs +++ b/Content.Shared/Buckle/Components/StrapComponent.cs @@ -3,6 +3,7 @@ using Content.Shared.Alert; using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Buckle.Components; @@ -115,7 +116,7 @@ public sealed partial class StrapComponent : Component /// [DataField] [ViewVariables(VVAccess.ReadWrite)] - public AlertType BuckledAlertType = AlertType.Buckled; + public ProtoId BuckledAlertType = "Buckled"; /// /// The sum of the sizes of all the buckled entities in this strap diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 0d67473ffe..4e94c6134b 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -40,6 +40,9 @@ public abstract partial class SharedBuckleSystem SubscribeLocalEvent(OnBuckleUpdateCanMove); } + [ValidatePrototypeId] + public const string BuckledAlertCategory = "Buckled"; + private void OnBuckleComponentStartup(EntityUid uid, BuckleComponent component, ComponentStartup args) { UpdateBuckleStatus(uid, component); @@ -165,7 +168,7 @@ public abstract partial class SharedBuckleSystem } else { - _alerts.ClearAlertCategory(uid, AlertCategory.Buckled); + _alerts.ClearAlertCategory(uid, BuckledAlertCategory); } } diff --git a/Content.Shared/Clothing/MagbootsComponent.cs b/Content.Shared/Clothing/MagbootsComponent.cs index 0d0d59f89f..0d074ff38b 100644 --- a/Content.Shared/Clothing/MagbootsComponent.cs +++ b/Content.Shared/Clothing/MagbootsComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Alert; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -16,4 +17,7 @@ public sealed partial class MagbootsComponent : Component [DataField("on"), AutoNetworkedField] public bool On; + + [DataField] + public ProtoId MagbootsAlert = "Magboots"; } diff --git a/Content.Shared/CombatMode/Pacification/PacificationSystem.cs b/Content.Shared/CombatMode/Pacification/PacificationSystem.cs index 6d94c087af..a927e1a697 100644 --- a/Content.Shared/CombatMode/Pacification/PacificationSystem.cs +++ b/Content.Shared/CombatMode/Pacification/PacificationSystem.cs @@ -7,7 +7,6 @@ using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Throwing; using Content.Shared.Weapons.Ranged.Events; -using Content.Shared.Weapons.Ranged.Systems; using Robust.Shared.Timing; namespace Content.Shared.CombatMode.Pacification; @@ -109,7 +108,7 @@ public sealed class PacificationSystem : EntitySystem _actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, false); } - _alertsSystem.ShowAlert(uid, AlertType.Pacified); + _alertsSystem.ShowAlert(uid, component.PacifiedAlert); } private void OnShutdown(EntityUid uid, PacifiedComponent component, ComponentShutdown args) @@ -121,7 +120,7 @@ public sealed class PacificationSystem : EntitySystem _combatSystem.SetCanDisarm(uid, true, combatMode); _actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, true); - _alertsSystem.ClearAlert(uid, AlertType.Pacified); + _alertsSystem.ClearAlert(uid, component.PacifiedAlert); } private void OnBeforeThrow(Entity ent, ref BeforeThrowEvent args) diff --git a/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs b/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs index 464ef77885..96081e5dc6 100644 --- a/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs +++ b/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs @@ -1,4 +1,6 @@ +using Content.Shared.Alert; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Shared.CombatMode.Pacification; @@ -42,4 +44,6 @@ public sealed partial class PacifiedComponent : Component [DataField] public EntityUid? LastAttackedEntity = null; + [DataField] + public ProtoId PacifiedAlert = "Pacified"; } diff --git a/Content.Shared/Cuffs/Components/CuffableComponent.cs b/Content.Shared/Cuffs/Components/CuffableComponent.cs index 5da6fa41a5..4ddfe1b53e 100644 --- a/Content.Shared/Cuffs/Components/CuffableComponent.cs +++ b/Content.Shared/Cuffs/Components/CuffableComponent.cs @@ -1,6 +1,8 @@ +using Content.Shared.Alert; using Content.Shared.Damage; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -39,6 +41,9 @@ public sealed partial class CuffableComponent : Component /// [DataField("canStillInteract"), ViewVariables(VVAccess.ReadWrite)] public bool CanStillInteract = true; + + [DataField] + public ProtoId CuffedAlert = "Handcuffed"; } [Serializable, NetSerializable] diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 0077f5a358..f0f9a94983 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -172,9 +172,9 @@ namespace Content.Shared.Cuffs _actionBlocker.UpdateCanMove(uid); if (component.CanStillInteract) - _alerts.ClearAlert(uid, AlertType.Handcuffed); + _alerts.ClearAlert(uid, component.CuffedAlert); else - _alerts.ShowAlert(uid, AlertType.Handcuffed); + _alerts.ShowAlert(uid, component.CuffedAlert); var ev = new CuffedStateChangeEvent(); RaiseLocalEvent(uid, ref ev); diff --git a/Content.Shared/Damage/Components/StaminaComponent.cs b/Content.Shared/Damage/Components/StaminaComponent.cs index 5a2fba4970..14c3f6d9f5 100644 --- a/Content.Shared/Damage/Components/StaminaComponent.cs +++ b/Content.Shared/Damage/Components/StaminaComponent.cs @@ -1,4 +1,6 @@ +using Content.Shared.Alert; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Damage.Components; @@ -51,4 +53,7 @@ public sealed partial class StaminaComponent : Component [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] [AutoPausedField] public TimeSpan NextUpdate = TimeSpan.Zero; + + [DataField] + public ProtoId StaminaAlert = "Stamina"; } diff --git a/Content.Shared/Damage/Systems/StaminaSystem.cs b/Content.Shared/Damage/Systems/StaminaSystem.cs index 840b2e0431..1f9a7f1dd8 100644 --- a/Content.Shared/Damage/Systems/StaminaSystem.cs +++ b/Content.Shared/Damage/Systems/StaminaSystem.cs @@ -79,8 +79,7 @@ public sealed partial class StaminaSystem : EntitySystem { RemCompDeferred(uid); } - - SetStaminaAlert(uid); + _alerts.ClearAlert(uid, component.StaminaAlert); } private void OnStartup(EntityUid uid, StaminaComponent component, ComponentStartup args) @@ -204,13 +203,10 @@ public sealed partial class StaminaSystem : EntitySystem private void SetStaminaAlert(EntityUid uid, StaminaComponent? component = null) { if (!Resolve(uid, ref component, false) || component.Deleted) - { - _alerts.ClearAlert(uid, AlertType.Stamina); return; - } var severity = ContentHelpers.RoundToLevels(MathF.Max(0f, component.CritThreshold - component.StaminaDamage), component.CritThreshold, 7); - _alerts.ShowAlert(uid, AlertType.Stamina, (short) severity); + _alerts.ShowAlert(uid, component.StaminaAlert, (short) severity); } /// diff --git a/Content.Shared/Ensnaring/Components/EnsnareableComponent.cs b/Content.Shared/Ensnaring/Components/EnsnareableComponent.cs index 553f6df1c7..2536fac4ed 100644 --- a/Content.Shared/Ensnaring/Components/EnsnareableComponent.cs +++ b/Content.Shared/Ensnaring/Components/EnsnareableComponent.cs @@ -1,5 +1,7 @@ +using Content.Shared.Alert; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Ensnaring.Components; @@ -40,6 +42,9 @@ public sealed partial class EnsnareableComponent : Component [DataField("state")] public string? State; + + [DataField] + public ProtoId EnsnaredAlert = "Ensnared"; } [Serializable, NetSerializable] diff --git a/Content.Shared/Gravity/SharedGravitySystem.cs b/Content.Shared/Gravity/SharedGravitySystem.cs index 100d2ee74f..df13be51fd 100644 --- a/Content.Shared/Gravity/SharedGravitySystem.cs +++ b/Content.Shared/Gravity/SharedGravitySystem.cs @@ -17,6 +17,9 @@ namespace Content.Shared.Gravity [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly InventorySystem _inventory = default!; + [ValidatePrototypeId] + public const string WeightlessAlert = "Weightless"; + public bool IsWeightless(EntityUid uid, PhysicsComponent? body = null, TransformComponent? xform = null) { Resolve(uid, ref body, false); @@ -93,11 +96,11 @@ namespace Content.Shared.Gravity if (!ev.HasGravity) { - _alerts.ShowAlert(uid, AlertType.Weightless); + _alerts.ShowAlert(uid, WeightlessAlert); } else { - _alerts.ClearAlert(uid, AlertType.Weightless); + _alerts.ClearAlert(uid, WeightlessAlert); } } } @@ -106,11 +109,11 @@ namespace Content.Shared.Gravity { if (IsWeightless(ev.Euid)) { - _alerts.ShowAlert(ev.Euid, AlertType.Weightless); + _alerts.ShowAlert(ev.Euid, WeightlessAlert); } else { - _alerts.ClearAlert(ev.Euid, AlertType.Weightless); + _alerts.ClearAlert(ev.Euid, WeightlessAlert); } } @@ -118,11 +121,11 @@ namespace Content.Shared.Gravity { if (IsWeightless(uid)) { - _alerts.ShowAlert(uid, AlertType.Weightless); + _alerts.ShowAlert(uid, WeightlessAlert); } else { - _alerts.ClearAlert(uid, AlertType.Weightless); + _alerts.ClearAlert(uid, WeightlessAlert); } } diff --git a/Content.Shared/Mobs/Components/MobThresholdsComponent.cs b/Content.Shared/Mobs/Components/MobThresholdsComponent.cs index e97d3672a2..0e37cf9b10 100644 --- a/Content.Shared/Mobs/Components/MobThresholdsComponent.cs +++ b/Content.Shared/Mobs/Components/MobThresholdsComponent.cs @@ -2,6 +2,7 @@ using Content.Shared.Alert; using Content.Shared.FixedPoint; using Content.Shared.Mobs.Systems; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Mobs.Components; @@ -24,13 +25,16 @@ public sealed partial class MobThresholdsComponent : Component /// Used for alternate health alerts (silicons, for example) /// [DataField("stateAlertDict")] - public Dictionary StateAlertDict = new() + public Dictionary> StateAlertDict = new() { - {MobState.Alive, AlertType.HumanHealth}, - {MobState.Critical, AlertType.HumanCrit}, - {MobState.Dead, AlertType.HumanDead}, + {MobState.Alive, "HumanHealth"}, + {MobState.Critical, "HumanCrit"}, + {MobState.Dead, "HumanDead"}, }; + [DataField] + public ProtoId HealthAlertCategory = "Health"; + /// /// Whether or not this entity should display damage overlays (robots don't feel pain, black out etc.) /// @@ -53,19 +57,19 @@ public sealed class MobThresholdsComponentState : ComponentState public MobState CurrentThresholdState; - public Dictionary StateAlertDict = new() - { - {MobState.Alive, AlertType.HumanHealth}, - {MobState.Critical, AlertType.HumanCrit}, - {MobState.Dead, AlertType.HumanDead}, - }; + public Dictionary> StateAlertDict; public bool ShowOverlays; public bool AllowRevives; - public MobThresholdsComponentState(Dictionary unsortedThresholds, bool triggersAlerts, MobState currentThresholdState, - Dictionary stateAlertDict, bool showOverlays, bool allowRevives) + public MobThresholdsComponentState(Dictionary unsortedThresholds, + bool triggersAlerts, + MobState currentThresholdState, + Dictionary> stateAlertDict, + bool showOverlays, + bool allowRevives) { UnsortedThresholds = unsortedThresholds; TriggersAlerts = triggersAlerts; diff --git a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs index 59d9fb4c23..b11de9eac5 100644 --- a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs +++ b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs @@ -431,7 +431,7 @@ public sealed class MobThresholdSystem : EntitySystem private void MobThresholdShutdown(EntityUid target, MobThresholdsComponent component, ComponentShutdown args) { if (component.TriggersAlerts) - _alerts.ClearAlertCategory(target, AlertCategory.Health); + _alerts.ClearAlertCategory(target, component.HealthAlertCategory); } private void OnUpdateMobState(EntityUid target, MobThresholdsComponent component, ref UpdateMobStateEvent args) diff --git a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs index db889e7e3b..100cd9d6d3 100644 --- a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs @@ -1,4 +1,6 @@ +using Content.Shared.Alert; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Shared.Movement.Pulling.Components; @@ -36,4 +38,7 @@ public sealed partial class PullableComponent : Component [Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)] [AutoNetworkedField, DataField] public bool PrevFixedRotation; + + [DataField] + public ProtoId PulledAlert = "Pulled"; } diff --git a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs index 1fc9b731bd..061ec13ed2 100644 --- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs @@ -1,5 +1,7 @@ -using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Alert; +using Content.Shared.Movement.Pulling.Systems; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Movement.Pulling.Components; @@ -38,4 +40,7 @@ public sealed partial class PullerComponent : Component /// [DataField] public bool NeedsHands = true; + + [DataField] + public ProtoId PullingAlert = "Pulling"; } diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index 81b2fee569..2781c49529 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -223,7 +223,7 @@ public sealed class PullingSystem : EntitySystem if (TryComp(oldPuller, out var pullerComp)) { var pullerUid = oldPuller.Value; - _alertsSystem.ClearAlert(pullerUid, AlertType.Pulling); + _alertsSystem.ClearAlert(pullerUid, pullerComp.PullingAlert); pullerComp.Pulling = null; Dirty(oldPuller.Value, pullerComp); @@ -237,7 +237,7 @@ public sealed class PullingSystem : EntitySystem } - _alertsSystem.ClearAlert(pullableUid, AlertType.Pulled); + _alertsSystem.ClearAlert(pullableUid, pullableComp.PulledAlert); } public bool IsPulled(EntityUid uid, PullableComponent? component = null) @@ -460,8 +460,8 @@ public sealed class PullingSystem : EntitySystem // Messaging var message = new PullStartedMessage(pullerUid, pullableUid); - _alertsSystem.ShowAlert(pullerUid, AlertType.Pulling); - _alertsSystem.ShowAlert(pullableUid, AlertType.Pulled); + _alertsSystem.ShowAlert(pullerUid, pullerComp.PullingAlert); + _alertsSystem.ShowAlert(pullableUid, pullableComp.PulledAlert); RaiseLocalEvent(pullerUid, message); RaiseLocalEvent(pullableUid, message); diff --git a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs index 0f3bff265c..91c816df5c 100644 --- a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs +++ b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Alert; using Content.Shared.Ninja.Systems; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -53,4 +54,7 @@ public sealed partial class SpaceNinjaComponent : Component /// [DataField] public EntProtoId SpiderChargeObjective = "SpiderChargeObjective"; + + [DataField] + public ProtoId SuitPowerAlert = "SuitPower"; } diff --git a/Content.Shared/Nutrition/Components/HungerComponent.cs b/Content.Shared/Nutrition/Components/HungerComponent.cs index 9ac82ba283..79d895ddae 100644 --- a/Content.Shared/Nutrition/Components/HungerComponent.cs +++ b/Content.Shared/Nutrition/Components/HungerComponent.cs @@ -2,6 +2,7 @@ using Content.Shared.Alert; using Content.Shared.Damage; using Content.Shared.Nutrition.EntitySystems; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; @@ -65,15 +66,18 @@ public sealed partial class HungerComponent : Component /// /// A dictionary relating hunger thresholds to corresponding alerts. /// - [DataField("hungerThresholdAlerts", customTypeSerializer: typeof(DictionarySerializer))] + [DataField("hungerThresholdAlerts")] [AutoNetworkedField] - public Dictionary HungerThresholdAlerts = new() + public Dictionary> HungerThresholdAlerts = new() { - { HungerThreshold.Peckish, AlertType.Peckish }, - { HungerThreshold.Starving, AlertType.Starving }, - { HungerThreshold.Dead, AlertType.Starving } + { HungerThreshold.Peckish, "Peckish" }, + { HungerThreshold.Starving, "Starving" }, + { HungerThreshold.Dead, "Starving" } }; + [DataField] + public ProtoId HungerAlertCategory = "Hunger"; + /// /// A dictionary relating HungerThreshold to how much they modify . /// diff --git a/Content.Shared/Nutrition/Components/ThirstComponent.cs b/Content.Shared/Nutrition/Components/ThirstComponent.cs index 731346401f..f3ac881361 100644 --- a/Content.Shared/Nutrition/Components/ThirstComponent.cs +++ b/Content.Shared/Nutrition/Components/ThirstComponent.cs @@ -1,6 +1,7 @@ using Content.Shared.Alert; using Content.Shared.Nutrition.EntitySystems; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Nutrition.Components; @@ -56,11 +57,14 @@ public sealed partial class ThirstComponent : Component {ThirstThreshold.Dead, 0.0f}, }; - public static readonly Dictionary ThirstThresholdAlertTypes = new() + [DataField] + public ProtoId ThirstyCategory = "Thirst"; + + public static readonly Dictionary> ThirstThresholdAlertTypes = new() { - {ThirstThreshold.Thirsty, AlertType.Thirsty}, - {ThirstThreshold.Parched, AlertType.Parched}, - {ThirstThreshold.Dead, AlertType.Parched}, + {ThirstThreshold.Thirsty, "Thirsty"}, + {ThirstThreshold.Parched, "Parched"}, + {ThirstThreshold.Dead, "Parched"}, }; } diff --git a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs index 4de4e4d5fe..bff15f06ff 100644 --- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs @@ -61,7 +61,7 @@ public sealed class HungerSystem : EntitySystem private void OnShutdown(EntityUid uid, HungerComponent component, ComponentShutdown args) { - _alerts.ClearAlertCategory(uid, AlertCategory.Hunger); + _alerts.ClearAlertCategory(uid, component.HungerAlertCategory); } private void OnRefreshMovespeed(EntityUid uid, HungerComponent component, RefreshMovementSpeedModifiersEvent args) @@ -142,7 +142,7 @@ public sealed class HungerSystem : EntitySystem } else { - _alerts.ClearAlertCategory(uid, AlertCategory.Hunger); + _alerts.ClearAlertCategory(uid, component.HungerAlertCategory); } if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier)) diff --git a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs index 8ea7d9140c..205d8d6cde 100644 --- a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs @@ -165,7 +165,7 @@ public sealed class ThirstSystem : EntitySystem } else { - _alerts.ClearAlertCategory(uid, AlertCategory.Thirst); + _alerts.ClearAlertCategory(uid, component.ThirstyCategory); } switch (component.CurrentThirstThreshold) diff --git a/Content.Shared/Revenant/Components/RevenantComponent.cs b/Content.Shared/Revenant/Components/RevenantComponent.cs index 947c1a4b3f..d7fb28ef13 100644 --- a/Content.Shared/Revenant/Components/RevenantComponent.cs +++ b/Content.Shared/Revenant/Components/RevenantComponent.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Shared.Alert; using Content.Shared.FixedPoint; using Content.Shared.Store; using Content.Shared.Whitelist; @@ -200,6 +201,9 @@ public sealed partial class RevenantComponent : Component public EntityWhitelist? MalfunctionBlacklist; #endregion + [DataField] + public ProtoId EssenceAlert = "Essence"; + #region Visualizer [DataField("state")] public string State = "idle"; diff --git a/Content.Shared/Shuttles/Components/PilotComponent.cs b/Content.Shared/Shuttles/Components/PilotComponent.cs index 1a6927cf81..cb42db4436 100644 --- a/Content.Shared/Shuttles/Components/PilotComponent.cs +++ b/Content.Shared/Shuttles/Components/PilotComponent.cs @@ -1,7 +1,9 @@ using System.Numerics; +using Content.Shared.Alert; using Content.Shared.Movement.Systems; using Robust.Shared.GameStates; using Robust.Shared.Map; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Shared.Shuttles.Components @@ -32,6 +34,9 @@ namespace Content.Shared.Shuttles.Components [ViewVariables] public ShuttleButtons HeldButtons = ShuttleButtons.None; + [DataField] + public ProtoId PilotingAlert = "PilotingShuttle"; + public override bool SendOnlyToOwner => true; } } diff --git a/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs b/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs index 71d3a7bd16..e1776873da 100644 --- a/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs +++ b/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs @@ -1,6 +1,8 @@ -using Content.Shared.Whitelist; +using Content.Shared.Alert; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Silicons.Borgs.Components; @@ -76,6 +78,12 @@ public sealed partial class BorgChassisComponent : Component [DataField("noMindState")] public string NoMindState = string.Empty; #endregion + + [DataField] + public ProtoId BatteryAlert = "BorgBattery"; + + [DataField] + public ProtoId NoBatteryAlert = "BorgBatteryNone"; } [Serializable, NetSerializable] diff --git a/Content.Shared/StatusEffect/StatusEffectPrototype.cs b/Content.Shared/StatusEffect/StatusEffectPrototype.cs index ae9e26879e..8b1f84e4d8 100644 --- a/Content.Shared/StatusEffect/StatusEffectPrototype.cs +++ b/Content.Shared/StatusEffect/StatusEffectPrototype.cs @@ -10,7 +10,7 @@ namespace Content.Shared.StatusEffect public string ID { get; private set; } = default!; [DataField("alert")] - public AlertType? Alert { get; private set; } + public ProtoId? Alert { get; private set; } /// /// Whether a status effect should be able to apply to any entity, diff --git a/Content.Shared/StatusEffect/StatusEffectsSystem.cs b/Content.Shared/StatusEffect/StatusEffectsSystem.cs index f3e3e12bd8..9806077f9b 100644 --- a/Content.Shared/StatusEffect/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffect/StatusEffectsSystem.cs @@ -219,7 +219,7 @@ namespace Content.Shared.StatusEffect /// This is mostly for stuns, since Stun and Knockdown share an alert key. Other times this pretty much /// will not be useful. /// - private (TimeSpan, TimeSpan)? GetAlertCooldown(EntityUid uid, AlertType alert, StatusEffectsComponent status) + private (TimeSpan, TimeSpan)? GetAlertCooldown(EntityUid uid, ProtoId alert, StatusEffectsComponent status) { (TimeSpan, TimeSpan)? maxCooldown = null; foreach (var kvp in status.ActiveEffects) diff --git a/Content.Shared/Weapons/Reflect/ReflectSystem.cs b/Content.Shared/Weapons/Reflect/ReflectSystem.cs index 36dbedb4cb..03ad97edff 100644 --- a/Content.Shared/Weapons/Reflect/ReflectSystem.cs +++ b/Content.Shared/Weapons/Reflect/ReflectSystem.cs @@ -42,6 +42,9 @@ public sealed class ReflectSystem : EntitySystem [Dependency] private readonly StandingStateSystem _standing = default!; [Dependency] private readonly AlertsSystem _alerts = default!; + [ValidatePrototypeId] + private const string DeflectingAlert = "Deflecting"; + public override void Initialize() { base.Initialize(); @@ -296,11 +299,11 @@ public sealed class ReflectSystem : EntitySystem private void EnableAlert(EntityUid alertee) { - _alerts.ShowAlert(alertee, AlertType.Deflecting); + _alerts.ShowAlert(alertee, DeflectingAlert); } private void DisableAlert(EntityUid alertee) { - _alerts.ClearAlert(alertee, AlertType.Deflecting); + _alerts.ClearAlert(alertee, DeflectingAlert); } } diff --git a/Content.Tests/Shared/Alert/AlertManagerTests.cs b/Content.Tests/Shared/Alert/AlertManagerTests.cs index 2d5f6af5a7..c57df63d5b 100644 --- a/Content.Tests/Shared/Alert/AlertManagerTests.cs +++ b/Content.Tests/Shared/Alert/AlertManagerTests.cs @@ -1,6 +1,5 @@ using System.IO; using Content.Client.Alerts; -using Content.Server.Alert; using Content.Shared.Alert; using NUnit.Framework; using Robust.Shared.GameObjects; @@ -45,15 +44,15 @@ namespace Content.Tests.Shared.Alert prototypeManager.Initialize(); prototypeManager.LoadFromStream(new StringReader(PROTOTYPES)); - Assert.That(alertsSystem.TryGet(AlertType.LowPressure, out var lowPressure)); - Assert.That(lowPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); - Assert.That(alertsSystem.TryGet(AlertType.HighPressure, out var highPressure)); - Assert.That(highPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/highpressure.png")))); + Assert.That(alertsSystem.TryGet("LowPressure", out var lowPressure)); + Assert.That(lowPressure!.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); + Assert.That(alertsSystem.TryGet("HighPressure", out var highPressure)); + Assert.That(highPressure!.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/highpressure.png")))); - Assert.That(alertsSystem.TryGet(AlertType.LowPressure, out lowPressure)); - Assert.That(lowPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); - Assert.That(alertsSystem.TryGet(AlertType.HighPressure, out highPressure)); - Assert.That(highPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/highpressure.png")))); + Assert.That(alertsSystem.TryGet("LowPressure", out lowPressure)); + Assert.That(lowPressure!.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); + Assert.That(alertsSystem.TryGet("HighPressure", out highPressure)); + Assert.That(highPressure!.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/highpressure.png")))); } } } diff --git a/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs b/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs index 56f76d46a9..efcd59acbb 100644 --- a/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs +++ b/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs @@ -85,24 +85,24 @@ namespace Content.Tests.Shared.Alert var alerts = prototypeManager.EnumeratePrototypes(); // ensure they sort according to our expected criteria - var expectedOrder = new List(); - expectedOrder.Add(AlertType.Handcuffed); - expectedOrder.Add(AlertType.Ensnared); - expectedOrder.Add(AlertType.HighPressure); + var expectedOrder = new List(); + expectedOrder.Add("Handcuffed"); + expectedOrder.Add("Ensnared"); + expectedOrder.Add("HighPressure"); // stuff with only category + same category ordered by enum value - expectedOrder.Add(AlertType.Peckish); - expectedOrder.Add(AlertType.Hot); - expectedOrder.Add(AlertType.Stun); - expectedOrder.Add(AlertType.LowPressure); - expectedOrder.Add(AlertType.Cold); - // stuff at end of list ordered by enum value - expectedOrder.Add(AlertType.Weightless); - expectedOrder.Add(AlertType.PilotingShuttle); + expectedOrder.Add("Peckish"); + expectedOrder.Add("Hot"); + expectedOrder.Add("Stun"); + expectedOrder.Add("LowPressure"); + expectedOrder.Add("Cold"); + // stuff at end of list ordered by ID + expectedOrder.Add("PilotingShuttle"); + expectedOrder.Add("Weightless"); var actual = alerts.ToList(); actual.Sort(alertOrder); - Assert.That(actual.Select(a => a.AlertType).ToList(), Is.EqualTo(expectedOrder)); + Assert.That(actual.Select(a => a.ID).ToList(), Is.EqualTo(expectedOrder)); } } } diff --git a/Content.Tests/Shared/Alert/AlertPrototypeTests.cs b/Content.Tests/Shared/Alert/AlertPrototypeTests.cs index d95acb8aff..43ae98b452 100644 --- a/Content.Tests/Shared/Alert/AlertPrototypeTests.cs +++ b/Content.Tests/Shared/Alert/AlertPrototypeTests.cs @@ -39,9 +39,9 @@ namespace Content.Tests.Shared.Alert [Test] public void TestAlertKey() { - Assert.That(new AlertKey(AlertType.HumanHealth, null), Is.Not.EqualTo(AlertKey.ForCategory(AlertCategory.Health))); - Assert.That((new AlertKey(null, AlertCategory.Health)), Is.EqualTo(AlertKey.ForCategory(AlertCategory.Health))); - Assert.That((new AlertKey(AlertType.Buckled, AlertCategory.Health)), Is.EqualTo(AlertKey.ForCategory(AlertCategory.Health))); + Assert.That(new AlertKey("HumanHealth", null), Is.Not.EqualTo(AlertKey.ForCategory("Health"))); + Assert.That((new AlertKey(null, "Health")), Is.EqualTo(AlertKey.ForCategory("Health"))); + Assert.That((new AlertKey("Buckled", "Health")), Is.EqualTo(AlertKey.ForCategory("Health"))); } [TestCase(0, "/Textures/Interface/Alerts/Human/human.rsi/human0.png")] diff --git a/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs b/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs index 47ae3ef74a..bcc32e13de 100644 --- a/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs +++ b/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs @@ -15,6 +15,9 @@ namespace Content.Tests.Shared.Alert public sealed class ServerAlertsComponentTests : ContentUnitTest { const string PROTOTYPES = @" +- type: alertCategory + id: Pressure + - type: alert id: LowPressure category: Pressure @@ -49,10 +52,10 @@ namespace Content.Tests.Shared.Alert var alertsComponent = new AlertsComponent(); alertsComponent = IoCManager.InjectDependencies(alertsComponent); - Assert.That(entManager.System().TryGet(AlertType.LowPressure, out var lowpressure)); - Assert.That(entManager.System().TryGet(AlertType.HighPressure, out var highpressure)); + Assert.That(entManager.System().TryGet("LowPressure", out var lowpressure)); + Assert.That(entManager.System().TryGet("HighPressure", out var highpressure)); - entManager.System().ShowAlert(alertsComponent.Owner, AlertType.LowPressure, null, null); + entManager.System().ShowAlert(alertsComponent.Owner, "LowPressure"); var getty = new ComponentGetState(); entManager.EventBus.RaiseComponentEvent(alertsComponent, getty); @@ -60,17 +63,17 @@ namespace Content.Tests.Shared.Alert var alertState = (AlertsComponent.AlertsComponent_AutoState) getty.State!; Assert.That(alertState, Is.Not.Null); Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); - Assert.That(alertState.Alerts.ContainsKey(lowpressure.AlertKey)); + Assert.That(alertState.Alerts.ContainsKey(lowpressure!.AlertKey)); - entManager.System().ShowAlert(alertsComponent.Owner, AlertType.HighPressure, null, null); + entManager.System().ShowAlert(alertsComponent.Owner, "HighPressure"); // Lazy entManager.EventBus.RaiseComponentEvent(alertsComponent, getty); alertState = (AlertsComponent.AlertsComponent_AutoState) getty.State!; Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); - Assert.That(alertState.Alerts.ContainsKey(highpressure.AlertKey)); + Assert.That(alertState.Alerts.ContainsKey(highpressure!.AlertKey)); - entManager.System().ClearAlertCategory(alertsComponent.Owner, AlertCategory.Pressure); + entManager.System().ClearAlertCategory(alertsComponent.Owner, "Pressure"); entManager.EventBus.RaiseComponentEvent(alertsComponent, getty); alertState = (AlertsComponent.AlertsComponent_AutoState) getty.State!; diff --git a/Resources/Prototypes/Alerts/categories.yml b/Resources/Prototypes/Alerts/categories.yml new file mode 100644 index 0000000000..2365422ed9 --- /dev/null +++ b/Resources/Prototypes/Alerts/categories.yml @@ -0,0 +1,35 @@ +- type: alertCategory + id: Pressure + +- type: alertCategory + id: Temperature + +- type: alertCategory + id: Breathing + +- type: alertCategory + id: Buckled + +- type: alertCategory + id: Health + +- type: alertCategory + id: Internals + +- type: alertCategory + id: Stamina + +- type: alertCategory + id: Piloting + +- type: alertCategory + id: Hunger + +- type: alertCategory + id: Thirst + +- type: alertCategory + id: Toxins + +- type: alertCategory + id: Battery From 5d5046746688639b95655dcb9cb3bab67484561b Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 24 May 2024 16:08:23 +1200 Subject: [PATCH 12/21] Content changes for engine delta-state PR (#28134) * Update GasTileOverlayState * Update DecalGridState * Update NavMapState * poke * poke2 * poke3 * Poke dem tests --- .../EntitySystems/GasTileOverlaySystem.cs | 39 ++++++++++------ Content.Client/Decals/DecalSystem.cs | 39 ++++++++++------ Content.Client/Pinpointer/NavMapSystem.cs | 41 +++++++++++------ .../Components/GasTileOverlayComponent.cs | 45 ++++++++---------- .../SharedGasTileOverlaySystem.cs | 2 +- Content.Shared/Decals/DecalGridComponent.cs | 35 ++++++-------- Content.Shared/Decals/SharedDecalSystem.cs | 2 +- .../Pinpointer/SharedNavMapSystem.cs | 46 ++++++++----------- 8 files changed, 129 insertions(+), 120 deletions(-) diff --git a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs index 78185ce6b0..86cf0a9eb8 100644 --- a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -1,4 +1,5 @@ using Content.Client.Atmos.Overlays; +using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.EntitySystems; using JetBrains.Annotations; @@ -36,28 +37,38 @@ namespace Content.Client.Atmos.EntitySystems private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args) { - if (args.Current is not GasTileOverlayState state) - return; + Dictionary modifiedChunks; - // is this a delta or full state? - if (!state.FullState) + switch (args.Current) { - foreach (var index in comp.Chunks.Keys) + // is this a delta or full state? + case GasTileOverlayDeltaState delta: { - if (!state.AllChunks!.Contains(index)) - comp.Chunks.Remove(index); + modifiedChunks = delta.ModifiedChunks; + foreach (var index in comp.Chunks.Keys) + { + if (!delta.AllChunks.Contains(index)) + comp.Chunks.Remove(index); + } + + break; } - } - else - { - foreach (var index in comp.Chunks.Keys) + case GasTileOverlayState state: { - if (!state.Chunks.ContainsKey(index)) - comp.Chunks.Remove(index); + modifiedChunks = state.Chunks; + foreach (var index in comp.Chunks.Keys) + { + if (!state.Chunks.ContainsKey(index)) + comp.Chunks.Remove(index); + } + + break; } + default: + return; } - foreach (var (index, data) in state.Chunks) + foreach (var (index, data) in modifiedChunks) { comp.Chunks[index] = data; } diff --git a/Content.Client/Decals/DecalSystem.cs b/Content.Client/Decals/DecalSystem.cs index 901ab270fb..41e5f39c28 100644 --- a/Content.Client/Decals/DecalSystem.cs +++ b/Content.Client/Decals/DecalSystem.cs @@ -56,34 +56,43 @@ namespace Content.Client.Decals private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args) { - if (args.Current is not DecalGridState state) - return; - // is this a delta or full state? _removedChunks.Clear(); + Dictionary modifiedChunks; - if (!state.FullState) + switch (args.Current) { - foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + case DecalGridDeltaState delta: { - if (!state.AllChunks!.Contains(key)) - _removedChunks.Add(key); + modifiedChunks = delta.ModifiedChunks; + foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + { + if (!delta.AllChunks.Contains(key)) + _removedChunks.Add(key); + } + + break; } - } - else - { - foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + case DecalGridState state: { - if (!state.Chunks.ContainsKey(key)) - _removedChunks.Add(key); + modifiedChunks = state.Chunks; + foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + { + if (!state.Chunks.ContainsKey(key)) + _removedChunks.Add(key); + } + + break; } + default: + return; } if (_removedChunks.Count > 0) RemoveChunks(gridUid, gridComp, _removedChunks); - if (state.Chunks.Count > 0) - UpdateChunks(gridUid, gridComp, state.Chunks); + if (modifiedChunks.Count > 0) + UpdateChunks(gridUid, gridComp, modifiedChunks); } private void OnChunkUpdate(DecalChunkUpdateEvent ev) diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs index e33bc5d329..9aeb792a42 100644 --- a/Content.Client/Pinpointer/NavMapSystem.cs +++ b/Content.Client/Pinpointer/NavMapSystem.cs @@ -14,27 +14,40 @@ public sealed partial class NavMapSystem : SharedNavMapSystem private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args) { - if (args.Current is not NavMapComponentState state) - return; + Dictionary modifiedChunks; + Dictionary beacons; - if (!state.FullState) + switch (args.Current) { - foreach (var index in component.Chunks.Keys) + case NavMapDeltaState delta: { - if (!state.AllChunks!.Contains(index)) - component.Chunks.Remove(index); + modifiedChunks = delta.ModifiedChunks; + beacons = delta.Beacons; + foreach (var index in component.Chunks.Keys) + { + if (!delta.AllChunks!.Contains(index)) + component.Chunks.Remove(index); + } + + break; } - } - else - { - foreach (var index in component.Chunks.Keys) + case NavMapState state: { - if (!state.Chunks.ContainsKey(index)) - component.Chunks.Remove(index); + modifiedChunks = state.Chunks; + beacons = state.Beacons; + foreach (var index in component.Chunks.Keys) + { + if (!state.Chunks.ContainsKey(index)) + component.Chunks.Remove(index); + } + + break; } + default: + return; } - foreach (var (origin, chunk) in state.Chunks) + foreach (var (origin, chunk) in modifiedChunks) { var newChunk = new NavMapChunk(origin); Array.Copy(chunk, newChunk.TileData, chunk.Length); @@ -42,7 +55,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem } component.Beacons.Clear(); - foreach (var (nuid, beacon) in state.Beacons) + foreach (var (nuid, beacon) in beacons) { component.Beacons[nuid] = beacon; } diff --git a/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs index e72a1d6758..2c3149b11a 100644 --- a/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs +++ b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs @@ -1,7 +1,6 @@ using Robust.Shared.GameStates; using Robust.Shared.Serialization; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Shared.Atmos.Components; @@ -24,55 +23,47 @@ public sealed partial class GasTileOverlayComponent : Component public GameTick ForceTick { get; set; } } +[Serializable, NetSerializable] +public sealed class GasTileOverlayState(Dictionary chunks) : ComponentState +{ + public readonly Dictionary Chunks = chunks; +} [Serializable, NetSerializable] -public sealed class GasTileOverlayState : ComponentState, IComponentDeltaState +public sealed class GasTileOverlayDeltaState( + Dictionary modifiedChunks, + HashSet allChunks) + : ComponentState, IComponentDeltaState { - public readonly Dictionary Chunks; - public bool FullState => AllChunks == null; + public readonly Dictionary ModifiedChunks = modifiedChunks; + public readonly HashSet AllChunks = allChunks; - // required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; - - public GasTileOverlayState(Dictionary chunks) + public void ApplyToFullState(GasTileOverlayState state) { - Chunks = chunks; - } - - public void ApplyToFullState(IComponentState fullState) - { - DebugTools.Assert(!FullState); - var state = (GasTileOverlayState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { - if (!AllChunks!.Contains(key)) + if (!AllChunks.Contains(key)) state.Chunks.Remove(key); } - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { state.Chunks[chunk] = new(data); } } - public IComponentState CreateNewFullState(IComponentState fullState) + public GasTileOverlayState CreateNewFullState(GasTileOverlayState state) { - DebugTools.Assert(!FullState); - var state = (GasTileOverlayState) fullState; - DebugTools.Assert(state.FullState); + var chunks = new Dictionary(AllChunks.Count); - var chunks = new Dictionary(state.Chunks.Count); - - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { chunks[chunk] = new(data); } foreach (var (chunk, data) in state.Chunks) { - if (AllChunks!.Contains(chunk)) + if (AllChunks.Contains(chunk)) chunks.TryAdd(chunk, new(data)); } diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index f468724db3..8e7dfdedaf 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -55,7 +55,7 @@ namespace Content.Shared.Atmos.EntitySystems data[index] = chunk; } - args.State = new GasTileOverlayState(data) { AllChunks = new(component.Chunks.Keys) }; + args.State = new GasTileOverlayDeltaState(data, new(component.Chunks.Keys)); } public static Vector2i GetGasChunkIndices(Vector2i indices) diff --git a/Content.Shared/Decals/DecalGridComponent.cs b/Content.Shared/Decals/DecalGridComponent.cs index 8ac05cb280..67a9c03769 100644 --- a/Content.Shared/Decals/DecalGridComponent.cs +++ b/Content.Shared/Decals/DecalGridComponent.cs @@ -62,46 +62,37 @@ namespace Content.Shared.Decals } [Serializable, NetSerializable] - public sealed class DecalGridState : ComponentState, IComponentDeltaState + public sealed class DecalGridState(Dictionary chunks) : ComponentState { - public Dictionary Chunks; - public bool FullState => AllChunks == null; + public Dictionary Chunks = chunks; + } - // required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; + [Serializable, NetSerializable] + public sealed class DecalGridDeltaState(Dictionary modifiedChunks, HashSet allChunks) + : ComponentState, IComponentDeltaState + { + public Dictionary ModifiedChunks = modifiedChunks; + public HashSet AllChunks = allChunks; - public DecalGridState(Dictionary chunks) + public void ApplyToFullState(DecalGridState state) { - Chunks = chunks; - } - - public void ApplyToFullState(IComponentState fullState) - { - DebugTools.Assert(!FullState); - var state = (DecalGridState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { if (!AllChunks!.Contains(key)) state.Chunks.Remove(key); } - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { state.Chunks[chunk] = new(data); } } - public IComponentState CreateNewFullState(IComponentState fullState) + public DecalGridState CreateNewFullState(DecalGridState state) { - DebugTools.Assert(!FullState); - var state = (DecalGridState) fullState; - DebugTools.Assert(state.FullState); - var chunks = new Dictionary(state.Chunks.Count); - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { chunks[chunk] = new(data); } diff --git a/Content.Shared/Decals/SharedDecalSystem.cs b/Content.Shared/Decals/SharedDecalSystem.cs index 0665ccbf84..0a2349ea29 100644 --- a/Content.Shared/Decals/SharedDecalSystem.cs +++ b/Content.Shared/Decals/SharedDecalSystem.cs @@ -49,7 +49,7 @@ namespace Content.Shared.Decals data[index] = chunk; } - args.State = new DecalGridState(data) { AllChunks = new(component.ChunkCollection.ChunkCollection.Keys) }; + args.State = new DecalGridDeltaState(data, new(component.ChunkCollection.ChunkCollection.Keys)); } private void OnGridInitialize(GridInitializeEvent msg) diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index ffe81c2d0e..7c12321b5d 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -96,7 +96,7 @@ public abstract class SharedNavMapSystem : EntitySystem chunks.Add(origin, chunk.TileData); } - args.State = new NavMapComponentState(chunks, component.Beacons); + args.State = new NavMapState(chunks, component.Beacons); return; } @@ -109,12 +109,7 @@ public abstract class SharedNavMapSystem : EntitySystem chunks.Add(origin, chunk.TileData); } - args.State = new NavMapComponentState(chunks, component.Beacons) - { - // TODO NAVMAP cache a single AllChunks hashset in the component. - // Or maybe just only send them if a chunk gets removed. - AllChunks = new(component.Chunks.Keys), - }; + args.State = new NavMapDeltaState(chunks, component.Beacons, new(component.Chunks.Keys)); } #endregion @@ -122,32 +117,35 @@ public abstract class SharedNavMapSystem : EntitySystem #region: System messages [Serializable, NetSerializable] - protected sealed class NavMapComponentState( + protected sealed class NavMapState( Dictionary chunks, Dictionary beacons) - : ComponentState, IComponentDeltaState + : ComponentState { public Dictionary Chunks = chunks; public Dictionary Beacons = beacons; + } - // Required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; + [Serializable, NetSerializable] + protected sealed class NavMapDeltaState( + Dictionary modifiedChunks, + Dictionary beacons, + HashSet allChunks) + : ComponentState, IComponentDeltaState + { + public Dictionary ModifiedChunks = modifiedChunks; + public Dictionary Beacons = beacons; + public HashSet AllChunks = allChunks; - public bool FullState => AllChunks == null; - - public void ApplyToFullState(IComponentState fullState) + public void ApplyToFullState(NavMapState state) { - DebugTools.Assert(!FullState); - var state = (NavMapComponentState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { if (!AllChunks!.Contains(key)) state.Chunks.Remove(key); } - foreach (var (index, data) in Chunks) + foreach (var (index, data) in ModifiedChunks) { if (!state.Chunks.TryGetValue(index, out var stateValue)) state.Chunks[index] = stateValue = new int[data.Length]; @@ -162,12 +160,8 @@ public abstract class SharedNavMapSystem : EntitySystem } } - public IComponentState CreateNewFullState(IComponentState fullState) + public NavMapState CreateNewFullState(NavMapState state) { - DebugTools.Assert(!FullState); - var state = (NavMapComponentState) fullState; - DebugTools.Assert(state.FullState); - var chunks = new Dictionary(state.Chunks.Count); foreach (var (index, data) in state.Chunks) { @@ -176,13 +170,13 @@ public abstract class SharedNavMapSystem : EntitySystem var newData = chunks[index] = new int[ArraySize]; - if (Chunks.TryGetValue(index, out var updatedData)) + if (ModifiedChunks.TryGetValue(index, out var updatedData)) Array.Copy(newData, updatedData, ArraySize); else Array.Copy(newData, data, ArraySize); } - return new NavMapComponentState(chunks, new(Beacons)); + return new NavMapState(chunks, new(Beacons)); } } From 01e7c9c37eaa34a4bc388f59eace26ab79684526 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 24 May 2024 16:11:45 +1200 Subject: [PATCH 13/21] Update engine to v223.0.0 (#28239) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index ec794ce4e4..6a6bfe33ca 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit ec794ce4e4693069d3b3ebf7a88ead5ff2f860e0 +Subproject commit 6a6bfe33ca98dd3df6035ef4dcb7429f4dd5db66 From 76e13eed3595747135a8e0f4d668a9011d2ea06f Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 24 May 2024 17:03:03 +1200 Subject: [PATCH 14/21] Improve InteractionSystem range & BUI checks (#27999) * Improve InteractionSystem range & BUI checks * Ghost fixes * AAA * Fix test * fix nullable * revert to broadcast event * Fixes for eengine PR * Ah buckle code * ) --- .../UI/InstrumentBoundUserInterface.cs | 10 +- .../Interactable/InteractionSystem.cs | 22 +- Content.IntegrationTests/Tests/EntityTest.cs | 4 + .../Configurable/ConfigurationSystem.cs | 1 + .../Interaction/InteractionSystem.cs | 29 +- .../StationEvents/Events/ImmovableRodRule.cs | 4 +- .../Weapons/Melee/MeleeWeaponSystem.cs | 9 +- Content.Shared/Actions/SharedActionsSystem.cs | 8 +- .../Buckle/SharedBuckleSystem.Buckle.cs | 2 +- .../Interaction/SharedInteractionSystem.cs | 254 ++++++++++++------ .../Inventory/InventorySystem.Equip.cs | 6 +- .../MagicMirror/SharedMagicMirrorSystem.cs | 10 +- .../Movement/Pulling/Systems/PullingSystem.cs | 13 +- .../EntitySystems/SharedStorageSystem.cs | 2 +- .../UserInterface/ActivatableUIComponent.cs | 2 +- .../UserInterface/ActivatableUISystem.cs | 88 ++---- Content.Shared/Verbs/SharedVerbSystem.cs | 14 +- .../Fun/Instruments/base_instruments.yml | 2 +- 18 files changed, 232 insertions(+), 248 deletions(-) diff --git a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs index 2a846ff708..0f5729f55b 100644 --- a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs +++ b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs @@ -37,14 +37,8 @@ namespace Content.Client.Instruments.UI protected override void ReceiveMessage(BoundUserInterfaceMessage message) { - switch (message) - { - case InstrumentBandResponseBuiMessage bandRx: - _bandMenu?.Populate(bandRx.Nearby, EntMan); - break; - default: - break; - } + if (message is InstrumentBandResponseBuiMessage bandRx) + _bandMenu?.Populate(bandRx.Nearby, EntMan); } protected override void Open() diff --git a/Content.Client/Interactable/InteractionSystem.cs b/Content.Client/Interactable/InteractionSystem.cs index 0af8830e9a..ff0a607920 100644 --- a/Content.Client/Interactable/InteractionSystem.cs +++ b/Content.Client/Interactable/InteractionSystem.cs @@ -4,24 +4,6 @@ using Robust.Shared.Containers; namespace Content.Client.Interactable { - public sealed class InteractionSystem : SharedInteractionSystem - { - [Dependency] private readonly SharedContainerSystem _container = default!; - - public override bool CanAccessViaStorage(EntityUid user, EntityUid target) - { - if (!EntityManager.EntityExists(target)) - return false; - - if (!_container.TryGetContainingContainer(target, out var container)) - return false; - - if (!HasComp(container.Owner)) - return false; - - // we don't check if the user can access the storage entity itself. This should be handed by the UI system. - // Need to return if UI is open or not - return true; - } - } + // TODO Remove Shared prefix + public sealed class InteractionSystem : SharedInteractionSystem; } diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index d3b1fb4722..54af64122b 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -350,8 +350,12 @@ namespace Content.IntegrationTests.Tests "DebrisFeaturePlacerController", // Above. "LoadedChunk", // Worldgen chunk loading malding. "BiomeSelection", // Whaddya know, requires config. + "ActivatableUI", // Requires enum key }; + // TODO TESTS + // auto ignore any components that have a "required" data field. + await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; var entityManager = server.ResolveDependency(); diff --git a/Content.Server/Configurable/ConfigurationSystem.cs b/Content.Server/Configurable/ConfigurationSystem.cs index 2683bf4e09..5f5f1ef7d1 100644 --- a/Content.Server/Configurable/ConfigurationSystem.cs +++ b/Content.Server/Configurable/ConfigurationSystem.cs @@ -24,6 +24,7 @@ public sealed class ConfigurationSystem : EntitySystem private void OnInteractUsing(EntityUid uid, ConfigurationComponent component, InteractUsingEvent args) { + // TODO use activatable ui system if (args.Handled) return; diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index 4eac7e9ef1..9ac82b2185 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -7,31 +7,6 @@ using Robust.Shared.Player; namespace Content.Server.Interaction { - /// - /// Governs interactions during clicking on entities - /// - [UsedImplicitly] - public sealed partial class InteractionSystem : SharedInteractionSystem - { - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - - public override bool CanAccessViaStorage(EntityUid user, EntityUid target) - { - if (Deleted(target)) - return false; - - if (!_container.TryGetContainingContainer(target, out var container)) - return false; - - if (!TryComp(container.Owner, out StorageComponent? storage)) - return false; - - if (storage.Container?.ID != container.ID) - return false; - - // we don't check if the user can access the storage entity itself. This should be handed by the UI system. - return _uiSystem.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, user); - } - } + // TODO Remove Shared prefix + public sealed class InteractionSystem : SharedInteractionSystem; } diff --git a/Content.Server/StationEvents/Events/ImmovableRodRule.cs b/Content.Server/StationEvents/Events/ImmovableRodRule.cs index cacb839cd3..aa193f2f4c 100644 --- a/Content.Server/StationEvents/Events/ImmovableRodRule.cs +++ b/Content.Server/StationEvents/Events/ImmovableRodRule.cs @@ -28,7 +28,9 @@ public sealed class ImmovableRodRule : StationEventSystem(out var rod) && proto.TryGetComponent(out var despawn)) { - TryFindRandomTile(out _, out _, out _, out var targetCoords); + if (!TryFindRandomTile(out _, out _, out _, out var targetCoords)) + return; + var speed = RobustRandom.NextFloat(rod.MinSpeed, rod.MaxSpeed); var angle = RobustRandom.NextAngle(); var direction = angle.ToVec(); diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 2612e99ec9..190a2d0263 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -187,15 +187,10 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem if (session is { } pSession) { (targetCoordinates, targetLocalAngle) = _lag.GetCoordinatesAngle(target, pSession); - } - else - { - var xform = Transform(target); - targetCoordinates = xform.Coordinates; - targetLocalAngle = xform.LocalRotation; + return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); } - return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); + return Interaction.InRangeUnobstructed(user, target, range); } protected override void DoDamageEffect(List targets, EntityUid? user, TransformComponent targetXform) diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 30687c9322..c92d67ba81 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -502,13 +502,7 @@ public abstract class SharedActionsSystem : EntitySystem return distance <= action.Range; } - if (_interactionSystem.InRangeUnobstructed(user, target, range: action.Range) - && _containerSystem.IsInSameOrParentContainer(user, target)) - { - return true; - } - - return _interactionSystem.CanAccessViaStorage(user, target); + return _interactionSystem.InRangeAndAccessible(user, target, range: action.Range); } public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, Entity action) diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 4e94c6134b..00040211e3 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -64,7 +64,7 @@ public abstract partial class SharedBuckleSystem return; var strapPosition = Transform(strapUid).Coordinates; - if (ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance)) + if (ev.NewPosition.EntityId.IsValid() && ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance)) return; TryUnbuckle(uid, uid, true, component); diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index c82a749755..3324ce5b9b 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -1,11 +1,10 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.ActionBlocker; -using Content.Shared.Administration; using Content.Shared.Administration.Logs; -using Content.Shared.Administration.Managers; using Content.Shared.CombatMode; using Content.Shared.Database; +using Content.Shared.Ghost; using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Input; @@ -15,12 +14,13 @@ using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Movement.Components; -using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Storage; using Content.Shared.Tag; using Content.Shared.Timing; +using Content.Shared.UserInterface; using Content.Shared.Verbs; using Content.Shared.Wall; using JetBrains.Annotations; @@ -36,6 +36,7 @@ using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Serialization; using Robust.Shared.Timing; +using Robust.Shared.Utility; #pragma warning disable 618 @@ -50,12 +51,11 @@ namespace Content.Shared.Interaction [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly ISharedAdminManager _adminManager = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!; + [Dependency] private readonly SharedPhysicsSystem _broadphase = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedVerbSystem _verbSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; @@ -64,6 +64,18 @@ namespace Content.Shared.Interaction [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + + private EntityQuery _ignoreUiRangeQuery; + private EntityQuery _fixtureQuery; + private EntityQuery _itemQuery; + private EntityQuery _physicsQuery; + private EntityQuery _handsQuery; + private EntityQuery _relayQuery; + private EntityQuery _combatQuery; + private EntityQuery _wallMountQuery; + private EntityQuery _delayQuery; + private EntityQuery _uiQuery; private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; @@ -76,6 +88,17 @@ namespace Content.Shared.Interaction public override void Initialize() { + _ignoreUiRangeQuery = GetEntityQuery(); + _fixtureQuery = GetEntityQuery(); + _itemQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + _handsQuery = GetEntityQuery(); + _relayQuery = GetEntityQuery(); + _combatQuery = GetEntityQuery(); + _wallMountQuery = GetEntityQuery(); + _delayQuery = GetEntityQuery(); + _uiQuery = GetEntityQuery(); + SubscribeLocalEvent(HandleUserInterfaceRangeCheck); SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); @@ -111,29 +134,57 @@ namespace Content.Shared.Interaction /// private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev) { - var user = ev.Actor; + _uiQuery.TryComp(ev.Target, out var uiComp); + if (!_actionBlockerSystem.CanInteract(ev.Actor, ev.Target)) + { + // We permit ghosts to open uis unless explicitly blocked + if (ev.Message is not OpenBoundInterfaceMessage || !HasComp(ev.Actor) || uiComp?.BlockSpectators == true) + { + ev.Cancel(); + return; + } + } - if (!_actionBlockerSystem.CanInteract(user, ev.Target)) + var range = _ui.GetUiRange(ev.Target, ev.UiKey); + + // As long as range>0, the UI frame updates should have auto-closed the UI if it is out of range. + DebugTools.Assert(range <= 0 || UiRangeCheck(ev.Actor, ev.Target, range)); + + if (range <= 0 && !IsAccessible(ev.Actor, ev.Target)) { ev.Cancel(); return; } - // Check if the bound entity is accessible. Note that we allow admins to ignore this restriction, so that - // they can fiddle with UI's that people can't normally interact with (e.g., placing things directly into - // other people's backpacks). - if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target) - && !CanAccessViaStorage(user, ev.Target) - && !_adminManager.HasAdminFlag(user, AdminFlags.Admin)) + if (uiComp == null) + return; + + if (uiComp.SingleUser && uiComp.CurrentSingleUser != ev.Actor) { ev.Cancel(); return; } - if (!InRangeUnobstructed(user, ev.Target)) - { + if (!uiComp.RequireHands) + return; + + if (!_handsQuery.TryComp(ev.Actor, out var hands) || hands.Hands.Count == 0) ev.Cancel(); - } + } + + private bool UiRangeCheck(Entity user, Entity target, float range) + { + if (!Resolve(target, ref target.Comp)) + return false; + + if (user.Owner == target.Owner) + return true; + + // Fast check: if the user is the parent of the entity (e.g., holding it), we always assume that it is in range + if (target.Comp.ParentUid == user.Owner) + return true; + + return InRangeAndAccessible(user, target, range) || _ignoreUiRangeQuery.HasComp(user); } /// @@ -190,10 +241,7 @@ namespace Content.Shared.Interaction if (!InRangeUnobstructed(userEntity.Value, uid, popup: true)) return false; - if (!TryComp(uid, out PullableComponent? pull)) - return false; - - _pullSystem.TogglePull(uid, userEntity.Value, pull); + _pullSystem.TogglePull(uid, userEntity.Value); return false; } @@ -269,7 +317,7 @@ namespace Content.Shared.Interaction public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target) { // Always allow attack in these cases - if (target == null || !TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) + if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) return false; // Only eat input if: @@ -277,7 +325,7 @@ namespace Content.Shared.Interaction // - Target doesn't cancel should-interact event // This is intended to allow items to be picked up in combat mode, // but to also allow items to force attacks anyway (like mobs which are items, e.g. mice) - if (!HasComp(target)) + if (!_itemQuery.HasComp(target)) return false; var combatEv = new CombatModeShouldHandInteractEvent(); @@ -307,7 +355,7 @@ namespace Content.Shared.Interaction bool checkAccess = true, bool checkCanUse = true) { - if (TryComp(user, out var relay) && relay.RelayEntity is not null) + if (_relayQuery.TryComp(user, out var relay) && relay.RelayEntity is not null) { // TODO this needs to be handled better. This probably bypasses many complex can-interact checks in weird roundabout ways. if (_actionBlockerSystem.CanInteract(user, target)) @@ -321,7 +369,7 @@ namespace Content.Shared.Interaction if (target != null && Deleted(target.Value)) return; - if (!altInteract && TryComp(user, out var combatMode) && combatMode.IsInCombatMode) + if (!altInteract && _combatQuery.TryComp(user, out var combatMode) && combatMode.IsInCombatMode) { if (!CombatModeCanHandInteract(user, target)) return; @@ -343,10 +391,7 @@ namespace Content.Shared.Interaction // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // Also checks if the item is accessible via some storage UI (e.g., open backpack) - if (checkAccess - && target != null - && !_containerSystem.IsInSameOrParentContainer(user, target.Value) - && !CanAccessViaStorage(user, target.Value)) + if (checkAccess && target != null && !IsAccessible(user, target.Value)) return; var inRangeUnobstructed = target == null @@ -354,7 +399,7 @@ namespace Content.Shared.Interaction : !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities // Does the user have hands? - if (!TryComp(user, out var hands) || hands.ActiveHand == null) + if (!_handsQuery.TryComp(user, out var hands) || hands.ActiveHand == null) { var ev = new InteractNoHandEvent(user, target, coordinates); RaiseLocalEvent(user, ev); @@ -494,7 +539,7 @@ namespace Content.Shared.Interaction predicate ??= _ => false; var ray = new CollisionRay(origin.Position, dir.Normalized(), collisionMask); - var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList(); + var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList(); if (rayResults.Count == 0) return dir.Length(); @@ -557,23 +602,29 @@ namespace Content.Shared.Interaction } var ray = new CollisionRay(origin.Position, dir.Normalized(), (int) collisionMask); - var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); + var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); return rayResults.Count == 0; } public bool InRangeUnobstructed( - EntityUid origin, - EntityUid other, + Entity origin, + Entity other, float range = InteractionRange, CollisionGroup collisionMask = InRangeUnobstructedMask, Ignored? predicate = null, bool popup = false) { - if (!TryComp(other, out TransformComponent? otherXform)) + if (!Resolve(other, ref other.Comp)) return false; - return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate, + return InRangeUnobstructed(origin, + other, + other.Comp.Coordinates, + other.Comp.LocalRotation, + range, + collisionMask, + predicate, popup); } @@ -605,8 +656,8 @@ namespace Content.Shared.Interaction /// True if the two points are within a given range without being obstructed. /// public bool InRangeUnobstructed( - EntityUid origin, - EntityUid other, + Entity origin, + Entity other, EntityCoordinates otherCoordinates, Angle otherAngle, float range = InteractionRange, @@ -614,10 +665,10 @@ namespace Content.Shared.Interaction Ignored? predicate = null, bool popup = false) { - Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false); + Ignored combinedPredicate = e => e == origin.Owner || (predicate?.Invoke(e) ?? false); var inRange = true; MapCoordinates originPos = default; - var targetPos = otherCoordinates.ToMap(EntityManager, _transform); + var targetPos = _transform.ToMapCoordinates(otherCoordinates); Angle targetRot = default; // So essentially: @@ -627,23 +678,30 @@ namespace Content.Shared.Interaction // Alternatively we could check centre distances first though // that means we wouldn't be able to easily check overlap interactions. if (range > 0f && - TryComp(origin, out var fixtureA) && + _fixtureQuery.TryComp(origin, out var fixtureA) && // These fixture counts are stuff that has the component but no fixtures for (e.g. buttons). // At least until they get removed. fixtureA.FixtureCount > 0 && - TryComp(other, out var fixtureB) && + _fixtureQuery.TryComp(other, out var fixtureB) && fixtureB.FixtureCount > 0 && - TryComp(origin, out TransformComponent? xformA)) + Resolve(origin, ref origin.Comp)) { - var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation(); + var (worldPosA, worldRotA) = origin.Comp.GetWorldPositionRotation(); var xfA = new Transform(worldPosA, worldRotA); var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId); var xfB = new Transform(targetPos.Position, parentRotB + otherAngle); // Different map or the likes. - if (!_sharedBroadphaseSystem.TryGetNearest(origin, other, - out _, out _, out var distance, - xfA, xfB, fixtureA, fixtureB)) + if (!_broadphase.TryGetNearest( + origin, + other, + out _, + out _, + out var distance, + xfA, + xfB, + fixtureA, + fixtureB)) { inRange = false; } @@ -665,15 +723,15 @@ namespace Content.Shared.Interaction else { // We'll still do the raycast from the centres but we'll bump the range as we know they're in range. - originPos = _transform.GetMapCoordinates(origin, xform: xformA); + originPos = _transform.GetMapCoordinates(origin, xform: origin.Comp); range = (originPos.Position - targetPos.Position).Length(); } } // No fixtures, e.g. wallmounts. else { - originPos = _transform.GetMapCoordinates(origin); - var otherParent = Transform(other).ParentUid; + originPos = _transform.GetMapCoordinates(origin, origin); + var otherParent = (other.Comp ?? Transform(other)).ParentUid; targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle; } @@ -724,13 +782,13 @@ namespace Content.Shared.Interaction { HashSet ignored = new(); - if (HasComp(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide) + if (_itemQuery.HasComp(target) && _physicsQuery.TryComp(target, out var physics) && physics.CanCollide) { // If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck // inside of walls, users can still pick them up. - ignored.UnionWith(_sharedBroadphaseSystem.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); + ignored.UnionWith(_broadphase.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); } - else if (TryComp(target, out WallMountComponent? wallMount)) + else if (_wallMountQuery.TryComp(target, out var wallMount)) { // wall-mount exemptions may be restricted to a specific angle range.da @@ -748,13 +806,7 @@ namespace Content.Shared.Interaction ignored.UnionWith(grid.GetAnchoredEntities(targetCoords)); } - Ignored combinedPredicate = e => - { - return e == target - || (predicate?.Invoke(e) ?? false) - || ignored.Contains(e); - }; - + Ignored combinedPredicate = e => e == target || (predicate?.Invoke(e) ?? false) || ignored.Contains(e); return combinedPredicate; } @@ -951,10 +1003,8 @@ namespace Content.Shared.Interaction bool checkUseDelay = true, bool checkAccess = true) { - UseDelayComponent? delayComponent = null; - if (checkUseDelay - && TryComp(used, out delayComponent) - && _useDelay.IsDelayed((used, delayComponent))) + _delayQuery.TryComp(used, out var delayComponent); + if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return false; if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) @@ -965,11 +1015,11 @@ namespace Content.Shared.Interaction // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI) - if (checkAccess && !_containerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used)) + if (checkAccess && !IsAccessible(user, used)) return false; // Does the user have hands? - if (!HasComp(user)) + if (!_handsQuery.HasComp(user)) return false; var activateMsg = new ActivateInWorldEvent(user, used); @@ -979,7 +1029,9 @@ namespace Content.Shared.Interaction DoContactInteraction(user, used, activateMsg); // Still need to call this even without checkUseDelay in case this gets relayed from Activate. - _useDelay.TryResetDelay(used, component: delayComponent); + if (delayComponent != null) + _useDelay.TryResetDelay(used, component: delayComponent); + if (!activateMsg.WasLogged) _adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}"); return true; @@ -1000,11 +1052,8 @@ namespace Content.Shared.Interaction bool checkCanInteract = true, bool checkUseDelay = true) { - UseDelayComponent? delayComponent = null; - - if (checkUseDelay - && TryComp(used, out delayComponent) - && _useDelay.IsDelayed((used, delayComponent))) + _delayQuery.TryComp(used, out var delayComponent); + if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return true; // if the item is on cooldown, we consider this handled. if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) @@ -1066,11 +1115,60 @@ namespace Content.Shared.Interaction } #endregion + /// + /// Check if a user can access a target (stored in the same containers) and is in range without obstructions. + /// + public bool InRangeAndAccessible( + Entity user, + Entity target, + float range = InteractionRange, + CollisionGroup collisionMask = InRangeUnobstructedMask, + Ignored? predicate = null) + { + if (user == target) + return true; + + if (!Resolve(user, ref user.Comp)) + return false; + + if (!Resolve(target, ref target.Comp)) + return false; + + return IsAccessible(user, target) && InRangeUnobstructed(user, target, range, collisionMask, predicate); + } + + /// + /// Check if a user can access a target or if they are stored in different containers. + /// + public bool IsAccessible(Entity user, Entity target) + { + if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container)) + return true; + + return container != null && CanAccessViaStorage(user, target, container); + } + /// /// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This /// checks if the user can access the item in these situations. /// - public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target); + public bool CanAccessViaStorage(EntityUid user, EntityUid target) + { + if (!_containerSystem.TryGetContainingContainer(target, out var container)) + return false; + + return CanAccessViaStorage(user, target, container); + } + + /// + public bool CanAccessViaStorage(EntityUid user, EntityUid target, BaseContainer container) + { + if (StorageComponent.ContainerId != container.ID) + return false; + + // we don't check if the user can access the storage entity itself. This should be handed by the UI system. + return _ui.IsUiOpen(target, StorageComponent.StorageUiKey.Key, user); + } /// /// Checks whether an entity currently equipped by another player is accessible to some user. This shouldn't @@ -1151,19 +1249,15 @@ namespace Content.Shared.Interaction RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA)); } + private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev) { if (ev.Result == BoundUserInterfaceRangeResult.Fail) return; - if (InRangeUnobstructed(ev.Actor, ev.Target, ev.Data.InteractionRange)) - { - ev.Result = BoundUserInterfaceRangeResult.Pass; - } - else - { - ev.Result = BoundUserInterfaceRangeResult.Fail; - } + ev.Result = UiRangeCheck(ev.Actor!, ev.Target, ev.Data.InteractionRange) + ? BoundUserInterfaceRangeResult.Pass + : BoundUserInterfaceRangeResult.Fail; } } diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 400dfb0beb..7fd156213b 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -210,11 +210,7 @@ public abstract partial class InventorySystem return false; // Can the actor reach the item? - if (_interactionSystem.InRangeUnobstructed(actor, itemUid) && _containerSystem.IsInSameOrParentContainer(actor, itemUid)) - return true; - - // Is the item in an open storage UI, i.e., is the user quick-equipping from an open backpack? - if (_interactionSystem.CanAccessViaStorage(actor, itemUid)) + if (_interactionSystem.InRangeAndAccessible(actor, itemUid)) return true; // Is the actor currently stripping the target? Here we could check if the actor has the stripping UI open, but diff --git a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs index 91059d60bf..433ad6b4fc 100644 --- a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs +++ b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Humanoid.Markings; using Content.Shared.Interaction; using Content.Shared.UserInterface; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Shared.MagicMirror; @@ -21,10 +22,13 @@ public abstract class SharedMagicMirrorSystem : EntitySystem private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args) { - if (!Exists(component.Target) || !_interaction.InRangeUnobstructed(uid, component.Target.Value)) - { + if (args.Result == BoundUserInterfaceRangeResult.Fail) + return; + + DebugTools.Assert(component.Target != null && Exists(component.Target)); + + if (!_interaction.InRangeUnobstructed(uid, component.Target.Value)) args.Result = BoundUserInterfaceRangeResult.Fail; - } } private void OnBeforeUIOpen(Entity ent, ref BeforeActivatableUIOpenEvent args) diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index 2781c49529..3de71172c7 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -362,14 +362,17 @@ public sealed class PullingSystem : EntitySystem return !startPull.Cancelled && !getPulled.Cancelled; } - public bool TogglePull(EntityUid pullableUid, EntityUid pullerUid, PullableComponent pullable) + public bool TogglePull(Entity pullable, EntityUid pullerUid) { - if (pullable.Puller == pullerUid) + if (!Resolve(pullable, ref pullable.Comp, false)) + return false; + + if (pullable.Comp.Puller == pullerUid) { - return TryStopPull(pullableUid, pullable); + return TryStopPull(pullable, pullable.Comp); } - return TryStartPull(pullerUid, pullableUid, pullableComp: pullable); + return TryStartPull(pullerUid, pullable, pullableComp: pullable); } public bool TogglePull(EntityUid pullerUid, PullerComponent puller) @@ -377,7 +380,7 @@ public sealed class PullingSystem : EntitySystem if (!TryComp(puller.Pulling, out var pullable)) return false; - return TogglePull(puller.Pulling.Value, pullerUid, pullable); + return TogglePull((puller.Pulling.Value, pullable), pullerUid); } public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index ea0c9632f0..778e2b2e74 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -1078,7 +1078,7 @@ public abstract class SharedStorageSystem : EntitySystem /// true if inserted, false otherwise public bool PlayerInsertEntityInWorld(Entity uid, EntityUid player, EntityUid toInsert) { - if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid)) + if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid.Owner)) return false; if (!Insert(uid, toInsert, out _, user: player, uid.Comp)) diff --git a/Content.Shared/UserInterface/ActivatableUIComponent.cs b/Content.Shared/UserInterface/ActivatableUIComponent.cs index 3f83816b7d..93f05acac0 100644 --- a/Content.Shared/UserInterface/ActivatableUIComponent.cs +++ b/Content.Shared/UserInterface/ActivatableUIComponent.cs @@ -57,7 +57,7 @@ namespace Content.Shared.UserInterface /// [ViewVariables(VVAccess.ReadWrite)] [DataField] - public bool AllowSpectator = true; + public bool BlockSpectators; /// /// Whether the item must be in the user's currently selected/active hand. diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index a6d27ac545..c1822c4ee3 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -8,7 +8,7 @@ using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Verbs; -using Robust.Shared.Containers; +using Robust.Shared.Utility; namespace Content.Shared.UserInterface; @@ -19,15 +19,12 @@ public sealed partial class ActivatableUISystem : EntitySystem [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedInteractionSystem _interaction = default!; - - private readonly List _toClose = new(); public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnUseInHand); SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnInteractUsing); @@ -37,28 +34,24 @@ public sealed partial class ActivatableUISystem : EntitySystem SubscribeLocalEvent>(GetActivationVerb); SubscribeLocalEvent>(GetVerb); - // TODO ActivatableUI - // Add UI-user component, and listen for user container changes. - // I.e., should lose a computer UI if a player gets shut into a locker. - SubscribeLocalEvent(OnGotInserted); - SubscribeLocalEvent(OnGotRemoved); - - SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); SubscribeLocalEvent(OnActionPerform); InitializePower(); } - private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev) + private void OnStartup(Entity ent, ref ComponentStartup args) { - if (!TryComp(ev.Target, out ActivatableUIComponent? comp)) + if (ent.Comp.Key == null) + { + Log.Error($"Missing UI Key for entity: {ToPrettyString(ent)}"); return; + } - if (!comp.RequireHands) - return; - - if (!TryComp(ev.Actor, out HandsComponent? hands) || hands.Hands.Count == 0) - ev.Cancel(); + // TODO BUI + // set interaction range to zero to avoid constant range checks. + // + // if (ent.Comp.InHandsOnly && _uiSystem.TryGetInterfaceData(ent.Owner, ent.Comp.Key, out var data)) + // data.InteractionRange = 0; } private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args) @@ -77,9 +70,10 @@ public sealed partial class ActivatableUISystem : EntitySystem args.Verbs.Add(new ActivationVerb { - // TODO VERBS add "open UI" icon Act = () => InteractUI(args.User, uid, component), - Text = Loc.GetString(component.VerbText) + Text = Loc.GetString(component.VerbText), + // TODO VERB ICON find a better icon + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), }); } @@ -90,9 +84,10 @@ public sealed partial class ActivatableUISystem : EntitySystem args.Verbs.Add(new Verb { - // TODO VERBS add "open UI" icon Act = () => InteractUI(args.User, uid, component), - Text = Loc.GetString(component.VerbText) + Text = Loc.GetString(component.VerbText), + // TODO VERB ICON find a better icon + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), }); } @@ -119,7 +114,7 @@ public sealed partial class ActivatableUISystem : EntitySystem } } - return args.CanInteract || component.AllowSpectator && HasComp(args.User); + return args.CanInteract || HasComp(args.User) && !component.BlockSpectators; } private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) @@ -191,7 +186,7 @@ public sealed partial class ActivatableUISystem : EntitySystem return true; } - if (!_blockerSystem.CanInteract(user, uiEntity) && (!aui.AllowSpectator || !HasComp(user))) + if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp(user) || aui.BlockSpectators)) return false; if (aui.RequireHands) @@ -286,47 +281,4 @@ public sealed partial class ActivatableUISystem : EntitySystem if (ent.Comp.RequireHands && ent.Comp.InHandsOnly) CloseAll(ent, ent); } - - private void OnGotInserted(Entity ent, ref EntGotInsertedIntoContainerMessage args) - { - CheckAccess((ent, ent)); - } - - private void OnGotRemoved(Entity ent, ref EntGotRemovedFromContainerMessage args) - { - CheckAccess((ent, ent)); - } - - public void CheckAccess(Entity ent) - { - if (!Resolve(ent, ref ent.Comp)) - return; - - if (ent.Comp.Key == null) - { - Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}"); - return; - } - - foreach (var user in _uiSystem.GetActors(ent.Owner, ent.Comp.Key)) - { - if (!_container.IsInSameOrParentContainer(user, ent) - && !_interaction.CanAccessViaStorage(user, ent)) - { - _toClose.Add(user); - continue; - - } - - if (!_interaction.InRangeUnobstructed(user, ent)) - _toClose.Add(user); - } - - foreach (var user in _toClose) - { - _uiSystem.CloseUi(ent.Owner, ent.Comp.Key, user); - } - - _toClose.Clear(); - } } diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 60714aea8f..e78fe98f4c 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -72,19 +72,7 @@ namespace Content.Shared.Verbs extraCategories = new(); // accessibility checks - bool canAccess = false; - if (force || target == user) - canAccess = true; - else if (_interactionSystem.InRangeUnobstructed(user, target)) - { - // Note that being in a container does not count as an obstruction for InRangeUnobstructed - // Therefore, we need extra checks to ensure the item is actually accessible: - if (ContainerSystem.IsInSameOrParentContainer(user, target)) - canAccess = true; - else - // the item might be in a backpack that the user has open - canAccess = _interactionSystem.CanAccessViaStorage(user, target); - } + var canAccess = force || _interactionSystem.InRangeAndAccessible(user, target); // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually // call ActionBlocker checks, just cache it for the verb request. diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml index 684260b737..122ff42eb2 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml @@ -29,7 +29,7 @@ components: - type: Instrument - type: ActivatableUI - allowSpectator: false # otherwise they can play client-side music + blockSpectators: true # otherwise they can play client-side music inHandsOnly: false singleUser: true requireHands: true From 933a679c1a03acb3adf090c7b485d2c7bf53b69a Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 24 May 2024 05:04:10 +0000 Subject: [PATCH 15/21] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 9ae89dad4a..d330b043c6 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Plykiya - changes: - - message: Pre-filled syringes start in inject mode now. - type: Tweak - id: 6114 - time: '2024-03-09T10:19:03.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25881 - author: EmoGarbage404 changes: - message: Added a new "colorblind friendly" toggle in the accessibility menu. This @@ -3875,3 +3868,10 @@ id: 6613 time: '2024-05-23T20:27:45.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28226 +- author: ElectroJr + changes: + - message: Ghosts can once again open paper & other UIs + type: Fix + id: 6614 + time: '2024-05-24T05:03:03.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27999 From 926f72ee13ab3bea948d517f34957d0c75ccbe3a Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 24 May 2024 19:02:04 +1200 Subject: [PATCH 16/21] Update engine to v223.1.1 (#28245) * Update engine to v223.1.0 * Update engine to v223.1.1 --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 6a6bfe33ca..c250010dad 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 6a6bfe33ca98dd3df6035ef4dcb7429f4dd5db66 +Subproject commit c250010dada8279a48f6861dc80c0eebafee3421 From b2ca9b6a9cafe1e6f630fc6f085393a367597da1 Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Fri, 24 May 2024 14:44:42 +0000 Subject: [PATCH 17/21] Fix firelock prediction issues with periodic pulses of closing lights (#28227) * Fix firelock prediction issues with periodic pulses of closing lights For some reason this function was setting a time for the next state which was triggering the door system to try to close the firelock. This does not happen serverside because the function only fires from an event called clientside apparently. It appears to be an attempt to stop firelocks from closing instantly that did not function properly, and I cannot discern any other purpose. As such I have removed it. * Remove redundant serverside check This became redundant with commit 439a87f2 --- Content.Server/Doors/Systems/FirelockSystem.cs | 15 --------------- .../Doors/Components/FirelockComponent.cs | 2 -- .../Doors/Systems/SharedFirelockSystem.cs | 15 --------------- 3 files changed, 32 deletions(-) diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index 5ad86fb20a..e2b8b5829d 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -28,8 +28,6 @@ namespace Content.Server.Doors.Systems { base.Initialize(); - - SubscribeLocalEvent(OnBeforeDoorAutoclose); SubscribeLocalEvent(OnAtmosAlarm); SubscribeLocalEvent(PowerChanged); @@ -80,19 +78,6 @@ namespace Content.Server.Doors.Systems } } - private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args) - { - if (!this.IsPowered(uid, EntityManager)) - args.Cancel(); - - // Make firelocks autoclose, but only if the last alarm type it - // remembers was a danger. This is to prevent people from - // flooding hallways with endless bad air/fire. - if (component.AlarmAutoClose && - (_atmosAlarmable.TryGetHighestAlert(uid, out var alarm) && alarm != AtmosAlarmType.Danger || alarm == null)) - args.Cancel(); - } - private void OnAtmosAlarm(EntityUid uid, FirelockComponent component, AtmosAlarmEvent args) { if (!this.IsPowered(uid, EntityManager)) diff --git a/Content.Shared/Doors/Components/FirelockComponent.cs b/Content.Shared/Doors/Components/FirelockComponent.cs index ca62daaa0f..3f7d6c3f70 100644 --- a/Content.Shared/Doors/Components/FirelockComponent.cs +++ b/Content.Shared/Doors/Components/FirelockComponent.cs @@ -19,8 +19,6 @@ namespace Content.Shared.Doors.Components [DataField("lockedPryTimeModifier"), ViewVariables(VVAccess.ReadWrite)] public float LockedPryTimeModifier = 1.5f; - [DataField("autocloseDelay")] public TimeSpan AutocloseDelay = TimeSpan.FromSeconds(3f); - /// /// Maximum pressure difference before the firelock will refuse to open, in kPa. /// diff --git a/Content.Shared/Doors/Systems/SharedFirelockSystem.cs b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs index 7d033efdd4..47a29a4ba8 100644 --- a/Content.Shared/Doors/Systems/SharedFirelockSystem.cs +++ b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs @@ -18,8 +18,6 @@ public abstract class SharedFirelockSystem : EntitySystem { base.Initialize(); - SubscribeLocalEvent(OnUpdateState); - // Access/Prying SubscribeLocalEvent(OnBeforeDoorOpened); SubscribeLocalEvent(OnDoorGetPryTimeModifier); @@ -46,19 +44,6 @@ public abstract class SharedFirelockSystem : EntitySystem return _doorSystem.OnPartialClose(uid, door); } - private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args) - { - var ev = new BeforeDoorAutoCloseEvent(); - RaiseLocalEvent(uid, ev); - UpdateVisuals(uid, component, args); - if (ev.Cancelled) - { - return; - } - - _doorSystem.SetNextStateChange(uid, component.AutocloseDelay); - } - #region Access/Prying private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) From aa09741a4143b469b78a7215def4f0fc5e5a068c Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 24 May 2024 14:45:49 +0000 Subject: [PATCH 18/21] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index d330b043c6..61b8e7ec78 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: EmoGarbage404 - changes: - - message: Added a new "colorblind friendly" toggle in the accessibility menu. This - allows you to toggle between a standard and colorblind-friendly palette for - things like progress bars and the medical HUD. - type: Add - - message: The medical HUD is now bright, even in low light levels. - type: Tweak - id: 6115 - time: '2024-03-09T11:43:20.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25318 - author: metalgearsloth changes: - message: Fix NPC mouse movement. @@ -3875,3 +3864,10 @@ id: 6614 time: '2024-05-24T05:03:03.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27999 +- author: nikthechampiongr + changes: + - message: Firelocks will no longer randomly pulse closing lights. + type: Fix + id: 6615 + time: '2024-05-24T14:44:42.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28227 From 98503fc793be26acde787a6ab765f38887d0ae78 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Fri, 24 May 2024 15:57:02 -0400 Subject: [PATCH 19/21] Hotfix for crashes from bad item names (#28256) --- Content.Client/Examine/ExamineSystem.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 45db4efa53..125ec62e49 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -239,8 +239,10 @@ namespace Content.Client.Examine if (knowTarget) { - var itemName = FormattedMessage.RemoveMarkup(Identity.Name(target, EntityManager, player)); - var labelMessage = FormattedMessage.FromMarkup($"[bold]{itemName}[/bold]"); + // TODO: FormattedMessage.RemoveMarkupPermissive + // var itemName = FormattedMessage.RemoveMarkupPermissive(Identity.Name(target, EntityManager, player)); + var itemName = FormattedMessage.FromMarkupPermissive(Identity.Name(target, EntityManager, player)).ToString(); + var labelMessage = FormattedMessage.FromMarkupPermissive($"[bold]{itemName}[/bold]"); var label = new RichTextLabel(); label.SetMessage(labelMessage); hBox.AddChild(label); From d0cf620a85bd14ce8bdc641f6dde1715e771879e Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 25 May 2024 15:13:24 +1200 Subject: [PATCH 20/21] Fix weapon error logs (#28264) --- .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 0fb6ac3eff..7fc440db47 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -174,49 +174,39 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem private void OnLightAttack(LightAttackEvent msg, EntitySessionEventArgs args) { - var user = args.SenderSession.AttachedEntity; - - if (user == null) + if (args.SenderSession.AttachedEntity is not {} user) return; - if (!TryGetWeapon(user.Value, out var weaponUid, out var weapon) || + if (!TryGetWeapon(user, out var weaponUid, out var weapon) || weaponUid != GetEntity(msg.Weapon)) { return; } - AttemptAttack(args.SenderSession.AttachedEntity!.Value, weaponUid, weapon, msg, args.SenderSession); + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } private void OnHeavyAttack(HeavyAttackEvent msg, EntitySessionEventArgs args) { - if (args.SenderSession.AttachedEntity == null) - { + if (args.SenderSession.AttachedEntity is not {} user) return; - } - if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon) || + if (!TryGetWeapon(user, out var weaponUid, out var weapon) || weaponUid != GetEntity(msg.Weapon)) { return; } - AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession); + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args) { - if (args.SenderSession.AttachedEntity == null) - { + if (args.SenderSession.AttachedEntity is not {} user) return; - } - if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon)) - { - return; - } - - AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession); + if (TryGetWeapon(user, out var weaponUid, out var weapon)) + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } /// @@ -343,23 +333,32 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem if (!CombatMode.IsInCombatMode(user)) return false; + EntityUid? target = null; switch (attack) { case LightAttackEvent light: - var lightTarget = GetEntity(light.Target); + if (light.Target != null && !TryGetEntity(light.Target, out target)) + { + // Target was lightly attacked & deleted. + return false; + } - if (!Blocker.CanAttack(user, lightTarget, (weaponUid, weapon))) + if (!Blocker.CanAttack(user, target, (weaponUid, weapon))) return false; // Can't self-attack if you're the weapon - if (weaponUid == lightTarget) + if (weaponUid == target) return false; break; case DisarmAttackEvent disarm: - var disarmTarget = GetEntity(disarm.Target); + if (disarm.Target != null && !TryGetEntity(disarm.Target, out target)) + { + // Target was lightly attacked & deleted. + return false; + } - if (!Blocker.CanAttack(user, disarmTarget, (weaponUid, weapon), true)) + if (!Blocker.CanAttack(user, target, (weaponUid, weapon), true)) return false; break; default: From a58323cca150af73814be6d4cb70741246289d54 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 25 May 2024 21:49:46 +1200 Subject: [PATCH 21/21] Update engine to v223.1.2 (#28273) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index c250010dad..796abe1230 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit c250010dada8279a48f6861dc80c0eebafee3421 +Subproject commit 796abe1230554c31daffbfa6559a0a4bcf50b1af