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)