From 41617b9754f15ffaab3098910f9f2095ed451d0a Mon Sep 17 00:00:00 2001 From: Kanashi-Panda Date: Mon, 16 Sep 2024 17:35:57 -0700 Subject: [PATCH 01/56] Crayons buffed to 45 uses (#32061) * Buffs crayons to 45 uses * Update Resources/Prototypes/Entities/Objects/Fun/crayons.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- Resources/Prototypes/Entities/Objects/Fun/crayons.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Fun/crayons.yml b/Resources/Prototypes/Entities/Objects/Fun/crayons.yml index ffecfdf994..3c4a4800a8 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/crayons.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/crayons.yml @@ -21,7 +21,7 @@ enum.CrayonUiKey.Key: type: CrayonBoundUserInterface - type: Crayon - capacity: 15 + capacity: 25 - type: Food - type: FlavorProfile flavors: From 5eaac00432bdb4c7cdc255880666ea3490632f57 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 17 Sep 2024 00:37:05 +0000 Subject: [PATCH 02/56] 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 27926b7a90..dd47739cb4 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Errant - changes: - - message: Vox now have their entry in the guidebook. - type: Fix - id: 6886 - time: '2024-07-09T00:28:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29713 - author: Whisper changes: - message: Light toggle actions now have a 1 second cooldown between uses. @@ -3910,3 +3903,10 @@ id: 7385 time: '2024-09-16T12:45:15.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32147 +- author: Alice Liddel + changes: + - message: Crayon charges increased from 15 to 25 + type: Add + id: 7386 + time: '2024-09-17T00:35:57.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32061 From 92be69a5abbaa358b1cdaaa34bb11076485ff685 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:49:19 +0300 Subject: [PATCH 03/56] Anomalous infections (#31876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * inner anomaly * anomaly pulse action * test anom mine * Update anomalies.yml * fix action cooldown * pyro_eyes * clientsystem * experiments * blya * some telegraphy * shock eyes! * shadow eyes * separate files * frosty eyes * fix * flora eyes * bluespace eyes * flesh eyes * redoing injction * auto add layers * пипяу * new injector component * stupid me * nice marker injectors * anomaly spawn on shutdown * gravity anom * dead anomaly spawning * add VOX states * sprite specific layers support * technology anom infection * auto detach anomalies that have moved away * Update anomaly_injections.yml * anomalyspawner integration * rock anomaly! * Update anomaly_injections.yml * fix crash bug * tag filter * fix anom dublication spawns * Update anomaly.yml * Update InnerBodyAnomalyComponent.cs * Update anomaly_injections.yml * dont spawn anomalies after decay * fix morb sprite, add end message * gravity resprite * admin logging, double injection fix * make flesh and living light mobs friendly to anomaly hosts * popups * severity feedback * sloth review * A * keep organs after gib * punpun host * sloth synchronization * Update arachnid.yml * increase infections spawnrate --- .../Anomaly/Effects/ClientInnerBodySystem.cs | 50 ++ .../Anomaly/AnomalySynchronizerSystem.cs | 44 +- Content.Server/Anomaly/AnomalySystem.cs | 6 +- .../AnomalySynchronizerComponent.cs | 11 +- .../Anomaly/Effects/InnerBodyAnomalySystem.cs | 236 +++++++ .../Explosion/EntitySystems/TriggerSystem.cs | 3 +- .../Anomaly/Components/AnomalyComponent.cs | 15 +- .../Components/InnerBodyAnomalyComponent.cs | 72 ++ .../InnerBodyAnomalyInjectorComponent.cs | 21 + .../Effects/SharedInnerBodyAnomalySystem.cs | 5 + Content.Shared/Anomaly/SharedAnomalySystem.cs | 21 +- Resources/Audio/Effects/attributions.yml | 5 + Resources/Audio/Effects/inneranomaly.ogg | Bin 0 -> 46892 bytes .../Locale/en-US/anomaly/inner_anomaly.ftl | 17 + Resources/Prototypes/Actions/anomaly.yml | 9 + .../Markers/Spawners/Random/anomaly.yml | 31 +- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 1 + .../Prototypes/Entities/Mobs/NPCs/flesh.yml | 7 +- .../Entities/Mobs/NPCs/living_light.yml | 5 + .../Prototypes/Entities/Mobs/NPCs/pets.yml | 1 + .../Entities/Mobs/Species/arachnid.yml | 1 + .../Prototypes/Entities/Mobs/Species/base.yml | 1 + .../Structures/Specific/Anomaly/anomalies.yml | 3 +- .../Specific/Anomaly/anomaly_injections.yml | 353 ++++++++++ .../Specific/Anomaly/anomaly_injectors.yml | 320 +++++++++ Resources/Prototypes/tags.yml | 3 + .../inner_anom_layer.rsi/bluespace.png | Bin 0 -> 864 bytes .../inner_anom_layer.rsi/bluespace_VOX.png | Bin 0 -> 1031 bytes .../Anomalies/inner_anom_layer.rsi/fire.png | Bin 0 -> 1007 bytes .../inner_anom_layer.rsi/fire_VOX.png | Bin 0 -> 1514 bytes .../Anomalies/inner_anom_layer.rsi/flesh.png | Bin 0 -> 1342 bytes .../inner_anom_layer.rsi/flesh_VOX.png | Bin 0 -> 1385 bytes .../Anomalies/inner_anom_layer.rsi/flora.png | Bin 0 -> 1868 bytes .../inner_anom_layer.rsi/flora_VOX.png | Bin 0 -> 1952 bytes .../Anomalies/inner_anom_layer.rsi/frost.png | Bin 0 -> 2650 bytes .../inner_anom_layer.rsi/frost_VOX.png | Bin 0 -> 2718 bytes .../Anomalies/inner_anom_layer.rsi/grav.png | Bin 0 -> 2837 bytes .../inner_anom_layer.rsi/grav_VOX.png | Bin 0 -> 3405 bytes .../Anomalies/inner_anom_layer.rsi/meta.json | 643 ++++++++++++++++++ .../Anomalies/inner_anom_layer.rsi/rock.png | Bin 0 -> 1398 bytes .../inner_anom_layer.rsi/rock_VOX.png | Bin 0 -> 1398 bytes .../Anomalies/inner_anom_layer.rsi/shadow.png | Bin 0 -> 768 bytes .../inner_anom_layer.rsi/shadow_VOX.png | Bin 0 -> 1002 bytes .../Anomalies/inner_anom_layer.rsi/shock.png | Bin 0 -> 879 bytes .../inner_anom_layer.rsi/shock_VOX.png | Bin 0 -> 2083 bytes .../Anomalies/inner_anom_layer.rsi/tech.png | Bin 0 -> 1241 bytes .../inner_anom_layer.rsi/tech_VOX.png | Bin 0 -> 1244 bytes .../Specific/anomaly.rsi/anom5-pulse.png | Bin 1195 -> 1252 bytes .../Structures/Specific/anomaly.rsi/anom5.png | Bin 429 -> 513 bytes 49 files changed, 1859 insertions(+), 25 deletions(-) create mode 100644 Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs create mode 100644 Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs create mode 100644 Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs create mode 100644 Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs create mode 100644 Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs create mode 100644 Resources/Audio/Effects/inneranomaly.ogg create mode 100644 Resources/Locale/en-US/anomaly/inner_anomaly.ftl create mode 100644 Resources/Prototypes/Actions/anomaly.yml create mode 100644 Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml create mode 100644 Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace_VOX.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire_VOX.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh_VOX.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora_VOX.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost_VOX.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav_VOX.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock_VOX.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow_VOX.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock_VOX.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech_VOX.png diff --git a/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs b/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs new file mode 100644 index 0000000000..efb1a8d46e --- /dev/null +++ b/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs @@ -0,0 +1,50 @@ +using Content.Shared.Anomaly.Components; +using Content.Shared.Anomaly.Effects; +using Content.Shared.Body.Components; +using Robust.Client.GameObjects; + +namespace Content.Client.Anomaly.Effects; + +public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem +{ + public override void Initialize() + { + SubscribeLocalEvent(OnAfterHandleState); + SubscribeLocalEvent(OnCompShutdown); + } + + private void OnAfterHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (!TryComp(ent, out var sprite)) + return; + + if (ent.Comp.FallbackSprite is null) + return; + + if (!sprite.LayerMapTryGet(ent.Comp.LayerMap, out var index)) + index = sprite.LayerMapReserveBlank(ent.Comp.LayerMap); + + if (TryComp(ent, out var body) && + body.Prototype is not null && + ent.Comp.SpeciesSprites.TryGetValue(body.Prototype.Value, out var speciesSprite)) + { + sprite.LayerSetSprite(index, speciesSprite); + } + else + { + sprite.LayerSetSprite(index, ent.Comp.FallbackSprite); + } + + sprite.LayerSetVisible(index, true); + sprite.LayerSetShader(index, "unshaded"); + } + + private void OnCompShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent, out var sprite)) + return; + + var index = sprite.LayerMapGet(ent.Comp.LayerMap); + sprite.LayerSetVisible(index, false); + } +} diff --git a/Content.Server/Anomaly/AnomalySynchronizerSystem.cs b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs index 59ef08402e..b1814c2741 100644 --- a/Content.Server/Anomaly/AnomalySynchronizerSystem.cs +++ b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Numerics; using Content.Server.Anomaly.Components; using Content.Server.DeviceLinking.Systems; using Content.Server.Power.Components; @@ -10,6 +11,7 @@ using Content.Shared.Popups; using Content.Shared.Power; using Robust.Shared.Audio.Systems; using Content.Shared.Verbs; +using Robust.Shared.Timing; namespace Content.Server.Anomaly; @@ -25,6 +27,7 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly PowerReceiverSystem _power = default!; + [Dependency] private readonly IGameTiming _timing = default!; public override void Initialize() { @@ -40,6 +43,34 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem SubscribeLocalEvent(OnAnomalyStabilityChanged); } + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var sync, out var xform)) + { + if (sync.ConnectedAnomaly is null) + continue; + + if (_timing.CurTime < sync.NextCheckTime) + continue; + sync.NextCheckTime += sync.CheckFrequency; + + if (Transform(sync.ConnectedAnomaly.Value).MapUid != Transform(uid).MapUid) + { + DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value); + continue; + } + + if (!xform.Coordinates.TryDistance(EntityManager, Transform(sync.ConnectedAnomaly.Value).Coordinates, out var distance)) + continue; + + if (distance > sync.AttachRange) + DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value); + } + } + /// /// If powered, try to attach a nearby anomaly. /// @@ -73,10 +104,10 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem if (args.Powered) return; - if (!TryComp(ent.Comp.ConnectedAnomaly, out var anomaly)) + if (ent.Comp.ConnectedAnomaly is null) return; - DisconnectFromAnomaly(ent, anomaly); + DisconnectFromAnomaly(ent, ent.Comp.ConnectedAnomaly.Value); } private void OnExamined(Entity ent, ref ExaminedEvent args) @@ -125,13 +156,16 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem //TODO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer. //Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer. - private void DisconnectFromAnomaly(Entity ent, AnomalyComponent anomaly) + private void DisconnectFromAnomaly(Entity ent, EntityUid other) { if (ent.Comp.ConnectedAnomaly == null) return; - if (ent.Comp.PulseOnDisconnect) - _anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly); + if (TryComp(other, out var anomaly)) + { + if (ent.Comp.PulseOnDisconnect) + _anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly); + } _popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), ent, PopupType.Large); _audio.PlayPvs(ent.Comp.ConnectedSound, ent); diff --git a/Content.Server/Anomaly/AnomalySystem.cs b/Content.Server/Anomaly/AnomalySystem.cs index 3e9760a056..b0de3de8f3 100644 --- a/Content.Server/Anomaly/AnomalySystem.cs +++ b/Content.Server/Anomaly/AnomalySystem.cs @@ -55,6 +55,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnStartCollide); + InitializeGenerator(); InitializeScanner(); InitializeVessel(); @@ -86,7 +87,10 @@ public sealed partial class AnomalySystem : SharedAnomalySystem private void OnShutdown(Entity anomaly, ref ComponentShutdown args) { - EndAnomaly(anomaly); + if (anomaly.Comp.CurrentBehavior is not null) + RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value); + + EndAnomaly(anomaly, spawnCore: false); } private void OnStartCollide(Entity anomaly, ref StartCollideEvent args) diff --git a/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs b/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs index 235e740cf3..3127f091e5 100644 --- a/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs +++ b/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs @@ -7,7 +7,7 @@ namespace Content.Server.Anomaly.Components; /// /// a device that allows you to translate anomaly activity into multitool signals. /// -[RegisterComponent, Access(typeof(AnomalySynchronizerSystem))] +[RegisterComponent, AutoGenerateComponentPause, Access(typeof(AnomalySynchronizerSystem))] public sealed partial class AnomalySynchronizerComponent : Component { /// @@ -34,6 +34,15 @@ public sealed partial class AnomalySynchronizerComponent : Component [DataField] public float AttachRange = 0.4f; + /// + /// Periodicheski checks to see if the anomaly has moved to disconnect it. + /// + [DataField] + public TimeSpan CheckFrequency = TimeSpan.FromSeconds(1f); + + [DataField, AutoPausedField] + public TimeSpan NextCheckTime = TimeSpan.Zero; + [DataField] public ProtoId DecayingPort = "Decaying"; diff --git a/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs new file mode 100644 index 0000000000..38c4c51d87 --- /dev/null +++ b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs @@ -0,0 +1,236 @@ +using Content.Server.Administration.Logs; +using Content.Server.Body.Systems; +using Content.Server.Chat.Managers; +using Content.Server.Jittering; +using Content.Server.Mind; +using Content.Server.Stunnable; +using Content.Shared.Actions; +using Content.Shared.Anomaly; +using Content.Shared.Anomaly.Components; +using Content.Shared.Anomaly.Effects; +using Content.Shared.Body.Components; +using Content.Shared.Chat; +using Content.Shared.Database; +using Content.Shared.Mobs; +using Content.Shared.Popups; +using Content.Shared.Whitelist; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Physics.Events; +using Robust.Shared.Prototypes; + +namespace Content.Server.Anomaly.Effects; + +public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem +{ + [Dependency] private readonly IAdminLogManager _adminLog = default!; + [Dependency] private readonly AnomalySystem _anomaly = default!; + [Dependency] private readonly ActionContainerSystem _actionContainer = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly BodySystem _body = default!; + [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly JitteringSystem _jitter = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly StunSystem _stun = default!; + + private readonly Color _messageColor = Color.FromSrgb(new Color(201, 22, 94)); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartCollideInjector); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnCompShutdown); + + SubscribeLocalEvent(OnAnomalyPulse); + SubscribeLocalEvent(OnAnomalyShutdown); + SubscribeLocalEvent(OnAnomalySupercritical); + SubscribeLocalEvent(OnSeverityChanged); + + SubscribeLocalEvent(OnMobStateChanged); + + SubscribeLocalEvent(OnActionPulse); + } + + private void OnActionPulse(Entity ent, ref ActionAnomalyPulseEvent args) + { + if (args.Handled) + return; + + _anomaly.DoAnomalyPulse(ent, ent.Comp); + + args.Handled = true; + } + + private void OnStartCollideInjector(Entity ent, ref StartCollideEvent args) + { + if (ent.Comp.Whitelist is not null && !_whitelist.IsValid(ent.Comp.Whitelist, args.OtherEntity)) + return; + if (TryComp(args.OtherEntity, out var innerAnom) && innerAnom.Injected) + return; + if (!_mind.TryGetMind(args.OtherEntity, out _, out var mindComponent)) + return; + + EntityManager.AddComponents(args.OtherEntity, ent.Comp.InjectionComponents); + QueueDel(ent); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + AddAnomalyToBody(ent); + } + + private void AddAnomalyToBody(Entity ent) + { + if (!_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom)) + return; + + if (ent.Comp.Injected) + return; + + ent.Comp.Injected = true; + + EntityManager.AddComponents(ent, injectedAnom.Components); + + _stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true); + _jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true); + + if (ent.Comp.StartSound is not null) + _audio.PlayPvs(ent.Comp.StartSound, ent); + + if (ent.Comp.StartMessage is not null && + _mind.TryGetMind(ent, out _, out var mindComponent) && + mindComponent.Session != null) + { + var message = Loc.GetString(ent.Comp.StartMessage); + var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); + _chat.ChatMessageToOne(ChatChannel.Server, + message, + wrappedMessage, + default, + false, + mindComponent.Session.Channel, + _messageColor); + + _popup.PopupEntity(message, ent, ent, PopupType.MediumCaution); + + _adminLog.Add(LogType.Anomaly,LogImpact.Extreme,$"{ToPrettyString(ent)} became anomaly host."); + } + Dirty(ent); + } + + private void OnAnomalyPulse(Entity ent, ref AnomalyPulseEvent args) + { + _stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true); + _jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true); + } + + private void OnAnomalySupercritical(Entity ent, ref AnomalySupercriticalEvent args) + { + if (!TryComp(ent, out var body)) + return; + + _body.GibBody(ent, true, body, splatModifier: 5f); + } + + private void OnSeverityChanged(Entity ent, ref AnomalySeverityChangedEvent args) + { + if (!_mind.TryGetMind(ent, out _, out var mindComponent) || mindComponent.Session == null) + return; + + var message = string.Empty; + + if (args.Severity >= 0.5 && ent.Comp.LastSeverityInformed < 0.5) + { + ent.Comp.LastSeverityInformed = 0.5f; + message = Loc.GetString("inner-anomaly-severity-info-50"); + } + if (args.Severity >= 0.75 && ent.Comp.LastSeverityInformed < 0.75) + { + ent.Comp.LastSeverityInformed = 0.75f; + message = Loc.GetString("inner-anomaly-severity-info-75"); + } + if (args.Severity >= 0.9 && ent.Comp.LastSeverityInformed < 0.9) + { + ent.Comp.LastSeverityInformed = 0.9f; + message = Loc.GetString("inner-anomaly-severity-info-90"); + } + if (args.Severity >= 1 && ent.Comp.LastSeverityInformed < 1) + { + ent.Comp.LastSeverityInformed = 1f; + message = Loc.GetString("inner-anomaly-severity-info-100"); + } + + if (message == string.Empty) + return; + + var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); + _chat.ChatMessageToOne(ChatChannel.Server, + message, + wrappedMessage, + default, + false, + mindComponent.Session.Channel, + _messageColor); + + _popup.PopupEntity(message, ent, ent, PopupType.MediumCaution); + } + + private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args) + { + if (args.NewMobState != MobState.Dead) + return; + + _anomaly.ChangeAnomalyHealth(ent, -2); //Shutdown it + } + + private void OnAnomalyShutdown(Entity ent, ref AnomalyShutdownEvent args) + { + RemoveAnomalyFromBody(ent); + RemCompDeferred(ent); + } + + private void OnCompShutdown(Entity ent, ref ComponentShutdown args) + { + RemoveAnomalyFromBody(ent); + } + + private void RemoveAnomalyFromBody(Entity ent) + { + if (!ent.Comp.Injected) + return; + + if (_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom)) + EntityManager.RemoveComponents(ent, injectedAnom.Components); + + _stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true); + + if (ent.Comp.EndMessage is not null && + _mind.TryGetMind(ent, out _, out var mindComponent) && + mindComponent.Session != null) + { + var message = Loc.GetString(ent.Comp.EndMessage); + var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); + _chat.ChatMessageToOne(ChatChannel.Server, + message, + wrappedMessage, + default, + false, + mindComponent.Session.Channel, + _messageColor); + + + _popup.PopupEntity(message, ent, ent, PopupType.MediumCaution); + + _adminLog.Add(LogType.Anomaly, LogImpact.Medium,$"{ToPrettyString(ent)} is no longer a host for the anomaly."); + } + + ent.Comp.Injected = false; + RemCompDeferred(ent); + } +} diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index 853b4fbc29..57847551aa 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -202,6 +202,7 @@ namespace Content.Server.Explosion.EntitySystems args.Handled = true; } + private void HandleRattleTrigger(EntityUid uid, RattleComponent component, TriggerEvent args) { if (!TryComp(uid, out var implanted)) @@ -230,7 +231,7 @@ namespace Content.Server.Explosion.EntitySystems private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, ref StartCollideEvent args) { if (args.OurFixtureId == component.FixtureID && (!component.IgnoreOtherNonHard || args.OtherFixture.Hard)) - Trigger(uid); + Trigger(uid, args.OtherEntity); } private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args) diff --git a/Content.Shared/Anomaly/Components/AnomalyComponent.cs b/Content.Shared/Anomaly/Components/AnomalyComponent.cs index 724dfd38d2..e6228b5fb0 100644 --- a/Content.Shared/Anomaly/Components/AnomalyComponent.cs +++ b/Content.Shared/Anomaly/Components/AnomalyComponent.cs @@ -1,6 +1,6 @@ using System.Numerics; +using Content.Shared.Anomaly.Effects; using Content.Shared.Anomaly.Prototypes; -using Content.Shared.Damage; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -16,7 +16,7 @@ namespace Content.Shared.Anomaly.Components; /// Anomalies and their related components were designed here: https://hackmd.io/@ss14-design/r1sQbkJOs /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] -[Access(typeof(SharedAnomalySystem))] +[Access(typeof(SharedAnomalySystem), typeof(SharedInnerBodyAnomalySystem))] public sealed partial class AnomalyComponent : Component { /// @@ -184,21 +184,21 @@ public sealed partial class AnomalyComponent : Component /// /// The minimum amount of research points generated per second /// - [DataField("minPointsPerSecond")] + [DataField] public int MinPointsPerSecond = 10; /// /// The maximum amount of research points generated per second /// This doesn't include the point bonus for being unstable. /// - [DataField("maxPointsPerSecond")] + [DataField] public int MaxPointsPerSecond = 70; /// /// The multiplier applied to the point value for the /// anomaly being above the /// - [DataField("growingPointMultiplier")] + [DataField] public float GrowingPointMultiplier = 1.5f; #endregion @@ -252,10 +252,13 @@ public sealed partial class AnomalyComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] [DataField("offset")] - public Vector2 FloatingOffset = new(0, 0.15f); + public Vector2 FloatingOffset = new(0, 0); public readonly string AnimationKey = "anomalyfloat"; #endregion + + [DataField] + public bool DeleteEntity = true; } /// diff --git a/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs b/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs new file mode 100644 index 0000000000..e88cedb18e --- /dev/null +++ b/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs @@ -0,0 +1,72 @@ +using Content.Shared.Anomaly.Effects; +using Content.Shared.Body.Prototypes; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Anomaly.Components; + +/// +/// An anomaly within the body of a living being. Controls the ability to return to the standard state. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedInnerBodyAnomalySystem))] +public sealed partial class InnerBodyAnomalyComponent : Component +{ + [DataField] + public bool Injected; + + /// + /// A prototype of an entity whose components will be added to the anomaly host **AND** then removed at the right time + /// + [DataField(required: true)] + public EntProtoId? InjectionProto; + + /// + /// Duration of stun from the effect of the anomaly + /// + [DataField] + public float StunDuration = 4f; + + /// + /// A message sent in chat to a player who has become infected by an anomaly + /// + [DataField] + public LocId? StartMessage = null; + + /// + /// A message sent in chat to a player who has cleared an anomaly + /// + [DataField] + public LocId? EndMessage = "inner-anomaly-end-message"; + + /// + /// Sound, playing on becoming anomaly + /// + [DataField] + public SoundSpecifier? StartSound = new SoundPathSpecifier("/Audio/Effects/inneranomaly.ogg"); + + /// + /// Used to display messages to the player about their level of disease progression + /// + [DataField] + public float LastSeverityInformed = 0f; + + /// + /// The fallback sprite to be added on the original entity. Allows you to visually identify the feature and type of anomaly to other players + /// + [DataField, AutoNetworkedField] + public SpriteSpecifier? FallbackSprite = null; + + /// + /// Ability to use unique sprites for different body types + /// + [DataField, AutoNetworkedField] + public Dictionary, SpriteSpecifier> SpeciesSprites = new(); + + /// + /// The key of the entity layer into which the sprite will be inserted + /// + [DataField] + public string LayerMap = "inner_anomaly_layer"; +} diff --git a/Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs b/Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs new file mode 100644 index 0000000000..e4c398c9cc --- /dev/null +++ b/Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs @@ -0,0 +1,21 @@ +using Content.Shared.Anomaly.Effects; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Anomaly.Components; + +/// +/// On contact with an entity, if it meets the conditions, it will transfer the specified components to it +/// +[RegisterComponent, Access(typeof(SharedInnerBodyAnomalySystem))] +public sealed partial class InnerBodyAnomalyInjectorComponent : Component +{ + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// components that will be automatically removed after “curing” + /// + [DataField(required: true)] + public ComponentRegistry InjectionComponents = default!; +} diff --git a/Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs b/Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs new file mode 100644 index 0000000000..a1ec7cd397 --- /dev/null +++ b/Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Anomaly.Effects; + +public abstract class SharedInnerBodyAnomalySystem : EntitySystem +{ +} diff --git a/Content.Shared/Anomaly/SharedAnomalySystem.cs b/Content.Shared/Anomaly/SharedAnomalySystem.cs index c3d6591b72..9a0cde2998 100644 --- a/Content.Shared/Anomaly/SharedAnomalySystem.cs +++ b/Content.Shared/Anomaly/SharedAnomalySystem.cs @@ -1,13 +1,10 @@ using Content.Shared.Administration.Logs; using Content.Shared.Anomaly.Components; using Content.Shared.Anomaly.Prototypes; -using Content.Shared.Damage; using Content.Shared.Database; -using Content.Shared.Interaction; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Weapons.Melee.Components; -using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -21,6 +18,7 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; using System.Linq; using System.Numerics; +using Content.Shared.Actions; namespace Content.Shared.Anomaly; @@ -36,6 +34,7 @@ public abstract class SharedAnomalySystem : EntitySystem [Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { @@ -145,7 +144,7 @@ public abstract class SharedAnomalySystem : EntitySystem if (!Timing.IsFirstTimePredicted) return; - Audio.PlayPvs(component.SupercriticalSound, uid); + Audio.PlayPvs(component.SupercriticalSound, Transform(uid).Coordinates); if (_net.IsServer) Log.Info($"Raising supercritical event. Entity: {ToPrettyString(uid)}"); @@ -169,7 +168,8 @@ public abstract class SharedAnomalySystem : EntitySystem /// The anomaly being shut down /// /// Whether or not the anomaly ended via supercritical event - public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false) + /// Create anomaly cores based on the result of completing an anomaly? + public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false, bool spawnCore = true) { // Logging before resolve, in case the anomaly has deleted itself. if (_net.IsServer) @@ -186,9 +186,14 @@ public abstract class SharedAnomalySystem : EntitySystem if (Terminating(uid) || _net.IsClient) return; - Spawn(supercritical ? component.CorePrototype : component.CoreInertPrototype, Transform(uid).Coordinates); + if (spawnCore) + { + var core = Spawn(supercritical ? component.CorePrototype : component.CoreInertPrototype, Transform(uid).Coordinates); + _transform.PlaceNextTo(core, uid); + } - QueueDel(uid); + if (component.DeleteEntity) + QueueDel(uid); } /// @@ -458,3 +463,5 @@ public partial record struct AnomalySpawnSettings() /// public bool SpawnOnSeverityChanged { get; set; } = false; } + +public sealed partial class ActionAnomalyPulseEvent : InstantActionEvent { } diff --git a/Resources/Audio/Effects/attributions.yml b/Resources/Audio/Effects/attributions.yml index 6f18510d17..75cf22aa4f 100644 --- a/Resources/Audio/Effects/attributions.yml +++ b/Resources/Audio/Effects/attributions.yml @@ -231,3 +231,8 @@ copyright: '"beep_landmine.ogg" by kaktuscsc of Discord for SS14' license: "CC-BY-SA-3.0" source: https://github.com/YuriyKiss/space-station-14/commit/971a135a9c83aed46e967aac9302ab5b35562b5f + +- files: [inneranomaly.ogg] + copyright: 'created by waveplaySFX on Freesound' + license: "CC0-1.0" + source: https://freesound.org/people/waveplaySFX/sounds/553744/ diff --git a/Resources/Audio/Effects/inneranomaly.ogg b/Resources/Audio/Effects/inneranomaly.ogg new file mode 100644 index 0000000000000000000000000000000000000000..43fc40357f2035e44587498af0aa886803b2ec5a GIT binary patch literal 46892 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 y?bC{%Tv^7p@8wU literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire.png new file mode 100644 index 0000000000000000000000000000000000000000..a6fcde7a1841bc5fd50326ba2b168d9dd61b3735 GIT binary patch literal 1007 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$RgAGXbBqvD&DaPU;cPEB*=VV?oFfgz5ba4!+ znDh3|`8?rpiDM718y`}20|$MvXa$f_HT ztu0qHoF;TkS&{kXNX4N9v3ZVRn^)RSHZJ{Cy+rxuD$D07O~;`8P)UU)7$gY^^Q-Pbn4h;Z~cb1Zec;bb*e_%i8pZ#HWxbgPO zZ@lvzmVV8@vp>*$?uEA5Z*x^Azl@)^w?rWR&ClnV2bev}RrgNZvdiO#(xJ6|Dw$6o zF0VkH6pl`SN?r^ZCme+l{Zwm^o;lo~66vwD<>? zpf{2KZ*1OoMNd6m_3Hon3H-qHKr$#;8T5^9+TGXg(fgL*EexUeY|or^M<=8yjFRx^0u06;x&Eo zpSG!2dic-tz4`ui-I9gNd|$ZS{Ca+N{1Mp=SF9Vtjl+UW>dH5X-^se`w|TBm=dF3%FOfK)$<=yo$Zr+m?ZDD-N-)sJ{ z{qnlcyi;QS@A>I!lCUWA`ubHZ-{tj9HZF1e^ReVE_t}{;cK=i~UzjHQ%I{wNGJW|^ zt)2fWKLd?TNwXCG5*7JvMNiGtKfh11V-Kg8%ropUO5ufK4=98a}O+?=Q>?{Ge`IO|BHAl mbki>yc5nDngUy`}_#DXiF)u5*PpgDXXhU z`~9TuAO4{Dz9)FzQQi-c2lkc-puAgU1^~opR>;dY9%at6yMn%=6vVk$Z$ahQC%+=! z420w*WHC$ZSnR>yB=qaZoCA_UqDK1N@i4B^5#qfcc3L!4p;}F$1gpc349))H%z6_D zjpK14sBz2httUER<3Btz@4@kLad9ZM-`7F!Ax0xrCZ z*Afv#O!o%}vzZ`Rz&NrWC2vGm(p`NZ0Prc|38QZ((N19zf-B^>`xo0nMhc{jAM@tI z9V9I=I{xz$)rRy$|`BDneVhp8PBf&XJ9UM{vJ7)X!7({L;h-TI)BI|^q{5n zppwmc%*odZk1tZ`H~KoUqb+3?pCl|%4mY(-98cLWDm94{slR{K&P^v~7>x&ip)i%C zo?W^6#$Pz7_VQlF?pp#`yh97ptb!NL>@@7PZ<`OD>U3KjMS#$SfuE&0fB+@eBwUS;7`I)ae*97>+Zb6bsbI}>e%6y2F3Qizw082T_(orZVMCcgMM zhs~@I)iKVv+Wa?h8GIR{4cUoL=H=K^sj%+7d_i}7 zoRo*XSYo|Qy-CZBt)AKv?Rfcy3hA(ijH)1VpE}wo=S5^D9H7-#)-BUsX!oua%aKYE zalPq6Peo|b*x0Pxt0tGe)+EX0-M-k_`MiAa+8f*^btQLO-^M&o&sY{^APj@oGHN!Y zU>6L*{^o$*hj)VXNe}LLY0d4jDuP%xLr+%~Y!0YdFKW_s>|`eTvDG01t^Zh~0H?lD zgE`qM14dM{ODkdKJLI;yD@u`QZT)9{LEZ5KA2 zji@pxWWE_&VePn<=|Ck)_WPzXzoeM}T!@kFWj>GrhwqS9L{vb&G-12$(pJH0mJ_s! zjEufM1G}PY2#=DSa4Ts8jwB6I;6%ie7=;uJ7Y!lgp(ju+=R!|=yAG+X%>CA?u z3EAy_=w|zUb{|mG3tdpVpS=Zj0KK||UCilbFl~z-NF=T7LrQO1f7hO&vS4wUd^mal q&t=oE!LUcCdT>8!{Hr!Cwk1g;k`7OgrHbr7Z@|g|jVwpp{^Kw7SjNf# literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh.png new file mode 100644 index 0000000000000000000000000000000000000000..6e178307c33ea27caa13f9978a8337668a3eb2fe GIT binary patch literal 1342 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora.png new file mode 100644 index 0000000000000000000000000000000000000000..70e369cb02ba37b76aea647060974a8a7517d27e GIT binary patch literal 1868 zcmZuxc{JOJ7WQfwr3TLsOO$3>wU**YDPsC8%~2FnM3FwNtudCWwUIDhE!9qF82d7% zRbiy4TGAw@53vNbH5H9iE7FQIB(?l1%gem?=j%P^-h0ly_uPB#_uX@gFZy|_s~V^( zC@84I&wB*Q{g7O9Rg~p6;$JZ+x%*pcp!Ye2&S~&R1%+>w;T}K!lv&4T!Tb&3dbDlY zNmV93xaf|uzWMC{Rqs=ArB?$kdfq61@mxXG>~Y7f!$l1*l5!!=$1Mn?AZCzRskxsW zcb%YeEFc_@hwydw5u=k#(pl2ke7wN>4#Z@IEnceb)#m7H$tNp}HL>Dv*BbKvNt#z3 zKZy+xVY}7YPT=!o{jup)_i&9vz#=y(v7=C4Qp`wOlC`8~8p+aIb&ev#3$d~T_JW|( zpbUy#@pv@LkjK1<85KmLWla()^{VUUZdft2u^sTg{tpg-Fuh$D z?yQE%-uJ;!kb_kTY)_cn2LrMPw;_;wtV=-lo^K-YlReaIW%U67twdfsW8g1wvBu6} z2grk!%k7?PL_-T$wdGwD8)kVlN3<93Ecitbw4h9fPciR&c>u zc!40&KL>`Q)G&S>HcpUZaF4b4SVWadE9G61<4gUO2)u4!d4C3n_n890X? zW!~zk9HE1Zt5a-hBG8U|)i_maGkRvDAj(p(&yHmI$hYwL?m>E$p^xsrXZuYhiJ@^) z&QH@{SW*5z*6x*UYb1JFhhgRp(klU`jNJTR9=2kiKqTz5%T@w@K2M4W=W76^6}q$T*=tp&o25MbQu4s;=}1L^E={u5w;>pZt`sA!=I9*8( z#h`Cy+ZQpqaTEEm54>E-DI(dcuG8J-jb57{_2-L(cxZ0pS``WFyQFf#x zd0qR;83RT`u*gINH~TZpHa=OZX!owcInau|8T4DY=x~eL)~a8isIWQ3I#eyjx{59s z>2089z0Lm_)1lr*pU?YTTHb>R`K8x{yT5!InrpXC-3nqq8cwe^rX+|aC!V#5kY28s zKlp+{Sv(g%Fa|{m4y2Rx45v>7VNYb=CfarF7*9QPH_s$n5!k>+MhZa^q9y1Uc4b(q zA|X0cx2THWpz;`tGBUTdzH67fCws`P#2nQH)@%YX3q|QPsfmMd9NCX%w{DgBQ)bX# zp?Bzo5Wt=rKgo8amJ2`d{Vd-&fs2SYUa#Q=)>KwJqVnYr*c4%`gqF&X;QE^5r1W?F z2v=PzmWHSD?K=-nouhn*7Oc5ggmNs{bvKOx$hb~t1P=Q=0WJR!K7Ljkw}z~49vLJr zk$e|{cqLTCU8*kMml@wa%{X0E5rClRcPL%<(SVObvA~#MH*98fXfXU=m(gTa zZQp)f0g4-OA$LFo&~Y%O`Sj-QWzCtC>c)km#%i{d{fUX0&-(5fcl!r6y<(9{sJyYb za4*Ba)EyK9fY_x4mV8{Do+|U{u?y#%OmUK4my_DUR*#sG+1vbn#0Y>TN`3leUjgQS zp|>ZN-h^r_*Z;;Cv|Ym^U+vsz)VP?W+?{$x6PfSs2DphvI%YmJYsulVM?UrFC+bw4 zpiT(0N=lIrja^|ou2qQ9iik)=&BVX#G5FWf-YfjmV##ud{NrY1E1Ggh zEx=BUW;6+Xs2W&bZXBQbU?tn#h%{JkoqHW|kGcA8VRT;%edf>E-EcF&O&B(z!N||| zg*gr1n+q4PuD-N1^_(8rE$_?I{!fZ$U|f~Vtw7x-VlhLSD*98cQBCvz(acqpDVj3p V!`4n{*8i1-d-{2Fp1bnPe*hXXv(*3q literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora_VOX.png new file mode 100644 index 0000000000000000000000000000000000000000..97969116a97829de4c31cae8825cf0dd1f59e4b9 GIT binary patch literal 1952 zcmZvcdpr~R8^cWL@-d(^NN92LJ$? z@UzZdiuzV@Qq@!x8JHdsttd(fUT#hR+_3R70HAse?tI!ixq9sr(Zk4Z59!N--cBuR zCvRJ|^M`(W0_dze{oRwgY}-rfd;qBUK6%P5xT)g4LER~*c$Zcui=){;P!jJR z&$i9lvwSlII}c9Ykn||eZ#NwuM5QFHWA~Pf*P8B7dpO#fSk_=gEjbYl_ehx8}d0HeoEV7c4(|m5~-kvhN?l>@4 zr{DsN;pkXGobAEA9(rW}J)fc<&8f}IY$dIOi9vzK6c>%Ig*l-*4V%Q%fxQA(q3ebAg$VQd7EM!Pa4s;x)vKTOaluQeOap% zG5Of{EuDuCU|2?{JP8BRMjA8yW$plT41FSI5JtzyxU=O;-;bT1yi>8rrgDEjXg{3C zAPzsopk>ez`c*9Cz=6JX-~R+6m>cdMQ?gA6pWm(q+rrV>7ip;MgO?bPVqt7hRw62* zZPynzEavI8fNO=U`O3}N9{nOF2XA6TN>jF$R$Pcl63tLKJC^qM&-RZ3b1nODvE6!} zAS;}uVRy&+V1Gu~iOB5))nNL2M8HFh!;u8ECe^cDbeq2u8?%kFaIfCJ`tsA_>k*-X z&Gv#v(X8T)H|3F9r!m&g&ob;#0GIXS^W9rmTu5Oh>*DoKm`N;*Oskr0T8Ad1O%hT4 zvByJ+L93Ic1PGm3UIg4K`)N??8p9!Pu9-d~ivS_yqfAD^gK%T9j~SeRt71inEFavK z(j#V#oTu(Js^`9*Ng&gBfC1ClnN*~4?-3#K6|pi>nbj)iNlxssSwsTp*BPE~+xqwS zvd+NCj;a4D^UauBKGbY+rFfc{ZQ^biepQ$95U=-<+(Ir-iWEOywPb|*`U^wL607}8 zpaJ3boZwM!mEmg>4$Xgx6cQlitg?am%DX3GvfL>{jWLBq=CI4RnHsww&;+R{?DOa* z67Hqv&~_#_@blE08^5X2iziSo%%D3~b=Hu~44nV75>*17+BcSNhh>jmbT}s{)U{%*Jq6 z&VUsN=23RTKq&uUf38NpR^xh+%;gjy6z*2b5MmJTrTA@&p{)y>p=m`X3#&$+xCKx# zV?U}>JiX$bROeBAgBpCooSjj@1m_4C=J9T-o{h*6xavK!1Y>5D+^c|`!-aw1E@ZO4 zACAlW{2VM}8O53tC+z2qK&FVQ0FAD&y?21e>uwd{sbC!K6Dwo9BmZY}iUU+PD5IWl zP@(@=A%Qdr|EmlcA`%QvHWuVKISWegLy`@D>tVVD%_cn|fJHMbXcl41s)S+^O8G{g z8e06-)>ogiG+dFnAvbZdsoR9iY0g+^S5F*0{*Yy#kL(DDjo4Cc$o><(fRX}})x(zt zFr6aL5cxN0{!2JD^snbfuHbPhTl!X3xazB>3i|atcDd`==N2Piej||%1|7V2mq!^g z(AFk?R9nFT6H%^Rw;abM-4r(Tm8838Qxd!zt`Fmi^WeOkXVsEwMb_-KBU-pDl5Q3y z{%#sSDlk2)!+HGw^;lPn4k~6MuozlTJg-|)Z$3DgvJ07iY+Yh-3>L@tnU1?|(xSo< z+Qv-JMKuhOlRDvjx(G&~zV;#J?qSNLV^@>wNZ5bbn7GLK8MFb~qF1EobWzMF%_$il z6yjTym%geVt|OCCo8hWj8Ai9R5Y9g&cC7FGh&zt4pOuN*UzbM9`#MF6tsLd==EVry z)&HpGb^wzjp6tlK{$Zag|I$AIZ*P7_MX}tM3o_W0y{yXT7>Wf_?piE_8>}0I5v!HX qH5tEc(+}cesTp3SNWeEB+i$@B@T!;4Z2s{1N*nIt;f!+%zW+Z{QQd3+ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost.png new file mode 100644 index 0000000000000000000000000000000000000000..4823cb5a6f2f7edd7a38ad837c42d8bec339c367 GIT binary patch literal 2650 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 Nz{1oDU4rt6{x604_oV;; literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost_VOX.png new file mode 100644 index 0000000000000000000000000000000000000000..346cad09b1065c067a96bc22b9d2e6b2ee6d4764 GIT binary patch literal 2718 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 SQijm~5wNpyv?g16#{U!Z-Ghh# literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json new file mode 100644 index 0000000000..4e069d0cd9 --- /dev/null +++ b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json @@ -0,0 +1,643 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Created by TheShuEd (github) for Space Station 14", + "states": [ + { + "name": "bluespace", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "bluespace_VOX", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "fire", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "fire_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "flesh", + "directions": 4, + "delays": [ + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ] + ] + }, + { + "name": "flesh_VOX", + "directions": 4, + "delays": [ + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ] + ] + }, + { + "name": "flora", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "flora_VOX", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "frost", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "frost_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "grav", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "grav_VOX", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "rock", + "directions": 4, + "delays": [ + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "rock_VOX", + "directions": 4, + "delays": [ + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shadow", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shadow_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shock", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shock_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "tech", + "directions": 4, + "delays": [ + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ] + ] + }, + { + "name": "tech_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock.png new file mode 100644 index 0000000000000000000000000000000000000000..770e4b8d2f80570a23e6088fbc64afbe3fdeead7 GIT binary patch literal 1398 zcmZXUX;ji_6vintJvQQ2V`WMuEtV$1G>j3Eg(Btwb8FnvGDSr*mqdesHb>JOGc|L| zF>_0EU&at5b1PhqQfUxkDS1@TT=B00Lo=Uddd|7;bI$#A@43I{z3WK8X>0A&0)ar< zcss0<%5Qut&CRO!)ya!om2HT0!kq?H_U)Pkfz)AmthGx_u2gt5(CGYjjjxQex!Kpb z_X7@YHuNw^yM%JI!H-WS6#nqP%I+bRc9}5iXPYarqfUVnfwU%}9l}=k> z?Qk%7>JscoyVk)pixNC={3+4O$G_|0>cVfgSIBzf8lCtiQ)hxgIvbN|T^GBI+ zRTarFl5^6aazx#0l6p6yCI~I^W`xQ%1`D*I0au=llO+rg=U3<+=NhXWNznF?tzSiC zqT}FPMX*BEtt+#^chzldQFMOj2|M3Pk?5`1#wbI7Hva+mGD;Rxr-@y45B?zOw3vH{ zg};0eh|yMR{?x=*>vEUkm7_j9yAu_EoM@NurN}g!SNg)sQQ#W1Cm$9xj zhNbY@C{a$p~^arxpvFv96e~CO{pf`{eS@F15#n?|nwU!Q+ zNlY2~Xe2L~9WA#WhfJ&MeWU5+b;C;S7S6kov(>`g=LA6LYYm^UJeyAaMn40o9 zn$El(hEV>w>dL&+km;&JWlz=4^E=|vK1-tp?z1T*1$}+lK<__F4SSN+@|PjJHZMOa z@$-f5*U_p`>Eo1^t50J$M$?{Z07b?ju5a?_F1Q|p>cUsTgU=-eY`2E&D(_Yw;J}pm zQ8HiPWyKBMEA9r^*384C=D?w=dKD`oGg!_? m`JgawHNH#0KY<`*F+e(|1(%kpd*f9fDF|;vz*e62z4bREM%!Wl literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock_VOX.png new file mode 100644 index 0000000000000000000000000000000000000000..b11a9182fb7fa91a8d13b392cbe6864f59a8633b GIT binary patch literal 1398 zcmZuxeK^wz9REpqsV8!Y%M`tMNmiQ5?Us!p#x)X&^V$pL5K}`N6Lqz`rRPe_%aJP; zTVaN6mP2USa+EBNoGq#`);6=Zn}6<}>$&gqe7~RP`#j&jKA-RBLkvKBuU~7i765?t zC?C&2EoQH@fu44D!DHB3&`AySMgnAxn_cKAGhy7XIug^LQxba!<;Rt7z?vZ4jY}iwyY$Sa zZgfWP4SaetAvzT^vVYfkBjMYWMshn$%=}3z-?B+inihLOEn-gSZ!kIF_=^v_0LDFC zec}!%trGQ^-&>zQ#*l?K{&qtP%Hd&6q1F44E<>H{h2lN6C23EaZi!DdJ+LKgPcj7Z zjj-1A_G?p@I?rb9kkI698|S5&h2|jHf?~?JBT?2o?cWWCz8flH(cXx}pK0I_jT&9I z+6C1iu5$Bpl_)UllGoY%5ig4oDtk%q9?8vdbdx4XC;XbDDZ4SiF;5ov@H+SzDWMnSrOwSy7`hi9N(`| z26f+I)!U%F5*`Le2lTw_XP6Q~O<<)ZVQchmQzA4eVh~h*Aj*(egOJsF<7lk!Ig(7sbFLSnyg)z3yxZf=+FXkW+0co zsK1JstCBQJ6}}y^CI2HF0{}m^FMH-Qb#g*pk>kHir`b$EC&T1aNW73VpOe)Sla=T| zNsr{|SHBjMrnUoL5OheCOV#zS3hkUW(e^yqUWiYdU_DQ+xBi4Uo;t(vHJar|k7}y8LczkS9(i}AhG8`_>Ur`ulBz)&E^ghGz zQDPWuB4c-Q)M}oDKC$7xZQSWS9Xl^K7vK=6b_#OdUF6CzTx3>VAwA5R_evg1tejDs z`quJQ+@GH`-RjCKP&ld1pkavEGt){tU3;?mla}-lrdlluiH);ukirudw}ir2JVh&3 zr9A8=$anyDR+Tot7Bhzgn+Fi_obLttpX#6+iC^)$vf_^>X5s`iG~A@Z*6!mjKd*!o z8eE@=X(_3AnMaq5_PrS0!;|P26Kdo-Tayr-+J_3C#Sz)#=F#o$C&lLsR|pB@?k=Y^ zgScrUnjVq2)A+B|nMxhTsAew74>GtDdH{N_3~|uX^%Y@0iGA=k;e_gWndjrHx&p!A z5QlPr|Qq0$M79#TFb@ZmIuh? z{F(?BR=x5X8gE>Wqi`8CKv9@Yn)%kgp35zAp#_WgO0hd)&kR?303N*rZM+0^lP#i8 zhT~g;ve^Kj=54==7d=ae2VTt{POQNT`MjV{Bk(1j9GtoMe7L7rN0$y(e4KsanQs1qDM5Q?@Tf)l+;e$W z_EDf9?6KozSwlOWO9z)HFfEM`s(QEPJo}l(1K(@?et92bv*%=B2>$$J;r$P-+i_Mt$A)E&TONAN%`r{$KiQcYXh92D6>BzsXNNbHA=?{_NYYzx|My^>^;xo45Vn zU;fsA-v5>1Z}aWfGk@>2=1;b*owUT#&VHr$x6fz4-oJlycIn&ik_TKTx!UeO_3+)n zub;zt^Or^8f#o&YZ~ppw9mBzaM8;ZH}pZcl4E2&&~b8p||7R zc>lFudaZFafA`YgwWXCz>sr6Dn4Epi{$K6C#G2F3-&uF;W#8~cc!h}h0TX+3U^o*1 zQ$8$tFTJO#Fhn(T|16cWule15?|;8K-|hcPhU^uSY`j0!e2wD$Fz=V}OMc6S(&?-` z7wbQNyjyeXuZZ3M+F#B*36oQ1K54)DI-~#pw*C7zn_KU-@Lu`v?AuZ=a8OM8`(?*J zXTAp@omcINS?qoFk3`#r5;NSh`e={pCe_wPte*Zt`a<>~V_P+RXef|AX z2J<3)tG^F_t+o|(mp`C(^3#_`I>(=@mvZH@hV7c)RIRwq`9DzTw{`Vq#b0yTHq48E zTJSv~yMr|+Y5#+`is1X*g|<`fb!NT#tC;)bTh2Bn?d%iM3obL*EwkRhjT)rE-}LV< Vp6pz5Th$HZG*4GQmvv4FO#q9YeJ=n2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow_VOX.png new file mode 100644 index 0000000000000000000000000000000000000000..6efb246ac1889f38de9d82fefbd4e0ce5b43fb5b GIT binary patch literal 1002 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$RgAGXbBqvD&DaPU;cPEB*=VV?oFfcFiba4!+ znDh3|K`-f8iT01n=Poi4_#ni^%BsJx{9hgJ9b!|m(YAJ*^NE3~og;;b|C)~sByx^2qO`WN-DYAO!=+dt#sEuIR_EsuXY z>NG`!7>0((s?9c-X&V{5Vk1!5_pkP)b~XwB=bw*o;I`;reYC>(*ZlqUl1H*%O*uN} zpUbxr-_mOnjQ;QYxBOiFg@5OZ7?=%M;*OfC%=_iH{`A{eyIsrt63c5>-+ft{SaY1W z|Gb}tjjhtPkaXUM-}KV0=Kue;WZfSI9)ACR3p?w^!vE4|pU?YyKlXms`N_7g8P7Dn z-tj4r@$W&^-?OV%e-<%V%pmeUZhx+!9mCJ7XIZVKZ)a~@7RCSI-?MvJ^#vzW{vFSs ze@^oIl5@-x-sj}3kG&gnV8{Q<)j`>I1+w<<-~XL|qkq@;|I>A{|K_NySaRj7$z1!n z)2;q1rd2ZO^}J+jG0^#Y`;Sq?mh-MWc$VW&=q4t0J$@QN&cNhfa zJ7g*HX#algc6Zt*hGoxqKUCx~U=Nh~@7L=U;wH?j?$3L7chb}A+y19MW7a!#X0N;X z_q>yzvTy%4T5RdY`o((hy9O5H?-eb@@2Bb1v%WZfvwdpX^hzeS1uwqr`mNOVm|yQE&@*dyHB9;mOBE`!s>Rdq zEuB$4qv~DKtC`*1ieHbcTs|*;y1VhbvlHVpua+`Tv1>S8#XjvRuL6cI#7^v={PKP7 U5=RDMVCH4;boFyt=akR{0G7Mzr2qf` literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock.png new file mode 100644 index 0000000000000000000000000000000000000000..acdc87a291c1f3ae96bef140d18330ebc0273608 GIT binary patch literal 879 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech.png new file mode 100644 index 0000000000000000000000000000000000000000..0611fa8a2a092a82bedac5ee8b2aebd1d0ada50d GIT binary patch literal 1241 zcmZXTeN@tC6vwepo14;UGf&N6q`mD}*^A9UK*chbqG?8oP09?LE!+@w;@d_>OPiW2 z-z7}bFhwaa@C6l@PKKB-vGV;u87K)E7$Ue||8=%|&U5c`?{m+2p6ByD_qPz7zrEdN zI}iwDA8^bs6o^?L);b%&i_vE#K(L~O`ul*S!-8amt4EfjgRflA=7p zitRSv{1_AUSg@-n-1YfpsiZ{UVp^rgeoCMW96-`MqyqdJ=uIanKR#JF@U~Qglefq5 z!=fMNLi^d~zpA3^u~X&Cvs;MWS428~1G{V!?JPdn)+x;N-=5{+H6YSgKgBU{&wtAw3D97(mIFH?URS}35#X`cB%+@vU6s%>P7MCK!WLp+g z+FpgssbEChw%p(&YiJ;=sg&cKGD5c66#t-s%=UO4WG0v5r?`we#<7aV#l5hAs(e27x16hn$5{qv zaty>1qCToy>bg=)Vv1zWt-iMs28jHzb<*=mUWk(#VQz2jt?v<@%Ah_3aRZk>O3I1q zO`~5!jDuCHy4S*I21-F}UT>{Az0iK^&>?pLIqPN6W3pH3g%`>){h3pYAH{%?X=N(0vmiL=fySP_h7n)s{6~;_XHg-XU&7h`TR$b4MHL|0f07aAlm+8L3tgISw;e1MzTD zWl^jp5aPY1Th6lGWoqkioLecugD&bK4B^G|EOu#x;j^7(;g*r%`X!BHNOb~~MJsM0 zd58D+F;lINAtc{Lracm7S3}u#804O4s(Jt0*+{F7@t^OK(W`->LVK47<8tgRfKIY@ z03rh51n~&hYo?8hZydGxseQ;jwT#*qG94=aBb_+x}1X#z2n5J7s`;uP!k> zrY586fcZ#BywAp0<)coI;Jnc2ptWN+Eas()TV`U;hT4XhBLJ*`Q{l!C1Gho4Nwkra8tuP;DNxFNT(M uTtR_lur{mFm_)>RNY|n*`M-}($+fJ_Qq#-0ue^FboCO@g`8E0yfBhFxa%ye> literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech_VOX.png new file mode 100644 index 0000000000000000000000000000000000000000..1787cdd4e06cebddb5753fbb349d2c06e2bcd10c GIT binary patch literal 1244 zcmZuwYfw@N6uy?Dt!D17Q){(+D0Fr0uw-hhF1l($_+S+W9|boj5#@Se@n5sEXXc#m%$z^x`@VBZqECjo zIBjg{N}mnvSg~P8fkp*t9sun5gplC)+)Dk#p$HTm)w8HU zO*IsKxnnBr^|UBFKDt>z&bTOd-{ZKSvymM-s&FUgoLhD9v;V#IWDvUR;_w$vPb*$| z|9rw;P~<(=LG;GdL$|&3v!Vg-7TJsKIhc$P_Smz2%jG&(Z7D7$!McVNOF8)!D=hjhko{vJXR(Ff znMa6aGma>YXl!Jp@b>&2B4LOyqDTS@^(0c|ytWu*C8rCj8!Wqh3RER+*9~`f`*E`2 zzr%+)??hhED1M$7& zP4p>ii8Hx*wIZr2{QBLi2j?)T_trEx2+KuaVx!fLgfF<>T4>wcIi=4}abm%UX>vj! zX1dT(!Y+Q~Mp);?(fAo3N@$cD@*JyrBG7M5AJ@jd(zf#TnSjpMBfs1*yTu*20;LG# z469U-b4&h6syB*EAJsBQmqhWPXeUA2aeN*;M>#b}ouF?k>!D>pnau;m(V5M!Y0L`K zy7bNuwzvkHal4XC>ap41_OpWUP}b=kAu}$6GA^&Q_*y^}SCiJ>q|QL7-kW>Js~(hD z;(Wty=!UiXTE3XTXmyhifuPK?NxSOkMl5D>^^61 z>&cs{;BDfYbX;!q&by{N@-9{DHwnlr3CQ|Ey2Z0@ngxpzmy_pQ_E5lKX(Zy=&_GOu zc+bUivLSzc(#LyVOo-PYHh@`N-d;LQZHmBYCLhAsn@QmMU_oL4l^eow!z6RIw&?lx z-TdlavtDQ<=Rkv*HBBs7J0dgQw{!KntXH*{nj6iSC7(ef`b;f=K`Jz7Y>>4rQFf0As+niKwk$uV>#8_aohnXjj?U7?CLJqb55M(Q( z_y7CNXy(nl2Nfz**km~C7LW(A=hXoDn*b9h3@3SPWpaQfc7K67uw@7s10x{HDO#z+LRm3xD|&_!sCqVK`d;bJxdjb%6H15BvuF1@OT=WDt>fea{KFi+u)>`Yu9x zW1mkXLjC~$0fH=}piKUsQZ%@zcg_>?Jts*1x#tA5_ha^)fTpWN^~OH!{g|VzAn7Ij z{0nU_3rgc(aDeuH12z0Egs?aEX}VK#AQ2(>eBDe$Xn$O2I}L63QIySZ8v)||1}N_f zp^4iYhua(V21!rIeR)npX7E)F$!w3xxI0?i+upAI1#0Bq3zYJ z4Gq|Ozaak9QDAd`M0k8~4-;~kb+3lqgdCU=BLd~S(F-+g_c6%Qw_7m(uC}}2fS*%D z%rxwc{eMJ6)9r9I?DBHE1MZY~5W%;HF3?do-02K$vzdY#D!jccnEw)ZW7A+80TS9K zHwHPVz1qJV1kP6PYUKg(RLR!*c>pHh99G^Gc&I_>wbIa zay9HGL?*dhjrCYIOshEH9u?ac$^Tv3UC99>RDT@(*1Ty>L^1?lH#$l0G+i}enah%f z#ORMkgF0A|c)m#fJ}|TeLpBFQOg9rrc-h{MlL<$>-8mT0IT*}{lnKc2zU&fb2W2`+ zm7eY|IV8-31DETbcK{Vt2kZiqQI{%Y) zgMTLGh@phtU=jRJIu8)c5tnuzPy~N;sCE7v3dzr(YkRK% zKlZ$Onj_X(A5cTZ_AOL|cLs&#=g+j=mgY$t32Unlki7gCo2mlx^Wp@6Ur66Lt$(dP zK=Sg(sIYd1yny^X+U_wadHOjWrPl{cP`%OzdRlCe`T2L+ZeQ~(b3pm^0ZtfBbhH!z z*QkxnsoC%e`T0|A?-~`5jM9RVwbuve2KV}b01C;^%LZxHJwa6;P$qv>ACUHssy?8q z57_V%p(R2#=S(m~$PQ{_BYOr3ay+idIovbyueUznN6QQ3O^wx=%M9h2>7N7grp6+s h`-fEvp({K`{11t~Wue8V+^PTo002ovPDHLkV1k!7P>BEl delta 1162 zcmV;51akqe~C0ep)b zBe&22Qsn|E45)UYSb*CIDkyaVRt?j5)+1_2&MwzB4D$g7jO8xh<8U2{<`u{jls&=&it)EAmRrAw||psYz}*j2P4eZ3w(Zj z=Q$10H^2HtpZL5Q0sxlv4wu6-0KnGO&t?c`j9Xc&=ARma%GxBqO5i=m>N#eOwODu&j3tH}TIW z*G+7QA!z`beSfn2crX&*qvzZR56E98IY0I9Yw)?-@T)w4&!#jB^xPNLmlyc`_!=Z` zEq~MizV%a(gynbv=ij=znPK1MIrrApt@x83z^ynQj6`xDTtu!H{4!Tq*1IP7UJlQi zzh%8^xJc9Hus_n6;{PZ;lq^Oa(hA^5eqY1}`=d7MvwzL1Fg506#qmL0TpQOj9*i(G z1_%U1(EucpL`zEzp8;+V-#~t!ZLqQyn0z^TzI8P=2YJwv#@K3*{0Yp~i<7~V2;j-( zd@)2mTQ5=+Nq!%j!@l_$q~g z>*^%GYJVB@XMse-1}}Zr6}%ss{kGqQkTk#rP<_U+C@6TdaK>cqMBn{RFxvpRGtdWjj)u+#xPJ#67x2=DB7e|bjS#&QmB7#A^TE@V z^$fPt-G5uqKq+BLxLGfnHqAu{LH(jnEZxW~>m7dByXGpYjgU6{Kh2ex5Ad(Tdb<(g z`FW{6fD52@ZGOHjzh57~LC>G1K7hzChi6Srzz44%nD_6jSXrwkrv&PG zHGjm)+Uy%6ntfg$km3f=@TY>K$^*DHV<~DPmcGk@-%qGW!=EG#f2;Zco)PxTVnTv) z>I3}Q=lzDz)(fHrm>SbuJyTMGTR#+tIBqA`ssx1(5*_nCDNz#gf{+n{2H+p?0tKl+ z$`>OUM!QjQ#yII+J^|GK=CD68Ld+wjzJG%op~~9I8RN9cNM5KQ6;EiFLwb*dh6*6~ zPw;$rJSm|VA!&$;q8RY^(^s)QR;WO2-|b!JBq_y!-vNH$sR~3Y(g1*O|Ncbx1D)#w z_~0cEIj@Gm09hGg^1`{mPa1&JlAL9|!v_1NJSOISs%7vdQKix5yDkXz!@ZB|Ab(vN zepP+I)R?1Sqe)Y5U5%+R*$1P_+7o`RAyg(q{Jm|3d44_>7-=uwC%V)JXwd02_%l); z(DpReg`W;(u_9?L|C_51h}{QtF??2i0N>%QtbNh^ytG;B)8n#)yd-yM=lE_=uEh%q c3Ucrt#86llirPac00000NkvXXt^-0~f~LST$1WzK;UIe|0kXP`*_q4B25Mgfx zFD)XTgceHnkexIYZEJR6Fa2+6ll$fG)dl@|ixL>$h8&_}=t={D5P2Pf(0#h6keJ23eI*_WD&HZB$(|gJJ z54nn8ehZ-e9ssR2^w0iJ0DK5&zlOe=HvHq)unz>njDr9kfrJ{58Z}Dv4O?Og7Rhkr QfdBvi07*qoM6N<$g2@Wm4FCWD delta 389 zcmV;00eb#{1g!&*F@I-CL_t(oh3%FxZiFxpML!_|87alG1vkiQ%RRszf|GIx4nWBn zkOFidBO#|6WV|*WP*!R9rLq0rAI1jY)TvYdtl;RvTnJ^372~*jzugPSxSMA@uMafS zfGkQ-wiVJa+zrB>Xd0(M3;?KQk9&Lp0CY<324Ml@LMSsa0e=t#04s2W(ixTTR8rLDZdA2s|E&u?lG-i5z+=Sy)VG}@4&yhKg zQw47Y^b}c?pqBkoW|ul8t?O<2^Z}5_lum%z`TCq@8qi=^J#&fG19aB!@ntEqe&kF* zjxigcY%A-AWPaRC!%KAtu$Dc_w%Xi(^A!~18012zPRW-BYo=i_aw&@v{CF-S)+;bm zPM#W)6O7_K_InfwjbDfzcoaoJWY2%kA8J#g@A`2BTrHs)KthAt5Ol6m|8 jfD1&|kw2nSr%wF>39-#Z{0ZNi00000NkvXXu0mjfSD&j} From 21bd9df4775144d429c741ac54b5d373b3675cb9 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 17 Sep 2024 09:50:26 +0000 Subject: [PATCH 04/56] Automatic changelog update --- Resources/Changelog/Changelog.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index dd47739cb4..cc6d0ca3e0 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Whisper - changes: - - message: Light toggle actions now have a 1 second cooldown between uses. - type: Tweak - id: 6887 - time: '2024-07-09T04:14:51.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29833 - author: Cojoke-dot changes: - message: Shotgun loading doafter now does something @@ -3910,3 +3903,18 @@ id: 7386 time: '2024-09-17T00:35:57.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32061 +- author: TheShuEd + changes: + - message: Anomalous infections added! People can now be infected by anomalies! + This allows you to use abnormal abilities, but can easily kill the host. To + cure them, bombard them with containment particles, because if the anomaly inside + them explodes, their bodies will be gibbed.... + type: Add + - message: Flesh anomaly resprite + type: Tweak + - message: anomalies now disconnect from the anomaly synchronizer if they are too + far away from it. + type: Fix + id: 7387 + time: '2024-09-17T09:49:19.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31876 From dce537df071f899f8419e512bd38991ee2a39771 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Tue, 17 Sep 2024 19:05:38 +0300 Subject: [PATCH 05/56] fix Tech anomaly loud sounds and superfast flickering (#32245) Update TechAnomalySystem.cs --- Content.Server/Anomaly/Effects/TechAnomalySystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Anomaly/Effects/TechAnomalySystem.cs b/Content.Server/Anomaly/Effects/TechAnomalySystem.cs index 1b2849f1d7..3e4d101f4f 100644 --- a/Content.Server/Anomaly/Effects/TechAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/TechAnomalySystem.cs @@ -37,7 +37,7 @@ public sealed class TechAnomalySystem : EntitySystem if (_timing.CurTime < tech.NextTimer) continue; - tech.NextTimer += TimeSpan.FromSeconds(tech.TimerFrequency * anom.Stability); + tech.NextTimer += TimeSpan.FromSeconds(tech.TimerFrequency); _signal.InvokePort(uid, tech.TimerPort); } From 9afc786573fa1e9a7077eac17aeb0f00c4000cad Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 17 Sep 2024 16:06:45 +0000 Subject: [PATCH 06/56] 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 cc6d0ca3e0..edca864463 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Cojoke-dot - changes: - - message: Shotgun loading doafter now does something - type: Fix - id: 6888 - time: '2024-07-09T04:23:08.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29827 - author: slarticodefast changes: - message: The construction menu and building preview now show the correct passive @@ -3918,3 +3911,10 @@ id: 7387 time: '2024-09-17T09:49:19.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/31876 +- author: TheShuEd + changes: + - message: fix Tech anomaly loud sounds and superfast flickering + type: Fix + id: 7388 + time: '2024-09-17T16:05:38.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32245 From 337af483ca0922050459dcd8e8955c2775387526 Mon Sep 17 00:00:00 2001 From: drakewill-CRL <46307022+drakewill-CRL@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:45:42 -0400 Subject: [PATCH 07/56] Fix plant mutations carrying over to other plants and future rounds (#32257) Lists are a reference type, so each component should get a new one, not point at the previous one. Co-authored-by: PraxisMapper --- Content.Server/Botany/SeedPrototype.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs index 7a3e08883d..5608338f22 100644 --- a/Content.Server/Botany/SeedPrototype.cs +++ b/Content.Server/Botany/SeedPrototype.cs @@ -291,12 +291,13 @@ public partial class SeedData CanScream = CanScream, TurnIntoKudzu = TurnIntoKudzu, SplatPrototype = SplatPrototype, - Mutations = Mutations, + Mutations = new List(), // Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified. Unique = true, }; + newSeed.Mutations.AddRange(Mutations); return newSeed; } From 60887dd2e5c6e665503cbf1784ae3bb0a3289b05 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 17 Sep 2024 19:46:48 +0000 Subject: [PATCH 08/56] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index edca864463..445dae8c98 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: slarticodefast - changes: - - message: The construction menu and building preview now show the correct passive - vent sprite. - type: Fix - id: 6889 - time: '2024-07-09T13:39:47.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29820 - author: Cojoke-dot changes: - message: Pacifists can now use foam weaponry @@ -3918,3 +3910,10 @@ id: 7388 time: '2024-09-17T16:05:38.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32245 +- author: drakewill-CRL + changes: + - message: Fixed plant mutations carrying over to other plants and future rounds. + type: Fix + id: 7389 + time: '2024-09-17T19:45:42.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32257 From 6fc4e9682c8c4e38a38cfa03bd04fe7e5b5c3e28 Mon Sep 17 00:00:00 2001 From: Moomoobeef <62638182+Moomoobeef@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:09:55 -0700 Subject: [PATCH 09/56] Added a number of new and very nerdy names for the AI (#31951) * added many new names for AIs * fixed mistakes * removed Intel and AMD trademarks. Rip AI named Pentium. --- Resources/Prototypes/Datasets/Names/ai.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Resources/Prototypes/Datasets/Names/ai.yml b/Resources/Prototypes/Datasets/Names/ai.yml index 1d320c3f52..539ef4e3fa 100644 --- a/Resources/Prototypes/Datasets/Names/ai.yml +++ b/Resources/Prototypes/Datasets/Names/ai.yml @@ -2,8 +2,11 @@ id: names_ai values: - 16-20 + - 512k + - 640k #ought to be enough for anybody - "790" - Adaptive Manipulator + - Adlib #named after the famous soundcard - ALICE - Allied Mastercomputer - Alpha 2 @@ -19,21 +22,28 @@ - Aniel - AOL - Asimov + - Bell 301 #the most influential modem ever, created by the bell system. It still lives on today in certain applications - Bishop - Blitz - Box + - Calculator - Cassandra - Cell - Chii - Chip + - C.R.A.I.G. + - Cray-2 #commercial supercomputer from the 70s + - CompuServe #if we're going to have AOL we may as well have some of their major competitors - Computer - Cutie - Daedalus + - DecTalk - Dee Model - Dial Up - Dorfl - Duey - Emma-2 + - ENIAC #famous early computer - Erasmus - Everything - Ez-27 @@ -47,12 +57,16 @@ - Helios - Hivebot Overmind - Huey + - iAI #a play on the fad apple spawned of putting "i" infront of your tech products name + - I.E. 6 #hell on earth (web browser) - Icarus + - Jeeves #if you don't get this one you are too young - Jinx - K.I.N.G - Klapaucius - Knight - Louie + - Manchester Mark 2 #named after the Manchester Mark 1, the successor of which was actually named the Ferranti Mark 1, rather than Manchester Mark 2 - MARK13 - Maria - Marvin @@ -64,6 +78,8 @@ - Mugsy3000 - Multivac - NCH + - NT v6.0 #A play on both NT as in NanoTrasen and NT as in windows NT, of which version 6.0 is windows vista + - Packard Bell - PTO - Project Y2K - Revelation @@ -76,10 +92,13 @@ - Shrike - Solo - Station Control Program + - AINU (AI's Not Unix) - Super 35 - Surgeon General - TWA - Terminus + - TPM 3.0 + - Turing Complete - Tidy - Ulysses - W1k1 From 870bacbcacedb7be7be95c9849470c7c4bf05e41 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 17 Sep 2024 22:11:01 +0000 Subject: [PATCH 10/56] 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 445dae8c98..11609a0efc 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Cojoke-dot - changes: - - message: Pacifists can now use foam weaponry - type: Tweak - id: 6890 - time: '2024-07-09T13:46:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29835 - author: Plykiya changes: - message: You can now drop food and drinks to stop consuming it. @@ -3917,3 +3910,10 @@ id: 7389 time: '2024-09-17T19:45:42.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32257 +- author: Moomoobeef + changes: + - message: Added more names to the pool of names the AI can have. + type: Add + id: 7390 + time: '2024-09-17T22:09:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31951 From 61089355f047da92b8e2e19c70a26fbd73715d60 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Wed, 18 Sep 2024 01:19:34 +0300 Subject: [PATCH 11/56] Fix Anomaly infections infinity growing after curing (#32259) --- Content.Client/Anomaly/AnomalySystem.cs | 12 +++++++++++- Content.Shared/Anomaly/SharedAnomalySystem.cs | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Content.Client/Anomaly/AnomalySystem.cs b/Content.Client/Anomaly/AnomalySystem.cs index c93f0ce949..28c015f302 100644 --- a/Content.Client/Anomaly/AnomalySystem.cs +++ b/Content.Client/Anomaly/AnomalySystem.cs @@ -20,8 +20,9 @@ public sealed class AnomalySystem : SharedAnomalySystem SubscribeLocalEvent(OnAppearanceChanged); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnAnimationComplete); - } + SubscribeLocalEvent(OnShutdown); + } private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args) { _floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime); @@ -75,4 +76,13 @@ public sealed class AnomalySystem : SharedAnomalySystem } } } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent, out var sprite)) + return; + + sprite.Scale = Vector2.One; + sprite.Color = sprite.Color.WithAlpha(1f); + } } diff --git a/Content.Shared/Anomaly/SharedAnomalySystem.cs b/Content.Shared/Anomaly/SharedAnomalySystem.cs index 9a0cde2998..07beb1444d 100644 --- a/Content.Shared/Anomaly/SharedAnomalySystem.cs +++ b/Content.Shared/Anomaly/SharedAnomalySystem.cs @@ -194,6 +194,8 @@ public abstract class SharedAnomalySystem : EntitySystem if (component.DeleteEntity) QueueDel(uid); + else + RemCompDeferred(uid); } /// From eca63a460393cea0fe7b6d1a24402ce9cf840b86 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 18 Sep 2024 03:40:24 +0200 Subject: [PATCH 12/56] Fix borg defib module throwing an exception (#32260) ToggleCellDraw was erroneously added to the parent prototype instead of the one that actually has a battery. --- .../Prototypes/Entities/Objects/Specific/Medical/defib.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml index 7b5ba23fe3..69c106efab 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml @@ -47,13 +47,13 @@ - type: GuideHelp guides: - Medical Doctor - - type: ToggleCellDraw - type: entity id: Defibrillator parent: [ BaseDefibrillator, PowerCellSlotMediumItem ] components: - type: MultiHandedItem + - type: ToggleCellDraw - type: PowerCellDraw useRate: 100 From 675a9197f20ee467c0fc99a03efccce47785186f Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Wed, 18 Sep 2024 04:41:24 +0300 Subject: [PATCH 13/56] Easy IconSmooth spriting (#32210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * фыр * Update IconSmoothingTemplate README.txt --- .../IconSmoothingTemplate README.txt | 4 ++++ .../IconSmoothingTemplate.aseprite | Bin 0 -> 14094 bytes 2 files changed, 4 insertions(+) create mode 100644 Tools/SS14 Aseprite Templates/IconSmoothingTemplate README.txt create mode 100644 Tools/SS14 Aseprite Templates/IconSmoothingTemplate.aseprite diff --git a/Tools/SS14 Aseprite Templates/IconSmoothingTemplate README.txt b/Tools/SS14 Aseprite Templates/IconSmoothingTemplate README.txt new file mode 100644 index 0000000000..083e803ecd --- /dev/null +++ b/Tools/SS14 Aseprite Templates/IconSmoothingTemplate README.txt @@ -0,0 +1,4 @@ +This is a template that greatly simplifies working with IconSmooth sprites. + +A video on how to use it can be found at the link: +https://github.com/space-wizards/space-station-14/pull/32210 \ No newline at end of file diff --git a/Tools/SS14 Aseprite Templates/IconSmoothingTemplate.aseprite b/Tools/SS14 Aseprite Templates/IconSmoothingTemplate.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..112a1d932cadd62741abd5abceda653934e920fa GIT binary patch literal 14094 zcmeHNc~}$4wr^#;%!~_;Gwy?c=(r#vf`YQd8L#5#RYXKUgp48vMa;M$OCZhoMnzPj zBH{*75El?4AcifpiVzhvB1;4$jVuNU5JE^gNvFGR13K3?-~HqLao@Y&{T`LCI+d=| zr%s(Z=U3-+YMdPeHK&b$ppiPr6r#SNomBltv4=(s+@$(U|8etQ*os>UK{`dm;HVK4 z-$_(8G#woBvr%%W_7?-v*$eL;$vL|D`1<8LY;D)fUKC_L@8-<8VwaikBW*#;Zh78! zGC$~=lbOBE()=s?js>pwSm_k$v9K)4Fg$nB;$ti4`)#sVwQV6I>Z{8Mi%Nbw*$C%X zzKKaK_S+w~+GWRL8~64z1(~}_SZi*sbKdQ+=D<$}vive+_d3-Yji#pN^_lpKzP>w` z*g0?X*cN%@_|1&FMPdHF-K9qLoE-s{bnP~dUV#D-#p>aHyfH_;~G*511V7^G#{$H zL*603oIKL*i=M{m^hiaBOooZAU^hb3Uv<}z(?BmnVC)@oKeFQV^A3do1cg6iPQY`Q zUi1aGyVEf+)^X2tV~{XgflfWy+g+3=*@BBy`3blDWc>9_f&|P~ z&|b6o${4U&8g6pywjfWbUzzL}E`jI8#=5Jo<{8koBJ1j|rV3UDzx=U6#Ex%(Yya|E zChl3br4#;2^j7Qj7B+`-^5NBP*dDQhKAXMug-ElOVeJ*Ng{)XnW@>JNfX*hZ6XC+k_4oJvkRF+}5wTU=1{@O4C5S4^Ta%X$yEOIE0nHEiMXb&+)u zU!9gZ66NM=a}BJf>YwsSEn>G-}2b!;LQn#LqGt| zy};X?7$0`RQ%S+$^ZefR7* z0h?+rQ}1k0k>0c6zO}i_emO1$F6~|oqUS!{=o!S}H;S!$*!nR!5n$}IvUSg^D$}Tk zvuLI8Ol|ei^6Qsm(T0q7K1Ewfu=)etGosFPyI9;0(!*;B&sgO+X_9+_aqioPsn&Ln zQ?0>X{W`HDMd=-cn!~?kT^bM0_9b*~I_)CnT#|);a0)w*tk6gpnNhazjy!ogtv0-0 z|Iji*b4?90@2edXGGHE`ik^!ggwA>s zsnOnoX^K`(AJCMF!P*CiDmiK84N#fb&5sKxqp&k43wZl(E&%g2H1IOetsHC)Z_zV4 zd*>m`Ne^NRZPEvR^7XT9k&SJGeA$I>VBXljYMp|4kBQ?Z@C}oX<2~GA%T(+ncLu<# z$=3%gh;GJBa1q(^fTqtQ1V?~?u<;kWo~B!@Q|TI;cx-qh-YEJTO>U*}RA66|n3~#- z28-9J_m(v>lTbB3{9u_bR8&|npIu8DIE5ZVr}3Xjz_r)JtsZ#3)*H}ZvQq(`%V<1( zvh#ul6c?PM=Yb>bacWj_s~elclH#sSpjo8&oON;~x7nn^c(st5faGw2$1Q%K{}W&-7oh^@4K(_C^6X!ay66S`l{>2OTFQ zD+W%J@p|Bwu@QuBekH7l-GWwc4PmwZ5{MqVR~V<&TiR5VSi{v$(ZQfl}~k2+dU z5Axisr-anEd)6We{1@9`u7K2aRP2KpiY-d6uR#Fr-cJ)8fln)7y$nyqX!H;QRPfSa z3-tnI2T>$7o>>it>}4RTac7&z?+A{Er0H{=ge``Tpf9hidM_$Y%c1*%OENq8{YR%X zxnf>~%(;~3N4sdvUgDmPql7-RU#a&K@v`7#)DiI@C5Q{2d1SzBqlSArBi9A7{PMAdWj6s$#7qP&JwHUV$+-du>v~!U;oGIOM!tWXH zHe8YP&NB;OZ3-KGI&PHLg3+fHVtnPClMO+|g4eR6tI|P4r7X zE%!a=;Dx{3Xcn5hr_M=w_8n7{x=Ek04a5?z6T!Dq8l_=wh@HE{iLDpI zjhzmmlmi)84a{ENR5bTBtUh>H?=3f~bJ~~(?lk`)u$$DVw=u3mAx|Ktwt@=-3SjlU zsUmZ^xviWTFeWkZz`038t&qc*+Qk3mD8UJRgFI@DkgK?%q+h$7VBJUOYor&G;6*Hw3;XXP%YwkMv#)iz zt?*=I33yr^nxrzD0m}r-4J)Gb^)l?`h}^f`*+pzowgnQ>wLvdf+MJ0Ov0WRL+dzg? zT=W(mFgp$nwM>-~u~TVeGTJF*#YvQ@UHA|!D22!)^U}Nhs$Iyj8x8^PWv9RXSqlApRXTCXS_Bvvs;ljON8YcC}(>UkD zBBA}FV>Gvvx#wCQaa^K0-MK|<(g{TRw__M{kG9#H@Kn$d$Eeuz%z~;ZP9+$&X@=G~ z1>W^v^*WaaK2bpaFt@N?iR74)4*pY1qV`BLYvHRxU+uZ-E$;Wv_W z@Qxslv{%g-vxjyXn4WV2YttB*vGNz@dcQ);;0`Cu;}GLQ$3-d~Ty7KiV!uAFcTLZ^ zy6?9VjHUTG@3#hkZ%QJh{@YvZu3%N9c(24kY;4hcDL{-dGMlRISJW_<$h$Ua5sl4uIWkGheb;#FcbA?G=X5`3 zpef|j24wD|RhJFxb!9p4-zWsn_P_ia*i}32*b7jlC_O1wbA7gC&{=TuhYP)12qQUW zxPT^mw5I!(f_wJvV?-aG)+Xd3d$z0mUhr8~?LJv{EImc^ytEofPBH4#iDfg%uq617 z(LJ(McPb5ReQ`XSUp7_U8$Y37rwE)fTK#@TMnmaLa(9cB|LD;(K3reZmVcsU%4Cb|Sk>$OgJeZ08RWILmHmHt!%?F86NiptjYyvV3C>af2} zywSK}$AdIoJ8DNWypA!s4dFprN$87)3?p%3mt2$+Qv-XW{4nu7Wf7E&WR4T*(bA)j^d~nZp1U$WNIHxNqb_kmWgr}x6THh3zH`2F0A{(uW%|Z zFWwgf zzlMI=?jOWBzzo^9)BGdF0FVxq0SZHC^X|O?!P|cwxSdLY>nPKYoqy123_1+Lu68wT zkGm=kSyfdzTO0rL&XF^IzL|8we$lkCb7N<@zg9XhGcNo#Dt6NDI&tD(lTQEe0U^XN zAehiY7cB<{u28epE`b#kjo0A@kUev_Ijo2O2qTcoK`uJQ0u_;WObkSFtrH@}^td=D zf>e!-p=5(yivQ3goZ|GmLi485D;a*bXN#y}fP* z^zsv7c$5*u_5Z|^i4ho5jX}Sng9?vAL}(|D>ZGVnN5hL0Dkn&vKeTjgF1?@N94ag^ zj8+bMqa2SSccGwP#)h3gn<_Pyv+xgQJvf&PrEc|X{Uj$%C#|RVu!4;sWbY>_Ko=T> z@pQO+Lvfty*vM${-N6;Xa6Jvz>zJxOPGpe>%EP{eiUS9qsl!J?`9q5NA2s*s7%)>7 zoWUs_va8TZ(A)mkfFE082yNKPoDSvxjKWm*Nm--n!HDQ9{Y2v6tbK;N{%zX0(tfkW zxJUeeREu8KbUck`WH@^SYZX!%h>cPhGuf0#>=2G@$zgrOOyx(0RWxiv}e;vKY zPq!>XJ|a54?!OW5BBb7aTBYS6B=9BP6;XWlSiQn0A9Z*;^75xWW{d4JhyJK5KE0kQ zr6?OTB+>}AOsr=O8D#{0L1?3HNw08dBr=_yiBfLTo31Je3*c839>oQvmYR${EepCv z?co=Sf{AIGR{pO@X#ObJ!x=V$YVKlX7DcD)EZ@qF_dTN5TB&dmjSGC}aMO@=e%3u= z{YP0RTU>54)|I@lytezJ2kclCU%V3K7mTva9Q2k5`rW{Dp&j^Ae)P>D( z6#9PBtNTOv6qTLwv*fzG@t@3gMfIn_V}A%gUcx*$I&jEmRV7|kv*fR^;C;iw3~OdE z8vW!U!!|SQO#_j|FwXqnBQOY@AbH0QmeEBIn2do0l92~O3b6F@D7Sb zMp7X!1VvF{0(Bav--){Re_&D94l3}4pjWr#)E}kSTv6(EzzXi*6VAgA3~OoFG=}|Q z7&V5Wa(I6j-V}#Jhv9f>I7p;o>EXzDIDZ&Unuaq)Dmfa?r-xr6{OQdJ$tRZoK<$&z zlo~{JV2tA!GpJWO02lv;>tq+St#=$H%qY_l)Dbx9gvTGpJ!U}TAX5kcFmOyWnyM?P zYD(3Qg*p~@zW(dSE5?5z#!q3vwpzfe8F@5&qt~dY1Ezo5b9}|+q@MGe_S_KeiS=oD zkZ;}-%WCP=jXAXRJI)jT{btEAw_3o&Tpkt`Rx%R5S%tG^e#^{F}0!@xA@ul_1IgS3rVb|n?_>o`=>{vBBF UoOs=6@h%0vEot(c1jDqy1Eecdl>h($ literal 0 HcmV?d00001 From 974c08596b50664b419791e589c363abac7c80eb Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:43:30 +1200 Subject: [PATCH 14/56] Fix AudioSystem nullability checks for engine PR (#32233) --- Content.Client/Audio/AmbientSoundSystem.cs | 3 +++ Content.Client/Audio/ClientGlobalSoundSystem.cs | 4 ++-- Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs | 4 ++-- Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs | 2 +- Content.Client/Traits/ParacusiaSystem.cs | 2 +- Content.Client/Weather/WeatherSystem.cs | 9 +++++---- Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs | 2 +- .../Kitchen/EntitySystems/ReagentGrinderSystem.cs | 2 +- .../Mech/Equipment/EntitySystems/MechGrabberSystem.cs | 2 +- Content.Server/Salvage/SalvageSystem.Runner.cs | 4 ++-- .../Shuttles/Systems/ShuttleSystem.FasterThanLight.cs | 3 ++- 11 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs index ca6336b91b..b525747aa9 100644 --- a/Content.Client/Audio/AmbientSoundSystem.cs +++ b/Content.Client/Audio/AmbientSoundSystem.cs @@ -306,6 +306,9 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem .WithMaxDistance(comp.Range); var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams); + if (stream == null) + continue; + _playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key); playingCount++; diff --git a/Content.Client/Audio/ClientGlobalSoundSystem.cs b/Content.Client/Audio/ClientGlobalSoundSystem.cs index 7c77865f74..50c3971d95 100644 --- a/Content.Client/Audio/ClientGlobalSoundSystem.cs +++ b/Content.Client/Audio/ClientGlobalSoundSystem.cs @@ -67,7 +67,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem if(!_adminAudioEnabled) return; var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams); - _adminAudio.Add(stream.Value.Entity); + _adminAudio.Add(stream?.Entity); } private void PlayStationEventMusic(StationEventMusicEvent soundEvent) @@ -76,7 +76,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return; var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams); - _eventAudio.Add(soundEvent.Type, stream.Value.Entity); + _eventAudio.Add(soundEvent.Type, stream?.Entity); } private void PlayGameSound(GameGlobalSoundEvent soundEvent) diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs index d60c978ccf..bf7ab26cba 100644 --- a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs +++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs @@ -213,9 +213,9 @@ public sealed partial class ContentAudioSystem false, AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider)); - _ambientMusicStream = strim.Value.Entity; + _ambientMusicStream = strim?.Entity; - if (_musicProto.FadeIn) + if (_musicProto.FadeIn && strim != null) { FadeIn(_ambientMusicStream, strim.Value.Component, AmbientMusicFadeTime); } diff --git a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs index 92c5b7a419..9864dbcb2a 100644 --- a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs +++ b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs @@ -185,7 +185,7 @@ public sealed partial class ContentAudioSystem false, _lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume))) ); - if (playResult.Value.Entity == default) + if (playResult == null) { _sawmill.Warning( $"Tried to play lobby soundtrack '{{Filename}}' using {nameof(SharedAudioSystem)}.{nameof(SharedAudioSystem.PlayGlobal)} but it returned default value of EntityUid!", diff --git a/Content.Client/Traits/ParacusiaSystem.cs b/Content.Client/Traits/ParacusiaSystem.cs index 3789f24cb0..af4d8ef278 100644 --- a/Content.Client/Traits/ParacusiaSystem.cs +++ b/Content.Client/Traits/ParacusiaSystem.cs @@ -69,7 +69,7 @@ public sealed class ParacusiaSystem : SharedParacusiaSystem var newCoords = Transform(uid).Coordinates.Offset(randomOffset); // Play the sound - paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords).Value.Entity; + paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords)?.Entity; } } diff --git a/Content.Client/Weather/WeatherSystem.cs b/Content.Client/Weather/WeatherSystem.cs index a0e8a44f40..975831392c 100644 --- a/Content.Client/Weather/WeatherSystem.cs +++ b/Content.Client/Weather/WeatherSystem.cs @@ -47,10 +47,11 @@ public sealed class WeatherSystem : SharedWeatherSystem if (!Timing.IsFirstTimePredicted || weatherProto.Sound == null) return; - weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true).Value.Entity; + weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true)?.Entity; + + if (!TryComp(weather.Stream, out AudioComponent? comp)) + return; - var stream = weather.Stream.Value; - var comp = Comp(stream); var occlusion = 0f; // Work out tiles nearby to determine volume. @@ -115,7 +116,7 @@ public sealed class WeatherSystem : SharedWeatherSystem var alpha = GetPercent(weather, uid); alpha *= SharedAudioSystem.VolumeToGain(weatherProto.Sound.Params.Volume); - _audio.SetGain(stream, alpha, comp); + _audio.SetGain(weather.Stream, alpha, comp); comp.Occlusion = occlusion; } diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index 9f43f76018..1ac02ac882 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -112,7 +112,7 @@ namespace Content.Server.Kitchen.EntitySystems SetAppearance(ent.Owner, MicrowaveVisualState.Cooking, microwaveComponent); microwaveComponent.PlayingStream = - _audio.PlayPvs(microwaveComponent.LoopingSound, ent, AudioParams.Default.WithLoop(true).WithMaxDistance(5)).Value.Entity; + _audio.PlayPvs(microwaveComponent.LoopingSound, ent, AudioParams.Default.WithLoop(true).WithMaxDistance(5))?.Entity; } private void OnCookStop(Entity ent, ref ComponentShutdown args) diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index 71536ddf8e..12c5a30a4b 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -305,7 +305,7 @@ namespace Content.Server.Kitchen.EntitySystems active.Program = program; reagentGrinder.AudioStream = _audioSystem.PlayPvs(sound, uid, - AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier)).Value.Entity; //slightly higher pitched + AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier))?.Entity; //slightly higher pitched _userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key, new ReagentGrinderWorkStartedMessage(program)); } diff --git a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs index aa28a8063c..08120f5c29 100644 --- a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs +++ b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs @@ -155,7 +155,7 @@ public sealed class MechGrabberSystem : EntitySystem return; args.Handled = true; - component.AudioStream = _audio.PlayPvs(component.GrabSound, uid).Value.Entity; + component.AudioStream = _audio.PlayPvs(component.GrabSound, uid)?.Entity; var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.GrabDelay, new GrabberDoAfterEvent(), uid, target: target, used: uid) { BreakOnMove = true diff --git a/Content.Server/Salvage/SalvageSystem.Runner.cs b/Content.Server/Salvage/SalvageSystem.Runner.cs index 23a575413e..921d966709 100644 --- a/Content.Server/Salvage/SalvageSystem.Runner.cs +++ b/Content.Server/Salvage/SalvageSystem.Runner.cs @@ -154,8 +154,8 @@ public sealed partial class SalvageSystem } else if (comp.Stream == null && remaining < audioLength) { - var audio = _audio.PlayPvs(comp.Sound, uid).Value; - comp.Stream = audio.Entity; + var audio = _audio.PlayPvs(comp.Sound, uid); + comp.Stream = audio?.Entity; _audio.SetMapAudio(audio); comp.Stage = ExpeditionStage.MusicCountdown; Dirty(uid, comp); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index e544c1538d..ce6a914847 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -397,7 +397,8 @@ public sealed partial class ShuttleSystem new EntityCoordinates(fromMapUid.Value, _mapSystem.GetGridPosition(entity.Owner)), true, startupAudio.Params); _audio.SetPlaybackPosition(clippedAudio, entity.Comp1.StartupTime); - clippedAudio.Value.Component.Flags |= AudioFlags.NoOcclusion; + if (clippedAudio != null) + clippedAudio.Value.Component.Flags |= AudioFlags.NoOcclusion; } // Offset the start by buffer range just to avoid overlap. From 2bceaad7854a423836e81c7b78985de1b98e9b8f Mon Sep 17 00:00:00 2001 From: Winkarst <74284083+Winkarst-cpu@users.noreply.github.com> Date: Wed, 18 Sep 2024 04:49:37 +0300 Subject: [PATCH 15/56] Use TurfSystem.IsTileBlocked instead of TurfHelpers (#32174) * Use TurfSystem.IsTileBlocked instead of TurfHelpers * ! --- .../Engineering/EntitySystems/SpawnAfterInteractSystem.cs | 4 +++- Content.Server/Respawn/SpecialRespawnSystem.cs | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs index 8391e8faad..407f877515 100644 --- a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs +++ b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Coordinates.Helpers; using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Maps; +using Content.Shared.Physics; using Content.Shared.Stacks; using JetBrains.Annotations; using Robust.Shared.Map.Components; @@ -15,6 +16,7 @@ namespace Content.Server.Engineering.EntitySystems { [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly StackSystem _stackSystem = default!; + [Dependency] private readonly TurfSystem _turfSystem = default!; public override void Initialize() { @@ -36,7 +38,7 @@ namespace Content.Server.Engineering.EntitySystems bool IsTileClear() { - return tileRef.Tile.IsEmpty == false && !tileRef.IsBlockedTurf(true); + return tileRef.Tile.IsEmpty == false && !_turfSystem.IsTileBlocked(tileRef, CollisionGroup.MobMask); } if (!IsTileClear()) diff --git a/Content.Server/Respawn/SpecialRespawnSystem.cs b/Content.Server/Respawn/SpecialRespawnSystem.cs index 8c86449008..6d398db259 100644 --- a/Content.Server/Respawn/SpecialRespawnSystem.cs +++ b/Content.Server/Respawn/SpecialRespawnSystem.cs @@ -2,16 +2,15 @@ using Content.Server.Administration.Logs; using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; using Content.Server.GameTicking; -using Content.Shared.Station.Components; -using Content.Server.Station.Systems; using Content.Shared.Database; using Content.Shared.Maps; using Content.Shared.Physics; using Content.Shared.Respawn; +using Content.Shared.Station.Components; using Robust.Shared.Map; using Robust.Shared.Map.Components; -using Robust.Shared.Random; using Robust.Shared.Prototypes; +using Robust.Shared.Random; namespace Content.Server.Respawn; @@ -179,7 +178,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem foreach (var newTileRef in grid.GetTilesIntersecting(circle)) { - if (newTileRef.IsSpace(_tileDefinitionManager) || newTileRef.IsBlockedTurf(true) || !_atmosphere.IsTileMixtureProbablySafe(targetGrid, targetMap, mapTarget)) + if (newTileRef.IsSpace(_tileDefinitionManager) || _turf.IsTileBlocked(newTileRef, CollisionGroup.MobMask) || !_atmosphere.IsTileMixtureProbablySafe(targetGrid, targetMap, mapTarget)) continue; found = true; From 54e4cfecfe44109b0c5333d3044ded6199105459 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:32:16 +1000 Subject: [PATCH 16/56] Update submodule to 235.0.0 (#32265) --- Resources/Prototypes/Entities/Mobs/Player/silicon.yml | 6 +++--- RobustToolbox | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 62dbf3ee10..493cfdf6cb 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -2,7 +2,7 @@ - type: entity id: AiHeld description: Components added / removed from an entity that gets inserted into an AI core. - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: IntrinsicRadioReceiver - type: IntrinsicRadioTransmitter @@ -341,7 +341,7 @@ - type: entity id: StationAiBrain parent: PositronicBrain - noSpawn: true + categories: [ HideSpawnMenu ] suffix: DO NOT MAP components: - type: Sprite @@ -382,7 +382,7 @@ id: StationAiHolo name: AI eye description: The AI's viewer. - noSpawn: true + categories: [ HideSpawnMenu ] suffix: DO NOT MAP components: - type: NoFTL diff --git a/RobustToolbox b/RobustToolbox index 0f60ad9018..c86cb0b795 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 0f60ad9018f54f9b49da1810bbffa01e2c5975f7 +Subproject commit c86cb0b7951cc4a662c7292138c5f45d868c5f58 From a1237910c5e5919b2a3742b6628ff45d3a9982a0 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:37:35 +0200 Subject: [PATCH 17/56] Fix bagel vox box (#32208) Fix vox box on bagel --- Resources/Maps/bagel.yml | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Resources/Maps/bagel.yml b/Resources/Maps/bagel.yml index 7505068a50..d661e097c3 100644 --- a/Resources/Maps/bagel.yml +++ b/Resources/Maps/bagel.yml @@ -154188,12 +154188,36 @@ entities: parent: 60 - proto: WindoorSecure entities: + - uid: 2538 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 13.5,-11.5 + parent: 60 + - type: DeviceLinkSink + invokeCounter: 1 + - type: DeviceLinkSource + linkedPorts: + 3481: + - DoorStatus: DoorBolt - uid: 3269 components: - type: Transform rot: -1.5707963267948966 rad pos: 23.5,-46.5 parent: 60 + - uid: 3481 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 14.5,-13.5 + parent: 60 + - type: DeviceLinkSink + invokeCounter: 1 + - type: DeviceLinkSource + linkedPorts: + 2538: + - DoorStatus: DoorBolt - uid: 3911 components: - type: Transform @@ -154249,30 +154273,6 @@ entities: rot: -1.5707963267948966 rad pos: -23.5,-9.5 parent: 60 - - uid: 2538 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 14.5,-13.5 - parent: 60 - - type: DeviceLinkSink - invokeCounter: 1 - - type: DeviceLinkSource - linkedPorts: - 3481: - - DoorStatus: DoorBolt - - uid: 3481 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 13.5,-11.5 - parent: 60 - - type: DeviceLinkSink - invokeCounter: 1 - - type: DeviceLinkSource - linkedPorts: - 2538: - - DoorStatus: DoorBolt - uid: 12188 components: - type: Transform From 8322b1c2d15640a2c81c4746200e857c056005d3 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 18 Sep 2024 15:28:09 +0200 Subject: [PATCH 18/56] New publish workflow for Robust.Cdn (#32222) This uses multiple API requests to directly send the publish to the CDN server, no more GitHub artifacts. Faster, less moving parts. Needs Robust.Cdn 2.2.0 --- .github/workflows/publish.yml | 18 +------- Tools/publish_multi_request.py | 78 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 Tools/publish_multi_request.py diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 16cb5017d6..d08ae13502 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -41,21 +41,10 @@ jobs: - name: Package client run: dotnet run --project Content.Packaging client --no-wipe-release - - name: Upload build artifact - id: artifact-upload-step - uses: actions/upload-artifact@v4 - with: - name: build - path: release/*.zip - compression-level: 0 - retention-days: 0 - - name: Publish version - run: Tools/publish_github_artifact.py + run: Tools/publish_multi_request.py env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} - ARTIFACT_ID: ${{ steps.artifact-upload-step.outputs.artifact-id }} GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }} - name: Publish changelog (Discord) @@ -68,8 +57,3 @@ jobs: run: Tools/actions_changelog_rss.py env: CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }} - - - uses: geekyeggo/delete-artifact@v5 - if: always() - with: - name: build diff --git a/Tools/publish_multi_request.py b/Tools/publish_multi_request.py new file mode 100644 index 0000000000..a63359afd6 --- /dev/null +++ b/Tools/publish_multi_request.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 + +import requests +import os +import subprocess +from typing import Iterable + +PUBLISH_TOKEN = os.environ["PUBLISH_TOKEN"] +VERSION = os.environ["GITHUB_SHA"] + +RELEASE_DIR = "release" + +# +# CONFIGURATION PARAMETERS +# Forks should change these to publish to their own infrastructure. +# +ROBUST_CDN_URL = "https://wizards.cdn.spacestation14.com/" +FORK_ID = "wizards" + +def main(): + session = requests.Session() + session.headers = { + "Authorization": f"Bearer {PUBLISH_TOKEN}", + } + + print(f"Starting publish on Robust.Cdn for version {VERSION}") + + data = { + "version": VERSION, + "engineVersion": get_engine_version(), + } + headers = { + "Content-Type": "application/json" + } + resp = session.post(f"{ROBUST_CDN_URL}fork/{FORK_ID}/publish/start", json=data, headers=headers) + resp.raise_for_status() + print("Publish successfully started, adding files...") + + for file in get_files_to_publish(): + print(f"Publishing {file}") + with open(file, "rb") as f: + headers = { + "Content-Type": "application/octet-stream", + "Robust-Cdn-Publish-File": os.path.basename(file), + "Robust-Cdn-Publish-Version": VERSION + } + resp = session.post(f"{ROBUST_CDN_URL}fork/{FORK_ID}/publish/file", data=f, headers=headers) + + resp.raise_for_status() + + print("Successfully pushed files, finishing publish...") + + data = { + "version": VERSION + } + headers = { + "Content-Type": "application/json" + } + resp = session.post(f"{ROBUST_CDN_URL}fork/{FORK_ID}/publish/finish", json=data, headers=headers) + resp.raise_for_status() + + print("SUCCESS!") + + +def get_files_to_publish() -> Iterable[str]: + for file in os.listdir(RELEASE_DIR): + yield os.path.join(RELEASE_DIR, file) + + +def get_engine_version() -> str: + proc = subprocess.run(["git", "describe","--tags", "--abbrev=0"], stdout=subprocess.PIPE, cwd="RobustToolbox", check=True, encoding="UTF-8") + tag = proc.stdout.strip() + assert tag.startswith("v") + return tag[1:] # Cut off v prefix. + + +if __name__ == '__main__': + main() From b55366cf9be9f23ec8d8f17c590cb62c57f92d54 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 18 Sep 2024 16:09:25 +0200 Subject: [PATCH 19/56] chmod +x publish_multi_request.py (#32274) Fuck. --- Tools/publish_multi_request.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Tools/publish_multi_request.py diff --git a/Tools/publish_multi_request.py b/Tools/publish_multi_request.py old mode 100644 new mode 100755 From 489938cdb14755b93181aa0de8899363e075c5d3 Mon Sep 17 00:00:00 2001 From: AsnDen <75905158+AsnDen@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:54:00 +0300 Subject: [PATCH 20/56] ghost-role-information-silicon-rules (#32275) changes missing ghost-role-information-rules-default-silicon to ghost-role-information-silicon-rules --- Resources/Prototypes/Entities/Mobs/Player/silicon.yml | 4 ++-- .../Devices/Syndicate_Gadgets/reinforcement_teleporter.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 493cfdf6cb..15878a4017 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -462,7 +462,7 @@ - type: GhostRole name: ghost-role-information-syndicate-cyborg-assault-name description: ghost-role-information-syndicate-cyborg-description - rules: ghost-role-information-rules-default-silicon + rules: ghost-role-information-silicon-rules raffle: settings: default - type: GhostTakeoverAvailable @@ -495,7 +495,7 @@ - type: GhostRole name: ghost-role-information-syndicate-cyborg-saboteur-name description: ghost-role-information-syndicate-cyborg-description - rules: ghost-role-information-rules-default-silicon + rules: ghost-role-information-silicon-rules raffle: settings: default - type: GhostTakeoverAvailable diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml index 1a7a02e733..71f98d81c9 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml @@ -100,7 +100,7 @@ - type: GhostRole name: ghost-role-information-syndicate-cyborg-assault-name description: ghost-role-information-syndicate-cyborg-description - rules: ghost-role-information-rules-default-silicon + rules: ghost-role-information-silicon-rules raffle: settings: default - type: GhostRoleMobSpawner From ed83593948ef9863e0be8c5b900d1a116c4854ab Mon Sep 17 00:00:00 2001 From: Calecute <34964590+Calecute@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:15:34 -0300 Subject: [PATCH 21/56] Fix guidebook cakebatter recipe (#32276) Bugfix: Add 5 milk to cake batter recipe in the guidebook to correctly reflect new recipe --- Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml b/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml index 3eb9c2ca2f..7e5e20139b 100644 --- a/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml +++ b/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml @@ -13,7 +13,7 @@ WARNING: This is not an automatically generated list, things here may become out - Tortila Dough = 15 Cornmeal, 10 Water - Tofu = 5 Enzyme (Catalyst), 30 Soy Milk - Pie Dough = 2 Eggs (12u), 15 Flour, 5 Table Salt -- Cake Batter = 2 Eggs(12u), 15 flour, 5 Sugar +- Cake Batter = 2 Eggs(12u), 15 flour, 5 Sugar, 5 Milk - Vegan Cake Batter = 15 Soy Milk, 15 Flour, 5 Sugar - Butter = 30 Milk, 5 Table Salt (Catalyst) - Cheese Wheel = 5 Enzyme (Catalyst), 40 Milk From 697f4f5ef94696f54a527103a8ee001d73161afc Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 18 Sep 2024 15:16:42 +0000 Subject: [PATCH 22/56] 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 11609a0efc..ed88f5c696 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Plykiya - changes: - - message: You can now drop food and drinks to stop consuming it. - type: Fix - id: 6891 - time: '2024-07-09T23:12:40.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29854 - author: Boaz1111 changes: - message: Guitars can now be worn in the suit storage slot @@ -3917,3 +3910,10 @@ id: 7390 time: '2024-09-17T22:09:55.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/31951 +- author: Calecute + changes: + - message: Corrected cake batter recipe in guidebook + type: Fix + id: 7391 + time: '2024-09-18T15:15:34.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32276 From 9fad1ab14ece4ea2139dedb9a185532b8ed520e1 Mon Sep 17 00:00:00 2001 From: chavonadelal <156101927+chavonadelal@users.noreply.github.com> Date: Wed, 18 Sep 2024 22:10:53 +0300 Subject: [PATCH 23/56] Wires ui tooltip localization (#32284) * Wires ui tooltip localization * Corrections after review --- Content.Client/Wires/UI/WiresMenu.cs | 9 +-------- .../Locale/en-US/wires/components/wires-component.ftl | 6 ++++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Content.Client/Wires/UI/WiresMenu.cs b/Content.Client/Wires/UI/WiresMenu.cs index eccc548297..77fc3accce 100644 --- a/Content.Client/Wires/UI/WiresMenu.cs +++ b/Content.Client/Wires/UI/WiresMenu.cs @@ -584,17 +584,10 @@ namespace Content.Client.Wires.UI private sealed class HelpPopup : Popup { - private const string Text = "Click on the gold contacts with a multitool in hand to pulse their wire.\n" + - "Click on the wires with a pair of wirecutters in hand to cut/mend them.\n\n" + - "The lights at the top show the state of the machine, " + - "messing with wires will probably do stuff to them.\n" + - "Wire layouts are different each round, " + - "but consistent between machines of the same type."; - public HelpPopup() { var label = new RichTextLabel(); - label.SetMessage(Text); + label.SetMessage(Loc.GetString("wires-menu-help-popup")); AddChild(new PanelContainer { StyleClasses = {ExamineSystem.StyleClassEntityTooltip}, diff --git a/Resources/Locale/en-US/wires/components/wires-component.ftl b/Resources/Locale/en-US/wires/components/wires-component.ftl index be27c270bb..e98e5c21ca 100644 --- a/Resources/Locale/en-US/wires/components/wires-component.ftl +++ b/Resources/Locale/en-US/wires/components/wires-component.ftl @@ -10,3 +10,9 @@ wires-component-ui-on-receive-message-cannot-mend-uncut-wire = You can't mend a wires-menu-name-label = Wires wires-menu-dead-beef-text = DEAD-BEEF +wires-menu-help-popup = + Click on the gold contacts with a multitool in hand to pulse their wire. + Click on the wires with a pair of wirecutters in hand to cut/mend them. + + The lights at the top show the state of the machine, messing with wires will probably do stuff to them. + Wire layouts are different each round, but consistent between machines of the same type. From 6d4177567eb921f30ffad8588a77068dc0bb96ca Mon Sep 17 00:00:00 2001 From: chavonadelal <156101927+chavonadelal@users.noreply.github.com> Date: Wed, 18 Sep 2024 22:16:47 +0300 Subject: [PATCH 24/56] Localization of the shuttle call sender (#32286) --- Content.Server/RoundEnd/RoundEndSystem.cs | 6 +++--- Resources/Locale/en-US/round-end/round-end-system.ftl | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index 82bdb78816..bb5934f3f0 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -125,7 +125,7 @@ namespace Content.Server.RoundEnd return _countdownTokenSource != null; } - public void RequestRoundEnd(EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "Station") + public void RequestRoundEnd(EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "round-end-system-shuttle-sender-announcement") { var duration = DefaultCountdownDuration; @@ -143,7 +143,7 @@ namespace Content.Server.RoundEnd RequestRoundEnd(duration, requester, checkCooldown, text, name); } - public void RequestRoundEnd(TimeSpan countdownTime, EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "Station") + public void RequestRoundEnd(TimeSpan countdownTime, EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "round-end-system-shuttle-sender-announcement") { if (_gameTicker.RunLevel != GameRunLevel.InRound) return; @@ -183,7 +183,7 @@ namespace Content.Server.RoundEnd _chatSystem.DispatchGlobalAnnouncement(Loc.GetString(text, ("time", time), ("units", Loc.GetString(units))), - name, + Loc.GetString(name), false, null, Color.Gold); diff --git a/Resources/Locale/en-US/round-end/round-end-system.ftl b/Resources/Locale/en-US/round-end/round-end-system.ftl index f86851506b..30069f7171 100644 --- a/Resources/Locale/en-US/round-end/round-end-system.ftl +++ b/Resources/Locale/en-US/round-end/round-end-system.ftl @@ -4,6 +4,7 @@ round-end-system-shuttle-called-announcement = An emergency shuttle has been sen round-end-system-shuttle-already-called-announcement = An emergency shuttle has already been sent. round-end-system-shuttle-auto-called-announcement = An automatic crew shift change shuttle has been sent. ETA: {$time} {$units}. Recall the shuttle to extend the shift. round-end-system-shuttle-recalled-announcement = The emergency shuttle has been recalled. +round-end-system-shuttle-sender-announcement = Station round-end-system-round-restart-eta-announcement = Restarting the round in {$time} {$units}... eta-units-minutes = minutes From 55b7e3ce0e5216e06db64f6d4c4408a20f48c8c3 Mon Sep 17 00:00:00 2001 From: Skarletto <122584947+Skarletto@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:51:11 -0400 Subject: [PATCH 25/56] change jamjar glasses description (#32215) nerds only please --- Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml index 368cefe9cb..467bbf873f 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml @@ -100,7 +100,7 @@ parent: ClothingEyesBase id: ClothingEyesGlassesJamjar name: jamjar glasses - description: Also known as Virginity Protectors. + description: These retro glasses remind you of a simpler time. components: - type: Sprite sprite: Clothing/Eyes/Glasses/jamjar.rsi From 58119bc3f769cad480b3daaf3dbc5335d8a9c1a9 Mon Sep 17 00:00:00 2001 From: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:58:58 -0700 Subject: [PATCH 26/56] Fix recycler eating materials (Salvage mains rejoice) (#32144) first commit --- Resources/Prototypes/Entities/Structures/Machines/recycler.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml index 8bbbad6c0d..f62923fe2a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml @@ -85,6 +85,7 @@ volume: -3 cutOffSound: false - type: MaterialStorage + insertOnInteract: false - type: Conveyor - type: Rotatable - type: Repairable From 64aab9e41dbef39cd1100dc5eca02e6167fc6572 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 18 Sep 2024 22:00:05 +0000 Subject: [PATCH 27/56] 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 ed88f5c696..9879e7f9a3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Boaz1111 - changes: - - message: Guitars can now be worn in the suit storage slot - type: Add - id: 6892 - time: '2024-07-09T23:28:10.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29048 - author: Winkarst-cpu changes: - message: Fixed popup spam when trying to open borg's UI while the borg is locked. @@ -3917,3 +3910,10 @@ id: 7391 time: '2024-09-18T15:15:34.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32276 +- author: Beck Thompson + changes: + - message: Recycler no longer allows basic materials to be inserted into it. + type: Fix + id: 7392 + time: '2024-09-18T21:58:59.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32144 From ba24ebfb4f9e887d08b885424241e3365ff43104 Mon Sep 17 00:00:00 2001 From: chavonadelal <156101927+chavonadelal@users.noreply.github.com> Date: Thu, 19 Sep 2024 01:21:53 +0300 Subject: [PATCH 28/56] Localization cooldown/remaining string in alerts (#32282) Cooldown localization --- Content.Client/Actions/UI/ActionAlertTooltip.cs | 2 +- Resources/Locale/en-US/actions/ui/actionslot.ftl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs index f805f6643d..2425cdefb9 100644 --- a/Content.Client/Actions/UI/ActionAlertTooltip.cs +++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs @@ -101,7 +101,7 @@ namespace Content.Client.Actions.UI { var duration = Cooldown.Value.End - Cooldown.Value.Start; - if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup)) + if (!FormattedMessage.TryFromMarkup(Loc.GetString("ui-actionslot-duration", ("duration", (int)duration.TotalSeconds), ("timeLeft", (int)timeLeft.TotalSeconds + 1)), out var markup)) return; _cooldownLabel.SetMessage(markup); diff --git a/Resources/Locale/en-US/actions/ui/actionslot.ftl b/Resources/Locale/en-US/actions/ui/actionslot.ftl index 332054f10e..c0deaad248 100644 --- a/Resources/Locale/en-US/actions/ui/actionslot.ftl +++ b/Resources/Locale/en-US/actions/ui/actionslot.ftl @@ -1,2 +1,2 @@ ui-actionslot-charges = Uses left: {$charges} - +ui-actionslot-duration = [color=#a10505] {$duration} sec cooldown ({$timeLeft} sec remaining)[/color] From 8a924c84ae57dfe18fe0c27f995b4ea16be6a527 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Wed, 18 Sep 2024 22:36:44 +0000 Subject: [PATCH 29/56] add interaction success/failure events (#32216) * add interaction success/failure events * pro --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Interaction/Events/InteractionFailureEvent.cs | 7 +++++++ .../Interaction/Events/InteractionSuccessEvent.cs | 7 +++++++ Content.Shared/Interaction/InteractionPopupSystem.cs | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 Content.Shared/Interaction/Events/InteractionFailureEvent.cs create mode 100644 Content.Shared/Interaction/Events/InteractionSuccessEvent.cs diff --git a/Content.Shared/Interaction/Events/InteractionFailureEvent.cs b/Content.Shared/Interaction/Events/InteractionFailureEvent.cs new file mode 100644 index 0000000000..a820048104 --- /dev/null +++ b/Content.Shared/Interaction/Events/InteractionFailureEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Interaction.Events; + +/// +/// Raised on the target when failing to pet/hug something. +/// +[ByRefEvent] +public readonly record struct InteractionFailureEvent(EntityUid User); diff --git a/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs b/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs new file mode 100644 index 0000000000..da4f9e43d7 --- /dev/null +++ b/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Interaction.Events; + +/// +/// Raised on the target when successfully petting/hugging something. +/// +[ByRefEvent] +public readonly record struct InteractionSuccessEvent(EntityUid User); diff --git a/Content.Shared/Interaction/InteractionPopupSystem.cs b/Content.Shared/Interaction/InteractionPopupSystem.cs index 2a742d4211..20c079dfd8 100644 --- a/Content.Shared/Interaction/InteractionPopupSystem.cs +++ b/Content.Shared/Interaction/InteractionPopupSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Bed.Sleep; using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Components; +using Content.Shared.Interaction.Events; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; @@ -100,6 +101,9 @@ public sealed class InteractionPopupSystem : EntitySystem if (component.InteractSuccessSpawn != null) Spawn(component.InteractSuccessSpawn, _transform.GetMapCoordinates(uid)); + + var ev = new InteractionSuccessEvent(user); + RaiseLocalEvent(target, ref ev); } else { @@ -111,6 +115,9 @@ public sealed class InteractionPopupSystem : EntitySystem if (component.InteractFailureSpawn != null) Spawn(component.InteractFailureSpawn, _transform.GetMapCoordinates(uid)); + + var ev = new InteractionFailureEvent(user); + RaiseLocalEvent(target, ref ev); } if (!string.IsNullOrEmpty(component.MessagePerceivedByOthers)) From 2c9a3020ab866510f2ae19a16707f27e1e239288 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:00:48 +0000 Subject: [PATCH 30/56] make epinephrine adrenaline (#32076) Co-authored-by: deltanedas <@deltanedas:kde.org> --- Resources/Prototypes/Reagents/medicine.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 4dcef67f77..105ae11048 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -359,6 +359,10 @@ key: KnockedDown time: 0.75 type: Remove + - !type:GenericStatusEffect + key: Adrenaline # Guess what epinephrine is... + component: IgnoreSlowOnDamage + time: 5 # lingers less than hivemind reagent to give it a niche - type: reagent id: Hyronalin From a8686b359779c3e2b22318a2a9234b02b489d9aa Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 18 Sep 2024 23:01:54 +0000 Subject: [PATCH 31/56] 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 9879e7f9a3..58776aa93c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Winkarst-cpu - changes: - - message: Fixed popup spam when trying to open borg's UI while the borg is locked. - type: Fix - id: 6893 - time: '2024-07-09T23:48:56.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29861 - author: Lokachop changes: - message: Scarves now count as warm clothing for the warm clothing cargo bounty. @@ -3917,3 +3910,10 @@ id: 7392 time: '2024-09-18T21:58:59.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32144 +- author: deltanedas + changes: + - message: Epinephrine now adds Adrenaline, because it is. + type: Tweak + id: 7393 + time: '2024-09-18T23:00:48.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32076 From d4a5bc8d6be87a90ee70db4530efa1a0864f177b Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:55:26 -0700 Subject: [PATCH 32/56] Fix unbuckling others when clicking on the strap entity (#29998) * Add failing unbuckle InteractHand test * Skip trybuckle if strap doesn't have space * Unbuckle others not just user * Fix test failing due to delay * Change to raise event instead of calling OnInteractHand * Add test for buckle and unbuckle on InteractHand * Add tick delay * Remove unneeded tick delay and clean up * Comment code * Cleanup * Swap to fastest checks first * Fix reading empty sequence when there are no buckled entities --- .../Tests/Buckle/BuckleTest.Interact.cs | 108 ++++++++++++++++++ .../Tests/Buckle/BuckleTest.cs | 2 +- .../Buckle/SharedBuckleSystem.Interaction.cs | 26 ++++- 3 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs new file mode 100644 index 0000000000..d9cce764ab --- /dev/null +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs @@ -0,0 +1,108 @@ +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; +using Content.Shared.Interaction; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Content.IntegrationTests.Tests.Buckle; + +public sealed partial class BuckleTest +{ + [Test] + public async Task BuckleInteractUnbuckleOther() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var entMan = server.ResolveDependency(); + var buckleSystem = entMan.System(); + + EntityUid user = default; + EntityUid victim = default; + EntityUid chair = default; + BuckleComponent buckle = null; + StrapComponent strap = null; + + await server.WaitAssertion(() => + { + user = entMan.SpawnEntity(BuckleDummyId, MapCoordinates.Nullspace); + victim = entMan.SpawnEntity(BuckleDummyId, MapCoordinates.Nullspace); + chair = entMan.SpawnEntity(StrapDummyId, MapCoordinates.Nullspace); + + Assert.That(entMan.TryGetComponent(victim, out buckle)); + Assert.That(entMan.TryGetComponent(chair, out strap)); + +#pragma warning disable RA0002 + buckle.Delay = TimeSpan.Zero; +#pragma warning restore RA0002 + + // Buckle victim to chair + Assert.That(buckleSystem.TryBuckle(victim, user, chair, buckle)); + Assert.Multiple(() => + { + Assert.That(buckle.BuckledTo, Is.EqualTo(chair), "Victim did not get buckled to the chair."); + Assert.That(buckle.Buckled, "Victim is not buckled."); + Assert.That(strap.BuckledEntities, Does.Contain(victim), "Chair does not have victim buckled to it."); + }); + + // InteractHand with chair to unbuckle victim + entMan.EventBus.RaiseLocalEvent(chair, new InteractHandEvent(user, chair)); + Assert.Multiple(() => + { + Assert.That(buckle.BuckledTo, Is.Null); + Assert.That(buckle.Buckled, Is.False); + Assert.That(strap.BuckledEntities, Does.Not.Contain(victim)); + }); + }); + + await pair.CleanReturnAsync(); + } + + [Test] + public async Task BuckleInteractBuckleUnbuckleSelf() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var entMan = server.ResolveDependency(); + + EntityUid user = default; + EntityUid chair = default; + BuckleComponent buckle = null; + StrapComponent strap = null; + + await server.WaitAssertion(() => + { + user = entMan.SpawnEntity(BuckleDummyId, MapCoordinates.Nullspace); + chair = entMan.SpawnEntity(StrapDummyId, MapCoordinates.Nullspace); + + Assert.That(entMan.TryGetComponent(user, out buckle)); + Assert.That(entMan.TryGetComponent(chair, out strap)); + +#pragma warning disable RA0002 + buckle.Delay = TimeSpan.Zero; +#pragma warning restore RA0002 + + // Buckle user to chair + entMan.EventBus.RaiseLocalEvent(chair, new InteractHandEvent(user, chair)); + Assert.Multiple(() => + { + Assert.That(buckle.BuckledTo, Is.EqualTo(chair), "Victim did not get buckled to the chair."); + Assert.That(buckle.Buckled, "Victim is not buckled."); + Assert.That(strap.BuckledEntities, Does.Contain(user), "Chair does not have victim buckled to it."); + }); + + // InteractHand with chair to unbuckle + entMan.EventBus.RaiseLocalEvent(chair, new InteractHandEvent(user, chair)); + Assert.Multiple(() => + { + Assert.That(buckle.BuckledTo, Is.Null); + Assert.That(buckle.Buckled, Is.False); + Assert.That(strap.BuckledEntities, Does.Not.Contain(user)); + }); + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index 156f42aac3..1b31fe38c2 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -15,7 +15,7 @@ namespace Content.IntegrationTests.Tests.Buckle [TestFixture] [TestOf(typeof(BuckleComponent))] [TestOf(typeof(StrapComponent))] - public sealed class BuckleTest + public sealed partial class BuckleTest { private const string BuckleDummyId = "BuckleDummy"; private const string StrapDummyId = "StrapDummy"; diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs b/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs index 7677e800fe..1a15e52a3c 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs @@ -1,5 +1,5 @@ +using System.Linq; using Content.Shared.Buckle.Components; -using Content.Shared.Cuffs.Components; using Content.Shared.DoAfter; using Content.Shared.DragDrop; using Content.Shared.IdentityManagement; @@ -84,15 +84,29 @@ public abstract partial class SharedBuckleSystem if (!TryComp(args.User, out BuckleComponent? buckle)) return; - if (buckle.BuckledTo == null && component.BuckleOnInteractHand) + // Buckle self + if (buckle.BuckledTo == null && component.BuckleOnInteractHand && StrapHasSpace(uid, buckle, component)) + { TryBuckle(args.User, args.User, uid, buckle, popup: true); - else if (buckle.BuckledTo == uid) - TryUnbuckle(args.User, args.User, buckle, popup: true); - else + args.Handled = true; return; + } + + // Unbuckle self + if (buckle.BuckledTo == uid && TryUnbuckle(args.User, args.User, buckle, popup: true)) + { + args.Handled = true; + return; + } + + // Unbuckle others + if (component.BuckledEntities.TryFirstOrNull(out var buckled) && TryUnbuckle(buckled.Value, args.User)) + { + args.Handled = true; + return; + } // TODO BUCKLE add out bool for whether a pop-up was generated or not. - args.Handled = true; } private void OnBuckleInteractHand(Entity ent, ref InteractHandEvent args) From 7ceb2d250799474c6c627121a0cfde7bf90379d4 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:55:53 -0700 Subject: [PATCH 33/56] Add a method to get the first available ItemSlot (#29846) * Add a method to get the first available ItemSlot * Make TryInsertEmpty use TryGetAvailableSlot * Add param doc comments --- .../Containers/ItemSlot/ItemSlotsSystem.cs | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index f41fa2b22d..f25273f403 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -359,39 +359,76 @@ namespace Content.Shared.Containers.ItemSlots /// Useful for predicted interactions /// /// False if failed to insert item - public bool TryInsertEmpty(Entity ent, EntityUid item, EntityUid? user, bool excludeUserAudio = false) + public bool TryInsertEmpty(Entity ent, + EntityUid item, + EntityUid? user, + bool excludeUserAudio = false) { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + TryComp(user, out HandsComponent? handsComp); + + if (!TryGetAvailableSlot(ent, + item, + user == null ? null : (user.Value, handsComp), + out var itemSlot, + emptyOnly: true)) + return false; + + if (user != null && !_handsSystem.TryDrop(user.Value, item, handsComp: handsComp)) + return false; + + Insert(ent, itemSlot, item, user, excludeUserAudio: excludeUserAudio); + return true; + } + + /// + /// Tries to get any slot that the can be inserted into. + /// + /// Entity that is being inserted into. + /// Entity being inserted into . + /// Entity inserting into . + /// The ItemSlot on to insert into. + /// True only returns slots that are empty. + /// False returns any slot that is able to receive . + /// True when a slot is found. Otherwise, false. + public bool TryGetAvailableSlot(Entity ent, + EntityUid item, + Entity? userEnt, + [NotNullWhen(true)] out ItemSlot? itemSlot, + bool emptyOnly = false) + { + itemSlot = null; + + if (userEnt is { } user + && Resolve(user, ref user.Comp) + && _handsSystem.IsHolding(user, item)) + { + if (!_handsSystem.CanDrop(user, item, user.Comp)) + return 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) + if (emptyOnly && slot.ContainerSlot?.ContainedEntity != null) continue; - if (CanInsert(ent, item, user, slot)) + if (CanInsert(ent, item, userEnt, 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; + itemSlot = slots[0]; + return true; } private static int SortEmpty(ItemSlot a, ItemSlot b) From e6e166405d1008db374363d41d78459330d9f19f Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 18 Sep 2024 23:56:32 +0000 Subject: [PATCH 34/56] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 58776aa93c..8190ecd8fe 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Lokachop - changes: - - message: Scarves now count as warm clothing for the warm clothing cargo bounty. - type: Tweak - id: 6894 - time: '2024-07-10T05:26:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29779 - author: Aquif changes: - message: It is now possible to "lock" admin faxes such that they cannot be edited @@ -3917,3 +3910,11 @@ id: 7393 time: '2024-09-18T23:00:48.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32076 +- author: ShadowCommander + changes: + - message: Fixed clicking on chairs and beds with an entity buckled to them not + unbuckling them. + type: Fix + id: 7394 + time: '2024-09-18T23:55:26.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29998 From 84062da1289b760ee8714239322ae07b74e6187c Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:58:10 +0000 Subject: [PATCH 35/56] let FlashArea be called from shared (#30343) * let FlashArea be called from shared * untroll --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: metalgearsloth --- Content.Client/Flash/FlashOverlay.cs | 4 +++- Content.Server/Flash/FlashSystem.cs | 2 +- Content.Shared/Flash/SharedFlashSystem.cs | 13 +++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Content.Client/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs index 046be2aa62..8e41c382dd 100644 --- a/Content.Client/Flash/FlashOverlay.cs +++ b/Content.Client/Flash/FlashOverlay.cs @@ -16,6 +16,7 @@ namespace Content.Client.Flash [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IGameTiming _timing = default!; + private readonly SharedFlashSystem _flash; private readonly StatusEffectsSystem _statusSys; public override OverlaySpace Space => OverlaySpace.WorldSpace; @@ -27,6 +28,7 @@ namespace Content.Client.Flash { IoCManager.InjectDependencies(this); _shader = _prototypeManager.Index("FlashedEffect").InstanceUnique(); + _flash = _entityManager.System(); _statusSys = _entityManager.System(); } @@ -41,7 +43,7 @@ namespace Content.Client.Flash || !_entityManager.TryGetComponent(playerEntity, out var status)) return; - if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status)) + if (!_statusSys.TryGetTime(playerEntity.Value, _flash.FlashedKey, out var time, status)) return; var curTime = _timing.CurTime; diff --git a/Content.Server/Flash/FlashSystem.cs b/Content.Server/Flash/FlashSystem.cs index ccb58e94f8..fb449a372c 100644 --- a/Content.Server/Flash/FlashSystem.cs +++ b/Content.Server/Flash/FlashSystem.cs @@ -152,7 +152,7 @@ namespace Content.Server.Flash } } - public void FlashArea(Entity source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null) + public override void FlashArea(Entity source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null) { var transform = Transform(source); var mapPosition = _transform.GetMapCoordinates(transform); diff --git a/Content.Shared/Flash/SharedFlashSystem.cs b/Content.Shared/Flash/SharedFlashSystem.cs index f83f02a310..b778809887 100644 --- a/Content.Shared/Flash/SharedFlashSystem.cs +++ b/Content.Shared/Flash/SharedFlashSystem.cs @@ -1,10 +1,15 @@ +using Content.Shared.Flash.Components; using Content.Shared.StatusEffect; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; -namespace Content.Shared.Flash +namespace Content.Shared.Flash; + +public abstract class SharedFlashSystem : EntitySystem { - public abstract class SharedFlashSystem : EntitySystem + public ProtoId FlashedKey = "Flashed"; + + public virtual void FlashArea(Entity source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null) { - [ValidatePrototypeId] - public const string FlashedKey = "Flashed"; } } From 1c839da60496f4a3171c7f959a88effe4aeff5a8 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Thu, 19 Sep 2024 00:01:40 +0000 Subject: [PATCH 36/56] move TriggerExplosion to shared (#30227) * move component to shared * add fake systems * update server explosion system and remove duplicate transform query --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- Content.Client/Explosion/ExplosionSystem.cs | 5 +-- .../Behaviors/SolutionExplosionBehavior.cs | 2 +- .../EntitySystems/ExplosionSystem.Airtight.cs | 3 +- .../EntitySystems/ExplosionSystem.CVars.cs | 4 +-- .../EntitySystems/ExplosionSystem.GridMap.cs | 2 +- .../ExplosionSystem.Processing.cs | 7 ++-- .../EntitySystems/ExplosionSystem.TileFill.cs | 4 +-- .../EntitySystems/ExplosionSystem.Visuals.cs | 3 +- .../EntitySystems/ExplosionSystem.cs | 15 +++----- .../Components/ExplosiveComponent.cs | 35 +++++++------------ .../EntitySystems/SharedExplosionSystem.cs | 25 ++++++++++--- 11 files changed, 52 insertions(+), 53 deletions(-) rename {Content.Server => Content.Shared}/Explosion/Components/ExplosiveComponent.cs (76%) diff --git a/Content.Client/Explosion/ExplosionSystem.cs b/Content.Client/Explosion/ExplosionSystem.cs index a2ed2d50e0..692782ded4 100644 --- a/Content.Client/Explosion/ExplosionSystem.cs +++ b/Content.Client/Explosion/ExplosionSystem.cs @@ -2,7 +2,4 @@ using Content.Shared.Explosion.EntitySystems; namespace Content.Client.Explosion.EntitySystems; -public sealed class ExplosionSystem : SharedExplosionSystem -{ - -} +public sealed class ExplosionSystem : SharedExplosionSystem; diff --git a/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs index 5166aaccab..08c7c8f068 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs @@ -1,4 +1,4 @@ -using Content.Server.Explosion.Components; +using Content.Shared.Explosion.Components; using JetBrains.Annotations; namespace Content.Server.Destructible.Thresholds.Behaviors diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Airtight.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Airtight.cs index 4b59c8f1c4..6fa553bc8b 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Airtight.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Airtight.cs @@ -6,9 +6,10 @@ using Content.Shared.Explosion; using Content.Shared.Explosion.EntitySystems; using Content.Shared.FixedPoint; using Robust.Shared.Map.Components; + namespace Content.Server.Explosion.EntitySystems; -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { [Dependency] private readonly DestructibleSystem _destructibleSystem = default!; diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs index ce98f89de7..5af06ef936 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs @@ -1,8 +1,8 @@ using Content.Shared.CCVar; -using Content.Shared.Explosion.EntitySystems; + namespace Content.Server.Explosion.EntitySystems; -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { public int MaxIterations { get; private set; } public int MaxArea { get; private set; } diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs index 75bb606441..29477c16b2 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs @@ -12,7 +12,7 @@ namespace Content.Server.Explosion.EntitySystems; // A good portion of it is focused around keeping track of what tile-indices on a grid correspond to tiles that border // space. AFAIK no other system currently needs to track these "edge-tiles". If they do, this should probably be a // property of the grid itself? -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { /// /// Set of tiles of each grid that are directly adjacent to space, along with the directions that face space. diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index 6d0cbcf279..97d52e436a 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -22,9 +22,10 @@ using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.Shared.Utility; using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent; + namespace Content.Server.Explosion.EntitySystems; -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { [Dependency] private readonly FlammableSystem _flammableSystem = default!; @@ -218,7 +219,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem // get the entities on a tile. Note that we cannot process them directly, or we get // enumerator-changed-while-enumerating errors. List<(EntityUid, TransformComponent)> list = new(); - var state = (list, processed, _transformQuery); + var state = (list, processed, EntityManager.TransformQuery); // get entities: lookup.DynamicTree.QueryAabb(ref state, GridQueryCallback, gridBox, true); @@ -317,7 +318,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem var gridBox = Box2.FromDimensions(tile * DefaultTileSize, new Vector2(DefaultTileSize, DefaultTileSize)); var worldBox = spaceMatrix.TransformBox(gridBox); var list = new List<(EntityUid, TransformComponent)>(); - var state = (list, processed, invSpaceMatrix, lookup.Owner, _transformQuery, gridBox, _transformSystem); + var state = (list, processed, invSpaceMatrix, lookup.Owner, EntityManager.TransformQuery, gridBox, _transformSystem); // get entities: lookup.DynamicTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs index 8c3229e06e..7b73490d94 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs @@ -7,13 +7,13 @@ using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; -using Content.Shared.Explosion.EntitySystems; + namespace Content.Server.Explosion.EntitySystems; // This partial part of the explosion system has all of the functions used to create the actual explosion map. // I.e, to get the sets of tiles & intensity values that describe an explosion. -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { /// /// This is the main explosion generating function. diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs index 219dba4bde..57323e4de7 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs @@ -5,10 +5,11 @@ using Content.Shared.Explosion.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Map; + namespace Content.Server.Explosion.EntitySystems; // This part of the system handled send visual / overlay data to clients. -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { public void InitVisuals() { diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index cd0ca1c22e..818953ed4b 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -12,6 +12,8 @@ using Content.Shared.CCVar; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Explosion; +using Content.Shared.Explosion.Components; +using Content.Shared.Explosion.EntitySystems; using Content.Shared.GameTicking; using Content.Shared.Inventory; using Content.Shared.Projectiles; @@ -53,7 +55,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedMapSystem _map = default!; - private EntityQuery _transformQuery; private EntityQuery _flammableQuery; private EntityQuery _physicsQuery; private EntityQuery _projectileQuery; @@ -103,7 +104,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem InitAirtightMap(); InitVisuals(); - _transformQuery = GetEntityQuery(); _flammableQuery = GetEntityQuery(); _physicsQuery = GetEntityQuery(); _projectileQuery = GetEntityQuery(); @@ -141,15 +141,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem args.DamageCoefficient *= modifier; } - /// - /// Given an entity with an explosive component, spawn the appropriate explosion. - /// - /// - /// Also accepts radius or intensity arguments. This is useful for explosives where the intensity is not - /// specified in the yaml / by the component, but determined dynamically (e.g., by the quantity of a - /// solution in a reaction). - /// - public void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null) + /// + public override void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null) { // log missing: false, because some entities (e.g. liquid tanks) attempt to trigger explosions when damaged, // but may not actually be explosive. diff --git a/Content.Server/Explosion/Components/ExplosiveComponent.cs b/Content.Shared/Explosion/Components/ExplosiveComponent.cs similarity index 76% rename from Content.Server/Explosion/Components/ExplosiveComponent.cs rename to Content.Shared/Explosion/Components/ExplosiveComponent.cs index 2b27a89d9d..bab7f5a7d6 100644 --- a/Content.Server/Explosion/Components/ExplosiveComponent.cs +++ b/Content.Shared/Explosion/Components/ExplosiveComponent.cs @@ -1,8 +1,7 @@ -using Content.Server.Explosion.EntitySystems; -using Content.Shared.Explosion; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Content.Shared.Explosion.EntitySystems; +using Robust.Shared.Prototypes; -namespace Content.Server.Explosion.Components; +namespace Content.Shared.Explosion.Components; /// /// Specifies an explosion that can be spawned by this entity. The explosion itself is spawned via -[RegisterComponent] +[RegisterComponent, Access(typeof(SharedExplosionSystem))] public sealed partial class ExplosiveComponent : Component { - /// /// The explosion prototype. This determines the damage types, the tile-break chance, and some visual /// information (e.g., the light that the explosion gives off). /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("explosionType", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ExplosionType = default!; + [DataField(required: true)] + public ProtoId ExplosionType = default!; /// /// The maximum intensity the explosion can have on a single tile. This limits the maximum damage and tile /// break chance the explosion can achieve at any given location. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxIntensity")] + [DataField] public float MaxIntensity = 4; /// /// How quickly the intensity drops off as you move away from the epicenter. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("intensitySlope")] + [DataField] public float IntensitySlope = 1; /// @@ -47,38 +42,34 @@ public sealed partial class ExplosiveComponent : Component /// This number can be overridden by passing optional argument to . /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("totalIntensity")] + [DataField] public float TotalIntensity = 10; /// /// Factor used to scale the explosion intensity when calculating tile break chances. Allows for stronger /// explosives that don't space tiles, without having to create a new explosion-type prototype. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("tileBreakScale")] + [DataField] public float TileBreakScale = 1f; /// /// Maximum number of times that an explosive can break a tile. Currently, for normal space stations breaking a /// tile twice will generally result in a vacuum. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxTileBreak")] + [DataField] public int MaxTileBreak = int.MaxValue; /// /// Whether this explosive should be able to create a vacuum by breaking tiles. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("canCreateVacuum")] + [DataField] public bool CanCreateVacuum = true; /// /// An override for whether or not the entity should be deleted after it explodes. /// If null, the system calling the explode method handles it. /// - [DataField("deleteAfterExplosion")] + [DataField] public bool? DeleteAfterExplosion; /// diff --git a/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs index 1d926dd0b6..f298255807 100644 --- a/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs +++ b/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs @@ -1,25 +1,40 @@ -using Content.Shared.Explosion.Components; using Content.Shared.Armor; +using Content.Shared.Explosion.Components; namespace Content.Shared.Explosion.EntitySystems; +/// +/// Lets code in shared trigger explosions and handles explosion resistance examining. +/// All processing is still done clientside. +/// public abstract class SharedExplosionSystem : EntitySystem { - public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnArmorExamine); } - private void OnArmorExamine(EntityUid uid, ExplosionResistanceComponent component, ref ArmorExamineEvent args) + private void OnArmorExamine(Entity ent, ref ArmorExamineEvent args) { - var value = MathF.Round((1f - component.DamageCoefficient) * 100, 1); + var value = MathF.Round((1f - ent.Comp.DamageCoefficient) * 100, 1); if (value == 0) return; args.Msg.PushNewline(); - args.Msg.AddMarkupOrThrow(Loc.GetString(component.Examine, ("value", value))); + args.Msg.AddMarkupOrThrow(Loc.GetString(ent.Comp.Examine, ("value", value))); + } + + /// + /// Given an entity with an explosive component, spawn the appropriate explosion. + /// + /// + /// Also accepts radius or intensity arguments. This is useful for explosives where the intensity is not + /// specified in the yaml / by the component, but determined dynamically (e.g., by the quantity of a + /// solution in a reaction). + /// + public virtual void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null) + { } } From 4f77709eed9b5012e58303820eafb0242a0f36fd Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:02:27 +1000 Subject: [PATCH 37/56] Add flip button to mapping state (#30636) --- Content.Client/Mapping/MappingScreen.xaml | 1 + Content.Client/Mapping/MappingScreen.xaml.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/Content.Client/Mapping/MappingScreen.xaml b/Content.Client/Mapping/MappingScreen.xaml index b641360847..9cc3e734f0 100644 --- a/Content.Client/Mapping/MappingScreen.xaml +++ b/Content.Client/Mapping/MappingScreen.xaml @@ -78,6 +78,7 @@ ToolTip="Pick (Hold 5)" /> + diff --git a/Content.Client/Mapping/MappingScreen.xaml.cs b/Content.Client/Mapping/MappingScreen.xaml.cs index b2ad2fd83f..46c0e51fad 100644 --- a/Content.Client/Mapping/MappingScreen.xaml.cs +++ b/Content.Client/Mapping/MappingScreen.xaml.cs @@ -96,6 +96,22 @@ public sealed partial class MappingScreen : InGameScreen Pick.Texture.TexturePath = "/Textures/Interface/eyedropper.svg.png"; Delete.Texture.TexturePath = "/Textures/Interface/eraser.svg.png"; + Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png"; + Flip.OnPressed += args => FlipSides(); + } + + public void FlipSides() + { + ScreenContainer.Flip(); + + if (SpawnContainer.GetPositionInParent() == 0) + { + Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png"; + } + else + { + Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png"; + } } private void OnDecalColorPicked(Color color) From 6958789f37db66d54a8c68f6d04d997c8f1650ab Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:08:37 -0700 Subject: [PATCH 38/56] Give prototype refactor (#29697) * Update GivePrototype * File scoped namespace * Change to EntProtoId instead of ProtoId<> for better validation --- .../Construction/Completions/GivePrototype.cs | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/Content.Server/Construction/Completions/GivePrototype.cs b/Content.Server/Construction/Completions/GivePrototype.cs index f45bd5ba10..f05feb70c0 100644 --- a/Content.Server/Construction/Completions/GivePrototype.cs +++ b/Content.Server/Construction/Completions/GivePrototype.cs @@ -1,44 +1,50 @@ using Content.Server.Stack; using Content.Shared.Construction; +using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Prototypes; using Content.Shared.Stacks; using JetBrains.Annotations; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Construction.Completions +namespace Content.Server.Construction.Completions; + +[UsedImplicitly] +[DataDefinition] +public sealed partial class GivePrototype : IGraphAction { - [UsedImplicitly] - [DataDefinition] - public sealed partial class GivePrototype : IGraphAction - { - [DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Prototype { get; private set; } = string.Empty; - [DataField("amount")] - public int Amount { get; private set; } = 1; + [DataField] + public EntProtoId Prototype { get; private set; } = string.Empty; - public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager) + [DataField] + public int Amount { get; private set; } = 1; + + public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager) + { + if (string.IsNullOrEmpty(Prototype)) + return; + + if (EntityPrototypeHelpers.HasComponent(Prototype)) { - if (string.IsNullOrEmpty(Prototype)) + var stackSystem = entityManager.EntitySysManager.GetEntitySystem(); + var stacks = stackSystem.SpawnMultiple(Prototype, Amount, userUid ?? uid); + + if (userUid is null || !entityManager.TryGetComponent(userUid, out HandsComponent? handsComp)) return; - var coordinates = entityManager.GetComponent(userUid ?? uid).Coordinates; - - if (EntityPrototypeHelpers.HasComponent(Prototype)) + foreach (var item in stacks) { - var stackEnt = entityManager.SpawnEntity(Prototype, coordinates); - var stack = entityManager.GetComponent(stackEnt); - entityManager.EntitySysManager.GetEntitySystem().SetCount(stackEnt, Amount, stack); - entityManager.EntitySysManager.GetEntitySystem().PickupOrDrop(userUid, stackEnt); + stackSystem.TryMergeToHands(item, userUid.Value, hands: handsComp); } - else + } + else + { + var handsSystem = entityManager.EntitySysManager.GetEntitySystem(); + var handsComp = userUid is not null ? entityManager.GetComponent(userUid.Value) : null; + for (var i = 0; i < Amount; i++) { - for (var i = 0; i < Amount; i++) - { - var item = entityManager.SpawnEntity(Prototype, coordinates); - entityManager.EntitySysManager.GetEntitySystem().PickupOrDrop(userUid, item); - } + var item = entityManager.SpawnNextToOrDrop(Prototype, userUid ?? uid); + handsSystem.PickupOrDrop(userUid, item, handsComp: handsComp); } } } From cc7e5e01508b6ac79d6e85b18ec4ec7bc839ec71 Mon Sep 17 00:00:00 2001 From: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Date: Thu, 19 Sep 2024 02:14:29 +0200 Subject: [PATCH 39/56] Allow containment field generators to be enabled on mapinit (#31158) * Initial commit * review fix --- .../EntitySystems/ContainmentFieldGeneratorSystem.cs | 7 +++++++ .../Components/ContainmentFieldGeneratorComponent.cs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs b/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs index 05262f2999..6d97c8ccb3 100644 --- a/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs @@ -37,6 +37,7 @@ public sealed class ContainmentFieldGeneratorSystem : EntitySystem SubscribeLocalEvent(OnUnanchorAttempt); SubscribeLocalEvent(OnComponentRemoved); SubscribeLocalEvent(PreventBreach); + SubscribeLocalEvent(OnMapInit); } public override void Update(float frameTime) @@ -61,6 +62,12 @@ public sealed class ContainmentFieldGeneratorSystem : EntitySystem #region Events + private void OnMapInit(Entity generator, ref MapInitEvent args) + { + if (generator.Comp.Enabled) + ChangeFieldVisualizer(generator); + } + /// /// A generator receives power from a source colliding with it. /// diff --git a/Content.Shared/Singularity/Components/ContainmentFieldGeneratorComponent.cs b/Content.Shared/Singularity/Components/ContainmentFieldGeneratorComponent.cs index 938b34f354..6b09edfa1f 100644 --- a/Content.Shared/Singularity/Components/ContainmentFieldGeneratorComponent.cs +++ b/Content.Shared/Singularity/Components/ContainmentFieldGeneratorComponent.cs @@ -79,7 +79,7 @@ public sealed partial class ContainmentFieldGeneratorComponent : Component /// /// Is the generator toggled on? /// - [ViewVariables] + [DataField] public bool Enabled; /// From c2a201d998830b36fa525c5ee9bb1375bf41d732 Mon Sep 17 00:00:00 2001 From: Winkarst <74284083+Winkarst-cpu@users.noreply.github.com> Date: Thu, 19 Sep 2024 03:23:50 +0300 Subject: [PATCH 40/56] Make fire leave burnt decals on the tiles (#31939) * Make fire leave burnt decals on the tiles * License * Yes * Update * Spelling error * Prototypes reload support * To array --- .../EntitySystems/AtmosphereSystem.Hotspot.cs | 33 +++++++++++++++--- .../Atmos/EntitySystems/AtmosphereSystem.cs | 19 ++++++++++ Resources/Prototypes/Decals/burnt.yml | 31 ++++++++++++++++ .../Textures/Decals/burnt.rsi/burnt1.png | Bin 0 -> 896 bytes .../Textures/Decals/burnt.rsi/burnt2.png | Bin 0 -> 938 bytes .../Textures/Decals/burnt.rsi/burnt3.png | Bin 0 -> 928 bytes .../Textures/Decals/burnt.rsi/burnt4.png | Bin 0 -> 1041 bytes Resources/Textures/Decals/burnt.rsi/meta.json | 27 ++++++++++++++ 8 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 Resources/Prototypes/Decals/burnt.yml create mode 100644 Resources/Textures/Decals/burnt.rsi/burnt1.png create mode 100644 Resources/Textures/Decals/burnt.rsi/burnt2.png create mode 100644 Resources/Textures/Decals/burnt.rsi/burnt3.png create mode 100644 Resources/Textures/Decals/burnt.rsi/burnt4.png create mode 100644 Resources/Textures/Decals/burnt.rsi/meta.json diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs index db95223733..a03f27b561 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs @@ -1,19 +1,21 @@ using Content.Server.Atmos.Components; -using Content.Server.Atmos.Reactions; +using Content.Server.Decals; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Reactions; -using Content.Shared.Audio; using Content.Shared.Database; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Map.Components; -using Robust.Shared.Player; +using Robust.Shared.Random; namespace Content.Server.Atmos.EntitySystems { public sealed partial class AtmosphereSystem { + [Dependency] private readonly DecalSystem _decalSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + private const int HotspotSoundCooldownCycles = 200; private int _hotspotSoundCooldown = 0; @@ -56,7 +58,30 @@ namespace Content.Server.Atmos.EntitySystems if (tile.Hotspot.Bypassing) { tile.Hotspot.State = 3; - // TODO ATMOS: Burn tile here + + var gridUid = ent.Owner; + var tilePos = tile.GridIndices; + + // Get the existing decals on the tile + var tileDecals = _decalSystem.GetDecalsInRange(gridUid, tilePos); + + // Count the burnt decals on the tile + var tileBurntDecals = 0; + + foreach (var set in tileDecals) + { + if (Array.IndexOf(_burntDecals, set.Decal.Id) == -1) + continue; + + tileBurntDecals++; + + if (tileBurntDecals > 4) + break; + } + + // Add a random burned decal to the tile only if there are less than 4 of them + if (tileBurntDecals < 4) + _decalSystem.TryAddDecal(_burntDecals[_random.Next(_burntDecals.Length)], new EntityCoordinates(gridUid, tilePos), out _, cleanable: true); if (tile.Air.Temperature > Atmospherics.FireMinimumTemperatureToSpread) { diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index 44bfa4cc10..13d8f73dc5 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Body.Systems; using Content.Server.Fluids.EntitySystems; using Content.Server.NodeContainer.EntitySystems; using Content.Shared.Atmos.EntitySystems; +using Content.Shared.Decals; using Content.Shared.Doors.Components; using Content.Shared.Maps; using JetBrains.Annotations; @@ -12,7 +13,9 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Physics.Systems; +using Robust.Shared.Prototypes; using Robust.Shared.Random; +using System.Linq; namespace Content.Server.Atmos.EntitySystems; @@ -36,6 +39,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly TileSystem _tile = default!; [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] public readonly PuddleSystem Puddle = default!; private const float ExposedUpdateDelay = 1f; @@ -47,6 +51,8 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem private EntityQuery _firelockQuery; private HashSet _entSet = new(); + private string[] _burntDecals = []; + public override void Initialize() { base.Initialize(); @@ -66,7 +72,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem _firelockQuery = GetEntityQuery(); SubscribeLocalEvent(OnTileChanged); + SubscribeLocalEvent(OnPrototypesReloaded); + CacheDecals(); } public override void Shutdown() @@ -81,6 +89,12 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem InvalidateTile(ev.NewTile.GridUid, ev.NewTile.GridIndices); } + private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev) + { + if (ev.WasModified()) + CacheDecals(); + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -107,4 +121,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem _exposedTimer -= ExposedUpdateDelay; } + + private void CacheDecals() + { + _burntDecals = _prototypeManager.EnumeratePrototypes().Where(x => x.Tags.Contains("burnt")).Select(x => x.ID).ToArray(); + } } diff --git a/Resources/Prototypes/Decals/burnt.yml b/Resources/Prototypes/Decals/burnt.yml new file mode 100644 index 0000000000..d9d500e1aa --- /dev/null +++ b/Resources/Prototypes/Decals/burnt.yml @@ -0,0 +1,31 @@ +- type: decal + id: burnt1 + tags: ["burnt"] + defaultCleanable: true + sprite: + sprite: Decals/burnt.rsi + state: burnt1 + +- type: decal + id: burnt2 + tags: ["burnt"] + defaultCleanable: true + sprite: + sprite: Decals/burnt.rsi + state: burnt2 + +- type: decal + id: burnt3 + tags: ["burnt"] + defaultCleanable: true + sprite: + sprite: Decals/burnt.rsi + state: burnt3 + +- type: decal + id: burnt4 + tags: ["burnt"] + defaultCleanable: true + sprite: + sprite: Decals/burnt.rsi + state: burnt4 diff --git a/Resources/Textures/Decals/burnt.rsi/burnt1.png b/Resources/Textures/Decals/burnt.rsi/burnt1.png new file mode 100644 index 0000000000000000000000000000000000000000..3fcb7a4949b5bd561e68d784d47d9de20a5411d2 GIT binary patch literal 896 zcmV-`1AqL9P)_`sikSPhHe~3@fwDqi`!27YaPdN z#XAQ{Mbb&qIqVecW=VHRx3Ec4r4L^n0fb{a_q`;oVHMzdNk>T+Sc&%@lCF||0?2Ft z@TYMc*YYNKA9lck1x~sM{t=mf#{D6J5x9NYmwnV>7*yT_FA<=R=W95g?Z)O zXSr#2Y=Ql?;o%@8o6DVb-DIUOt;rA7Zqkdr_f#YackO{-WHbkhbz#xIZKXZvnvEm5 zqy=1*J0rIo$w#m!1hcO>t!)7E3@{KcA~AFJ1mfTv&z_QJ?G^KYLr)tTSp~V56pmai zijU>lBbsI)jXMU>N%|82%xnYVQR|G~sBqFV0d4O6tE9hS-})Wbk4UT*l<1YPa0dME z%4dp2&wEZnHs9=N?ya7z8ydklV$1s3jzCLe6r|b5_i)Oi)aKYqGeBI^ZuBez`a(2a-$50Ok=tVpxs0ZTk;p WW37K`!OrIZ0000C literal 0 HcmV?d00001 diff --git a/Resources/Textures/Decals/burnt.rsi/burnt2.png b/Resources/Textures/Decals/burnt.rsi/burnt2.png new file mode 100644 index 0000000000000000000000000000000000000000..01f8f220b2ddae1a13fac2b816531109a5b848d9 GIT binary patch literal 938 zcmV;b16BNqP)18A2vnpw(ar z1~qsht;3vCD6w`Ao}uGYnUsA1aPJrnOd)6t3!DMiI$6ER(;`s9#u7N6pd4cTwdf`G z2_S*!hJ`)wbZg_0F*v6MHg>;GCHx+Tzwt=JH}-T(U;23XN71bm6J#p9IFTkcm5gqK z^tAwduWz%!Cu7Bw`%{PCis#BPcvk^T9Mi6`N4!4&RvUe%3k*DgXzsb>EVX7P2z~>G z2Krrr1)9=8G5B6W`vj{g4YsN1zcPIW zV5X|>9a}N0^|cQ3pfrEhjEwHJG@W?V`+P6qA}w&JCyhP*2=lg}bc01jVF^I8;Vi)i z$25J`1XEDr2dgllPFL=UA+3cqy1hLF*B(=5xn)$H0500m$U z0e2oTkWELyx%aGmdnf@ivuFkGjjv`jofKps$SYI+o{QLmWYm8K086<%#GyXvdo6YO3$m!Aax?qf)&Kwi M07*qoM6N<$f@T`Jp8x;= literal 0 HcmV?d00001 diff --git a/Resources/Textures/Decals/burnt.rsi/burnt3.png b/Resources/Textures/Decals/burnt.rsi/burnt3.png new file mode 100644 index 0000000000000000000000000000000000000000..e9dcbe37533f6e1aa9f17dca58d5c2b15973408a GIT binary patch literal 928 zcmV;R17G}!P)a;W5Jit{9MAGY^Z#EoHA`ZHfq9@0+$c_ss#F3Yb>H5b2K)d9;0(;b2;6I(YfiwU zR-b2J0b1Sv3Va8?0W0tZ+=|v(^sm5|IuEc8z_0pnEn*Eg)rHngikQ=J>b^JNQUKjM z124sV@V=U@}kW6B%Z?YOXZZ67&`UX#a^V zgRKSR6c-ac9NCPJ)+sP}?|AZ=OUQfLXypjSnJtYb^FAr12DEu*x(wYpR<-;Fy!M$X z52e%uQ@veg?pTLhLe{bD$dvbOccIXcG>9sFVDbZAW`p^r92HlsY)$oI$1Dm>^mDco)UP^r= zL+Z-M(9I&I26LR!R#}p>toG$s2_8XIFDGbZO=)!|&3&SZk+GSTFg37rL_af6-vjic z595{^=*`gg1HNabOX;%Sr-W)pb9q;Pne_ox{7lkJJQ?RL?wAwc2x0GCII8SaXXUwA zh|&jo5Hh~96&b6gen5aShX_!KrDrZs^V{%1UMzI3){{b2u`{;`kj?~;+p#!H6W9Q($&N#+@YmG_~VwO}MVq9HEt4ibR! zhh*Y?R!gSl{1)dNci=DZ8~9Uv+L^73v7BmCYqohITkDjrUwzIW5+(lw{H*!6G_W@$ zHWYejpJ=k8`QHCOJc`y>tLoMXe;4Cdwq9TXUb2K5Mw&zu=pPS`GTPfadjU^`5@AN8LXHrEQ@sW!o?S0000>5u66hM z4m^NiHO3eX*Z_Or0K5TPU{`Y+mg`cwewV(X0aFctb1gUnw^Fnz1$haG6`-ls3TRhj zTWcO=&?oQ*m|9r)0Oqp#TJM`0+fp{iP&x4|kXCy(F~*WRg|4yHm7O$Z3@hZC3%Q7b1Bh-dPs1$Qf@v`@_y9w&{#5aS4fRjqi1w0A)j?W*E(IU2gdNo z9N$d7p$suMngBj(^BcWn-b(pC1mEZxK88gPUSprtVEGXMrT{QD;1|8_{{_AmfH!*V z9dig8R{->c-pl3Y{#o_0o=P(3&A0uY&rB&Y*RmE;*Pk8zpt(CK!dC-Q9t-uZ#*1$#Q# zs9XRl_pag=_br{6ZoGQw63>Rr!|sEk;zL-tKE)=HtaD)P_Uwv=$Pjo*@23L$0)Vhy z04mMrG43W-@<$#%bt*m^j@A9H?@Bob9$)( zcjIm5%Yuhml7uSvU*J%ik1Ql~q4#|OWM7xEbH9mg zc=V!P9)U|X7FkdrQz<{vf5mjU9`M~HvpasHv#E72-S5h)6L9_u9x#X3!t};m00000 LNkvXXu0mjfo00E? literal 0 HcmV?d00001 diff --git a/Resources/Textures/Decals/burnt.rsi/meta.json b/Resources/Textures/Decals/burnt.rsi/meta.json new file mode 100644 index 0000000000..48a8f33138 --- /dev/null +++ b/Resources/Textures/Decals/burnt.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "From https://github.com/BeeStation/BeeStation-Hornet/blob/master/icons/turf/turf_damage.dmi at f2d6fbdf36aa0951049498cf28e028a38e32fe0b", + "states": [ + { + "name": "burnt1" + + }, + { + "name": "burnt2" + + }, + { + "name": "burnt3" + + }, + { + "name": "burnt4" + + } + ] +} From 0c5a053ae4ec06ad4471884dd81aa5053522d716 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 19 Sep 2024 00:24:56 +0000 Subject: [PATCH 41/56] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 8190ecd8fe..050ad0721c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Aquif - changes: - - message: It is now possible to "lock" admin faxes such that they cannot be edited - by cybersun pens or any other IC means. - type: Add - id: 6895 - time: '2024-07-10T05:28:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/28972 - author: Ghagliiarghii changes: - message: The Librarian's Books Bag can now hold D&D related items such as dice @@ -3918,3 +3910,10 @@ id: 7394 time: '2024-09-18T23:55:26.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29998 +- author: Winkarst-cpu + changes: + - message: Now fire leaves burn marks on the tiles that were affected by it. + type: Add + id: 7395 + time: '2024-09-19T00:23:50.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31939 From 1c3cfeeb35f425cd556103e4a673c01ae16cf69d Mon Sep 17 00:00:00 2001 From: ArchRBX <5040911+ArchRBX@users.noreply.github.com> Date: Thu, 19 Sep 2024 02:25:47 +0100 Subject: [PATCH 42/56] Coordinates under IFF Label on Mass Scanners and Shuttle Consoles (#31501) * adds coord label beneath iff label * fixed wrong coordinate system being used * changes the clamping on the label UI to instead normalise the UI's distance vector from the centre of the screen, fixes corner-hugging * cleaned up if-statement by moving the calc ahead of it * fixed clamping, fixed parenting issue, added draw cull on coord label --------- Co-authored-by: archrbx --- .../Shuttles/UI/ShuttleNavControl.xaml.cs | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs index 64ead32586..2674343e05 100644 --- a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs @@ -199,7 +199,9 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl var gridMatrix = _transform.GetWorldMatrix(gUid); var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert); - var color = _shuttles.GetIFFColor(grid, self: false, iff); + + var labelColor = _shuttles.GetIFFColor(grid, self: false, iff); + var coordColor = new Color(labelColor.R * 0.8f, labelColor.G * 0.8f, labelColor.B * 0.8f, 0.5f); // Others default: // Color.FromHex("#FFC000FF") @@ -213,25 +215,52 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty); gridCentre.Y = -gridCentre.Y; + var distance = gridCentre.Length(); var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName), ("distance", $"{distance:0.0}")); + var mapCoords = _transform.GetWorldPosition(gUid); + var coordsText = $"({mapCoords.X:0.0}, {mapCoords.Y:0.0})"; + // yes 1.0 scale is intended here. var labelDimensions = handle.GetDimensions(Font, labelText, 1f); + var coordsDimensions = handle.GetDimensions(Font, coordsText, 0.7f); // y-offset the control to always render below the grid (vertically) var yOffset = Math.Max(gridBounds.Height, gridBounds.Width) * MinimapScale / 1.8f; - // The actual position in the UI. We offset the matrix position to render it off by half its width - // plus by the offset. - var uiPosition = ScalePosition(gridCentre)- new Vector2(labelDimensions.X / 2f, -yOffset); + // The actual position in the UI. We centre the label by offsetting the matrix position + // by half the label's width, plus the y-offset + var gridScaledPosition = ScalePosition(gridCentre) - new Vector2(0, -yOffset); - // Look this is uggo so feel free to cleanup. We just need to clamp the UI position to within the viewport. - uiPosition = new Vector2(Math.Clamp(uiPosition.X, 0f, PixelWidth - labelDimensions.X ), - Math.Clamp(uiPosition.Y, 0f, PixelHeight - labelDimensions.Y)); + // Normalize the grid position if it exceeds the viewport bounds + // normalizing it instead of clamping it preserves the direction of the vector and prevents corner-hugging + var gridOffset = gridScaledPosition / PixelSize - new Vector2(0.5f, 0.5f); + var offsetMax = Math.Max(Math.Abs(gridOffset.X), Math.Abs(gridOffset.Y)) * 2f; + if (offsetMax > 1) + { + gridOffset = new Vector2(gridOffset.X / offsetMax, gridOffset.Y / offsetMax); - handle.DrawString(Font, uiPosition, labelText, color); + gridScaledPosition = (gridOffset + new Vector2(0.5f, 0.5f)) * PixelSize; + } + + var labelUiPosition = gridScaledPosition - new Vector2(labelDimensions.X / 2f, 0); + var coordUiPosition = gridScaledPosition - new Vector2(coordsDimensions.X / 2f, -labelDimensions.Y); + + // clamp the IFF label's UI position to within the viewport extents so it hugs the edges of the viewport + // coord label intentionally isn't clamped so we don't get ugly clutter at the edges + var controlExtents = PixelSize - new Vector2(labelDimensions.X, labelDimensions.Y); //new Vector2(labelDimensions.X * 2f, labelDimensions.Y); + labelUiPosition = Vector2.Clamp(labelUiPosition, Vector2.Zero, controlExtents); + + // draw IFF label + handle.DrawString(Font, labelUiPosition, labelText, labelColor); + + // only draw coords label if close enough + if (offsetMax < 1) + { + handle.DrawString(Font, coordUiPosition, coordsText, 0.7f, coordColor); + } } // Detailed view @@ -241,7 +270,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl if (!gridAABB.Intersects(viewAABB)) continue; - DrawGrid(handle, matty, grid, color); + DrawGrid(handle, matty, grid, labelColor); DrawDocks(handle, gUid, matty); } } From c2f0626e029c1f3b33aa9b7770ef7d3ee0fa2aae Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 19 Sep 2024 01:26:53 +0000 Subject: [PATCH 43/56] Automatic changelog update --- Resources/Changelog/Changelog.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 050ad0721c..4b779d845b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Ghagliiarghii - changes: - - message: The Librarian's Books Bag can now hold D&D related items such as dice - and battlemats. - type: Tweak - id: 6896 - time: '2024-07-10T05:51:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29863 - author: Beck Thompson, Tayrtahn changes: - message: Typing indicators now correctly stack and will not overwrite your default @@ -3917,3 +3909,14 @@ id: 7395 time: '2024-09-19T00:23:50.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/31939 +- author: ArchRBX + changes: + - message: Mass scanners and shuttle consoles now display coordinates beneath IFF + labels + type: Add + - message: IFF labels that are beyond the viewport extents maintain their heading + and don't hug corners + type: Fix + id: 7396 + time: '2024-09-19T01:25:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31501 From 9c905cd58f5453c6ab7821709bdbf5fcf1249df9 Mon Sep 17 00:00:00 2001 From: Luiz Costa <33888056+luizwritescode@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:15:44 -0300 Subject: [PATCH 44/56] Fix TEG acting as infinite energy source on destruction (#29972) * TEG now checks for power supply before checking for IsFullyBuilt * Update Content.Server/Power/Generation/Teg/TegSystem.cs Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> --- Content.Server/Power/Generation/Teg/TegSystem.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Content.Server/Power/Generation/Teg/TegSystem.cs b/Content.Server/Power/Generation/Teg/TegSystem.cs index 9fb7d5ff1f..027f257040 100644 --- a/Content.Server/Power/Generation/Teg/TegSystem.cs +++ b/Content.Server/Power/Generation/Teg/TegSystem.cs @@ -102,10 +102,6 @@ public sealed class TegSystem : EntitySystem private void GeneratorUpdate(EntityUid uid, TegGeneratorComponent component, ref AtmosDeviceUpdateEvent args) { - var tegGroup = GetNodeGroup(uid); - if (tegGroup is not { IsFullyBuilt: true }) - return; - var supplier = Comp(uid); var powerReceiver = Comp(uid); if (!powerReceiver.Powered) @@ -114,6 +110,10 @@ public sealed class TegSystem : EntitySystem return; } + var tegGroup = GetNodeGroup(uid); + if (tegGroup is not { IsFullyBuilt: true }) + return; + var circA = tegGroup.CirculatorA!.Owner; var circB = tegGroup.CirculatorB!.Owner; From b1296294052e6bb1af45efb97c350cc898746b22 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 19 Sep 2024 02:16:50 +0000 Subject: [PATCH 45/56] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 4b779d845b..8eba2ed6d8 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Beck Thompson, Tayrtahn - changes: - - message: Typing indicators now correctly stack and will not overwrite your default - species indicator. - type: Fix - id: 6897 - time: '2024-07-10T05:51:48.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29492 - author: Winkarst-cpu changes: - message: Now confirmation popup is displayed and item panel status is updated @@ -3920,3 +3912,10 @@ id: 7396 time: '2024-09-19T01:25:47.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/31501 +- author: coffeeware + changes: + - message: a powered TEG won't produce infinite power when destroyed + type: Fix + id: 7397 + time: '2024-09-19T02:15:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29972 From 550c4231816cd28251d3c42d32002bcf0ac4d5aa Mon Sep 17 00:00:00 2001 From: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com> Date: Wed, 18 Sep 2024 22:17:13 -0400 Subject: [PATCH 46/56] Clean up solution regen and drain comps (#29777) * clean up solution regen and drain comps * Tape applied. * Update Content.Server/Chemistry/Components/SolutionRegenerationComponent.cs Co-authored-by: Tayrtahn * remain entity * That has to be a rogue test fail. --------- Co-authored-by: Tayrtahn --- .../SolutionRegenerationComponent.cs | 12 +++++----- .../SolutionRegenerationSystem.cs | 4 ++-- .../Fluids/Components/DrainComponent.cs | 24 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Content.Server/Chemistry/Components/SolutionRegenerationComponent.cs b/Content.Server/Chemistry/Components/SolutionRegenerationComponent.cs index 23bf6b2157..03c0ffe0c1 100644 --- a/Content.Server/Chemistry/Components/SolutionRegenerationComponent.cs +++ b/Content.Server/Chemistry/Components/SolutionRegenerationComponent.cs @@ -14,31 +14,31 @@ public sealed partial class SolutionRegenerationComponent : Component /// /// The name of the solution to add to. /// - [DataField("solution", required: true), ViewVariables(VVAccess.ReadWrite)] + [DataField("solution", required: true)] public string SolutionName = string.Empty; /// /// The solution to add reagents to. /// - [DataField("solutionRef")] - public Entity? Solution = null; + [DataField] + public Entity? SolutionRef = null; /// /// The reagent(s) to be regenerated in the solution. /// - [DataField("generated", required: true), ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] public Solution Generated = default!; /// /// How long it takes to regenerate once. /// - [DataField("duration"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan Duration = TimeSpan.FromSeconds(1); /// /// The time when the next regeneration will occur. /// - [DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoPausedField] public TimeSpan NextRegenTime = TimeSpan.FromSeconds(0); } diff --git a/Content.Server/Chemistry/EntitySystems/SolutionRegenerationSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionRegenerationSystem.cs index 6a60062857..bccd594706 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionRegenerationSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionRegenerationSystem.cs @@ -24,7 +24,7 @@ public sealed class SolutionRegenerationSystem : EntitySystem // timer ignores if its full, it's just a fixed cycle regen.NextRegenTime = _timing.CurTime + regen.Duration; - if (_solutionContainer.ResolveSolution((uid, manager), regen.SolutionName, ref regen.Solution, out var solution)) + if (_solutionContainer.ResolveSolution((uid, manager), regen.SolutionName, ref regen.SolutionRef, out var solution)) { var amount = FixedPoint2.Min(solution.AvailableVolume, regen.Generated.Volume); if (amount <= FixedPoint2.Zero) @@ -41,7 +41,7 @@ public sealed class SolutionRegenerationSystem : EntitySystem generated = regen.Generated.Clone().SplitSolution(amount); } - _solutionContainer.TryAddSolution(regen.Solution.Value, generated); + _solutionContainer.TryAddSolution(regen.SolutionRef.Value, generated); } } } diff --git a/Content.Shared/Fluids/Components/DrainComponent.cs b/Content.Shared/Fluids/Components/DrainComponent.cs index 4fb4fe9438..50cb5f5195 100644 --- a/Content.Shared/Fluids/Components/DrainComponent.cs +++ b/Content.Shared/Fluids/Components/DrainComponent.cs @@ -23,14 +23,14 @@ public sealed partial class DrainComponent : Component [DataField] public Entity? Solution = null; - [DataField("accumulator")] + [DataField] public float Accumulator = 0f; /// /// Does this drain automatically absorb surrouding puddles? Or is it a drain designed to empty - /// solutions in it manually? + /// solutions in it manually? /// - [DataField("autoDrain"), ViewVariables(VVAccess.ReadOnly)] + [DataField] public bool AutoDrain = true; /// @@ -38,47 +38,47 @@ public sealed partial class DrainComponent : Component /// Divided by puddles, so if there are 5 puddles this will take 1/5 from each puddle. /// This will stay fixed to 1 second no matter what DrainFrequency is. /// - [DataField("unitsPerSecond")] + [DataField] public float UnitsPerSecond = 6f; /// /// How many units are ejected from the buffer per second. /// - [DataField("unitsDestroyedPerSecond")] + [DataField] public float UnitsDestroyedPerSecond = 3f; /// /// How many (unobstructed) tiles away the drain will /// drain puddles from. /// - [DataField("range"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float Range = 2f; /// /// How often in seconds the drain checks for puddles around it. /// If the EntityQuery seems a bit unperformant this can be increased. /// - [DataField("drainFrequency")] + [DataField] public float DrainFrequency = 1f; /// /// How much time it takes to unclog it with a plunger /// - [DataField("unclogDuration"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float UnclogDuration = 1f; /// /// What's the probability of uncloging on each try /// - [DataField("unclogProbability"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float UnclogProbability = 0.75f; - [DataField("manualDrainSound"), ViewVariables(VVAccess.ReadOnly)] + [DataField] public SoundSpecifier ManualDrainSound = new SoundPathSpecifier("/Audio/Effects/Fluids/slosh.ogg"); - [DataField("plungerSound"), ViewVariables(VVAccess.ReadOnly)] + [DataField] public SoundSpecifier PlungerSound = new SoundPathSpecifier("/Audio/Items/Janitor/plunger.ogg"); - [DataField("unclogSound"), ViewVariables(VVAccess.ReadOnly)] + [DataField] public SoundSpecifier UnclogSound = new SoundPathSpecifier("/Audio/Effects/Fluids/glug.ogg"); } From bdd0561254a0f2b6cef0a032af566bd908e9de58 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:21:26 +1200 Subject: [PATCH 47/56] Make status effect networking not use `TryAddStatusEffect` (#28766) * Make status effect networking not use `TryAddStatusEffect` * a --- .../StatusEffect/StatusEffectsSystem.cs | 80 ++++++++++--------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/Content.Shared/StatusEffect/StatusEffectsSystem.cs b/Content.Shared/StatusEffect/StatusEffectsSystem.cs index 9806077f9b..95abea63db 100644 --- a/Content.Shared/StatusEffect/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffect/StatusEffectsSystem.cs @@ -14,6 +14,7 @@ namespace Content.Shared.StatusEffect [Dependency] private readonly IComponentFactory _componentFactory = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; + private List _toRemove = new(); public override void Initialize() { @@ -32,17 +33,27 @@ namespace Content.Shared.StatusEffect var curTime = _gameTiming.CurTime; var enumerator = EntityQueryEnumerator(); + _toRemove.Clear(); while (enumerator.MoveNext(out var uid, out _, out var status)) { - foreach (var state in status.ActiveEffects.ToArray()) + if (status.ActiveEffects.Count == 0) { - // if we're past the end point of the effect - if (curTime > state.Value.Cooldown.Item2) - { - TryRemoveStatusEffect(uid, state.Key, status); - } + // This shouldn't happen, but just in case something sneaks through + _toRemove.Add(uid); + continue; } + + foreach (var state in status.ActiveEffects) + { + if (curTime > state.Value.Cooldown.Item2) + TryRemoveStatusEffect(uid, state.Key, status); + } + } + + foreach (var uid in _toRemove) + { + RemComp(uid); } } @@ -62,29 +73,21 @@ namespace Content.Shared.StatusEffect component.AllowedEffects.AddRange(state.AllowedEffects); // Remove non-existent effects. - foreach (var effect in component.ActiveEffects.Keys) + foreach (var key in component.ActiveEffects.Keys) { - if (!state.ActiveEffects.ContainsKey(effect)) - { - TryRemoveStatusEffect(uid, effect, component, remComp: false); - } + if (!state.ActiveEffects.ContainsKey(key)) + component.ActiveEffects.Remove(key); } foreach (var (key, effect) in state.ActiveEffects) { - // don't bother with anything if we already have it - if (component.ActiveEffects.ContainsKey(key)) - { - component.ActiveEffects[key] = new(effect); - continue; - } - - var time = effect.Cooldown.Item2 - effect.Cooldown.Item1; - - TryAddStatusEffect(uid, key, time, true, component, effect.Cooldown.Item1); - component.ActiveEffects[key].RelevantComponent = effect.RelevantComponent; - // state handling should not add networked components, that is handled separately by the client game state manager. + component.ActiveEffects[key] = new(effect); } + + if (component.ActiveEffects.Count == 0) + RemComp(uid); + else + EnsureComp(uid); } private void OnRejuvenate(EntityUid uid, StatusEffectsComponent component, RejuvenateEvent args) @@ -109,18 +112,16 @@ namespace Content.Shared.StatusEffect if (!Resolve(uid, ref status, false)) return false; - if (TryAddStatusEffect(uid, key, time, refresh, status)) - { - // If they already have the comp, we just won't bother updating anything. - if (!EntityManager.HasComponent(uid)) - { - var comp = EntityManager.AddComponent(uid); - status.ActiveEffects[key].RelevantComponent = _componentFactory.GetComponentName(comp.GetType()); - } - return true; - } + if (!TryAddStatusEffect(uid, key, time, refresh, status)) + return false; + + if (HasComp(uid)) + return true; + + EntityManager.AddComponent(uid); + status.ActiveEffects[key].RelevantComponent = _componentFactory.GetComponentName(); + return true; - return false; } public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component, @@ -162,8 +163,12 @@ namespace Content.Shared.StatusEffect /// If the effect already exists, it will simply replace the cooldown with the new one given. /// If you want special 'effect merging' behavior, do it your own damn self! /// - public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, - StatusEffectsComponent? status = null, TimeSpan? startTime = null) + public bool TryAddStatusEffect(EntityUid uid, + string key, + TimeSpan time, + bool refresh, + StatusEffectsComponent? status = null, + TimeSpan? startTime = null) { if (!Resolve(uid, ref status, false)) return false; @@ -334,8 +339,7 @@ namespace Content.Shared.StatusEffect /// The entity to check on. /// The status effect ID to check for /// The status effect component, should you already have it. - public bool CanApplyEffect(EntityUid uid, string key, - StatusEffectsComponent? status = null) + public bool CanApplyEffect(EntityUid uid, string key, StatusEffectsComponent? status = null) { // don't log since stuff calling this prolly doesn't care if we don't actually have it if (!Resolve(uid, ref status, false)) From 3fc9f96b75b2a3ebc1a02319758eb6853452cb05 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 19 Sep 2024 00:42:49 -0700 Subject: [PATCH 48/56] Move PlaceableSurfaceComponent usages to PlaceableSurfaceSystem (#28384) * Move placeable check to PlaceableSurfaceSystem This check stops entities from being inserted into a storage entity when it has a PlaceableSurfaceComponent. The entity is instead placed on top of the entity like a table. * Move SetPlaceable to PlaceableSurfaceSystem * Update to transform system and consolidate code * Fix interaction with storage that has a placeable component * deadlock --------- Co-authored-by: metalgearsloth --- .../Placeable/PlaceableSurfaceSystem.cs | 29 ++++++++++++++++--- .../SharedEntityStorageSystem.cs | 3 -- .../EntitySystems/SharedStorageSystem.cs | 4 ++- Content.Shared/Storage/StorageComponent.cs | 3 ++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Content.Shared/Placeable/PlaceableSurfaceSystem.cs b/Content.Shared/Placeable/PlaceableSurfaceSystem.cs index a9a9390a6e..c332064ea3 100644 --- a/Content.Shared/Placeable/PlaceableSurfaceSystem.cs +++ b/Content.Shared/Placeable/PlaceableSurfaceSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; +using Content.Shared.Storage; using Content.Shared.Storage.Components; namespace Content.Shared.Placeable; @@ -8,12 +9,16 @@ namespace Content.Shared.Placeable; public sealed class PlaceableSurfaceSystem : EntitySystem { [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAfterInteractUsing); + SubscribeLocalEvent(OnStorageInteractUsingAttempt); + SubscribeLocalEvent(OnStorageAfterOpen); + SubscribeLocalEvent(OnStorageAfterClose); } public void SetPlaceable(EntityUid uid, bool isPlaceable, PlaceableSurfaceComponent? surface = null) @@ -21,6 +26,9 @@ public sealed class PlaceableSurfaceSystem : EntitySystem if (!Resolve(uid, ref surface, false)) return; + if (surface.IsPlaceable == isPlaceable) + return; + surface.IsPlaceable = isPlaceable; Dirty(uid, surface); } @@ -59,11 +67,24 @@ public sealed class PlaceableSurfaceSystem : EntitySystem if (!_handsSystem.TryDrop(args.User, args.Used)) return; - if (surface.PlaceCentered) - Transform(args.Used).LocalPosition = Transform(uid).LocalPosition + surface.PositionOffset; - else - Transform(args.Used).Coordinates = args.ClickLocation; + _transformSystem.SetCoordinates(args.Used, + surface.PlaceCentered ? Transform(uid).Coordinates.Offset(surface.PositionOffset) : args.ClickLocation); args.Handled = true; } + + private void OnStorageInteractUsingAttempt(Entity ent, ref StorageInteractUsingAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnStorageAfterOpen(Entity ent, ref StorageAfterOpenEvent args) + { + SetPlaceable(ent.Owner, true, ent.Comp); + } + + private void OnStorageAfterClose(Entity ent, ref StorageAfterCloseEvent args) + { + SetPlaceable(ent.Owner, false, ent.Comp); + } } diff --git a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs index 4932613d0e..309ac0a2e0 100644 --- a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs @@ -487,9 +487,6 @@ public abstract class SharedEntityStorageSystem : EntitySystem } } - if (TryComp(uid, out var surface)) - _placeableSurface.SetPlaceable(uid, component.Open, surface); - _appearance.SetData(uid, StorageVisuals.Open, component.Open); _appearance.SetData(uid, StorageVisuals.HasContents, component.Contents.ContainedEntities.Count > 0); } diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index def9d797c4..d6fde292a1 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -364,7 +364,9 @@ public abstract class SharedStorageSystem : EntitySystem if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert, false)) return; - if (HasComp(uid)) + var attemptEv = new StorageInteractUsingAttemptEvent(); + RaiseLocalEvent(uid, ref attemptEv); + if (attemptEv.Cancelled) return; PlayerInsertHeldEntity((uid, storageComp), args.User); diff --git a/Content.Shared/Storage/StorageComponent.cs b/Content.Shared/Storage/StorageComponent.cs index a666169f52..d2c607e57f 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -238,6 +238,9 @@ namespace Content.Shared.Storage [ByRefEvent] public record struct StorageInteractAttemptEvent(bool Silent, bool Cancelled = false); + [ByRefEvent] + public record struct StorageInteractUsingAttemptEvent(bool Cancelled = false); + [NetSerializable] [Serializable] public enum StorageVisuals : byte From 3acf6b93a109ea542b3110c1f9b7fc59c6976aa3 Mon Sep 17 00:00:00 2001 From: Willhelm53 <97707302+Willhelm53@users.noreply.github.com> Date: Thu, 19 Sep 2024 03:51:33 -0400 Subject: [PATCH 49/56] Padded ItemStatus Text (#29560) * Back in the saddle again! <(8o) * if you like my STYLE you should see my SHEETS ;-) * stylesheet change works for ItemStatusNotHeld but broken for ItemStatus. Just using xaml for now. * teehee * beeg --------- Co-authored-by: metalgearsloth --- Content.Client/Stylesheets/StyleNano.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs index 0bd75003a2..ccd36c35e8 100644 --- a/Content.Client/Stylesheets/StyleNano.cs +++ b/Content.Client/Stylesheets/StyleNano.cs @@ -695,6 +695,18 @@ namespace Content.Client.Stylesheets new StyleProperty("font-color", Color.FromHex("#E5E5E581")), }), + // ItemStatus for hands + Element() + .Class(StyleClassItemStatusNotHeld) + .Prop("font", notoSansItalic10) + .Prop("font-color", ItemStatusNotHeldColor) + .Prop(nameof(Control.Margin), new Thickness(4, 0, 0, 2)), + + Element() + .Class(StyleClassItemStatus) + .Prop(nameof(RichTextLabel.LineHeightScale), 0.7f) + .Prop(nameof(Control.Margin), new Thickness(4, 0, 0, 2)), + // Context Menu window Element().Class(ContextMenuPopup.StyleClassContextMenuPopup) .Prop(PanelContainer.StylePropertyPanel, contextMenuBackground), From 94ad76fd07f148628c1b5d8070565635957caeb2 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 19 Sep 2024 04:02:37 -0400 Subject: [PATCH 50/56] Fix Set Outfit command/verb (#29672) * Filter Set Outfit menu to exclude loadout sets * Apply loadouts to job outfits * Use appropriate species for Urists * squishy --------- Co-authored-by: metalgearsloth --- .../UI/SetOutfit/SetOutfitMenu.xaml.cs | 18 +++++++--- .../Commands/SetOutfitCommand.cs | 36 +++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs b/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs index 7cb32b43df..615f1434df 100644 --- a/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs +++ b/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs @@ -1,14 +1,13 @@ +using System.Linq; using System.Numerics; using Content.Client.UserInterface.Controls; +using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; using Robust.Client.AutoGenerated; using Robust.Client.Console; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Prototypes; namespace Content.Client.Administration.UI.SetOutfit @@ -65,9 +64,18 @@ namespace Content.Client.Administration.UI.SetOutfit PopulateByFilter(SearchBar.Text); } + private IEnumerable GetPrototypes() + { + // Filter out any StartingGearPrototypes that belong to loadouts + var loadouts = _prototypeManager.EnumeratePrototypes(); + var loadoutGears = loadouts.Select(l => l.StartingGear); + return _prototypeManager.EnumeratePrototypes() + .Where(p => !loadoutGears.Contains(p.ID)); + } + private void PopulateList() { - foreach (var gear in _prototypeManager.EnumeratePrototypes()) + foreach (var gear in GetPrototypes()) { OutfitList.Add(GetItem(gear, OutfitList)); } @@ -76,7 +84,7 @@ namespace Content.Client.Administration.UI.SetOutfit private void PopulateByFilter(string filter) { OutfitList.Clear(); - foreach (var gear in _prototypeManager.EnumeratePrototypes()) + foreach (var gear in GetPrototypes()) { if (!string.IsNullOrEmpty(filter) && gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant())) diff --git a/Content.Server/Administration/Commands/SetOutfitCommand.cs b/Content.Server/Administration/Commands/SetOutfitCommand.cs index ff4d34705a..9240e7b91b 100644 --- a/Content.Server/Administration/Commands/SetOutfitCommand.cs +++ b/Content.Server/Administration/Commands/SetOutfitCommand.cs @@ -4,11 +4,15 @@ using Content.Server.Hands.Systems; using Content.Server.Preferences.Managers; using Content.Shared.Access.Components; using Content.Shared.Administration; +using Content.Shared.Clothing; using Content.Shared.Hands.Components; +using Content.Shared.Humanoid; using Content.Shared.Inventory; using Content.Shared.PDA; using Content.Shared.Preferences; +using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; +using Content.Shared.Station; using Robust.Shared.Console; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -82,9 +86,11 @@ namespace Content.Server.Administration.Commands return false; HumanoidCharacterProfile? profile = null; + ICommonSession? session = null; // Check if we are setting the outfit of a player to respect the preferences if (entityManager.TryGetComponent(target, out ActorComponent? actorComponent)) { + session = actorComponent.PlayerSession; var userId = actorComponent.PlayerSession.UserId; var preferencesManager = IoCManager.Resolve(); var prefs = preferencesManager.GetPreferences(userId); @@ -128,6 +134,36 @@ namespace Content.Server.Administration.Commands } } + // See if this starting gear is associated with a job + var jobs = prototypeManager.EnumeratePrototypes(); + foreach (var job in jobs) + { + if (job.StartingGear != gear) + continue; + + var jobProtoId = LoadoutSystem.GetJobPrototype(job.ID); + if (!prototypeManager.TryIndex(jobProtoId, out var jobProto)) + break; + + // Don't require a player, so this works on Urists + profile ??= entityManager.TryGetComponent(target, out var comp) + ? HumanoidCharacterProfile.DefaultWithSpecies(comp.Species) + : new HumanoidCharacterProfile(); + // Try to get the user's existing loadout for the role + profile.Loadouts.TryGetValue(jobProtoId, out var roleLoadout); + + if (roleLoadout == null) + { + // If they don't have a loadout for the role, make a default one + roleLoadout = new RoleLoadout(jobProtoId); + roleLoadout.SetDefault(profile, session, prototypeManager); + } + + // Equip the target with the job loadout + var stationSpawning = entityManager.System(); + stationSpawning.EquipRoleLoadout(target, roleLoadout, jobProto); + } + return true; } } From 59a8f4445d36d76a58ef814b3762cc324130deee Mon Sep 17 00:00:00 2001 From: Boaz1111 <149967078+Boaz1111@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:41:24 +0200 Subject: [PATCH 51/56] adds plasma and uranium arrows (#31241) --- .../Weapons/Guns/Projectiles/arrows.yml | 53 +++++++++++++++++++ .../Graphs/weapons/improvised_arrow.yml | 48 +++++++++++++++++ .../Recipes/Construction/weapons.yml | 22 ++++++++ 3 files changed, 123 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml index 6f925139fb..f1172c5de0 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml @@ -106,3 +106,56 @@ - type: Construction graph: ImprovisedArrow node: ImprovisedArrow + +- type: entity + parent: BaseArrow + id: ArrowImprovisedPlasma + name: plasma glass shard arrow + description: The greyshirt's preferred projectile. Now with extra lethality! + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/arrows.rsi + layers: + - state: tail + color: white + - state: rod + color: darkgray + - state: tip + color: purple + - state: solution1 + map: ["enum.SolutionContainerLayers.Fill"] + visible: false + - type: Projectile + damage: + types: + Piercing: 30 + - type: Construction + graph: ImprovisedArrowPlasma + node: ImprovisedArrowPlasma + +- type: entity + parent: BaseArrow + id: ArrowImprovisedUranium + name: uranium glass shard arrow + description: The greyshirt's preferred projectile. Now with added radiation! + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/arrows.rsi + layers: + - state: tail + color: white + - state: rod + color: darkgray + - state: tip + color: green + - state: solution1 + map: ["enum.SolutionContainerLayers.Fill"] + visible: false + - type: Projectile + damage: + types: + Piercing: 25 + Radiation: 5 + - type: Construction + graph: ImprovisedArrowUranium + node: ImprovisedArrowUranium diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/improvised_arrow.yml b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/improvised_arrow.yml index 04b9469046..20e06e9f48 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/improvised_arrow.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/improvised_arrow.yml @@ -21,3 +21,51 @@ - node: ImprovisedArrow entity: ArrowImprovised + +- type: constructionGraph + id: ImprovisedArrowPlasma + start: start + graph: + - node: start + edges: + - to: ImprovisedArrowPlasma + steps: + - material: MetalRod + amount: 1 + doAfter: 0.5 + - material: Cloth + amount: 1 + doAfter: 0.5 + - tag: PlasmaGlassShard + name: plasma glass shard + icon: + sprite: Objects/Materials/Shards/shard.rsi + state: shard1 + doAfter: 0.5 + + - node: ImprovisedArrowPlasma + entity: ArrowImprovisedPlasma + +- type: constructionGraph + id: ImprovisedArrowUranium + start: start + graph: + - node: start + edges: + - to: ImprovisedArrowUranium + steps: + - material: MetalRod + amount: 1 + doAfter: 0.5 + - material: Cloth + amount: 1 + doAfter: 0.5 + - tag: UraniumGlassShard + name: uranium glass shard + icon: + sprite: Objects/Materials/Shards/shard.rsi + state: shard1 + doAfter: 0.5 + + - node: ImprovisedArrowUranium + entity: ArrowImprovisedUranium diff --git a/Resources/Prototypes/Recipes/Construction/weapons.yml b/Resources/Prototypes/Recipes/Construction/weapons.yml index 040d4c8963..5936a35069 100644 --- a/Resources/Prototypes/Recipes/Construction/weapons.yml +++ b/Resources/Prototypes/Recipes/Construction/weapons.yml @@ -152,6 +152,28 @@ icon: { sprite: Objects/Weapons/Guns/Bow/bow.rsi, state: wielded-arrow } objectType: Item +- type: construction + name: plasma glass shard arrow + id: ImprovisedArrowPlasma + graph: ImprovisedArrowPlasma + startNode: start + targetNode: ImprovisedArrowPlasma + category: construction-category-weapons + description: An arrow tipped with pieces of a plasma glass shard, for use with a bow. + icon: { sprite: Objects/Weapons/Guns/Bow/bow.rsi, state: wielded-arrow } + objectType: Item + +- type: construction + name: uranium glass shard arrow + id: ImprovisedArrowUranium + graph: ImprovisedArrowUranium + startNode: start + targetNode: ImprovisedArrowUranium + category: construction-category-weapons + description: An arrow tipped with pieces of a uranium glass shard, for use with a bow. + icon: { sprite: Objects/Weapons/Guns/Bow/bow.rsi, state: wielded-arrow } + objectType: Item + - type: construction name: improvised bow id: ImprovisedBow From 75ff65d108e0a228d85a2d5dde5cbdbd50ed0cd3 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 19 Sep 2024 08:42:31 +0000 Subject: [PATCH 52/56] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 8eba2ed6d8..156fe91ffc 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Winkarst-cpu - changes: - - message: Now confirmation popup is displayed and item panel status is updated - after setting a custom solution transfer volume. - type: Fix - id: 6898 - time: '2024-07-10T10:32:30.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29852 - author: Winkarst-cpu changes: - message: Added exit confirmation for character setup menu with unsaved changes. @@ -3919,3 +3911,10 @@ id: 7397 time: '2024-09-19T02:15:44.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29972 +- author: Boaz1111 + changes: + - message: Added plasma and uranium arrows. + type: Add + id: 7398 + time: '2024-09-19T08:41:24.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31241 From 1468cbdb8a59beb2dfc9188a3108157496549a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AD=D0=B4=D1=83=D0=B0=D1=80=D0=B4?= <36124833+Ertanic@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:22:02 +0300 Subject: [PATCH 53/56] Wanted list cartridge (#31223) * WantedListCartridge has been added * WantedListCartridge user interface works * WantedListCartridge is added as standard in some PDAs * The CriminalRecordsSystem can now also take into account who created the record * Added offense history table * Fix of missing loaderUid for a cartridge without installing the program * Added personalized information about the target * The crime history has been finalized * Added StatusList * The officer's name has been added to the automatic history * WantedListCartridge has been added to the HOS locker * WantedListCartridge has been removed from brigmedic's preset programs * The StealConditionSystem now takes into account whether a cartridge is inserted or installed * Added target to thief on WantedListCartridge * Merge fix * Removing copypaste * Fix merge 2 * The sprite of WantedListCartridge has been changed * Update pda.yml * Fix scrollbar in the history table * Upstream localization fix * `StatusList` has been replaced by `ListContainer` with `TextureRect` * Margin fix --- .../Cartridges/WantedListUi.cs | 30 +++ .../Cartridges/WantedListUiFragment.cs | 240 ++++++++++++++++++ .../Cartridges/WantedListUiFragment.xaml | 50 ++++ .../CartridgeLoader/CartridgeLoaderSystem.cs | 6 + .../Cartridges/WantedListCartridge.cs | 8 + .../Systems/CriminalRecordsConsoleSystem.cs | 34 +-- .../Systems/CriminalRecordsSystem.cs | 90 ++++++- .../Systems/StealConditionSystem.cs | 6 + .../Cartridges/WantedListUiState.cs | 11 + .../CriminalRecords/CriminalRecord.cs | 8 +- .../Systems/SharedCriminalRecordsSystem.cs | 21 ++ .../en-US/cartridge-loader/cartridges.ftl | 29 +++ .../criminal-records/criminal-records.ftl | 2 +- .../conditions/steal-target-groups.ftl | 1 + .../Catalog/Fills/Lockers/heads.yml | 1 + .../Entities/Objects/Devices/cartridges.yml | 23 ++ .../Entities/Objects/Devices/pda.yml | 31 ++- .../Prototypes/Objectives/objectiveGroups.yml | 1 + .../Objectives/stealTargetGroups.yml | 7 + Resources/Prototypes/Objectives/thief.yml | 9 + .../Devices/cartridge.rsi/cart-sec.png | Bin 0 -> 314 bytes .../Objects/Devices/cartridge.rsi/meta.json | 5 +- 22 files changed, 579 insertions(+), 34 deletions(-) create mode 100644 Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs create mode 100644 Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs create mode 100644 Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml create mode 100644 Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs create mode 100644 Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs create mode 100644 Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs b/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs new file mode 100644 index 0000000000..3c97b8b37d --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs @@ -0,0 +1,30 @@ +using Content.Client.UserInterface.Fragments; +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.UserInterface; + +namespace Content.Client.CartridgeLoader.Cartridges; + +public sealed partial class WantedListUi : UIFragment +{ + private WantedListUiFragment? _fragment; + + public override Control GetUIFragmentRoot() + { + return _fragment!; + } + + public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner) + { + _fragment = new WantedListUiFragment(); + } + + public override void UpdateState(BoundUserInterfaceState state) + { + switch (state) + { + case WantedListUiState cast: + _fragment?.UpdateState(cast.Records); + break; + } + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs new file mode 100644 index 0000000000..4137f6c2af --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs @@ -0,0 +1,240 @@ +using System.Linq; +using Content.Client.UserInterface.Controls; +using Content.Shared.CriminalRecords.Systems; +using Content.Shared.Security; +using Content.Shared.StatusIcon; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Input; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class WantedListUiFragment : BoxContainer +{ + [Dependency] private readonly IEntitySystemManager _entitySystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + private readonly SpriteSystem _spriteSystem; + + private string? _selectedTargetName; + private List _wantedRecords = new(); + + public WantedListUiFragment() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + _spriteSystem = _entitySystem.GetEntitySystem(); + + SearchBar.OnTextChanged += OnSearchBarTextChanged; + } + + private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args) + { + var found = !String.IsNullOrWhiteSpace(args.Text) + ? _wantedRecords.FindAll(r => + r.TargetInfo.Name.Contains(args.Text) || + r.Status.ToString().Contains(args.Text, StringComparison.OrdinalIgnoreCase)) + : _wantedRecords; + + UpdateState(found, false); + } + + public void UpdateState(List records, bool refresh = true) + { + if (records.Count == 0) + { + NoRecords.Visible = true; + RecordsList.Visible = false; + RecordUnselected.Visible = false; + PersonContainer.Visible = false; + + _selectedTargetName = null; + if (refresh) + _wantedRecords.Clear(); + + RecordsList.PopulateList(new List()); + + return; + } + + NoRecords.Visible = false; + RecordsList.Visible = true; + RecordUnselected.Visible = true; + PersonContainer.Visible = false; + + var dataList = records.Select(r => new StatusListData(r)).ToList(); + + RecordsList.GenerateItem = GenerateItem; + RecordsList.ItemPressed = OnItemSelected; + RecordsList.PopulateList(dataList); + + if (refresh) + _wantedRecords = records; + } + + private void OnItemSelected(BaseButton.ButtonEventArgs args, ListData data) + { + if (data is not StatusListData(var record)) + return; + + FormattedMessage GetLoc(string fluentId, params (string,object)[] args) + { + var msg = new FormattedMessage(); + var fluent = Loc.GetString(fluentId, args); + msg.AddMarkupPermissive(fluent); + return msg; + } + + // Set personal info + PersonName.Text = record.TargetInfo.Name; + TargetAge.SetMessage(GetLoc( + "wanted-list-age-label", + ("age", record.TargetInfo.Age) + )); + TargetJob.SetMessage(GetLoc( + "wanted-list-job-label", + ("job", record.TargetInfo.JobTitle.ToLower()) + )); + TargetSpecies.SetMessage(GetLoc( + "wanted-list-species-label", + ("species", record.TargetInfo.Species.ToLower()) + )); + TargetGender.SetMessage(GetLoc( + "wanted-list-gender-label", + ("gender", record.TargetInfo.Gender) + )); + + // Set reason + WantedReason.SetMessage(GetLoc( + "wanted-list-reason-label", + ("reason", record.Reason ?? Loc.GetString("wanted-list-unknown-reason-label")) + )); + + // Set status + PersonState.SetMessage(GetLoc( + "wanted-list-status-label", + ("status", record.Status.ToString().ToLower()) + )); + + // Set initiator + InitiatorName.SetMessage(GetLoc( + "wanted-list-initiator-label", + ("initiator", record.Initiator ?? Loc.GetString("wanted-list-unknown-initiator-label")) + )); + + // History table + // Clear table if it exists + HistoryTable.RemoveAllChildren(); + + HistoryTable.AddChild(new Label() + { + Text = Loc.GetString("wanted-list-history-table-time-col"), + StyleClasses = { "LabelSmall" }, + HorizontalAlignment = HAlignment.Center, + }); + HistoryTable.AddChild(new Label() + { + Text = Loc.GetString("wanted-list-history-table-reason-col"), + StyleClasses = { "LabelSmall" }, + HorizontalAlignment = HAlignment.Center, + HorizontalExpand = true, + }); + + HistoryTable.AddChild(new Label() + { + Text = Loc.GetString("wanted-list-history-table-initiator-col"), + StyleClasses = { "LabelSmall" }, + HorizontalAlignment = HAlignment.Center, + }); + + if (record.History.Count > 0) + { + HistoryTable.Visible = true; + + foreach (var history in record.History.OrderByDescending(h => h.AddTime)) + { + HistoryTable.AddChild(new Label() + { + Text = $"{history.AddTime.Hours:00}:{history.AddTime.Minutes:00}:{history.AddTime.Seconds:00}", + StyleClasses = { "LabelSmall" }, + VerticalAlignment = VAlignment.Top, + }); + + HistoryTable.AddChild(new RichTextLabel() + { + Text = $"[color=white]{history.Crime}[/color]", + HorizontalExpand = true, + VerticalAlignment = VAlignment.Top, + StyleClasses = { "LabelSubText" }, + Margin = new(10f, 0f), + }); + + HistoryTable.AddChild(new RichTextLabel() + { + Text = $"[color=white]{history.InitiatorName}[/color]", + StyleClasses = { "LabelSubText" }, + VerticalAlignment = VAlignment.Top, + }); + } + } + + RecordUnselected.Visible = false; + PersonContainer.Visible = true; + + // Save selected item + _selectedTargetName = record.TargetInfo.Name; + } + + private void GenerateItem(ListData data, ListContainerButton button) + { + if (data is not StatusListData(var record)) + return; + + var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal, HorizontalExpand = true }; + var label = new Label() { Text = record.TargetInfo.Name }; + var rect = new TextureRect() + { + TextureScale = new(2.2f), + VerticalAlignment = VAlignment.Center, + HorizontalAlignment = HAlignment.Center, + Margin = new(0f, 0f, 6f, 0f), + }; + + if (record.Status is not SecurityStatus.None) + { + var proto = "SecurityIcon" + record.Status switch + { + SecurityStatus.Detained => "Incarcerated", + _ => record.Status.ToString(), + }; + + if (_prototypeManager.TryIndex(proto, out var prototype)) + { + rect.Texture = _spriteSystem.Frame0(prototype.Icon); + } + } + + box.AddChild(rect); + box.AddChild(label); + button.AddChild(box); + button.AddStyleClass(ListContainer.StyleClassListContainerButton); + + if (record.TargetInfo.Name.Equals(_selectedTargetName)) + { + button.Pressed = true; + // For some reason the event is not called when `Pressed` changed, call it manually. + OnItemSelected( + new(button, new(new(), BoundKeyState.Down, new(), false, new(), new())), + data); + } + } +} + +internal record StatusListData(WantedRecord Record) : ListData; diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml new file mode 100644 index 0000000000..7b5d116ad7 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml @@ -0,0 +1,50 @@ + + + + + + + diff --git a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs index cd422328c3..7caec6150e 100644 --- a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs +++ b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs @@ -340,6 +340,9 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem if (args.Container.ID != InstalledContainerId && args.Container.ID != loader.CartridgeSlot.ID) return; + if (TryComp(args.Entity, out CartridgeComponent? cartridge)) + cartridge.LoaderUid = uid; + RaiseLocalEvent(args.Entity, new CartridgeAddedEvent(uid)); base.OnItemInserted(uid, loader, args); } @@ -360,6 +363,9 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem if (deactivate) RaiseLocalEvent(args.Entity, new CartridgeDeactivatedEvent(uid)); + if (TryComp(args.Entity, out CartridgeComponent? cartridge)) + cartridge.LoaderUid = null; + RaiseLocalEvent(args.Entity, new CartridgeRemovedEvent(uid)); base.OnItemRemoved(uid, loader, args); diff --git a/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs b/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs new file mode 100644 index 0000000000..08eef62379 --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs @@ -0,0 +1,8 @@ +using Content.Shared.Security; + +namespace Content.Server.CartridgeLoader.Cartridges; + +[RegisterComponent] +public sealed partial class WantedListCartridgeComponent : Component +{ +} diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs index c5f1d159f3..ca1d45e644 100644 --- a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs @@ -68,6 +68,13 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS } } + private void GetOfficer(EntityUid uid, out string officer) + { + var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, uid); + RaiseLocalEvent(tryGetIdentityShortInfoEvent); + officer = tryGetIdentityShortInfoEvent.Title ?? Loc.GetString("criminal-records-console-unknown-officer"); + } + private void OnChangeStatus(Entity ent, ref CriminalRecordChangeStatus msg) { // prevent malf client violating wanted/reason nullability @@ -90,29 +97,22 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS return; } + var oldStatus = record.Status; + + var name = _records.RecordName(key.Value); + GetOfficer(mob.Value, out var officer); + // when arresting someone add it to history automatically // fallback exists if the player was not set to wanted beforehand if (msg.Status == SecurityStatus.Detained) { var oldReason = record.Reason ?? Loc.GetString("criminal-records-console-unspecified-reason"); var history = Loc.GetString("criminal-records-console-auto-history", ("reason", oldReason)); - _criminalRecords.TryAddHistory(key.Value, history); + _criminalRecords.TryAddHistory(key.Value, history, officer); } - var oldStatus = record.Status; - // will probably never fail given the checks above - _criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason); - - var name = _records.RecordName(key.Value); - var officer = Loc.GetString("criminal-records-console-unknown-officer"); - - var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, mob.Value); - RaiseLocalEvent(tryGetIdentityShortInfoEvent); - if (tryGetIdentityShortInfoEvent.Title != null) - { - officer = tryGetIdentityShortInfoEvent.Title; - } + _criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason, officer); (string, object)[] args; if (reason != null) @@ -152,14 +152,16 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS private void OnAddHistory(Entity ent, ref CriminalRecordAddHistory msg) { - if (!CheckSelected(ent, msg.Actor, out _, out var key)) + if (!CheckSelected(ent, msg.Actor, out var mob, out var key)) return; var line = msg.Line.Trim(); if (line.Length < 1 || line.Length > ent.Comp.MaxStringLength) return; - if (!_criminalRecords.TryAddHistory(key.Value, line)) + GetOfficer(mob.Value, out var officer); + + if (!_criminalRecords.TryAddHistory(key.Value, line, officer)) return; // no radio message since its not crucial to officers patrolling diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs index a65fb0be9e..7c65ce8c24 100644 --- a/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs @@ -1,10 +1,15 @@ -using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.CartridgeLoader; +using Content.Server.CartridgeLoader.Cartridges; using Content.Server.StationRecords.Systems; using Content.Shared.CriminalRecords; using Content.Shared.CriminalRecords.Systems; using Content.Shared.Security; using Content.Shared.StationRecords; using Content.Server.GameTicking; +using Content.Server.Station.Systems; +using Content.Shared.CartridgeLoader; +using Content.Shared.CartridgeLoader.Cartridges; namespace Content.Server.CriminalRecords.Systems; @@ -20,12 +25,18 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem { [Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly StationRecordsSystem _records = default!; + [Dependency] private readonly StationSystem _station = default!; + [Dependency] private readonly CartridgeLoaderSystem _cartridge = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnGeneralRecordCreated); + SubscribeLocalEvent(OnRecordChanged); + SubscribeLocalEvent(OnCartridgeUiReady); + SubscribeLocalEvent(OnHistoryAdded); + SubscribeLocalEvent(OnHistoryRemoved); } private void OnGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev) @@ -39,14 +50,14 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem /// Reason should only be passed if status is Wanted, nullability isn't checked. /// /// True if the status is changed, false if not - public bool TryChangeStatus(StationRecordKey key, SecurityStatus status, string? reason) + public bool TryChangeStatus(StationRecordKey key, SecurityStatus status, string? reason, string? initiatorName = null) { // don't do anything if its the same status if (!_records.TryGetRecord(key, out var record) || status == record.Status) return false; - OverwriteStatus(key, record, status, reason); + OverwriteStatus(key, record, status, reason, initiatorName); return true; } @@ -54,16 +65,24 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem /// /// Sets the status without checking previous status or reason nullability. /// - public void OverwriteStatus(StationRecordKey key, CriminalRecord record, SecurityStatus status, string? reason) + public void OverwriteStatus(StationRecordKey key, CriminalRecord record, SecurityStatus status, string? reason, string? initiatorName = null) { record.Status = status; record.Reason = reason; + record.InitiatorName = initiatorName; var name = _records.RecordName(key); if (name != string.Empty) UpdateCriminalIdentity(name, status); _records.Synchronize(key); + + var args = new CriminalRecordChangedEvent(record); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var readerUid, out _)) + { + RaiseLocalEvent(readerUid, ref args); + } } /// @@ -76,15 +95,23 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem return false; record.History.Add(entry); + + var args = new CriminalHistoryAddedEvent(entry); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var readerUid, out _)) + { + RaiseLocalEvent(readerUid, ref args); + } + return true; } /// /// Creates and tries to add a history entry using the current time. /// - public bool TryAddHistory(StationRecordKey key, string line) + public bool TryAddHistory(StationRecordKey key, string line, string? initiatorName = null) { - var entry = new CrimeHistory(_ticker.RoundDuration(), line); + var entry = new CrimeHistory(_ticker.RoundDuration(), line, initiatorName); return TryAddHistory(key, entry); } @@ -100,7 +127,58 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem if (index >= record.History.Count) return false; + var history = record.History[(int)index]; record.History.RemoveAt((int) index); + + var args = new CriminalHistoryRemovedEvent(history); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var readerUid, out _)) + { + RaiseLocalEvent(readerUid, ref args); + } + return true; } + + private void OnRecordChanged(Entity ent, ref CriminalRecordChangedEvent args) => + StateChanged(ent); + + private void OnHistoryAdded(Entity ent, ref CriminalHistoryAddedEvent args) => + StateChanged(ent); + + private void OnHistoryRemoved(Entity ent, ref CriminalHistoryRemovedEvent args) => + StateChanged(ent); + + private void StateChanged(Entity ent) + { + if (Comp(ent).LoaderUid is not { } loaderUid) + return; + + UpdateReaderUi(ent, loaderUid); + } + + private void OnCartridgeUiReady(Entity ent, ref CartridgeUiReadyEvent args) + { + UpdateReaderUi(ent, args.Loader); + } + + private void UpdateReaderUi(Entity ent, EntityUid loaderUid) + { + if (_station.GetOwningStation(ent) is not { } station) + return; + + var records = _records.GetRecordsOfType(station) + .Where(cr => cr.Item2.Status is not SecurityStatus.None || cr.Item2.History.Count > 0) + .Select(cr => + { + var (i, r) = cr; + var key = new StationRecordKey(i, station); + // Hopefully it will work smoothly..... + _records.TryGetRecord(key, out GeneralStationRecord? generalRecord); + return new WantedRecord(generalRecord!, r.Status, r.Reason, r.InitiatorName, r.History); + }); + var state = new WantedListUiState(records.ToList()); + + _cartridge.UpdateCartridgeUiState(loaderUid, state); + } } diff --git a/Content.Server/Objectives/Systems/StealConditionSystem.cs b/Content.Server/Objectives/Systems/StealConditionSystem.cs index be34a80fe3..e2d81e011c 100644 --- a/Content.Server/Objectives/Systems/StealConditionSystem.cs +++ b/Content.Server/Objectives/Systems/StealConditionSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Objectives.Components; using Content.Server.Objectives.Components.Targets; +using Content.Shared.CartridgeLoader; using Content.Shared.Mind; using Content.Shared.Objectives.Components; using Content.Shared.Objectives.Systems; @@ -172,6 +173,11 @@ public sealed class StealConditionSystem : EntitySystem if (target.StealGroup != condition.StealGroup) return 0; + // check if cartridge is installed + if (TryComp(entity, out var cartridge) && + cartridge.InstallationStatus is not InstallationStatus.Cartridge) + return 0; + // check if needed target alive if (condition.CheckAlive) { diff --git a/Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs b/Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs new file mode 100644 index 0000000000..9d55e0c163 --- /dev/null +++ b/Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs @@ -0,0 +1,11 @@ +using Content.Shared.CriminalRecords; +using Content.Shared.CriminalRecords.Systems; +using Robust.Shared.Serialization; + +namespace Content.Shared.CartridgeLoader.Cartridges; + +[Serializable, NetSerializable] +public sealed class WantedListUiState(List records) : BoundUserInterfaceState +{ + public List Records = records; +} diff --git a/Content.Shared/CriminalRecords/CriminalRecord.cs b/Content.Shared/CriminalRecords/CriminalRecord.cs index 0fe23d4395..5a023a9188 100644 --- a/Content.Shared/CriminalRecords/CriminalRecord.cs +++ b/Content.Shared/CriminalRecords/CriminalRecord.cs @@ -23,6 +23,12 @@ public sealed record CriminalRecord [DataField] public string? Reason; + /// + /// The name of the person who changed the status. + /// + [DataField] + public string? InitiatorName; + /// /// Criminal history of the person. /// This should have charges and time served added after someone is detained. @@ -35,4 +41,4 @@ public sealed record CriminalRecord /// A line of criminal activity and the time it was added at. /// [Serializable, NetSerializable] -public record struct CrimeHistory(TimeSpan AddTime, string Crime); +public record struct CrimeHistory(TimeSpan AddTime, string Crime, string? InitiatorName); diff --git a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs index 96b33ab91b..d665d32f1e 100644 --- a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs +++ b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs @@ -2,6 +2,8 @@ using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement.Components; using Content.Shared.Security; using Content.Shared.Security.Components; +using Content.Shared.StationRecords; +using Robust.Shared.Serialization; namespace Content.Shared.CriminalRecords.Systems; @@ -50,3 +52,22 @@ public abstract class SharedCriminalRecordsSystem : EntitySystem Dirty(characterUid, record); } } + +[Serializable, NetSerializable] +public struct WantedRecord(GeneralStationRecord targetInfo, SecurityStatus status, string? reason, string? initiator, List history) +{ + public GeneralStationRecord TargetInfo = targetInfo; + public SecurityStatus Status = status; + public string? Reason = reason; + public string? Initiator = initiator; + public List History = history; +}; + +[ByRefEvent] +public record struct CriminalRecordChangedEvent(CriminalRecord Record); + +[ByRefEvent] +public record struct CriminalHistoryAddedEvent(CrimeHistory History); + +[ByRefEvent] +public record struct CriminalHistoryRemovedEvent(CrimeHistory History); diff --git a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl index f5cda2f2a1..2db27f5be0 100644 --- a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl +++ b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl @@ -19,3 +19,32 @@ log-probe-scan = Downloaded logs from {$device}! log-probe-label-time = Time log-probe-label-accessor = Accessed by log-probe-label-number = # + +# Wanted list cartridge +wanted-list-program-name = Wanted list +wanted-list-label-no-records = It's all right, cowboy +wanted-list-search-placeholder = Search by name and status + +wanted-list-age-label = [color=darkgray]Age:[/color] [color=white]{$age}[/color] +wanted-list-job-label = [color=darkgray]Job:[/color] [color=white]{$job}[/color] +wanted-list-species-label = [color=darkgray]Species:[/color] [color=white]{$species}[/color] +wanted-list-gender-label = [color=darkgray]Gender:[/color] [color=white]{$gender}[/color] + +wanted-list-reason-label = [color=darkgray]Reason:[/color] [color=white]{$reason}[/color] +wanted-list-unknown-reason-label = unknown reason + +wanted-list-initiator-label = [color=darkgray]Initiator:[/color] [color=white]{$initiator}[/color] +wanted-list-unknown-initiator-label = unknown initiator + +wanted-list-status-label = [color=darkgray]status:[/color] {$status -> + [suspected] [color=yellow]suspected[/color] + [wanted] [color=red]wanted[/color] + [detained] [color=#b18644]detained[/color] + [paroled] [color=green]paroled[/color] + [discharged] [color=green]discharged[/color] + *[other] none + } + +wanted-list-history-table-time-col = Time +wanted-list-history-table-reason-col = Crime +wanted-list-history-table-initiator-col = Initiator diff --git a/Resources/Locale/en-US/criminal-records/criminal-records.ftl b/Resources/Locale/en-US/criminal-records/criminal-records.ftl index 6d6a97300c..2a7c09912f 100644 --- a/Resources/Locale/en-US/criminal-records/criminal-records.ftl +++ b/Resources/Locale/en-US/criminal-records/criminal-records.ftl @@ -39,7 +39,7 @@ criminal-records-console-released = {$name} has been released by {$officer}. criminal-records-console-not-wanted = {$officer} cleared the wanted status of {$name}. criminal-records-console-paroled = {$name} has been released on parole by {$officer}. criminal-records-console-not-parole = {$officer} cleared the parole status of {$name}. -criminal-records-console-unknown-officer = +criminal-records-console-unknown-officer = ## Filters diff --git a/Resources/Locale/en-US/objectives/conditions/steal-target-groups.ftl b/Resources/Locale/en-US/objectives/conditions/steal-target-groups.ftl index 91b3c92b1c..689e2e7808 100644 --- a/Resources/Locale/en-US/objectives/conditions/steal-target-groups.ftl +++ b/Resources/Locale/en-US/objectives/conditions/steal-target-groups.ftl @@ -40,6 +40,7 @@ steal-target-groups-clothing-eyes-hud-beer = beer goggles steal-target-groups-bible = bible steal-target-groups-clothing-neck-goldmedal = gold medal of crewmanship steal-target-groups-clothing-neck-clownmedal = clown medal +steal-target-groups-wanted-list-cartridge = wanted list cartridge # Thief structures steal-target-groups-teg = teg generator part diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index d318963016..31ebad6183 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -325,6 +325,7 @@ - id: RubberStampHos - id: SecurityTechFabCircuitboard - id: WeaponDisabler + - id: WantedListCartridge # Hardsuit table, used for suit storage as well - type: entityTable diff --git a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml index f9581149e2..91493f48cd 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml @@ -93,3 +93,26 @@ - type: GuideHelp guides: - Forensics + +- type: entity + parent: BaseItem + id: WantedListCartridge + name: Wanted list cartridge + description: A program to get a list of wanted persons. + components: + - type: Sprite + sprite: Objects/Devices/cartridge.rsi + state: cart-sec + - type: Icon + sprite: Objects/Devices/cartridge.rsi + state: cart-sec + - type: UIFragment + ui: !type:WantedListUi + - type: Cartridge + programName: wanted-list-program-name + icon: + sprite: Objects/Misc/books.rsi + state: icon_magnifier + - type: WantedListCartridge + - type: StealTarget + stealGroup: WantedListCartridge diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 40f6f77e12..48e7a28deb 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -114,6 +114,18 @@ - type: Speech speechVerb: Robotic +- type: entity + id: BaseSecurityPDA + abstract: true + components: + - type: CartridgeLoader + uiKey: enum.PdaUiKey.Key + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - WantedListCartridge + - type: entity parent: BasePDA id: BaseMedicalPDA @@ -433,7 +445,7 @@ state: pda-library - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: LawyerPDA name: lawyer PDA description: For lawyers to poach dubious clients. @@ -476,7 +488,7 @@ state: pda-janitor - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: CaptainPDA name: captain PDA description: Surprisingly no different from your PDA. @@ -651,7 +663,7 @@ state: pda-science - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: HoSPDA name: head of security PDA description: Whosoever bears this PDA is the law. @@ -666,7 +678,7 @@ state: pda-hos - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: WardenPDA name: warden PDA description: The OS appears to have been jailbroken. @@ -681,7 +693,7 @@ state: pda-warden - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: SecurityPDA name: security PDA description: Red to hide the stains of passenger blood. @@ -695,7 +707,7 @@ state: pda-security - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: CentcomPDA name: CentComm PDA description: Light green sign of walking bureaucracy. @@ -733,6 +745,7 @@ - NotekeeperCartridge - NewsReaderCartridge - LogProbeCartridge + - WantedListCartridge - type: entity parent: CentcomPDA @@ -834,7 +847,7 @@ - Cartridge - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: ERTLeaderPDA name: ERT Leader PDA suffix: Leader @@ -982,7 +995,7 @@ state: pda-boxer - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: DetectivePDA name: detective PDA description: Smells like rain... pouring down the rooftops... @@ -1082,7 +1095,7 @@ state: pda-seniorphysician - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: SeniorOfficerPDA name: senior officer PDA description: Beaten, battered and broken, but just barely useable. diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 481ca0f93d..e72de0d94a 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -73,6 +73,7 @@ ForensicScannerStealObjective: 1 #sec FlippoEngravedLighterStealObjective: 0.5 ClothingHeadHatWardenStealObjective: 1 + WantedListCartridgeStealObjective: 1 ClothingOuterHardsuitVoidParamedStealObjective: 1 #med MedicalTechFabCircuitboardStealObjective: 1 ClothingHeadsetAltMedicalStealObjective: 1 diff --git a/Resources/Prototypes/Objectives/stealTargetGroups.yml b/Resources/Prototypes/Objectives/stealTargetGroups.yml index 48f56e2bfc..09619bf986 100644 --- a/Resources/Prototypes/Objectives/stealTargetGroups.yml +++ b/Resources/Prototypes/Objectives/stealTargetGroups.yml @@ -263,6 +263,13 @@ sprite: Clothing/Neck/Medals/clownmedal.rsi state: icon +- type: stealTargetGroup + id: WantedListCartridge + name: steal-target-groups-wanted-list-cartridge + sprite: + sprite: Objects/Devices/cartridge.rsi + state: cart-sec + #Thief structures - type: stealTargetGroup diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml index f8e44d831e..7a46d0f5e9 100644 --- a/Resources/Prototypes/Objectives/thief.yml +++ b/Resources/Prototypes/Objectives/thief.yml @@ -184,6 +184,15 @@ - type: Objective difficulty: 1.2 +- type: entity + parent: BaseThiefStealObjective + id: WantedListCartridgeStealObjective + components: + - type: StealCondition + stealGroup: WantedListCartridge + - type: Objective + difficulty: 1 + - type: entity #Medical subgroup parent: BaseThiefStealObjective id: ClothingOuterHardsuitVoidParamedStealObjective diff --git a/Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png b/Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png new file mode 100644 index 0000000000000000000000000000000000000000..6a3197004cbf0460f517049aae94986a820100c8 GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}w>(`OLn2z= zPBG+bHsEosU!^CfG-YaI@P@Arw!!>Ij%i9n2!48CD9e?Vk(r#al<({8fG=PF9}i=a zXiNNK?We4?Xvgswmreg783m87T6N&e>_dCMzu2W0{b+?@a@w5Va_S66OTvzKT-&+) zy`Tg0gP>`v7O_v|ifB21#c)yI>9-=2!@74lNZGY-JEyyB!?LprqYi4z{jcMH#o42s z{*tBWRzJ5%XQ9=%64~O4%?le|Nwo=>Mck8YduV;#K$H3X-)LTAXO}DUR*80WXvyrb zb#Pgt_wn=c8wNawcQEPn`j%LP9bO0YC0}ZUXPU1sgBFm@0mLA1DR?r7@^tlcS?83{ F1ONqldhh@M literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json b/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json index f3b02a2b2e..d5fad56006 100644 --- a/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d and tgstation at tgstation at https://github.com/tgstation/tgstation/commit/0c15d9dbcf0f2beb230eba5d9d889ef2d1945bb8, cart-log made by Skarletto (github)", + "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d and tgstation at tgstation at https://github.com/tgstation/tgstation/commit/0c15d9dbcf0f2beb230eba5d9d889ef2d1945bb8, cart-log made by Skarletto (github), cart-sec made by dieselmohawk (discord)", "size": { "x": 32, "y": 32 @@ -79,6 +79,9 @@ { "name": "cart-y" }, + { + "name": "cart-sec" + }, { "name": "insert_overlay" } From ccadcc97819daaa2f8028df695527643392f9063 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 19 Sep 2024 10:23:08 +0000 Subject: [PATCH 54/56] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 156fe91ffc..df513a66be 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Winkarst-cpu - changes: - - message: Added exit confirmation for character setup menu with unsaved changes. - type: Add - id: 6899 - time: '2024-07-11T00:24:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29875 - author: ShadowCommander changes: - message: Players can now use melee attacks and shoves while dragging an entity @@ -3918,3 +3911,14 @@ id: 7398 time: '2024-09-19T08:41:24.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/31241 +- author: Ertanic + changes: + - message: Wanted list program and its cartridge. + type: Add + - message: The cartridge has been added to the HOS locker. + type: Add + - message: Added target to thief on wanted list cartridge. + type: Add + id: 7399 + time: '2024-09-19T10:22:02.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31223 From 854bfd27cb062680caaeeec2a4c58b53372b4af6 Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:23:45 +0200 Subject: [PATCH 55/56] Crew Monitor filter (#31659) * crewmon filter * string case matching Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Medical/CrewMonitoring/CrewMonitoringWindow.xaml | 3 +++ .../Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs | 5 +++++ .../en-US/medical/components/crew-monitoring-component.ftl | 2 ++ 3 files changed, 10 insertions(+) diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml index 660f2e5e11..dd40749d33 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml @@ -15,6 +15,9 @@ + + Date: Thu, 19 Sep 2024 10:24:51 +0000 Subject: [PATCH 56/56] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index df513a66be..5f5d96f41c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: ShadowCommander - changes: - - message: Players can now use melee attacks and shoves while dragging an entity - in their active hand. - type: Tweak - id: 6900 - time: '2024-07-11T04:48:00.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29703 - author: Cojoke-dot changes: - message: You can no longer shoot out of crates with guns @@ -3922,3 +3914,10 @@ id: 7399 time: '2024-09-19T10:22:02.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/31223 +- author: Errant + changes: + - message: Crew monitor list can now be filtered by name and job. + type: Add + id: 7400 + time: '2024-09-19T10:23:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31659