From 45f91a84a4b5215811d6492d1fb43bbb6674d875 Mon Sep 17 00:00:00 2001 From: Whatstone <166147148+whatston3@users.noreply.github.com> Date: Wed, 29 Jul 2026 16:48:46 -0400 Subject: [PATCH] Bugfix: Guardian popup spam, mech attack blocker (#45013) * guardian popup fixes * actually use the entityqueries * finish a thought challenge - difficulty impossible * curse of fildrance * can't believe he's done this --- .../Guardian/Components/GuardianComponent.cs | 16 +++- Content.Shared/Guardian/GuardianSystem.cs | 86 ++++++++++--------- 2 files changed, 62 insertions(+), 40 deletions(-) diff --git a/Content.Shared/Guardian/Components/GuardianComponent.cs b/Content.Shared/Guardian/Components/GuardianComponent.cs index f32c37a0d3..a5c08ab9ee 100644 --- a/Content.Shared/Guardian/Components/GuardianComponent.cs +++ b/Content.Shared/Guardian/Components/GuardianComponent.cs @@ -1,12 +1,14 @@ using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Guardian.Components; /// /// Given to guardians to monitor their link with the host. /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class GuardianComponent : Component { /// @@ -50,4 +52,16 @@ public sealed partial class GuardianComponent : Component /// [DataField] public SoundSpecifier DeathSound = new SoundPathSpecifier("/Audio/Voice/Human/malescream_guardian.ogg", AudioParams.Default.WithVariation(0.2f)); + + /// + /// The last time that the entity received an attack popup. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] + public TimeSpan LastAttackPopupTime; + + /// + /// The delay between showing popups to the guardian when trying to attack its host. + /// + [DataField] + public TimeSpan AttackPopupDelay = TimeSpan.FromSeconds(1); } diff --git a/Content.Shared/Guardian/GuardianSystem.cs b/Content.Shared/Guardian/GuardianSystem.cs index 413818fdc2..3a3d3bdd03 100644 --- a/Content.Shared/Guardian/GuardianSystem.cs +++ b/Content.Shared/Guardian/GuardianSystem.cs @@ -36,35 +36,13 @@ public sealed partial class GuardianSystem : EntitySystem [Dependency] private SharedPopupSystem _popup = default!; [Dependency] private SharedTransformSystem _transform = default!; - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnCreatorUse); - SubscribeLocalEvent(OnCreatorInteract); - SubscribeLocalEvent(OnCreatorExamine); - SubscribeLocalEvent(OnDoAfter); - - SubscribeLocalEvent(OnGuardianShutdown); - SubscribeLocalEvent(OnGuardianMove); - SubscribeLocalEvent(OnGuardianDamaged); - SubscribeLocalEvent(OnGuardianPlayerAttached); - SubscribeLocalEvent(OnGuardianPlayerDetached); - - SubscribeLocalEvent(OnHostInit); - SubscribeLocalEvent(OnHostMove); - SubscribeLocalEvent(OnHostStateChange); - SubscribeLocalEvent(OnHostShutdown); - - SubscribeLocalEvent(OnPerformAction); - - SubscribeLocalEvent(OnGuardianAttackAttempt); - - SubscribeLocalEvent>(OnPilotAttackAttempt); - } + [Dependency] private EntityQuery _guardianQuery; + [Dependency] private EntityQuery _guardianHostQuery; + [SubscribeLocalEvent] private void OnGuardianShutdown(Entity ent, ref ComponentShutdown args) { - if (!TryComp(ent, out var hostComponent)) + if (!_guardianHostQuery.TryComp(ent, out GuardianHostComponent? hostComponent)) return; _container.Remove(ent.Owner, hostComponent.GuardianContainer); @@ -77,6 +55,7 @@ public sealed partial class GuardianSystem : EntitySystem Dirty(ent, hostComponent); } + [SubscribeLocalEvent] private void OnPerformAction(Entity ent, ref GuardianToggleActionEvent args) { if (args.Handled) @@ -94,9 +73,10 @@ public sealed partial class GuardianSystem : EntitySystem args.Handled = true; } + [SubscribeLocalEvent] private void OnGuardianPlayerDetached(Entity ent, ref PlayerDetachedEvent args) { - if (!TryComp(ent.Comp.Host, out var hostComponent) || + if (!_guardianHostQuery.TryComp(ent.Comp.Host, out GuardianHostComponent? hostComponent) || TerminatingOrDeleted(ent.Owner)) { PredictedQueueDel(ent.Owner); @@ -108,10 +88,11 @@ public sealed partial class GuardianSystem : EntitySystem RetractGuardian((ent.Comp.Host.Value, hostComponent), (ent.Owner, ent.Comp)); } + [SubscribeLocalEvent] private void OnGuardianPlayerAttached(Entity ent, ref PlayerAttachedEvent args) { var host = ent.Comp.Host; - if (!HasComp(host)) + if (!_guardianHostQuery.HasComp(host)) { PredictedQueueDel(ent.Owner); ent.Comp.Host = null; @@ -122,12 +103,14 @@ public sealed partial class GuardianSystem : EntitySystem _popup.PopupEntity(Loc.GetString("guardian-available"), host.Value, host.Value); } + [SubscribeLocalEvent] private void OnHostInit(Entity ent, ref ComponentInit args) { ent.Comp.GuardianContainer = _container.EnsureContainer(ent.Owner, "GuardianContainer"); _action.AddAction(ent.Owner, ref ent.Comp.ActionEntity, ent.Comp.Action); } + [SubscribeLocalEvent] private void OnHostShutdown(Entity ent, ref ComponentShutdown args) { if (ent.Comp.HostedGuardian is not { } guardian) @@ -144,30 +127,46 @@ public sealed partial class GuardianSystem : EntitySystem Dirty(ent); } + [SubscribeLocalEvent] private void OnGuardianAttackAttempt(Entity ent, ref AttackAttemptEvent args) { if (args.Cancelled || args.Target != ent.Comp.Host) return; - _popup.PopupCursor(Loc.GetString("guardian-attack-host"), ent.Owner, PopupType.LargeCaution); + if (_timing.CurTime >= ent.Comp.LastAttackPopupTime + ent.Comp.AttackPopupDelay) + { + _popup.PopupCursor(Loc.GetString("guardian-attack-host"), ent.Owner, PopupType.LargeCaution); + } + ent.Comp.LastAttackPopupTime = _timing.CurTime; + args.Cancel(); } + [SubscribeLocalEvent] private void OnPilotAttackAttempt(Entity ent, ref MechPilotRelayedEvent args) { - if (args.Args.Cancelled) + if (args.Args.Cancelled || args.Args.Attacker != ent.Comp.HostedGuardian) return; - _popup.PopupCursor(Loc.GetString("guardian-attack-host"), - args.Args.Attacker, - PopupType.LargeCaution); + if (_guardianQuery.TryComp(args.Args.Attacker, out var guardian)) + { + if (_timing.CurTime >= guardian.LastAttackPopupTime + guardian.AttackPopupDelay) + { + _popup.PopupCursor(Loc.GetString("guardian-attack-host"), + args.Args.Attacker, + PopupType.LargeCaution); + } + guardian.LastAttackPopupTime = _timing.CurTime; + } + args.Args.Cancelled = true; } + [SubscribeLocalEvent] private void ToggleGuardian(Entity ent) { - if (!TryComp(ent.Comp.HostedGuardian, out var guardianComponent)) + if (!_guardianQuery.TryComp(ent.Comp.HostedGuardian, out var guardianComponent)) return; if (guardianComponent.GuardianLoose) @@ -179,6 +178,7 @@ public sealed partial class GuardianSystem : EntitySystem /// /// Adds the guardian host component to the user and spawns the guardian inside said component. /// + [SubscribeLocalEvent] private void OnCreatorUse(Entity ent, ref UseInHandEvent args) { if (args.Handled) @@ -188,6 +188,7 @@ public sealed partial class GuardianSystem : EntitySystem UseCreator(args.User, args.User, ent); } + [SubscribeLocalEvent] private void OnCreatorInteract(Entity ent, ref AfterInteractEvent args) { if (args.Handled || args.Target == null || !args.CanReach) @@ -216,7 +217,7 @@ public sealed partial class GuardianSystem : EntitySystem } // If user is already a host don't duplicate. - if (HasComp(target)) + if (_guardianHostQuery.HasComp(target)) { _popup.PopupEntity(Loc.GetString("guardian-already-present-invalid-creation"), user, user); return; @@ -236,12 +237,13 @@ public sealed partial class GuardianSystem : EntitySystem }); } + [SubscribeLocalEvent] private void OnDoAfter(Entity ent, ref GuardianCreatorDoAfterEvent args) { if (args.Handled || args.Args.Target == null || args.Cancelled || ent.Comp.Deleted || ent.Comp.Used) return; - if (!_hands.IsHolding(args.Args.User, ent.Owner) || HasComp(args.Args.Target)) + if (!_hands.IsHolding(args.Args.User, ent.Owner) || _guardianHostQuery.HasComp(args.Args.Target)) return; var hostXform = Transform(args.Args.Target.Value); @@ -253,9 +255,10 @@ public sealed partial class GuardianSystem : EntitySystem _container.Insert(guardian, host.GuardianContainer); host.HostedGuardian = guardian; - if (TryComp(guardian, out var guardianComp)) + if (_guardianQuery.TryComp(guardian, out GuardianComponent? guardianComp)) { guardianComp.Host = args.Args.Target.Value; + Dirty(guardian, guardianComp); _audio.PlayPredicted(ent.Comp.UsedSound, ent.Owner, args.Args.Target); @@ -278,10 +281,11 @@ public sealed partial class GuardianSystem : EntitySystem /// /// Triggers when the host receives damage which puts the host in either critical or killed state. /// + [SubscribeLocalEvent] private void OnHostStateChange(Entity ent, ref MobStateChangedEvent args) { if (ent.Comp.HostedGuardian == null || - !TryComp(ent.Comp.HostedGuardian, out var guardianComp)) + !_guardianQuery.TryComp(ent.Comp.HostedGuardian, out GuardianComponent? guardianComp)) return; if (args.NewMobState == MobState.Critical) @@ -301,6 +305,7 @@ public sealed partial class GuardianSystem : EntitySystem /// /// Handles guardian receiving damage and splitting it with the host according to his defense percent. /// + [SubscribeLocalEvent] private void OnGuardianDamaged(Entity ent, ref DamageDealtEvent args) { if (_timing.ApplyingState) @@ -321,6 +326,7 @@ public sealed partial class GuardianSystem : EntitySystem /// /// Triggers while trying to examine an activator to see if it's used. /// + [SubscribeLocalEvent] private void OnCreatorExamine(Entity ent, ref ExaminedEvent args) { if (!ent.Comp.Used) @@ -332,9 +338,10 @@ public sealed partial class GuardianSystem : EntitySystem /// /// Called every time the host moves, to make sure the host and the guardian are not too far away from each other. /// + [SubscribeLocalEvent] private void OnHostMove(Entity ent, ref MoveEvent args) { - if (!TryComp(ent.Comp.HostedGuardian, out var guardianComponent) || + if (!_guardianQuery.TryComp(ent.Comp.HostedGuardian, out GuardianComponent? guardianComponent) || !guardianComponent.GuardianLoose) { return; @@ -346,6 +353,7 @@ public sealed partial class GuardianSystem : EntitySystem /// /// Called every time the guardian moves: makes sure it's not out of it's allowed distance. /// + [SubscribeLocalEvent] private void OnGuardianMove(Entity ent, ref MoveEvent args) { if (!ent.Comp.GuardianLoose)