From 06581a004541e12caabe49164fd62533d8ab47ee Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 2 Aug 2025 17:34:36 -0700 Subject: [PATCH] Fix rotate verbs not being predicted (#38165) * Fix rotate verbs not being predicted * fixes --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Rotatable/FlippableComponent.cs | 15 -- Content.Server/Rotatable/RotatableSystem.cs | 197 ---------------- .../Rotatable/FlippableComponent.cs | 17 ++ .../Rotatable/RotatableComponent.cs | 47 ++-- Content.Shared/Rotatable/RotatableSystem.cs | 213 ++++++++++++++++++ .../components/rotatable-component.ftl | 3 + 6 files changed, 257 insertions(+), 235 deletions(-) delete mode 100644 Content.Server/Rotatable/FlippableComponent.cs delete mode 100644 Content.Server/Rotatable/RotatableSystem.cs create mode 100644 Content.Shared/Rotatable/FlippableComponent.cs create mode 100644 Content.Shared/Rotatable/RotatableSystem.cs diff --git a/Content.Server/Rotatable/FlippableComponent.cs b/Content.Server/Rotatable/FlippableComponent.cs deleted file mode 100644 index 98eb0f6d5c..0000000000 --- a/Content.Server/Rotatable/FlippableComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Rotatable -{ - [RegisterComponent] - public sealed partial class FlippableComponent : Component - { - /// - /// Entity to replace this entity with when the current one is 'flipped'. - /// - [DataField("mirrorEntity", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string MirrorEntity = default!; - } -} diff --git a/Content.Server/Rotatable/RotatableSystem.cs b/Content.Server/Rotatable/RotatableSystem.cs deleted file mode 100644 index 5190b0e92e..0000000000 --- a/Content.Server/Rotatable/RotatableSystem.cs +++ /dev/null @@ -1,197 +0,0 @@ -using Content.Server.Popups; -using Content.Shared.ActionBlocker; -using Content.Shared.Input; -using Content.Shared.Interaction; -using Content.Shared.Rotatable; -using Content.Shared.Verbs; -using Robust.Shared.Input.Binding; -using Robust.Shared.Map; -using Robust.Shared.Player; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Components; -using Robust.Shared.Utility; - -namespace Content.Server.Rotatable -{ - /// - /// Handles verbs for the and components. - /// - public sealed class RotatableSystem : EntitySystem - { - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly SharedInteractionSystem _interaction = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - - public override void Initialize() - { - SubscribeLocalEvent>(AddFlipVerb); - SubscribeLocalEvent>(AddRotateVerbs); - - CommandBinds.Builder - .Bind(ContentKeyFunctions.RotateObjectClockwise, new PointerInputCmdHandler(HandleRotateObjectClockwise)) - .Bind(ContentKeyFunctions.RotateObjectCounterclockwise, new PointerInputCmdHandler(HandleRotateObjectCounterclockwise)) - .Bind(ContentKeyFunctions.FlipObject, new PointerInputCmdHandler(HandleFlipObject)) - .Register(); - } - - private void AddFlipVerb(EntityUid uid, FlippableComponent component, GetVerbsEvent args) - { - if (!args.CanAccess - || !args.CanInteract - || !args.CanComplexInteract) - return; - - // Check if the object is anchored. - if (TryComp(uid, out PhysicsComponent? physics) && physics.BodyType == BodyType.Static) - return; - - Verb verb = new() - { - Act = () => Flip(uid, component), - Text = Loc.GetString("flippable-verb-get-data-text"), - Category = VerbCategory.Rotate, - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/flip.svg.192dpi.png")), - Priority = -3, // show flip last - DoContactInteraction = true - }; - args.Verbs.Add(verb); - } - - private void AddRotateVerbs(EntityUid uid, RotatableComponent component, GetVerbsEvent args) - { - if (!args.CanAccess - || !args.CanInteract - || !args.CanComplexInteract - || Transform(uid).NoLocalRotation) // Good ol prototype inheritance, eh? - return; - - // Check if the object is anchored, and whether we are still allowed to rotate it. - if (!component.RotateWhileAnchored && - TryComp(uid, out PhysicsComponent? physics) && - physics.BodyType == BodyType.Static) - return; - - Verb resetRotation = new() - { - DoContactInteraction = true, - Act = () => Comp(uid).LocalRotation = Angle.Zero, - Category = VerbCategory.Rotate, - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/refresh.svg.192dpi.png")), - Text = "Reset", - Priority = -2, // show CCW, then CW, then reset - CloseMenu = false, - }; - args.Verbs.Add(resetRotation); - - // rotate clockwise - Verb rotateCW = new() - { - Act = () => Comp(uid).LocalRotation -= component.Increment, - Category = VerbCategory.Rotate, - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png")), - Priority = -1, - CloseMenu = false, // allow for easy double rotations. - }; - args.Verbs.Add(rotateCW); - - // rotate counter-clockwise - Verb rotateCCW = new() - { - Act = () => Comp(uid).LocalRotation += component.Increment, - Category = VerbCategory.Rotate, - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png")), - Priority = 0, - CloseMenu = false, // allow for easy double rotations. - }; - args.Verbs.Add(rotateCCW); - } - - /// - /// Replace a flippable entity with it's flipped / mirror-symmetric entity. - /// - public void Flip(EntityUid uid, FlippableComponent component) - { - var oldTransform = Comp(uid); - var entity = Spawn(component.MirrorEntity, oldTransform.Coordinates); - var newTransform = Comp(entity); - newTransform.LocalRotation = oldTransform.LocalRotation; - _transform.Unanchor(entity, newTransform); - Del(uid); - } - - public bool HandleRotateObjectClockwise(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity) - { - if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player)) - return false; - - if (!TryComp(entity, out var rotatableComp)) - return false; - - if (!_actionBlocker.CanInteract(player, entity) - || !_actionBlocker.CanComplexInteract(player) - || !_interaction.InRangeAndAccessible(player, entity)) - return false; - - // Check if the object is anchored, and whether we are still allowed to rotate it. - if (!rotatableComp.RotateWhileAnchored && TryComp(entity, out PhysicsComponent? physics) && - physics.BodyType == BodyType.Static) - { - _popup.PopupEntity(Loc.GetString("rotatable-component-try-rotate-stuck"), entity, player); - return false; - } - - Transform(entity).LocalRotation -= rotatableComp.Increment; - return true; - } - - public bool HandleRotateObjectCounterclockwise(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity) - { - if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player)) - return false; - - if (!TryComp(entity, out var rotatableComp)) - return false; - - if (!_actionBlocker.CanInteract(player, entity) - || !_actionBlocker.CanComplexInteract(player) - || !_interaction.InRangeAndAccessible(player, entity)) - return false; - - // Check if the object is anchored, and whether we are still allowed to rotate it. - if (!rotatableComp.RotateWhileAnchored && TryComp(entity, out PhysicsComponent? physics) && - physics.BodyType == BodyType.Static) - { - _popup.PopupEntity(Loc.GetString("rotatable-component-try-rotate-stuck"), entity, player); - return false; - } - - Transform(entity).LocalRotation += rotatableComp.Increment; - return true; - } - - public bool HandleFlipObject(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity) - { - if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player)) - return false; - - if (!TryComp(entity, out var flippableComp)) - return false; - - if (!_actionBlocker.CanInteract(player, entity) - || !_actionBlocker.CanComplexInteract(player) - || !_interaction.InRangeAndAccessible(player, entity)) - return false; - - // Check if the object is anchored. - if (TryComp(entity, out PhysicsComponent? physics) && physics.BodyType == BodyType.Static) - { - _popup.PopupEntity(Loc.GetString("flippable-component-try-flip-is-stuck"), entity, player); - return false; - } - - Flip(entity, flippableComp); - return true; - } - } -} diff --git a/Content.Shared/Rotatable/FlippableComponent.cs b/Content.Shared/Rotatable/FlippableComponent.cs new file mode 100644 index 0000000000..fdc8c15c8a --- /dev/null +++ b/Content.Shared/Rotatable/FlippableComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Rotatable; + +/// +/// Allows an entity to be flipped (mirrored) by using a verb. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class FlippableComponent : Component +{ + /// + /// Entity to replace this entity with when the current one is 'flipped'. + /// + [DataField(required: true), AutoNetworkedField] + public EntProtoId MirrorEntity = default!; +} diff --git a/Content.Shared/Rotatable/RotatableComponent.cs b/Content.Shared/Rotatable/RotatableComponent.cs index b008c28160..336c484d8c 100644 --- a/Content.Shared/Rotatable/RotatableComponent.cs +++ b/Content.Shared/Rotatable/RotatableComponent.cs @@ -1,27 +1,28 @@ -namespace Content.Shared.Rotatable +using Robust.Shared.GameStates; + +namespace Content.Shared.Rotatable; + +/// +/// Allows an entity to be rotated by using a verb. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RotatableComponent : Component { - [RegisterComponent] - public sealed partial class RotatableComponent : Component - { - /// - /// If true, this entity can be rotated even while anchored. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("rotateWhileAnchored")] - public bool RotateWhileAnchored { get; private set; } + /// + /// If true, this entity can be rotated even while anchored. + /// + [DataField, AutoNetworkedField] + public bool RotateWhileAnchored; - /// - /// If true, will rotate entity in players direction when pulled - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("rotateWhilePulling")] - public bool RotateWhilePulling { get; private set; } = true; + /// + /// If true, will rotate entity in players direction when pulled + /// + [DataField, AutoNetworkedField] + public bool RotateWhilePulling = true; - /// - /// The angular value to change when using the rotate verbs. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("increment")] - public Angle Increment { get; private set; } = Angle.FromDegrees(90); - } + /// + /// The angular value to change when using the rotate verbs. + /// + [DataField, AutoNetworkedField] + public Angle Increment = Angle.FromDegrees(90); } diff --git a/Content.Shared/Rotatable/RotatableSystem.cs b/Content.Shared/Rotatable/RotatableSystem.cs new file mode 100644 index 0000000000..94d9206247 --- /dev/null +++ b/Content.Shared/Rotatable/RotatableSystem.cs @@ -0,0 +1,213 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Input; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Input.Binding; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Shared.Rotatable; + +/// +/// Handles verbs for the and components. +/// +public sealed class RotatableSystem : EntitySystem +{ + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void Initialize() + { + SubscribeLocalEvent>(AddFlipVerb); + SubscribeLocalEvent>(AddRotateVerbs); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.RotateObjectClockwise, new PointerInputCmdHandler(HandleRotateObjectClockwise)) + .Bind(ContentKeyFunctions.RotateObjectCounterclockwise, new PointerInputCmdHandler(HandleRotateObjectCounterclockwise)) + .Bind(ContentKeyFunctions.FlipObject, new PointerInputCmdHandler(HandleFlipObject)) + .Register(); + } + + private void AddFlipVerb(EntityUid uid, FlippableComponent component, GetVerbsEvent args) + { + if (!args.CanAccess + || !args.CanInteract + || !args.CanComplexInteract) + return; + + // Check if the object is anchored. + if (TryComp(uid, out var physics) && physics.BodyType == BodyType.Static) + return; + + Verb verb = new() + { + Act = () => Flip(uid, component), + Text = Loc.GetString("flippable-verb-get-data-text"), + Category = VerbCategory.Rotate, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/flip.svg.192dpi.png")), + Priority = -3, // show flip last + DoContactInteraction = true + }; + args.Verbs.Add(verb); + } + + private void AddRotateVerbs(EntityUid uid, RotatableComponent component, GetVerbsEvent args) + { + if (!args.CanAccess + || !args.CanInteract + || !args.CanComplexInteract + || Transform(uid).NoLocalRotation) // Good ol prototype inheritance, eh? + return; + + // Check if the object is anchored, and whether we are still allowed to rotate it. + if (!component.RotateWhileAnchored && + TryComp(uid, out var physics) && + physics.BodyType == BodyType.Static) + return; + + Verb resetRotation = new() + { + DoContactInteraction = true, + Act = () => ResetRotation(uid), + Category = VerbCategory.Rotate, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/refresh.svg.192dpi.png")), + Text = Loc.GetString("rotate-reset-verb-get-data-text"), + Priority = -2, // show CCW, then CW, then reset + CloseMenu = false, + }; + args.Verbs.Add(resetRotation); + + // rotate clockwise + Verb rotateCW = new() + { + Act = () => Rotate(uid, -component.Increment), + Category = VerbCategory.Rotate, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png")), + Text = Loc.GetString("rotate-verb-get-data-text"), + Priority = -1, + CloseMenu = false, // allow for easy double rotations. + }; + args.Verbs.Add(rotateCW); + + // rotate counter-clockwise + Verb rotateCCW = new() + { + Act = () => Rotate(uid, component.Increment), + Category = VerbCategory.Rotate, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png")), + Text = Loc.GetString("rotate-counter-verb-get-data-text"), + Priority = 0, + CloseMenu = false, // allow for easy double rotations. + }; + args.Verbs.Add(rotateCCW); + } + + /// + /// Replace a flippable entity with it's flipped / mirror-symmetric entity. + /// + public void Flip(EntityUid uid, FlippableComponent component) + { + var oldTransform = Comp(uid); + var entity = PredictedSpawnAtPosition(component.MirrorEntity, oldTransform.Coordinates); + var newTransform = Comp(entity); + _transform.SetLocalRotation(entity, oldTransform.LocalRotation); + _transform.Unanchor(entity, newTransform); + PredictedDel(uid); + } + + private bool HandleRotateObjectClockwise(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity) + { + if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player)) + return false; + + if (!TryComp(entity, out var rotatableComp)) + return false; + + if (!_actionBlocker.CanInteract(player, entity) + || !_actionBlocker.CanComplexInteract(player) + || !_interaction.InRangeAndAccessible(player, entity)) + return false; + + // Check if the object is anchored, and whether we are still allowed to rotate it. + if (!rotatableComp.RotateWhileAnchored && TryComp(entity, out var physics) && + physics.BodyType == BodyType.Static) + { + _popup.PopupClient(Loc.GetString("rotatable-component-try-rotate-stuck"), entity, player); + return false; + } + + Rotate(entity, -rotatableComp.Increment); + return false; + } + + private bool HandleRotateObjectCounterclockwise(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity) + { + if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player)) + return false; + + if (!TryComp(entity, out var rotatableComp)) + return false; + + if (!_actionBlocker.CanInteract(player, entity) + || !_actionBlocker.CanComplexInteract(player) + || !_interaction.InRangeAndAccessible(player, entity)) + return false; + + // Check if the object is anchored, and whether we are still allowed to rotate it. + if (!rotatableComp.RotateWhileAnchored && TryComp(entity, out var physics) && + physics.BodyType == BodyType.Static) + { + _popup.PopupClient(Loc.GetString("rotatable-component-try-rotate-stuck"), entity, player); + return false; + } + + Rotate(entity, rotatableComp.Increment); + return false; + } + + private bool HandleFlipObject(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity) + { + if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player)) + return false; + + if (!TryComp(entity, out var flippableComp)) + return false; + + if (!_actionBlocker.CanInteract(player, entity) + || !_actionBlocker.CanComplexInteract(player) + || !_interaction.InRangeAndAccessible(player, entity)) + return false; + + // Check if the object is anchored. + if (TryComp(entity, out var physics) && physics.BodyType == BodyType.Static) + { + _popup.PopupClient(Loc.GetString("flippable-component-try-flip-is-stuck"), entity, player); + return false; + } + + Flip(entity, flippableComp); + return false; + } + + private void Rotate(Entity ent, Angle angle) + { + if (!Resolve(ent, ref ent.Comp, false)) + return; + + _transform.SetLocalRotation(ent.Owner, ent.Comp.LocalRotation + angle); + } + + private void ResetRotation(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return; + + _transform.SetLocalRotation(ent.Owner, Angle.Zero); + } +} diff --git a/Resources/Locale/en-US/rotation/components/rotatable-component.ftl b/Resources/Locale/en-US/rotation/components/rotatable-component.ftl index bc23e488fb..18d8d55bdd 100644 --- a/Resources/Locale/en-US/rotation/components/rotatable-component.ftl +++ b/Resources/Locale/en-US/rotation/components/rotatable-component.ftl @@ -6,3 +6,6 @@ rotate-verb-get-data-text = Rotate clockwise # RotateCounterVerb rotate-counter-verb-get-data-text = Rotate counter-clockwise + +# ResetVerb +rotate-reset-verb-get-data-text = Reset