| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- 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.Movement.Pulling.Components;
- using Content.Shared.Movement.Pulling.Systems;
- using Content.Shared.Popups;
- 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.Prototypes;
- using Robust.Shared.Serialization;
- using Robust.Shared.Utility;
- using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
- namespace Content.Shared.Construction.EntitySystems;
- public sealed partial class AnchorableSystem : EntitySystem
- {
- [Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly PullingSystem _pulling = default!;
- [Dependency] private readonly SharedToolSystem _tool = default!;
- [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
- [Dependency] private readonly TagSystem _tagSystem = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- private EntityQuery<PhysicsComponent> _physicsQuery;
- public readonly ProtoId<TagPrototype> Unstackable = "Unstackable";
- public override void Initialize()
- {
- base.Initialize();
- _physicsQuery = GetEntityQuery<PhysicsComponent>();
- SubscribeLocalEvent<AnchorableComponent, InteractUsingEvent>(OnInteractUsing,
- before: new[] { typeof(ItemSlotsSystem) }, after: new[] { typeof(SharedConstructionSystem) });
- SubscribeLocalEvent<AnchorableComponent, TryAnchorCompletedEvent>(OnAnchorComplete);
- SubscribeLocalEvent<AnchorableComponent, TryUnanchorCompletedEvent>(OnUnanchorComplete);
- SubscribeLocalEvent<AnchorableComponent, ExaminedEvent>(OnAnchoredExamine);
- SubscribeLocalEvent<AnchorableComponent, ComponentStartup>(OnAnchorStartup);
- SubscribeLocalEvent<AnchorableComponent, AnchorStateChangedEvent>(OnAnchorStateChange);
- }
- private void OnAnchorStartup(EntityUid uid, AnchorableComponent comp, ComponentStartup args)
- {
- _appearance.SetData(uid, AnchorVisuals.Anchored, Transform(uid).Anchored);
- }
- private void OnAnchorStateChange(EntityUid uid, AnchorableComponent comp, AnchorStateChangedEvent args)
- {
- _appearance.SetData(uid, AnchorVisuals.Anchored, args.Anchored);
- }
- /// <summary>
- /// Tries to unanchor the entity.
- /// </summary>
- /// <returns>true if unanchored, false otherwise</returns>
- 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;
- // 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}");
- _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) || !_tool.HasQuality(args.Used, anchorable.Tool, usedTool))
- 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<TransformComponent>(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<PhysicsComponent>(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<PullableComponent>(uid, out var pullable) && pullable.Puller != null)
- {
- _pulling.TryStopPull(uid, 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}"
- );
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <returns>true if toggled, false otherwise</returns>
- public void TryToggleAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid,
- AnchorableComponent? anchorable = null,
- TransformComponent? transform = null,
- PullableComponent? pullable = null,
- ToolComponent? usingTool = null)
- {
- if (!Resolve(uid, ref transform))
- return;
- if (transform.Anchored)
- {
- TryUnAnchor(uid, userUid, usingUid, anchorable, transform, usingTool);
- }
- else
- {
- TryAnchor(uid, userUid, usingUid, anchorable, transform, pullable, usingTool);
- }
- }
- /// <summary>
- /// Tries to anchor the entity.
- /// </summary>
- /// <returns>true if anchored, false otherwise</returns>
- private void TryAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid,
- AnchorableComponent? anchorable = null,
- TransformComponent? transform = null,
- PullableComponent? 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;
- // 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}");
- if (TryComp<PhysicsComponent>(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;
- if (anchoring && (anchorable.Flags & AnchorableFlags.Anchorable) == 0x0)
- return false;
- if (!anchoring && (anchorable.Flags & AnchorableFlags.Unanchorable) == 0x0)
- 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 (!TryComp<MapGridComponent>(gridUid, out var grid))
- return false;
- var tileIndices = grid.TileIndicesFor(coordinates);
- return TileFree(grid, tileIndices, anchorBody.CollisionLayer, anchorBody.CollisionMask);
- }
- /// <summary>
- /// Returns true if no hard anchored entities match the collision layer or mask specified.
- /// </summary>
- /// <param name="grid"></param>
- 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;
- }
- /// <summary>
- /// Returns true if any unstackables are also on the corresponding tile.
- /// </summary>
- 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) && AnyUnstackablesAnchoredAt(location);
- }
- public bool AnyUnstackablesAnchoredAt(EntityCoordinates location)
- {
- var gridUid = location.GetGridUid(EntityManager);
- if (!TryComp<MapGridComponent>(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))
- return true;
- }
- return false;
- }
- [Serializable, NetSerializable]
- private sealed partial class TryUnanchorCompletedEvent : SimpleDoAfterEvent
- {
- }
- [Serializable, NetSerializable]
- private sealed partial class TryAnchorCompletedEvent : SimpleDoAfterEvent
- {
- }
- }
- [Serializable, NetSerializable]
- public enum AnchorVisuals : byte
- {
- Anchored
- }
|