From 71c3fa8fd732bc7b4f4444713ee12934d330d8e2 Mon Sep 17 00:00:00 2001
From: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Date: Mon, 5 Jan 2026 13:17:17 +0100
Subject: [PATCH] Predict thieving beacon (#39610)
predict thieving beacon
---
.../Components/StealAreaComponent.cs | 23 -----------
.../Foldable/DeployFoldableSystem.cs | 4 +-
Content.Shared/Foldable/FoldableSystem.cs | 15 ++++----
.../Components/StealAreaComponent.cs | 38 +++++++++++++++++++
.../Thief/Components/ThiefBeaconComponent.cs | 8 ++--
.../Thief/Systems/ThiefBeaconSystem.cs | 36 ++++++++++--------
6 files changed, 73 insertions(+), 51 deletions(-)
delete mode 100644 Content.Server/Objectives/Components/StealAreaComponent.cs
create mode 100644 Content.Shared/Objectives/Components/StealAreaComponent.cs
rename {Content.Server => Content.Shared}/Thief/Components/ThiefBeaconComponent.cs (71%)
rename {Content.Server => Content.Shared}/Thief/Systems/ThiefBeaconSystem.cs (70%)
diff --git a/Content.Server/Objectives/Components/StealAreaComponent.cs b/Content.Server/Objectives/Components/StealAreaComponent.cs
deleted file mode 100644
index 26e752f2f2..0000000000
--- a/Content.Server/Objectives/Components/StealAreaComponent.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Content.Server.Objectives.Systems;
-using Content.Server.Thief.Systems;
-
-namespace Content.Server.Objectives.Components;
-
-///
-/// An abstract component that allows other systems to count adjacent objects as "stolen" when controlling other systems
-///
-[RegisterComponent, Access(typeof(StealConditionSystem), typeof(ThiefBeaconSystem))]
-public sealed partial class StealAreaComponent : Component
-{
- [DataField]
- public bool Enabled = true;
-
- [DataField]
- public float Range = 1f;
-
- ///
- /// all the minds that will be credited with stealing from this area.
- ///
- [DataField]
- public HashSet Owners = new();
-}
diff --git a/Content.Shared/Foldable/DeployFoldableSystem.cs b/Content.Shared/Foldable/DeployFoldableSystem.cs
index c690f3d51f..8b510664e4 100644
--- a/Content.Shared/Foldable/DeployFoldableSystem.cs
+++ b/Content.Shared/Foldable/DeployFoldableSystem.cs
@@ -37,7 +37,7 @@ public sealed class DeployFoldableSystem : EntitySystem
private void OnDragDropDragged(Entity ent, ref DragDropDraggedEvent args)
{
if (!TryComp(ent, out var foldable)
- || !_foldable.TrySetFolded(ent, foldable, true))
+ || !_foldable.TrySetFolded(ent, foldable, true, args.User))
return;
_hands.PickupOrDrop(args.User, ent.Owner);
@@ -77,7 +77,7 @@ public sealed class DeployFoldableSystem : EntitySystem
|| !_hands.TryDrop((args.User, hands), args.Used, targetDropLocation: args.ClickLocation))
return;
- if (!_foldable.TrySetFolded(ent, foldable, false))
+ if (!_foldable.TrySetFolded(ent, foldable, false, args.User))
{
_hands.TryPickup(args.User, args.Used, handsComp: hands);
return;
diff --git a/Content.Shared/Foldable/FoldableSystem.cs b/Content.Shared/Foldable/FoldableSystem.cs
index 63ef376d5f..acfec8ac1f 100644
--- a/Content.Shared/Foldable/FoldableSystem.cs
+++ b/Content.Shared/Foldable/FoldableSystem.cs
@@ -78,14 +78,14 @@ public sealed class FoldableSystem : EntitySystem
///
/// Set the folded state of the given
///
- public void SetFolded(EntityUid uid, FoldableComponent component, bool folded)
+ public void SetFolded(EntityUid uid, FoldableComponent component, bool folded, EntityUid? user = null)
{
component.IsFolded = folded;
Dirty(uid, component);
_appearance.SetData(uid, FoldedVisuals.State, folded);
_buckle.StrapSetEnabled(uid, !component.IsFolded);
- var ev = new FoldedEvent(folded);
+ var ev = new FoldedEvent(folded, user);
RaiseLocalEvent(uid, ref ev);
}
@@ -97,7 +97,7 @@ public sealed class FoldableSystem : EntitySystem
public bool TryToggleFold(EntityUid uid, FoldableComponent comp, EntityUid? folder = null)
{
- var result = TrySetFolded(uid, comp, !comp.IsFolded);
+ var result = TrySetFolded(uid, comp, !comp.IsFolded, folder);
if (!result && folder != null)
{
if (comp.IsFolded)
@@ -129,7 +129,7 @@ public sealed class FoldableSystem : EntitySystem
///
/// Try to fold/unfold
///
- public bool TrySetFolded(EntityUid uid, FoldableComponent comp, bool state)
+ public bool TrySetFolded(EntityUid uid, FoldableComponent comp, bool state, EntityUid? user = null)
{
if (state == comp.IsFolded)
return false;
@@ -137,7 +137,7 @@ public sealed class FoldableSystem : EntitySystem
if (!CanToggleFold(uid, comp))
return false;
- SetFolded(uid, comp, state);
+ SetFolded(uid, comp, state, user);
return true;
}
@@ -180,6 +180,7 @@ public record struct FoldAttemptEvent(FoldableComponent Comp, bool Cancelled = f
///
/// Event raised on an entity after it has been folded.
///
-///
+/// True is it has been folded, false if it has been unfolded.
+/// The player who did the folding.
[ByRefEvent]
-public readonly record struct FoldedEvent(bool IsFolded);
+public readonly record struct FoldedEvent(bool IsFolded, EntityUid? User);
diff --git a/Content.Shared/Objectives/Components/StealAreaComponent.cs b/Content.Shared/Objectives/Components/StealAreaComponent.cs
new file mode 100644
index 0000000000..50e9d328ac
--- /dev/null
+++ b/Content.Shared/Objectives/Components/StealAreaComponent.cs
@@ -0,0 +1,38 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Objectives.Components;
+
+///
+/// An abstract component that allows other systems to count adjacent objects as "stolen" when controlling other systems
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class StealAreaComponent : Component
+{
+ ///
+ /// Is the component currently enabled?
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Enabled = true;
+
+ ///
+ /// The range to check for items in.
+ ///
+ [DataField, AutoNetworkedField]
+ public float Range = 1f;
+
+ ///
+ /// All the minds that will be credited with stealing from this area.
+ ///
+ ///
+ /// TODO: Network this when we have WeakEntityReference.
+ ///
+ [DataField]
+ public HashSet Owners = new();
+
+ ///
+ /// The count of the owner hashset.
+ /// This is a separate datafield because networking the list would cause PVS errors if an entity inside would be deleted and networked.
+ ///
+ [DataField, AutoNetworkedField]
+ public int OwnerCount = 0;
+}
diff --git a/Content.Server/Thief/Components/ThiefBeaconComponent.cs b/Content.Shared/Thief/Components/ThiefBeaconComponent.cs
similarity index 71%
rename from Content.Server/Thief/Components/ThiefBeaconComponent.cs
rename to Content.Shared/Thief/Components/ThiefBeaconComponent.cs
index 65db79f861..5701254c98 100644
--- a/Content.Server/Thief/Components/ThiefBeaconComponent.cs
+++ b/Content.Shared/Thief/Components/ThiefBeaconComponent.cs
@@ -1,12 +1,14 @@
-using Content.Server.Thief.Systems;
+using Content.Shared.Thief.Systems;
using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
-namespace Content.Server.Thief.Components;
+namespace Content.Shared.Thief.Components;
///
/// working together with StealAreaComponent, allows the thief to count objects near the beacon as stolen when setting up.
///
-[RegisterComponent, Access(typeof(ThiefBeaconSystem))]
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(ThiefBeaconSystem))]
public sealed partial class ThiefBeaconComponent : Component
{
[DataField]
diff --git a/Content.Server/Thief/Systems/ThiefBeaconSystem.cs b/Content.Shared/Thief/Systems/ThiefBeaconSystem.cs
similarity index 70%
rename from Content.Server/Thief/Systems/ThiefBeaconSystem.cs
rename to Content.Shared/Thief/Systems/ThiefBeaconSystem.cs
index 069966a8a4..bff33cb781 100644
--- a/Content.Server/Thief/Systems/ThiefBeaconSystem.cs
+++ b/Content.Shared/Thief/Systems/ThiefBeaconSystem.cs
@@ -1,15 +1,15 @@
-using Content.Server.Mind;
-using Content.Server.Objectives.Components;
-using Content.Server.Thief.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Roles;
+using Content.Shared.Thief.Components;
using Content.Shared.Examine;
using Content.Shared.Foldable;
using Content.Shared.Popups;
using Content.Shared.Verbs;
-using Content.Shared.Roles;
using Content.Shared.Roles.Components;
using Robust.Shared.Audio.Systems;
-namespace Content.Server.Thief.Systems;
+namespace Content.Shared.Thief.Systems;
///
///
@@ -18,7 +18,7 @@ public sealed class ThiefBeaconSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly MindSystem _mind = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
public override void Initialize()
{
@@ -46,7 +46,7 @@ public sealed class ThiefBeaconSystem : EntitySystem
{
Act = () =>
{
- SetCoordinate(beacon, mind.Value);
+ SetCoordinate(beacon, mind.Value, user);
},
Message = Loc.GetString("thief-fulton-verb-message"),
Text = Loc.GetString("thief-fulton-verb-text"),
@@ -56,7 +56,7 @@ public sealed class ThiefBeaconSystem : EntitySystem
private void OnFolded(Entity beacon, ref FoldedEvent args)
{
if (args.IsFolded)
- ClearCoordinate(beacon);
+ ClearCoordinate(beacon, args.User);
}
private void OnExamined(Entity beacon, ref ExaminedEvent args)
@@ -64,23 +64,25 @@ public sealed class ThiefBeaconSystem : EntitySystem
if (!TryComp(beacon, out var area))
return;
- args.PushText(Loc.GetString(area.Owners.Count == 0
+ args.PushText(Loc.GetString(area.OwnerCount == 0
? "thief-fulton-examined-unset"
: "thief-fulton-examined-set"));
}
- private void SetCoordinate(Entity beacon, EntityUid mind)
+ private void SetCoordinate(Entity beacon, EntityUid mind, EntityUid? user = null)
{
if (!TryComp(beacon, out var area))
return;
- _audio.PlayPvs(beacon.Comp.LinkSound, beacon);
- _popup.PopupEntity(Loc.GetString("thief-fulton-set"), beacon);
- area.Owners.Clear(); //We only reconfigure the beacon for ourselves, we don't need multiple thieves to steal from the same beacon.
+ _audio.PlayPredicted(beacon.Comp.LinkSound, beacon, user);
+ _popup.PopupClient(Loc.GetString("thief-fulton-set"), beacon, user);
+ area.Owners.Clear(); // We only reconfigure the beacon for ourselves, we don't need multiple thieves to steal from the same beacon.
area.Owners.Add(mind);
+ area.OwnerCount = area.Owners.Count;
+ Dirty(beacon.Owner, area);
}
- private void ClearCoordinate(Entity beacon)
+ private void ClearCoordinate(Entity beacon, EntityUid? user = null)
{
if (!TryComp(beacon, out var area))
return;
@@ -88,8 +90,10 @@ public sealed class ThiefBeaconSystem : EntitySystem
if (area.Owners.Count == 0)
return;
- _audio.PlayPvs(beacon.Comp.UnlinkSound, beacon);
- _popup.PopupEntity(Loc.GetString("thief-fulton-clear"), beacon);
+ _audio.PlayPredicted(beacon.Comp.UnlinkSound, beacon, user);
+ _popup.PopupClient(Loc.GetString("thief-fulton-clear"), beacon, user);
area.Owners.Clear();
+ area.OwnerCount = area.Owners.Count;
+ Dirty(beacon.Owner, area);
}
}
--
2.52.0