From c858c76341e90a0dfdb89c073d13bc6bbc1b0a7a Mon Sep 17 00:00:00 2001 From: Ben <50087092+benev0@users.noreply.github.com> Date: Thu, 29 Jun 2023 08:35:54 -0400 Subject: [PATCH] Anchorable shared (#17422) Co-authored-by: BenOwnby Co-authored-by: metalgearsloth Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- .../EntitySystems/AnchorableSystem.cs | 5 - .../Abilities/Mime/MimePowersSystem.cs | 2 +- .../Botany/Systems/PlantHolderSystem.cs | 2 +- .../ReactionEffects/AreaReactionEffect.cs | 2 +- .../Construction/AnchorableSystem.cs | 268 -------------- .../Construction/Completions/SnapToGrid.cs | 2 +- .../ConstructionSystem.Interactions.cs | 5 +- .../EntitySystems/SpawnAfterInteractSystem.cs | 2 +- .../Fluids/EntitySystems/SmokeSystem.cs | 2 +- Content.Server/Holosign/HolosignSystem.cs | 2 +- .../Kitchen/EntitySystems/MicrowaveSystem.cs | 1 + Content.Server/Magic/MagicSystem.cs | 2 +- Content.Server/Maps/TileSystem.cs | 4 +- Content.Server/Nuke/NukeSystem.cs | 2 +- Content.Server/Procedural/DungeonJob.cs | 1 + Content.Server/Procedural/DungeonSystem.cs | 1 + .../Components/AnchorableComponent.cs | 2 +- .../Conditions/NoUnstackableInTile.cs | 2 +- .../EntitySystems/AnchorableSystem.cs | 342 ++++++++++++++++++ .../EntitySystems/SharedAnchorableSystem.cs | 97 ----- .../Coordinates/Helpers/SnapgridHelper.cs | 2 +- 21 files changed, 363 insertions(+), 385 deletions(-) delete mode 100644 Content.Client/Construction/EntitySystems/AnchorableSystem.cs delete mode 100644 Content.Server/Construction/AnchorableSystem.cs create mode 100644 Content.Shared/Construction/EntitySystems/AnchorableSystem.cs delete mode 100644 Content.Shared/Construction/EntitySystems/SharedAnchorableSystem.cs rename {Content.Server => Content.Shared}/Coordinates/Helpers/SnapgridHelper.cs (96%) diff --git a/Content.Client/Construction/EntitySystems/AnchorableSystem.cs b/Content.Client/Construction/EntitySystems/AnchorableSystem.cs deleted file mode 100644 index 731d5cec9f..0000000000 --- a/Content.Client/Construction/EntitySystems/AnchorableSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Content.Shared.Construction.EntitySystems; - -namespace Content.Client.Construction.EntitySystems; - -public sealed class AnchorableSystem : SharedAnchorableSystem { } diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index cd5e2044c4..8b11a3bb11 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -1,7 +1,7 @@ using Content.Server.Popups; -using Content.Server.Coordinates.Helpers; using Content.Shared.Actions; using Content.Shared.Alert; +using Content.Shared.Coordinates.Helpers; using Content.Shared.Physics; using Content.Shared.Doors.Components; using Content.Shared.Maps; diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 04ec24e9fc..66d373873d 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; using Content.Server.Botany.Components; using Content.Server.Chemistry.EntitySystems; -using Content.Server.Coordinates.Helpers; using Content.Server.Fluids.Components; using Content.Server.Ghost.Roles.Components; using Content.Server.Kitchen.Components; @@ -10,6 +9,7 @@ using Content.Server.Popups; using Content.Shared.Botany; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; +using Content.Shared.Coordinates.Helpers; using Content.Shared.Examine; using Content.Shared.FixedPoint; using Content.Shared.Hands.Components; diff --git a/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs index 8c1c3f4835..e323d9c834 100644 --- a/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs +++ b/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs @@ -1,9 +1,9 @@ using Content.Server.Chemistry.Components; using Content.Server.Chemistry.EntitySystems; -using Content.Server.Coordinates.Helpers; using Content.Server.Fluids.EntitySystems; using Content.Shared.Audio; using Content.Shared.Chemistry.Reagent; +using Content.Shared.Coordinates.Helpers; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Maps; diff --git a/Content.Server/Construction/AnchorableSystem.cs b/Content.Server/Construction/AnchorableSystem.cs deleted file mode 100644 index 309ed9ebe6..0000000000 --- a/Content.Server/Construction/AnchorableSystem.cs +++ /dev/null @@ -1,268 +0,0 @@ -using Content.Server.Administration.Logs; -using Content.Server.Construction.Components; -using Content.Server.Coordinates.Helpers; -using Content.Server.Popups; -using Content.Server.Pulling; -using Content.Shared.Construction.Components; -using Content.Shared.Construction.Conditions; -using Content.Shared.Construction.EntitySystems; -using Content.Shared.Database; -using Content.Shared.DoAfter; -using Content.Shared.Examine; -using Content.Shared.Pulling.Components; -using Content.Shared.Tag; -using Content.Shared.Tools; -using Content.Shared.Tools.Components; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Physics.Components; - -namespace Content.Server.Construction -{ - public sealed class AnchorableSystem : SharedAnchorableSystem - { - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly SharedToolSystem _tool = default!; - [Dependency] private readonly PullingSystem _pulling = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnAnchorComplete); - SubscribeLocalEvent(OnUnanchorComplete); - SubscribeLocalEvent(OnAnchoredExamine); - } - - private void OnAnchoredExamine(EntityUid uid, AnchorableComponent component, ExaminedEvent args) - { - var isAnchored = Comp(uid).Anchored; - var messageId = isAnchored ? "examinable-anchored" : "examinable-unanchored"; - args.PushMarkup(Loc.GetString(messageId, ("target", uid))); - } - - private void OnUnanchorComplete(EntityUid uid, AnchorableComponent component, TryUnanchorCompletedEvent args) - { - if (args.Cancelled || args.Used is not { } used) - return; - - var xform = Transform(uid); - - RaiseLocalEvent(uid, new BeforeUnanchoredEvent(args.User, used)); - _transform.Unanchor(uid, xform); - RaiseLocalEvent(uid, new UserUnanchoredEvent(args.User, used)); - - _popup.PopupEntity(Loc.GetString("anchorable-unanchored"), uid); - - _adminLogger.Add( - LogType.Unanchor, - LogImpact.Low, - $"{EntityManager.ToPrettyString(args.User):user} unanchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(used):using}" - ); - } - - private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryAnchorCompletedEvent args) - { - if (args.Cancelled || args.Used is not { } used) - return; - - var xform = Transform(uid); - if (TryComp(uid, out var anchorBody) && - !TileFree(xform.Coordinates, anchorBody)) - { - _popup.PopupEntity(Loc.GetString("anchorable-occupied"), uid, args.User); - return; - } - - // Snap rotation to cardinal (multiple of 90) - var rot = xform.LocalRotation; - xform.LocalRotation = Math.Round(rot / (Math.PI / 2)) * (Math.PI / 2); - - if (TryComp(uid, out var pullable) && pullable.Puller != null) - { - _pulling.TryStopPull(pullable); - } - - // TODO: Anchoring snaps rn anyway! - if (component.Snap) - { - var coordinates = xform.Coordinates.SnapToGrid(EntityManager, _mapManager); - - if (AnyUnstackable(uid, coordinates)) - { - _popup.PopupEntity(Loc.GetString("construction-step-condition-no-unstackable-in-tile"), uid, args.User); - return; - } - - _transform.SetCoordinates(uid, coordinates); - } - - RaiseLocalEvent(uid, new BeforeAnchoredEvent(args.User, used)); - - if (!xform.Anchored) - _transform.AnchorEntity(uid, xform); - - RaiseLocalEvent(uid, new UserAnchoredEvent(args.User, used)); - - _popup.PopupEntity(Loc.GetString("anchorable-anchored"), uid); - - _adminLogger.Add( - LogType.Anchor, - LogImpact.Low, - $"{EntityManager.ToPrettyString(args.User):user} anchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(used):using}" - ); - } - - private bool TileFree(EntityCoordinates coordinates, PhysicsComponent anchorBody) - { - // Probably ignore CanCollide on the anchoring body? - var gridUid = coordinates.GetGridUid(EntityManager); - - if (!_mapManager.TryGetGrid(gridUid, out var grid)) - return false; - - var tileIndices = grid.TileIndicesFor(coordinates); - return TileFree(grid, tileIndices, anchorBody.CollisionLayer, anchorBody.CollisionMask); - } - - public bool TileFree(MapGridComponent grid, Vector2i gridIndices, int collisionLayer = 0, int collisionMask = 0) - { - var enumerator = grid.GetAnchoredEntitiesEnumerator(gridIndices); - var bodyQuery = GetEntityQuery(); - - while (enumerator.MoveNext(out var ent)) - { - if (!bodyQuery.TryGetComponent(ent, out var body) || - !body.CanCollide || - !body.Hard) - { - continue; - } - - if ((body.CollisionMask & collisionLayer) != 0x0 || - (body.CollisionLayer & collisionMask) != 0x0) - { - return false; - } - } - - return true; - } - - /// - /// Checks if a tool can change the anchored status. - /// - /// true if it is valid, false otherwise - private bool Valid(EntityUid uid, EntityUid userUid, EntityUid usingUid, bool anchoring, AnchorableComponent? anchorable = null, ToolComponent? usingTool = null) - { - if (!Resolve(uid, ref anchorable)) - return false; - - if (!Resolve(usingUid, ref usingTool)) - return false; - - BaseAnchoredAttemptEvent attempt = - anchoring ? new AnchorAttemptEvent(userUid, usingUid) : new UnanchorAttemptEvent(userUid, usingUid); - - // Need to cast the event or it will be raised as BaseAnchoredAttemptEvent. - if (anchoring) - RaiseLocalEvent(uid, (AnchorAttemptEvent) attempt); - else - RaiseLocalEvent(uid, (UnanchorAttemptEvent) attempt); - - anchorable.Delay += attempt.Delay; - - return !attempt.Cancelled; - } - - /// - /// Tries to anchor the entity. - /// - /// true if anchored, false otherwise - private void TryAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid, - AnchorableComponent? anchorable = null, - TransformComponent? transform = null, - SharedPullableComponent? pullable = null, - ToolComponent? usingTool = null) - { - if (!Resolve(uid, ref anchorable, ref transform)) - return; - - // Optional resolves. - Resolve(uid, ref pullable, false); - - if (!Resolve(usingUid, ref usingTool)) - return; - - if (!Valid(uid, userUid, usingUid, true, anchorable, usingTool)) - return; - - if (TryComp(uid, out var anchorBody) && - !TileFree(transform.Coordinates, anchorBody)) - { - _popup.PopupEntity(Loc.GetString("anchorable-occupied"), uid, userUid); - return; - } - - if (AnyUnstackable(uid, transform.Coordinates)) - { - _popup.PopupEntity(Loc.GetString("construction-step-condition-no-unstackable-in-tile"), uid, userUid); - return; - } - - _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, new TryAnchorCompletedEvent()); - } - - /// - /// Tries to unanchor the entity. - /// - /// true if unanchored, false otherwise - private void TryUnAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid, - AnchorableComponent? anchorable = null, - TransformComponent? transform = null, - ToolComponent? usingTool = null) - { - if (!Resolve(uid, ref anchorable, ref transform)) - return; - - if (!Resolve(usingUid, ref usingTool)) - return; - - if (!Valid(uid, userUid, usingUid, false)) - return; - - _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, new TryUnanchorCompletedEvent()); - } - - /// - /// Tries to toggle the anchored status of this component's owner. - /// - /// true if toggled, false otherwise - public override void TryToggleAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid, - AnchorableComponent? anchorable = null, - TransformComponent? transform = null, - SharedPullableComponent? pullable = null, - ToolComponent? usingTool = null) - { - if (!Resolve(uid, ref transform)) - return; - - if (transform.Anchored) - { - TryUnAnchor(uid, userUid, usingUid, anchorable, transform, usingTool); - - // Log unanchor attempt - _adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to unanchor {ToPrettyString(uid):entity} from {transform.Coordinates:targetlocation}"); - } - else - { - TryAnchor(uid, userUid, usingUid, anchorable, transform, pullable, usingTool); - - // Log anchor attempt - _adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to anchor {ToPrettyString(uid):entity} to {transform.Coordinates:targetlocation}"); - } - } - } -} diff --git a/Content.Server/Construction/Completions/SnapToGrid.cs b/Content.Server/Construction/Completions/SnapToGrid.cs index 2ef0663972..4793a73a42 100644 --- a/Content.Server/Construction/Completions/SnapToGrid.cs +++ b/Content.Server/Construction/Completions/SnapToGrid.cs @@ -1,4 +1,4 @@ -using Content.Server.Coordinates.Helpers; +using Content.Shared.Coordinates.Helpers; using Content.Shared.Construction; using JetBrains.Annotations; diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs index 9204669fe8..dee013bf71 100644 --- a/Content.Server/Construction/ConstructionSystem.Interactions.cs +++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs @@ -4,6 +4,7 @@ using Content.Server.Construction.Components; using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; using Content.Shared.Construction; +using Content.Shared.Construction.EntitySystems; using Content.Shared.Construction.Steps; using Content.Shared.DoAfter; using Content.Shared.Interaction; @@ -34,7 +35,9 @@ namespace Content.Server.Construction SubscribeLocalEvent(EnqueueEvent); // Event handling. Add your subscriptions here! Just make sure they're all handled by EnqueueEvent. - SubscribeLocalEvent(EnqueueEvent, new []{typeof(AnchorableSystem)}, new []{typeof(EncryptionKeySystem)}); + SubscribeLocalEvent(EnqueueEvent, + new []{typeof(AnchorableSystem)}, + new []{typeof(EncryptionKeySystem)}); SubscribeLocalEvent(EnqueueEvent); } diff --git a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs index 89a74b2558..696db2ec3a 100644 --- a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs +++ b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Coordinates.Helpers; +using Content.Shared.Coordinates.Helpers; using Content.Server.Engineering.Components; using Content.Server.Stack; using Content.Shared.DoAfter; diff --git a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs index 9de99ef939..831ca9c72b 100644 --- a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs @@ -5,12 +5,12 @@ using Content.Server.Body.Systems; using Content.Server.Chemistry.Components; using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.ReactionEffects; -using Content.Server.Coordinates.Helpers; using Content.Server.Spreader; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; +using Content.Shared.Coordinates.Helpers; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Smoking; diff --git a/Content.Server/Holosign/HolosignSystem.cs b/Content.Server/Holosign/HolosignSystem.cs index b832a86bca..67f4cec43d 100644 --- a/Content.Server/Holosign/HolosignSystem.cs +++ b/Content.Server/Holosign/HolosignSystem.cs @@ -1,6 +1,6 @@ using Content.Shared.Interaction.Events; using Content.Shared.Examine; -using Content.Server.Coordinates.Helpers; +using Content.Shared.Coordinates.Helpers; using Content.Server.Power.Components; using Content.Server.PowerCell; diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index ecf6fa58a8..66b3c03910 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -10,6 +10,7 @@ using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; using Content.Shared.Body.Components; using Content.Shared.Body.Part; +using Content.Shared.Construction.EntitySystems; using Content.Shared.Destructible; using Content.Shared.FixedPoint; using Content.Shared.Interaction; diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs index cd2d356ef2..a8ada5280c 100644 --- a/Content.Server/Magic/MagicSystem.cs +++ b/Content.Server/Magic/MagicSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chat.Systems; -using Content.Server.Coordinates.Helpers; using Content.Server.Doors.Systems; using Content.Server.Magic.Components; using Content.Server.Magic.Events; @@ -9,6 +8,7 @@ using Content.Server.Weapons.Ranged.Systems; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; using Content.Shared.Body.Components; +using Content.Shared.Coordinates.Helpers; using Content.Shared.DoAfter; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; diff --git a/Content.Server/Maps/TileSystem.cs b/Content.Server/Maps/TileSystem.cs index eba9e8e76b..9d31c752c2 100644 --- a/Content.Server/Maps/TileSystem.cs +++ b/Content.Server/Maps/TileSystem.cs @@ -1,5 +1,5 @@ -using Content.Server.Coordinates.Helpers; -using Content.Server.Decals; +using Content.Server.Decals; +using Content.Shared.Coordinates.Helpers; using Content.Shared.Decals; using Content.Shared.Maps; using Robust.Shared.Map; diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index d6cc2f4795..cd033cc81a 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -1,13 +1,13 @@ using Content.Server.AlertLevel; using Content.Server.Audio; using Content.Server.Chat.Systems; -using Content.Server.Coordinates.Helpers; using Content.Server.Explosion.EntitySystems; using Content.Server.Popups; using Content.Server.Station.Systems; using Content.Shared.Audio; using Content.Shared.Construction.Components; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Coordinates.Helpers; using Content.Shared.DoAfter; using Content.Shared.Nuke; using Content.Shared.Popups; diff --git a/Content.Server/Procedural/DungeonJob.cs b/Content.Server/Procedural/DungeonJob.cs index f2093e51cf..4da585586a 100644 --- a/Content.Server/Procedural/DungeonJob.cs +++ b/Content.Server/Procedural/DungeonJob.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Content.Server.Construction; using Robust.Shared.CPUJob.JobQueues; using Content.Server.Decals; +using Content.Shared.Construction.EntitySystems; using Content.Shared.Procedural; using Content.Shared.Procedural.DungeonGenerators; using Content.Shared.Procedural.PostGeneration; diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index f2be4fbbcc..e7ffc4fc02 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -5,6 +5,7 @@ using Robust.Shared.CPUJob.JobQueues.Queues; using Content.Server.Decals; using Content.Server.GameTicking.Events; using Content.Shared.CCVar; +using Content.Shared.Construction.EntitySystems; using Content.Shared.Procedural; using Robust.Server.GameObjects; using Robust.Shared.Configuration; diff --git a/Content.Shared/Construction/Components/AnchorableComponent.cs b/Content.Shared/Construction/Components/AnchorableComponent.cs index 0a92b28abe..455ce2f9ba 100644 --- a/Content.Shared/Construction/Components/AnchorableComponent.cs +++ b/Content.Shared/Construction/Components/AnchorableComponent.cs @@ -4,7 +4,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Shared.Construction.Components { - [RegisterComponent, Access(typeof(SharedAnchorableSystem))] + [RegisterComponent, Access(typeof(AnchorableSystem))] public sealed class AnchorableComponent : Component { [DataField("tool", customTypeSerializer: typeof(PrototypeIdSerializer))] diff --git a/Content.Shared/Construction/Conditions/NoUnstackableInTile.cs b/Content.Shared/Construction/Conditions/NoUnstackableInTile.cs index 84e048db9a..27315efbd6 100644 --- a/Content.Shared/Construction/Conditions/NoUnstackableInTile.cs +++ b/Content.Shared/Construction/Conditions/NoUnstackableInTile.cs @@ -16,7 +16,7 @@ public sealed class NoUnstackableInTile : IConstructionCondition public bool Condition(EntityUid user, EntityCoordinates location, Direction direction) { var sysMan = IoCManager.Resolve(); - var anchorable = sysMan.GetEntitySystem(); + var anchorable = sysMan.GetEntitySystem(); return !anchorable.AnyUnstackablesAnchoredAt(location); } diff --git a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs new file mode 100644 index 0000000000..0951583c53 --- /dev/null +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -0,0 +1,342 @@ +using Content.Shared.Administration.Logs; +using Content.Shared.Examine; +using Content.Shared.Construction.Components; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Coordinates.Helpers; +using Content.Shared.Database; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; +using Content.Shared.Tools; +using Content.Shared.Tools.Components; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics.Components; +using Content.Shared.Tag; +using Robust.Shared.Player; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Construction.EntitySystems; + +public sealed class AnchorableSystem : EntitySystem +{ + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPullingSystem _pulling = default!; + [Dependency] private readonly SharedToolSystem _tool = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + + private EntityQuery _physicsQuery; + private EntityQuery _tagQuery; + + public const string Unstackable = "Unstackable"; + + public override void Initialize() + { + base.Initialize(); + + _physicsQuery = GetEntityQuery(); + _tagQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnInteractUsing, + before: new[] { typeof(ItemSlotsSystem) }, after: new[] { typeof(SharedConstructionSystem) }); + SubscribeLocalEvent(OnAnchorComplete); + SubscribeLocalEvent(OnUnanchorComplete); + SubscribeLocalEvent(OnAnchoredExamine); + } + + /// + /// Tries to unanchor the entity. + /// + /// true if unanchored, false otherwise + private void TryUnAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid, + AnchorableComponent? anchorable = null, + TransformComponent? transform = null, + ToolComponent? usingTool = null) + { + if (!Resolve(uid, ref anchorable, ref transform)) + return; + + if (!Resolve(usingUid, ref usingTool)) + return; + + if (!Valid(uid, userUid, usingUid, false)) + return; + + _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, new TryUnanchorCompletedEvent()); + } + + private void OnInteractUsing(EntityUid uid, AnchorableComponent anchorable, InteractUsingEvent args) + { + if (args.Handled) + return; + + // If the used entity doesn't have a tool, return early. + if (!TryComp(args.Used, out ToolComponent? usedTool) || !usedTool.Qualities.Contains(anchorable.Tool)) + return; + + args.Handled = true; + TryToggleAnchor(uid, args.User, args.Used, anchorable, usingTool: usedTool); + } + + private void OnAnchoredExamine(EntityUid uid, AnchorableComponent component, ExaminedEvent args) + { + var isAnchored = Comp(uid).Anchored; + var messageId = isAnchored ? "examinable-anchored" : "examinable-unanchored"; + args.PushMarkup(Loc.GetString(messageId, ("target", uid))); + } + + private void OnUnanchorComplete(EntityUid uid, AnchorableComponent component, TryUnanchorCompletedEvent args) + { + if (args.Cancelled || args.Used is not { } used) + return; + + var xform = Transform(uid); + + RaiseLocalEvent(uid, new BeforeUnanchoredEvent(args.User, used)); + _transformSystem.Unanchor(uid, xform); + RaiseLocalEvent(uid, new UserUnanchoredEvent(args.User, used)); + + _popup.PopupClient(Loc.GetString("anchorable-unanchored"), uid, args.User); + + _adminLogger.Add( + LogType.Unanchor, + LogImpact.Low, + $"{EntityManager.ToPrettyString(args.User):user} unanchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(used):using}" + ); + } + + private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryAnchorCompletedEvent args) + { + if (args.Cancelled || args.Used is not { } used) + return; + + var xform = Transform(uid); + if (TryComp(uid, out var anchorBody) && + !TileFree(xform.Coordinates, anchorBody)) + { + _popup.PopupClient(Loc.GetString("anchorable-occupied"), uid, args.User); + return; + } + + // Snap rotation to cardinal (multiple of 90) + var rot = xform.LocalRotation; + xform.LocalRotation = Math.Round(rot / (Math.PI / 2)) * (Math.PI / 2); + + if (TryComp(uid, out var pullable) && pullable.Puller != null) + { + _pulling.TryStopPull(pullable); + } + + // TODO: Anchoring snaps rn anyway! + if (component.Snap) + { + var coordinates = xform.Coordinates.SnapToGrid(EntityManager, _mapManager); + + if (AnyUnstackable(uid, coordinates)) + { + _popup.PopupClient(Loc.GetString("construction-step-condition-no-unstackable-in-tile"), uid, args.User); + return; + } + + _transformSystem.SetCoordinates(uid, coordinates); + } + + RaiseLocalEvent(uid, new BeforeAnchoredEvent(args.User, used)); + + if (!xform.Anchored) + _transformSystem.AnchorEntity(uid, xform); + + RaiseLocalEvent(uid, new UserAnchoredEvent(args.User, used)); + + _popup.PopupClient(Loc.GetString("anchorable-anchored"), uid, args.User); + + _adminLogger.Add( + LogType.Anchor, + LogImpact.Low, + $"{EntityManager.ToPrettyString(args.User):user} anchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(used):using}" + ); + } + + /// + /// Tries to toggle the anchored status of this component's owner. + /// override is used due to popup and adminlog being server side systems in this case. + /// + /// true if toggled, false otherwise + public void TryToggleAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid, + AnchorableComponent? anchorable = null, + TransformComponent? transform = null, + SharedPullableComponent? pullable = null, + ToolComponent? usingTool = null) + { + if (!Resolve(uid, ref transform)) + return; + + if (transform.Anchored) + { + TryUnAnchor(uid, userUid, usingUid, anchorable, transform, usingTool); + + // Log unanchor attempt (server only) + _adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to unanchor {ToPrettyString(uid):entity} from {transform.Coordinates:targetlocation}"); + } + else + { + TryAnchor(uid, userUid, usingUid, anchorable, transform, pullable, usingTool); + + // Log anchor attempt (server only) + _adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to anchor {ToPrettyString(uid):entity} to {transform.Coordinates:targetlocation}"); + } + } + + /// + /// Tries to anchor the entity. + /// + /// true if anchored, false otherwise + private void TryAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid, + AnchorableComponent? anchorable = null, + TransformComponent? transform = null, + SharedPullableComponent? pullable = null, + ToolComponent? usingTool = null) + { + if (!Resolve(uid, ref anchorable, ref transform)) + return; + + // Optional resolves. + Resolve(uid, ref pullable, false); + + if (!Resolve(usingUid, ref usingTool)) + return; + + if (!Valid(uid, userUid, usingUid, true, anchorable, usingTool)) + return; + + if (TryComp(uid, out var anchorBody) && + !TileFree(transform.Coordinates, anchorBody)) + { + _popup.PopupClient(Loc.GetString("anchorable-occupied"), uid, userUid); + return; + } + + if (AnyUnstackable(uid, transform.Coordinates)) + { + _popup.PopupClient(Loc.GetString("construction-step-condition-no-unstackable-in-tile"), uid, userUid); + return; + } + + _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, new TryAnchorCompletedEvent()); + } + + private bool Valid( + EntityUid uid, + EntityUid userUid, + EntityUid usingUid, + bool anchoring, + AnchorableComponent? anchorable = null, + ToolComponent? usingTool = null) + { + if (!Resolve(uid, ref anchorable)) + return false; + + if (!Resolve(usingUid, ref usingTool)) + return false; + + BaseAnchoredAttemptEvent attempt = + anchoring ? new AnchorAttemptEvent(userUid, usingUid) : new UnanchorAttemptEvent(userUid, usingUid); + + // Need to cast the event or it will be raised as BaseAnchoredAttemptEvent. + if (anchoring) + RaiseLocalEvent(uid, (AnchorAttemptEvent) attempt); + else + RaiseLocalEvent(uid, (UnanchorAttemptEvent) attempt); + + anchorable.Delay += attempt.Delay; + + return !attempt.Cancelled; + } + + private bool TileFree(EntityCoordinates coordinates, PhysicsComponent anchorBody) + { + // Probably ignore CanCollide on the anchoring body? + var gridUid = coordinates.GetGridUid(EntityManager); + + if (!_mapManager.TryGetGrid(gridUid, out var grid)) + return false; + + var tileIndices = grid.TileIndicesFor(coordinates); + return TileFree(grid, tileIndices, anchorBody.CollisionLayer, anchorBody.CollisionMask); + } + + /// + /// Returns true if no hard anchored entities match the collision layer or mask specified. + /// + /// + public bool TileFree(MapGridComponent grid, Vector2i gridIndices, int collisionLayer = 0, int collisionMask = 0) + { + var enumerator = grid.GetAnchoredEntitiesEnumerator(gridIndices); + + while (enumerator.MoveNext(out var ent)) + { + if (!_physicsQuery.TryGetComponent(ent, out var body) || + !body.CanCollide || + !body.Hard) + { + continue; + } + + if ((body.CollisionMask & collisionLayer) != 0x0 || + (body.CollisionLayer & collisionMask) != 0x0) + { + return false; + } + } + + return true; + } + + /// + /// Returns true if any unstackables are also on the corresponding tile. + /// + public bool AnyUnstackable(EntityUid uid, EntityCoordinates location) + { + DebugTools.Assert(!Transform(uid).Anchored); + + // If we are unstackable, iterate through any other entities anchored on the current square + return _tagSystem.HasTag(uid, Unstackable, _tagQuery) && AnyUnstackablesAnchoredAt(location); + } + + public bool AnyUnstackablesAnchoredAt(EntityCoordinates location) + { + var gridUid = location.GetGridUid(EntityManager); + + if (!TryComp(gridUid, out var grid)) + return false; + + var enumerator = grid.GetAnchoredEntitiesEnumerator(grid.LocalToTile(location)); + + while (enumerator.MoveNext(out var entity)) + { + // If we find another unstackable here, return true. + if (_tagSystem.HasTag(entity.Value, Unstackable, _tagQuery)) + { + return true; + } + } + + return false; + } + + [Serializable, NetSerializable] + private sealed class TryUnanchorCompletedEvent : SimpleDoAfterEvent + { + } + + [Serializable, NetSerializable] + private sealed class TryAnchorCompletedEvent : SimpleDoAfterEvent + { + } +} diff --git a/Content.Shared/Construction/EntitySystems/SharedAnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/SharedAnchorableSystem.cs deleted file mode 100644 index df15b9b872..0000000000 --- a/Content.Shared/Construction/EntitySystems/SharedAnchorableSystem.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Content.Shared.Construction.Components; -using Content.Shared.Containers.ItemSlots; -using Content.Shared.DoAfter; -using Content.Shared.Interaction; -using Content.Shared.Pulling.Components; -using Content.Shared.Tag; -using Content.Shared.Tools.Components; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Serialization; - -namespace Content.Shared.Construction.EntitySystems; - -public abstract class SharedAnchorableSystem : EntitySystem -{ - [Dependency] private readonly TagSystem _tagSystem = default!; - - protected EntityQuery TagQuery; - - public const string Unstackable = "Unstackable"; - - public override void Initialize() - { - base.Initialize(); - - TagQuery = GetEntityQuery(); - - SubscribeLocalEvent(OnInteractUsing, - before: new[] { typeof(ItemSlotsSystem) }, after: new[] { typeof(SharedConstructionSystem) }); - } - - private void OnInteractUsing(EntityUid uid, AnchorableComponent anchorable, InteractUsingEvent args) - { - if (args.Handled) - return; - - // If the used entity doesn't have a tool, return early. - if (!TryComp(args.Used, out ToolComponent? usedTool) || !usedTool.Qualities.Contains(anchorable.Tool)) - return; - - args.Handled = true; - TryToggleAnchor(uid, args.User, args.Used, anchorable, usingTool: usedTool); - } - - public virtual void TryToggleAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid, - AnchorableComponent? anchorable = null, - TransformComponent? transform = null, - SharedPullableComponent? pullable = null, - ToolComponent? usingTool = null) - { - // Thanks tool system. - - // TODO tool system is fixed now, make this actually shared. - } - - public bool AnyUnstackablesAnchoredAt(EntityCoordinates location) - { - var gridUid = location.GetGridUid(EntityManager); - - if (!TryComp(gridUid, out var grid)) - return false; - - var enumerator = grid.GetAnchoredEntitiesEnumerator(grid.LocalToTile(location)); - - while (enumerator.MoveNext(out var entity)) - { - // If we find another unstackable here, return true. - if (_tagSystem.HasTag(entity.Value, Unstackable, TagQuery)) - { - return true; - } - } - - return false; - } - - public bool AnyUnstackable(EntityUid uid, EntityCoordinates location) - { - // If we are unstackable, iterate through any other entities anchored on the current square - if (_tagSystem.HasTag(uid, Unstackable, TagQuery)) - { - return AnyUnstackablesAnchoredAt(location); - } - - return false; - } - - [Serializable, NetSerializable] - protected sealed class TryUnanchorCompletedEvent : SimpleDoAfterEvent - { - } - - [Serializable, NetSerializable] - protected sealed class TryAnchorCompletedEvent : SimpleDoAfterEvent - { - } -} diff --git a/Content.Server/Coordinates/Helpers/SnapgridHelper.cs b/Content.Shared/Coordinates/Helpers/SnapgridHelper.cs similarity index 96% rename from Content.Server/Coordinates/Helpers/SnapgridHelper.cs rename to Content.Shared/Coordinates/Helpers/SnapgridHelper.cs index 406ac9838b..05aee050cc 100644 --- a/Content.Server/Coordinates/Helpers/SnapgridHelper.cs +++ b/Content.Shared/Coordinates/Helpers/SnapgridHelper.cs @@ -1,7 +1,7 @@ using Robust.Shared.Map; using Robust.Shared.Map.Components; -namespace Content.Server.Coordinates.Helpers +namespace Content.Shared.Coordinates.Helpers { public static class SnapgridHelper {