]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Climbing refactor (#20516)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Tue, 10 Oct 2023 23:41:11 +0000 (10:41 +1100)
committerGitHub <noreply@github.com>
Tue, 10 Oct 2023 23:41:11 +0000 (16:41 -0700)
30 files changed:
Content.Client/Interaction/DragDropHelper.cs [moved from Content.Client/DragDrop/DragDropHelper.cs with 99% similarity]
Content.Client/Interaction/DragDropSystem.cs [moved from Content.Client/DragDrop/DragDropSystem.cs with 97% similarity]
Content.Client/Movement/Systems/ClimbSystem.cs [deleted file]
Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
Content.IntegrationTests/Tests/Climbing/ClimbingTest.cs
Content.Server/Climbing/ClimbSystem.cs [deleted file]
Content.Server/Interaction/DragDropSystem.cs [new file with mode: 0644]
Content.Server/Interaction/InteractionSystem.cs
Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs
Content.Server/Medical/CryoPodSystem.cs
Content.Server/Medical/MedicalScannerSystem.cs
Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs
Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs
Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs
Content.Server/NPC/Systems/NPCSteeringSystem.cs
Content.Shared/ActionBlocker/ActionBlockerSystem.cs
Content.Shared/Climbing/ClimbingComponent.cs [deleted file]
Content.Shared/Climbing/Components/BonkableComponent.cs [moved from Content.Shared/Climbing/BonkableComponent.cs with 90% similarity]
Content.Shared/Climbing/Components/ClimbableComponent.cs [moved from Content.Shared/Climbing/ClimbableComponent.cs with 84% similarity]
Content.Shared/Climbing/Components/ClimbingComponent.cs [new file with mode: 0644]
Content.Shared/Climbing/Components/GlassTableComponent.cs [moved from Content.Server/Climbing/Components/GlassTableComponent.cs with 90% similarity]
Content.Shared/Climbing/Events/ClimbedOnEvent.cs [new file with mode: 0644]
Content.Shared/Climbing/Events/EndClimbEvent.cs
Content.Shared/Climbing/Events/StartClimbEvent.cs [new file with mode: 0644]
Content.Shared/Climbing/SharedClimbSystem.cs [deleted file]
Content.Shared/Climbing/Systems/BonkSystem.cs [moved from Content.Shared/Climbing/BonkSystem.cs with 88% similarity]
Content.Shared/Climbing/Systems/ClimbSystem.cs [new file with mode: 0644]
Content.Shared/DoAfter/SharedDoAfterSystem.cs
Content.Shared/DragDrop/SharedDragDropSystem.cs
Content.Shared/Interaction/SharedInteractionSystem.cs

similarity index 99%
rename from Content.Client/DragDrop/DragDropHelper.cs
rename to Content.Client/Interaction/DragDropHelper.cs
index d8286ee7054056c8d53e76359fd77bf993ecd733..ce5e08207c27fa75238027c85a8294eca4574d3b 100644 (file)
@@ -1,7 +1,7 @@
 using Robust.Client.Input;
 using Robust.Shared.Map;
 
-namespace Content.Client.DragDrop;
+namespace Content.Client.Interaction;
 
 /// <summary>
 /// Helper for implementing drag and drop interactions.
similarity index 97%
rename from Content.Client/DragDrop/DragDropSystem.cs
rename to Content.Client/Interaction/DragDropSystem.cs
index a8c1a06686e437cc36aea1ed052f7090aac3c9f4..66571a9d27f7789db922e456702d6b827b0c3d39 100644 (file)
@@ -1,3 +1,4 @@
+using System.Numerics;
 using Content.Client.CombatMode;
 using Content.Client.Gameplay;
 using Content.Client.Outline;
@@ -7,7 +8,6 @@ using Content.Shared.DragDrop;
 using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Popups;
-using JetBrains.Annotations;
 using Robust.Client.GameObjects;
 using Robust.Client.Graphics;
 using Robust.Client.Input;
@@ -20,15 +20,13 @@ using Robust.Shared.Map;
 using Robust.Shared.Player;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
-using System.Numerics;
 using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
 
-namespace Content.Client.DragDrop;
+namespace Content.Client.Interaction;
 
 /// <summary>
 /// Handles clientside drag and drop logic
 /// </summary>
-[UsedImplicitly]
 public sealed class DragDropSystem : SharedDragDropSystem
 {
     [Dependency] private readonly IStateManager _stateManager = default!;
@@ -45,8 +43,6 @@ public sealed class DragDropSystem : SharedDragDropSystem
     [Dependency] private readonly EntityLookupSystem _lookup = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
 
-    private ISawmill _sawmill = default!;
-
     // how often to recheck possible targets (prevents calling expensive
     // check logic each update)
     private const float TargetRecheckInterval = 0.25f;
@@ -110,7 +106,6 @@ public sealed class DragDropSystem : SharedDragDropSystem
     public override void Initialize()
     {
         base.Initialize();
-        _sawmill = Logger.GetSawmill("drag_drop");
         UpdatesOutsidePrediction = true;
         UpdatesAfter.Add(typeof(SharedEyeSystem));
 
@@ -263,7 +258,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
             return;
         }
 
-        _sawmill.Warning($"Unable to display drag shadow for {ToPrettyString(_draggedEntity.Value)} because it has no sprite component.");
+        Log.Warning($"Unable to display drag shadow for {ToPrettyString(_draggedEntity.Value)} because it has no sprite component.");
     }
 
     private bool UpdateDrag(float frameTime)
@@ -392,7 +387,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
             }
 
             // tell the server about the drop attempt
-            RaiseNetworkEvent(new DragDropRequestEvent(GetNetEntity(_draggedEntity.Value), GetNetEntity(entity)));
+            RaisePredictiveEvent(new DragDropRequestEvent(GetNetEntity(_draggedEntity.Value), GetNetEntity(entity)));
             EndDrag();
             return true;
         }
diff --git a/Content.Client/Movement/Systems/ClimbSystem.cs b/Content.Client/Movement/Systems/ClimbSystem.cs
deleted file mode 100644 (file)
index 003b478..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Client.Interactable;
-using Content.Shared.Climbing;
-using Content.Shared.DragDrop;
-
-namespace Content.Client.Movement.Systems;
-
-public sealed class ClimbSystem : SharedClimbSystem
-{
-    [Dependency] private readonly InteractionSystem _interactionSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<ClimbableComponent, CanDropTargetEvent>(OnCanDragDropOn);
-    }
-
-    protected override void OnCanDragDropOn(EntityUid uid, ClimbableComponent component, ref CanDropTargetEvent args)
-    {
-        base.OnCanDragDropOn(uid, component, ref args);
-
-        if (!args.CanDrop)
-            return;
-
-        var user = args.User;
-        var target = uid;
-        var dragged = args.Dragged;
-        bool Ignored(EntityUid entity) => entity == target || entity == user || entity == dragged;
-
-        args.CanDrop = _interactionSystem.InRangeUnobstructed(user, target, component.Range, predicate: Ignored)
-                       && _interactionSystem.InRangeUnobstructed(user, dragged, component.Range, predicate: Ignored);
-        args.Handled = true;
-    }
-}
index bb83e370fe4309144d509495389ad58c78542e77..b2ff36d05c37db44ba0730319561b7904dc576bc 100644 (file)
@@ -3,9 +3,9 @@ using System.Numerics;
 using System.Runtime.InteropServices;
 using Content.Client.Actions;
 using Content.Client.Construction;
-using Content.Client.DragDrop;
 using Content.Client.Gameplay;
 using Content.Client.Hands;
+using Content.Client.Interaction;
 using Content.Client.Outline;
 using Content.Client.UserInterface.Controls;
 using Content.Client.UserInterface.Systems.Actions.Controls;
index f6bcc6e12916a355f870a5d7f0f0cd9f28bf58c9..d8d3086520eba81f09247f47eb887a926ded0926 100644 (file)
@@ -1,8 +1,8 @@
 #nullable enable
 using Content.IntegrationTests.Tests.Interaction;
-using Content.Server.Climbing;
-using Content.Shared.Climbing;
 using Robust.Shared.Maths;
+using ClimbingComponent = Content.Shared.Climbing.Components.ClimbingComponent;
+using ClimbSystem = Content.Shared.Climbing.Systems.ClimbSystem;
 
 namespace Content.IntegrationTests.Tests.Climbing;
 
diff --git a/Content.Server/Climbing/ClimbSystem.cs b/Content.Server/Climbing/ClimbSystem.cs
deleted file mode 100644 (file)
index e9d25f3..0000000
+++ /dev/null
@@ -1,476 +0,0 @@
-using System.Numerics;
-using Content.Server.Body.Systems;
-using Content.Server.Climbing.Components;
-using Content.Server.Interaction;
-using Content.Server.Popups;
-using Content.Server.Stunnable;
-using Content.Shared.ActionBlocker;
-using Content.Shared.Body.Components;
-using Content.Shared.Body.Part;
-using Content.Shared.Buckle.Components;
-using Content.Shared.Climbing;
-using Content.Shared.Climbing.Events;
-using Content.Shared.Damage;
-using Content.Shared.DoAfter;
-using Content.Shared.DragDrop;
-using Content.Shared.GameTicking;
-using Content.Shared.Hands.Components;
-using Content.Shared.IdentityManagement;
-using Content.Shared.Physics;
-using Content.Shared.Popups;
-using Content.Shared.Verbs;
-using JetBrains.Annotations;
-using Robust.Server.GameObjects;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Collision.Shapes;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Dynamics;
-using Robust.Shared.Physics.Events;
-using Robust.Shared.Physics.Systems;
-using Robust.Shared.Player;
-
-namespace Content.Server.Climbing;
-
-[UsedImplicitly]
-public sealed class ClimbSystem : SharedClimbSystem
-{
-    [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
-    [Dependency] private readonly AudioSystem _audio = default!;
-    [Dependency] private readonly BodySystem _bodySystem = default!;
-    [Dependency] private readonly DamageableSystem _damageableSystem = default!;
-    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
-    [Dependency] private readonly FixtureSystem _fixtureSystem = default!;
-    [Dependency] private readonly PopupSystem _popupSystem = default!;
-    [Dependency] private readonly InteractionSystem _interactionSystem = default!;
-    [Dependency] private readonly StunSystem _stunSystem = default!;
-    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
-
-    private const string ClimbingFixtureName = "climb";
-    private const int ClimbingCollisionGroup = (int) (CollisionGroup.TableLayer | CollisionGroup.LowImpassable);
-
-    private readonly Dictionary<EntityUid, Dictionary<string, Fixture>> _fixtureRemoveQueue = new();
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
-        SubscribeLocalEvent<ClimbableComponent, GetVerbsEvent<AlternativeVerb>>(AddClimbableVerb);
-        SubscribeLocalEvent<ClimbableComponent, DragDropTargetEvent>(OnClimbableDragDrop);
-
-        SubscribeLocalEvent<ClimbingComponent, ClimbDoAfterEvent>(OnDoAfter);
-        SubscribeLocalEvent<ClimbingComponent, EndCollideEvent>(OnClimbEndCollide);
-        SubscribeLocalEvent<ClimbingComponent, BuckleChangeEvent>(OnBuckleChange);
-
-        SubscribeLocalEvent<GlassTableComponent, ClimbedOnEvent>(OnGlassClimbed);
-    }
-
-    protected override void OnCanDragDropOn(EntityUid uid, ClimbableComponent component, ref CanDropTargetEvent args)
-    {
-        base.OnCanDragDropOn(uid, component, ref args);
-
-        if (!args.CanDrop)
-            return;
-
-        string reason;
-        var canVault = args.User == args.Dragged
-            ? CanVault(component, args.User, uid, out reason)
-            : CanVault(component, args.User, args.Dragged, uid, out reason);
-
-        if (!canVault)
-            _popupSystem.PopupEntity(reason, args.User, args.User);
-
-        args.CanDrop = canVault;
-        args.Handled = true;
-    }
-
-    private void AddClimbableVerb(EntityUid uid, ClimbableComponent component, GetVerbsEvent<AlternativeVerb> args)
-    {
-        if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanMove(args.User))
-            return;
-
-        if (!TryComp(args.User, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing)
-            return;
-
-        // TODO VERBS ICON add a climbing icon?
-        args.Verbs.Add(new AlternativeVerb
-        {
-            Act = () => TryClimb(args.User, args.User, args.Target, out _, component),
-            Text = Loc.GetString("comp-climbable-verb-climb")
-        });
-    }
-
-    private void OnClimbableDragDrop(EntityUid uid, ClimbableComponent component, ref DragDropTargetEvent args)
-    {
-        // definitely a better way to check if two entities are equal
-        // but don't have computer access and i have to do this without syntax
-        if (args.Handled || args.User != args.Dragged && !HasComp<HandsComponent>(args.User))
-            return;
-        TryClimb(args.User, args.Dragged, uid, out _, component);
-    }
-
-    public bool TryClimb(EntityUid user,
-        EntityUid entityToMove,
-        EntityUid climbable,
-        out DoAfterId? id,
-        ClimbableComponent? comp = null,
-        ClimbingComponent? climbing = null)
-    {
-        id = null;
-
-        if (!Resolve(climbable, ref comp) || !Resolve(entityToMove, ref climbing))
-            return false;
-
-        // Note, IsClimbing does not mean a DoAfter is active, it means the target has already finished a DoAfter and
-        // is currently on top of something..
-        if (climbing.IsClimbing)
-            return true;
-
-        var args = new DoAfterArgs(EntityManager, user, comp.ClimbDelay, new ClimbDoAfterEvent(), entityToMove, target: climbable, used: entityToMove)
-        {
-            BreakOnTargetMove = true,
-            BreakOnUserMove = true,
-            BreakOnDamage = true
-        };
-
-        _audio.PlayPvs(comp.StartClimbSound, climbable);
-        _doAfterSystem.TryStartDoAfter(args, out id);
-        return true;
-    }
-
-    private void OnDoAfter(EntityUid uid, ClimbingComponent component, ClimbDoAfterEvent args)
-    {
-        if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null)
-            return;
-
-        Climb(uid, args.Args.User, args.Args.Used.Value, args.Args.Target.Value, climbing: component);
-
-        args.Handled = true;
-    }
-
-    private void Climb(EntityUid uid, EntityUid user, EntityUid instigator, EntityUid climbable, bool silent = false, ClimbingComponent? climbing = null,
-        PhysicsComponent? physics = null, FixturesComponent? fixtures = null, ClimbableComponent? comp = null)
-    {
-        if (!Resolve(uid, ref climbing, ref physics, ref fixtures, false))
-            return;
-
-        if (!Resolve(climbable, ref comp))
-            return;
-
-        if (!ReplaceFixtures(climbing, fixtures))
-            return;
-
-        climbing.IsClimbing = true;
-        Dirty(climbing);
-
-        _audio.PlayPvs(comp.FinishClimbSound, climbable);
-        MoveEntityToward(uid, climbable, physics, climbing);
-        // we may potentially need additional logic since we're forcing a player onto a climbable
-        // there's also the cases where the user might collide with the person they are forcing onto the climbable that i haven't accounted for
-
-        RaiseLocalEvent(uid, new StartClimbEvent(climbable), false);
-        RaiseLocalEvent(climbable, new ClimbedOnEvent(uid, user), false);
-
-        if (silent)
-            return;
-        if (user == uid)
-        {
-            var othersMessage = Loc.GetString("comp-climbable-user-climbs-other", ("user", Identity.Entity(uid, EntityManager)),
-                ("climbable", climbable));
-            uid.PopupMessageOtherClients(othersMessage);
-
-            var selfMessage = Loc.GetString("comp-climbable-user-climbs", ("climbable", climbable));
-            uid.PopupMessage(selfMessage);
-        }
-        else
-        {
-            var othersMessage = Loc.GetString("comp-climbable-user-climbs-force-other", ("user", Identity.Entity(user, EntityManager)),
-                ("moved-user", Identity.Entity(uid, EntityManager)), ("climbable", climbable));
-            user.PopupMessageOtherClients(othersMessage);
-
-            var selfMessage = Loc.GetString("comp-climbable-user-climbs-force", ("moved-user", Identity.Entity(uid, EntityManager)),
-                ("climbable", climbable));
-            user.PopupMessage(selfMessage);
-        }
-    }
-
-    /// <summary>
-    /// Replaces the current fixtures with non-climbing collidable versions so that climb end can be detected
-    /// </summary>
-    /// <returns>Returns whether adding the new fixtures was successful</returns>
-    private bool ReplaceFixtures(ClimbingComponent climbingComp, FixturesComponent fixturesComp)
-    {
-        var uid = climbingComp.Owner;
-
-        // Swap fixtures
-        foreach (var (name, fixture) in fixturesComp.Fixtures)
-        {
-            if (climbingComp.DisabledFixtureMasks.ContainsKey(name)
-                || fixture.Hard == false
-                || (fixture.CollisionMask & ClimbingCollisionGroup) == 0)
-                continue;
-
-            climbingComp.DisabledFixtureMasks.Add(name, fixture.CollisionMask & ClimbingCollisionGroup);
-            _physics.SetCollisionMask(uid, name, fixture, fixture.CollisionMask & ~ClimbingCollisionGroup, fixturesComp);
-        }
-
-        if (!_fixtureSystem.TryCreateFixture(
-                uid,
-                new PhysShapeCircle(0.35f),
-                ClimbingFixtureName,
-                collisionLayer: (int) CollisionGroup.None,
-                collisionMask: ClimbingCollisionGroup,
-                hard: false,
-                manager: fixturesComp))
-        {
-            return false;
-        }
-
-        return true;
-    }
-
-    private void OnClimbEndCollide(EntityUid uid, ClimbingComponent component, ref EndCollideEvent args)
-    {
-        if (args.OurFixtureId != ClimbingFixtureName
-            || !component.IsClimbing
-            || component.OwnerIsTransitioning)
-            return;
-
-        foreach (var fixture in args.OurFixture.Contacts.Keys)
-        {
-            if (fixture == args.OtherFixture)
-                continue;
-            // If still colliding with a climbable, do not stop climbing
-            if (HasComp<ClimbableComponent>(args.OtherEntity))
-                return;
-        }
-
-        StopClimb(uid, component);
-    }
-
-    private void StopClimb(EntityUid uid, ClimbingComponent? climbing = null, FixturesComponent? fixtures = null)
-    {
-        if (!Resolve(uid, ref climbing, ref fixtures, false))
-            return;
-
-        foreach (var (name, fixtureMask) in climbing.DisabledFixtureMasks)
-        {
-            if (!fixtures.Fixtures.TryGetValue(name, out var fixture))
-            {
-                continue;
-            }
-
-            _physics.SetCollisionMask(uid, name, fixture, fixture.CollisionMask | fixtureMask, fixtures);
-        }
-        climbing.DisabledFixtureMasks.Clear();
-
-        if (!_fixtureRemoveQueue.TryGetValue(uid, out var removeQueue))
-        {
-            removeQueue = new Dictionary<string, Fixture>();
-            _fixtureRemoveQueue.Add(uid, removeQueue);
-        }
-
-        if (fixtures.Fixtures.TryGetValue(ClimbingFixtureName, out var climbingFixture))
-            removeQueue.Add(ClimbingFixtureName, climbingFixture);
-
-        climbing.IsClimbing = false;
-        climbing.OwnerIsTransitioning = false;
-        var ev = new EndClimbEvent();
-        RaiseLocalEvent(uid, ref ev);
-        Dirty(climbing);
-    }
-
-    /// <summary>
-    ///     Checks if the user can vault the target
-    /// </summary>
-    /// <param name="component">The component of the entity that is being vaulted</param>
-    /// <param name="user">The entity that wants to vault</param>
-    /// <param name="target">The object that is being vaulted</param>
-    /// <param name="reason">The reason why it cant be dropped</param>
-    /// <returns></returns>
-    public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid target, out string reason)
-    {
-        if (!_actionBlockerSystem.CanInteract(user, target))
-        {
-            reason = Loc.GetString("comp-climbable-cant-interact");
-            return false;
-        }
-
-        if (!HasComp<ClimbingComponent>(user)
-            || !TryComp(user, out BodyComponent? body)
-            || !_bodySystem.BodyHasPartType(user, BodyPartType.Leg, body)
-            || !_bodySystem.BodyHasPartType(user, BodyPartType.Foot, body))
-        {
-            reason = Loc.GetString("comp-climbable-cant-climb");
-            return false;
-        }
-
-        if (!_interactionSystem.InRangeUnobstructed(user, target, component.Range))
-        {
-            reason = Loc.GetString("comp-climbable-cant-reach");
-            return false;
-        }
-
-        reason = string.Empty;
-        return true;
-    }
-
-    /// <summary>
-    ///     Checks if the user can vault the dragged entity onto the the target
-    /// </summary>
-    /// <param name="component">The climbable component of the object being vaulted onto</param>
-    /// <param name="user">The user that wants to vault the entity</param>
-    /// <param name="dragged">The entity that is being vaulted</param>
-    /// <param name="target">The object that is being vaulted onto</param>
-    /// <param name="reason">The reason why it cant be dropped</param>
-    /// <returns></returns>
-    public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid dragged, EntityUid target,
-        out string reason)
-    {
-        if (!_actionBlockerSystem.CanInteract(user, dragged) || !_actionBlockerSystem.CanInteract(user, target))
-        {
-            reason = Loc.GetString("comp-climbable-cant-interact");
-            return false;
-        }
-
-        if (!HasComp<ClimbingComponent>(dragged))
-        {
-            reason = Loc.GetString("comp-climbable-cant-climb");
-            return false;
-        }
-
-        bool Ignored(EntityUid entity) => entity == target || entity == user || entity == dragged;
-
-        if (!_interactionSystem.InRangeUnobstructed(user, target, component.Range, predicate: Ignored)
-            || !_interactionSystem.InRangeUnobstructed(user, dragged, component.Range, predicate: Ignored))
-        {
-            reason = Loc.GetString("comp-climbable-cant-reach");
-            return false;
-        }
-
-        reason = string.Empty;
-        return true;
-    }
-
-    public void ForciblySetClimbing(EntityUid uid, EntityUid climbable, ClimbingComponent? component = null)
-    {
-        Climb(uid, uid, uid, climbable, true, component);
-    }
-
-    private void OnBuckleChange(EntityUid uid, ClimbingComponent component, ref BuckleChangeEvent args)
-    {
-        if (!args.Buckling)
-            return;
-        StopClimb(uid, component);
-    }
-
-    private void OnGlassClimbed(EntityUid uid, GlassTableComponent component, ClimbedOnEvent args)
-    {
-        if (TryComp<PhysicsComponent>(args.Climber, out var physics) && physics.Mass <= component.MassLimit)
-            return;
-
-        _damageableSystem.TryChangeDamage(args.Climber, component.ClimberDamage, origin: args.Climber);
-        _damageableSystem.TryChangeDamage(uid, component.TableDamage, origin: args.Climber);
-        _stunSystem.TryParalyze(args.Climber, TimeSpan.FromSeconds(component.StunTime), true);
-
-        // Not shown to the user, since they already get a 'you climb on the glass table' popup
-        _popupSystem.PopupEntity(
-            Loc.GetString("glass-table-shattered-others", ("table", uid), ("climber", Identity.Entity(args.Climber, EntityManager))), args.Climber,
-            Filter.PvsExcept(args.Climber), true);
-    }
-
-    /// <summary>
-    /// Moves the entity toward the target climbed entity
-    /// </summary>
-    public void MoveEntityToward(EntityUid uid, EntityUid target, PhysicsComponent? physics = null, ClimbingComponent? climbing = null)
-    {
-        if (!Resolve(uid, ref physics, ref climbing, false))
-            return;
-
-        var from = Transform(uid).WorldPosition;
-        var to = Transform(target).WorldPosition;
-        var (x, y) = (to - from).Normalized();
-
-        if (MathF.Abs(x) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
-            to = new Vector2(from.X, to.Y);
-        else if (MathF.Abs(y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
-            to = new Vector2(to.X, from.Y);
-
-        var velocity = (to - from).Length();
-
-        if (velocity <= 0.0f)
-            return;
-
-        // Since there are bodies with different masses:
-        // mass * 10 seems enough to move entity
-        // instead of launching cats like rockets against the walls with constant impulse value.
-        _physics.ApplyLinearImpulse(uid, (to - from).Normalized() * velocity * physics.Mass * 10, body: physics);
-        _physics.SetBodyType(uid, BodyType.Dynamic, body: physics);
-        climbing.OwnerIsTransitioning = true;
-        _actionBlockerSystem.UpdateCanMove(uid);
-
-        // Transition back to KinematicController after BufferTime
-        climbing.Owner.SpawnTimer((int) (ClimbingComponent.BufferTime * 1000), () =>
-        {
-            if (climbing.Deleted)
-                return;
-
-            _physics.SetBodyType(uid, BodyType.KinematicController);
-            climbing.OwnerIsTransitioning = false;
-            _actionBlockerSystem.UpdateCanMove(uid);
-        });
-    }
-
-    public override void Update(float frameTime)
-    {
-        foreach (var (uid, fixtures) in _fixtureRemoveQueue)
-        {
-            if (!TryComp<PhysicsComponent>(uid, out var physicsComp)
-                || !TryComp<FixturesComponent>(uid, out var fixturesComp))
-            {
-                continue;
-            }
-
-            foreach (var fixture in fixtures)
-            {
-                _fixtureSystem.DestroyFixture(uid, fixture.Key, fixture.Value, body: physicsComp, manager: fixturesComp);
-            }
-        }
-
-        _fixtureRemoveQueue.Clear();
-    }
-
-    private void Reset(RoundRestartCleanupEvent ev)
-    {
-        _fixtureRemoveQueue.Clear();
-    }
-
-}
-
-/// <summary>
-///     Raised on an entity when it is climbed on.
-/// </summary>
-public sealed class ClimbedOnEvent : EntityEventArgs
-{
-    public EntityUid Climber;
-    public EntityUid Instigator;
-
-    public ClimbedOnEvent(EntityUid climber, EntityUid instigator)
-    {
-        Climber = climber;
-        Instigator = instigator;
-    }
-}
-
-/// <summary>
-///     Raised on an entity when it successfully climbs on something.
-/// </summary>
-public sealed class StartClimbEvent : EntityEventArgs
-{
-    public EntityUid Climbable;
-
-    public StartClimbEvent(EntityUid climbable)
-    {
-        Climbable = climbable;
-    }
-}
diff --git a/Content.Server/Interaction/DragDropSystem.cs b/Content.Server/Interaction/DragDropSystem.cs
new file mode 100644 (file)
index 0000000..9a4c26e
--- /dev/null
@@ -0,0 +1,8 @@
+using Content.Shared.DragDrop;
+
+namespace Content.Server.Interaction;
+
+public sealed class DragDropSystem : SharedDragDropSystem
+{
+
+}
index c39c086960dfe5cae0f602b6d067722fc98d6c07..a612b738400a20a1fa155b0ef1dc0b81e4f42c0d 100644 (file)
@@ -32,8 +32,6 @@ namespace Content.Server.Interaction
         {
             base.Initialize();
 
-            SubscribeNetworkEvent<DragDropRequestEvent>(HandleDragDropRequestEvent);
-
             SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
         }
 
@@ -58,45 +56,6 @@ namespace Content.Server.Interaction
             return _uiSystem.SessionHasOpenUi(container.Owner, StorageComponent.StorageUiKey.Key, actor.PlayerSession);
         }
 
-        #region Drag drop
-
-        private void HandleDragDropRequestEvent(DragDropRequestEvent msg, EntitySessionEventArgs args)
-        {
-            var dragged = GetEntity(msg.Dragged);
-            var target = GetEntity(msg.Target);
-
-            if (Deleted(dragged) || Deleted(target))
-                return;
-
-            var user = args.SenderSession.AttachedEntity;
-
-            if (user == null || !_actionBlockerSystem.CanInteract(user.Value, target))
-                return;
-
-            // must be in range of both the target and the object they are drag / dropping
-            // Client also does this check but ya know we gotta validate it.
-            if (!InRangeUnobstructed(user.Value, dragged, popup: true)
-                || !InRangeUnobstructed(user.Value, target, popup: true))
-            {
-                return;
-            }
-
-            var dragArgs = new DragDropDraggedEvent(user.Value, target);
-
-            // trigger dragdrops on the dropped entity
-            RaiseLocalEvent(dragged, ref dragArgs);
-
-            if (dragArgs.Handled)
-                return;
-
-            var dropArgs = new DragDropTargetEvent(user.Value, dragged);
-
-            // trigger dragdrops on the target entity (what you are dropping onto)
-            RaiseLocalEvent(GetEntity(msg.Target), ref dropArgs);
-        }
-
-        #endregion
-
         private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev)
         {
             if (ev.Player.AttachedEntity is not { } user)
index 40637c5362b936d91a4b6c4fafacca3dd5c5a002..45f8d2ed98325d9462396faf1bed0ebd3c40749e 100644 (file)
@@ -1,6 +1,5 @@
 using System.Numerics;
 using Content.Server.Body.Components;
-using Content.Server.Climbing;
 using Content.Server.Construction;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Materials;
@@ -9,6 +8,7 @@ using Content.Shared.Administration.Logs;
 using Content.Shared.Audio;
 using Content.Shared.CCVar;
 using Content.Shared.Chemistry.Components;
+using Content.Shared.Climbing.Events;
 using Content.Shared.Construction.Components;
 using Content.Shared.Database;
 using Content.Shared.DoAfter;
@@ -160,7 +160,7 @@ namespace Content.Server.Medical.BiomassReclaimer
             });
         }
 
-        private void OnClimbedOn(EntityUid uid, BiomassReclaimerComponent component, ClimbedOnEvent args)
+        private void OnClimbedOn(EntityUid uid, BiomassReclaimerComponent component, ref ClimbedOnEvent args)
         {
             if (!CanGib(uid, args.Climber, component))
             {
index ddd29d26a2c90fa17fe67f5a6489c34851062879..98f8e305b631b77a6ae4d2167e01d82ad65f801b 100644 (file)
@@ -7,7 +7,6 @@ using Content.Server.Body.Components;
 using Content.Server.Body.Systems;
 using Content.Server.Chemistry.Components.SolutionManager;
 using Content.Server.Chemistry.EntitySystems;
-using Content.Server.Climbing;
 using Content.Server.Medical.Components;
 using Content.Server.NodeContainer;
 using Content.Server.NodeContainer.EntitySystems;
@@ -32,6 +31,7 @@ using Content.Shared.Verbs;
 using Robust.Server.GameObjects;
 using Robust.Shared.Timing;
 using Content.Server.Temperature.Components;
+using Content.Shared.Climbing.Systems;
 
 namespace Content.Server.Medical;
 
index 57ca815cb7bbb0ff86e6e94eca4c6e9b0ead1b6e..d4694e8fb8dac6a128dd1aba8d6b3d5cd868350f 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.Climbing;
 using Content.Server.Cloning;
 using Content.Server.Medical.Components;
 using Content.Shared.Destructible;
@@ -13,6 +12,7 @@ using Content.Server.DeviceLinking.Systems;
 using Content.Shared.DeviceLinking.Events;
 using Content.Server.Power.EntitySystems;
 using Content.Shared.Body.Components;
+using Content.Shared.Climbing.Systems;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
 using Robust.Server.Containers;
index 95d267f7d73f75a0e8f480be6e7c914e84f6a8cd..72d6606c910920df1f6f74b21285c4fc68a9079a 100644 (file)
@@ -18,6 +18,7 @@ using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Events;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
+using ClimbableComponent = Content.Shared.Climbing.Components.ClimbableComponent;
 
 namespace Content.Server.NPC.Pathfinding;
 
index 920db537dfed0b3069dabc5468e6ede0c9e78918..6507f24edf648c2b4d83101961aed8d9ef1881a6 100644 (file)
@@ -10,6 +10,7 @@ using Content.Shared.NPC;
 using Content.Shared.Physics;
 using Robust.Shared.Map;
 using Robust.Shared.Physics.Components;
+using ClimbingComponent = Content.Shared.Climbing.Components.ClimbingComponent;
 
 namespace Content.Server.NPC.Systems;
 
index 87deec9ea9dcb5b45b137d1c08eb4bc6de8bda11..70d1e89bc4fea33d0d352aa470c71741316ca601 100644 (file)
@@ -9,6 +9,8 @@ using Content.Shared.NPC;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Utility;
+using ClimbableComponent = Content.Shared.Climbing.Components.ClimbableComponent;
+using ClimbingComponent = Content.Shared.Climbing.Components.ClimbingComponent;
 
 namespace Content.Server.NPC.Systems;
 
@@ -132,7 +134,7 @@ public sealed partial class NPCSteeringSystem
                     {
                         return SteeringObstacleStatus.Completed;
                     }
-                    else if (climbing.OwnerIsTransitioning)
+                    else if (climbing.NextTransition != null)
                     {
                         return SteeringObstacleStatus.Continuing;
                     }
index 0fa28f6af795b8138ff5e18c845bca24a20c95ba..61b43df6f0023faf61128a2c680e1c070928fd76 100644 (file)
@@ -1,21 +1,19 @@
-using System.Linq;
 using System.Numerics;
 using System.Threading;
 using System.Threading.Tasks;
 using Content.Server.Administration.Managers;
-using Content.Server.Climbing;
 using Content.Server.DoAfter;
 using Content.Server.Doors.Systems;
 using Content.Server.NPC.Components;
 using Content.Server.NPC.Events;
 using Content.Server.NPC.Pathfinding;
 using Content.Shared.CCVar;
+using Content.Shared.Climbing.Systems;
 using Content.Shared.CombatMode;
 using Content.Shared.Interaction;
 using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Systems;
 using Content.Shared.NPC;
-using Content.Shared.NPC;
 using Content.Shared.NPC.Events;
 using Content.Shared.Physics;
 using Content.Shared.Weapons.Melee;
@@ -28,7 +26,6 @@ using Robust.Shared.Physics.Systems;
 using Robust.Shared.Player;
 using Robust.Shared.Players;
 using Robust.Shared.Random;
-using Robust.Shared.Threading;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 using Content.Shared.Prying.Systems;
index 93aa5dd9099f300d485288e8c8097e94da9bd6f5..d2b12a4b2925f59f28ee9ee6b9f0e7bf7d361d0b 100644 (file)
@@ -47,7 +47,7 @@ namespace Content.Shared.ActionBlocker
             RaiseLocalEvent(uid, ev);
 
             if (component.CanMove == ev.Cancelled)
-                Dirty(component);
+                Dirty(uid, component);
 
             component.CanMove = !ev.Cancelled;
             return !ev.Cancelled;
diff --git a/Content.Shared/Climbing/ClimbingComponent.cs b/Content.Shared/Climbing/ClimbingComponent.cs
deleted file mode 100644 (file)
index cd443af..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Climbing;
-
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
-public sealed partial class ClimbingComponent : Component
-{
-    /// <summary>
-    /// Whether the owner is climbing on a climbable entity.
-    /// </summary>
-    [ViewVariables, AutoNetworkedField]
-    public bool IsClimbing { get; set; }
-
-    /// <summary>
-    /// Whether the owner is being moved onto the climbed entity.
-    /// </summary>
-    [ViewVariables, AutoNetworkedField]
-    public bool OwnerIsTransitioning { get; set; }
-
-    /// <summary>
-    ///     We'll launch the mob onto the table and give them at least this amount of time to be on it.
-    /// </summary>
-    public const float BufferTime = 0.3f;
-
-    [ViewVariables]
-    public Dictionary<string, int> DisabledFixtureMasks { get; } = new();
-}
similarity index 90%
rename from Content.Shared/Climbing/BonkableComponent.cs
rename to Content.Shared/Climbing/Components/BonkableComponent.cs
index afffe1ff991fdb9503eb681371c9247ca0b88829..cc85e1c5626f0ba971852006f537a5e5fe4e4a82 100644 (file)
@@ -2,13 +2,13 @@ using Content.Shared.Damage;
 using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
 
-namespace Content.Shared.Climbing;
+namespace Content.Shared.Climbing.Components;
 
 /// <summary>
 ///     Makes entity do damage and stun entities with ClumsyComponent
 ///     upon DragDrop or Climb interactions.
 /// </summary>
-[RegisterComponent, NetworkedComponent, Access(typeof(BonkSystem))]
+[RegisterComponent, NetworkedComponent, Access(typeof(Systems.BonkSystem))]
 public sealed partial class BonkableComponent : Component
 {
     /// <summary>
similarity index 84%
rename from Content.Shared/Climbing/ClimbableComponent.cs
rename to Content.Shared/Climbing/Components/ClimbableComponent.cs
index 7ad289348ab97493165ded087cd690f620e17b96..1a924e5c3052a3442d8e854da4f6f5e18d35e838 100644 (file)
@@ -1,11 +1,12 @@
-using Content.Shared.CCVar;
-using Content.Shared.Damage;
 using Content.Shared.Interaction;
 using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
 
-namespace Content.Shared.Climbing
+namespace Content.Shared.Climbing.Components
 {
+    /// <summary>
+    /// Indicates this entity can be vaulted on top of.
+    /// </summary>
     [RegisterComponent, NetworkedComponent]
     public sealed partial class ClimbableComponent : Component
     {
@@ -18,7 +19,7 @@ namespace Content.Shared.Climbing
         ///     The time it takes to climb onto the entity.
         /// </summary>
         [DataField("delay")]
-        public float ClimbDelay = 0.8f;
+        public float ClimbDelay = 1.5f;
 
         /// <summary>
         ///     Sound to be played when a climb is started.
diff --git a/Content.Shared/Climbing/Components/ClimbingComponent.cs b/Content.Shared/Climbing/Components/ClimbingComponent.cs
new file mode 100644 (file)
index 0000000..9738c0c
--- /dev/null
@@ -0,0 +1,36 @@
+using System.Numerics;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Climbing.Components;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ClimbingComponent : Component
+{
+    /// <summary>
+    /// Whether the owner is climbing on a climbable entity.
+    /// </summary>
+    [AutoNetworkedField, DataField]
+    public bool IsClimbing;
+
+    /// <summary>
+    /// Whether the owner is being moved onto the climbed entity.
+    /// </summary>
+    [AutoNetworkedField, DataField(customTypeSerializer:typeof(TimeOffsetSerializer))]
+    public TimeSpan? NextTransition;
+
+    /// <summary>
+    /// Direction to move when transition.
+    /// </summary>
+    [AutoNetworkedField, DataField]
+    public Vector2 Direction;
+
+    /// <summary>
+    /// How fast the entity is moved when climbing.
+    /// </summary>
+    [DataField]
+    public float TransitionRate = 5f;
+
+    [AutoNetworkedField, DataField]
+    public Dictionary<string, int> DisabledFixtureMasks = new();
+}
similarity index 90%
rename from Content.Server/Climbing/Components/GlassTableComponent.cs
rename to Content.Shared/Climbing/Components/GlassTableComponent.cs
index 009fba91f44aae901a8391cbdbda5b7ff106baa6..d191793adf47a15ca255ade8c69f31f5d154b0b2 100644 (file)
@@ -1,13 +1,13 @@
 using Content.Shared.Damage;
 
-namespace Content.Server.Climbing.Components;
+namespace Content.Shared.Climbing.Components;
 
 /// <summary>
 ///     Glass tables shatter and stun you when climbed on.
 ///     This is a really entity-specific behavior, so opted to make it
 ///     not very generalized with regards to naming.
 /// </summary>
-[RegisterComponent, Access(typeof(ClimbSystem))]
+[RegisterComponent, Access(typeof(Systems.ClimbSystem))]
 public sealed partial class GlassTableComponent : Component
 {
     /// <summary>
diff --git a/Content.Shared/Climbing/Events/ClimbedOnEvent.cs b/Content.Shared/Climbing/Events/ClimbedOnEvent.cs
new file mode 100644 (file)
index 0000000..8b0484d
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Content.Shared.Climbing.Events;
+
+/// <summary>
+///     Raised on an entity when it is climbed on.
+/// </summary>
+[ByRefEvent]
+public readonly record struct ClimbedOnEvent(EntityUid Climber, EntityUid Instigator);
index 12eaac236d79c9b9bd983b95135c700b670824f9..6963cabf301e7566dca8bfdac1035cbe02df14ea 100644 (file)
@@ -4,7 +4,4 @@ namespace Content.Shared.Climbing.Events;
 /// Raised on an entity when it ends climbing.
 /// </summary>
 [ByRefEvent]
-public readonly record struct EndClimbEvent
-{
-
-}
+public readonly record struct EndClimbEvent;
diff --git a/Content.Shared/Climbing/Events/StartClimbEvent.cs b/Content.Shared/Climbing/Events/StartClimbEvent.cs
new file mode 100644 (file)
index 0000000..3563a39
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Content.Shared.Climbing.Events;
+
+/// <summary>
+///     Raised on an entity when it successfully climbs on something.
+/// </summary>
+[ByRefEvent]
+public readonly record struct StartClimbEvent(EntityUid Climbable);
diff --git a/Content.Shared/Climbing/SharedClimbSystem.cs b/Content.Shared/Climbing/SharedClimbSystem.cs
deleted file mode 100644 (file)
index 12b84bb..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-using Content.Shared.DoAfter;
-using Content.Shared.DragDrop;
-using Content.Shared.Movement.Events;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Climbing;
-
-public abstract partial class SharedClimbSystem : EntitySystem
-{
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<ClimbingComponent, UpdateCanMoveEvent>(HandleMoveAttempt);
-    }
-
-    private static void HandleMoveAttempt(EntityUid uid, ClimbingComponent component, UpdateCanMoveEvent args)
-    {
-        if (component.LifeStage > ComponentLifeStage.Running)
-            return;
-
-        if (component.OwnerIsTransitioning)
-            args.Cancel();
-    }
-
-    protected virtual void OnCanDragDropOn(EntityUid uid, ClimbableComponent component, ref CanDropTargetEvent args)
-    {
-        args.CanDrop = HasComp<ClimbingComponent>(args.Dragged);
-    }
-
-    [Serializable, NetSerializable]
-    protected sealed partial class ClimbDoAfterEvent : SimpleDoAfterEvent
-    {
-    }
-}
similarity index 88%
rename from Content.Shared/Climbing/BonkSystem.cs
rename to Content.Shared/Climbing/Systems/BonkSystem.cs
index eda392fa31f5c9359186eefbcc969be85196661a..6ded524b19d90d84cb7f9238529ff638b0508b8a 100644 (file)
@@ -1,17 +1,18 @@
-using Content.Shared.Interaction;
-using Content.Shared.Stunnable;
 using Content.Shared.CCVar;
+using Content.Shared.Climbing.Components;
 using Content.Shared.Damage;
 using Content.Shared.DoAfter;
 using Content.Shared.DragDrop;
-using Robust.Shared.Configuration;
-using Content.Shared.Popups;
 using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
 using Content.Shared.Interaction.Components;
+using Content.Shared.Popups;
+using Content.Shared.Stunnable;
+using Robust.Shared.Configuration;
 using Robust.Shared.Player;
 using Robust.Shared.Serialization;
 
-namespace Content.Shared.Climbing;
+namespace Content.Shared.Climbing.Systems;
 
 public sealed partial class BonkSystem : EntitySystem
 {
@@ -30,7 +31,7 @@ public sealed partial class BonkSystem : EntitySystem
         SubscribeLocalEvent<BonkableComponent, BonkDoAfterEvent>(OnBonkDoAfter);
     }
 
-    private void OnBonkDoAfter(EntityUid uid, BonkableComponent component, BonkDoAfterEvent args)
+    private void OnBonkDoAfter(EntityUid uid, Components.BonkableComponent component, BonkDoAfterEvent args)
     {
         if (args.Handled || args.Cancelled || args.Args.Target == null)
             return;
@@ -41,7 +42,7 @@ public sealed partial class BonkSystem : EntitySystem
     }
 
 
-    public bool TryBonk(EntityUid user, EntityUid bonkableUid, BonkableComponent? bonkableComponent = null)
+    public bool TryBonk(EntityUid user, EntityUid bonkableUid, Components.BonkableComponent? bonkableComponent = null)
     {
         if (!Resolve(bonkableUid, ref bonkableComponent, false))
             return false;
@@ -71,7 +72,7 @@ public sealed partial class BonkSystem : EntitySystem
 
     }
 
-    private void OnDragDrop(EntityUid uid, BonkableComponent component, ref DragDropTargetEvent args)
+    private void OnDragDrop(EntityUid uid, Components.BonkableComponent component, ref DragDropTargetEvent args)
     {
         if (args.Handled || !HasComp<ClumsyComponent>(args.Dragged))
             return;
diff --git a/Content.Shared/Climbing/Systems/ClimbSystem.cs b/Content.Shared/Climbing/Systems/ClimbSystem.cs
new file mode 100644 (file)
index 0000000..4e25fa4
--- /dev/null
@@ -0,0 +1,486 @@
+using System.Numerics;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.Body.Systems;
+using Content.Shared.Buckle.Components;
+using Content.Shared.Climbing.Components;
+using Content.Shared.Climbing.Events;
+using Content.Shared.Damage;
+using Content.Shared.DoAfter;
+using Content.Shared.DragDrop;
+using Content.Shared.Hands.Components;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
+using Content.Shared.Movement.Events;
+using Content.Shared.Movement.Systems;
+using Content.Shared.Physics;
+using Content.Shared.Popups;
+using Content.Shared.Stunnable;
+using Content.Shared.Verbs;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Collision.Shapes;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Controllers;
+using Robust.Shared.Physics.Events;
+using Robust.Shared.Physics.Systems;
+using Robust.Shared.Player;
+using Robust.Shared.Serialization;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Climbing.Systems;
+
+public sealed partial class ClimbSystem : VirtualController
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
+    [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+    [Dependency] private readonly FixtureSystem _fixtureSystem = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedBodySystem _bodySystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+    [Dependency] private readonly SharedStunSystem _stunSystem = default!;
+    [Dependency] private readonly SharedTransformSystem _xformSystem = default!;
+
+    private const string ClimbingFixtureName = "climb";
+    private const int ClimbingCollisionGroup = (int) (CollisionGroup.TableLayer | CollisionGroup.LowImpassable);
+
+    private EntityQuery<FixturesComponent> _fixturesQuery;
+    private EntityQuery<TransformComponent> _xformQuery;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        _fixturesQuery = GetEntityQuery<FixturesComponent>();
+        _xformQuery = GetEntityQuery<TransformComponent>();
+
+        SubscribeLocalEvent<ClimbingComponent, UpdateCanMoveEvent>(OnMoveAttempt);
+        SubscribeLocalEvent<ClimbingComponent, EntParentChangedMessage>(OnParentChange);
+        SubscribeLocalEvent<ClimbingComponent, ClimbDoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<ClimbingComponent, EndCollideEvent>(OnClimbEndCollide);
+        SubscribeLocalEvent<ClimbingComponent, BuckleChangeEvent>(OnBuckleChange);
+        SubscribeLocalEvent<ClimbingComponent, EntityUnpausedEvent>(OnClimbableUnpaused);
+
+        SubscribeLocalEvent<ClimbableComponent, CanDropTargetEvent>(OnCanDragDropOn);
+        SubscribeLocalEvent<ClimbableComponent, GetVerbsEvent<AlternativeVerb>>(AddClimbableVerb);
+        SubscribeLocalEvent<ClimbableComponent, DragDropTargetEvent>(OnClimbableDragDrop);
+
+        SubscribeLocalEvent<GlassTableComponent, ClimbedOnEvent>(OnGlassClimbed);
+    }
+
+    private void OnClimbableUnpaused(EntityUid uid, ClimbingComponent component, ref EntityUnpausedEvent args)
+    {
+        if (component.NextTransition == null)
+            return;
+
+        component.NextTransition = component.NextTransition.Value + args.PausedTime;
+        Dirty(uid, component);
+    }
+
+    public override void UpdateBeforeSolve(bool prediction, float frameTime)
+    {
+        base.UpdateBeforeSolve(prediction, frameTime);
+
+        var query = EntityQueryEnumerator<ClimbingComponent>();
+        var curTime = _timing.CurTime;
+
+        // Move anything still climb in the specified direction.
+        while (query.MoveNext(out var uid, out var comp))
+        {
+            if (comp.NextTransition == null)
+                continue;
+
+            if (comp.NextTransition < curTime)
+            {
+                FinishTransition(uid, comp);
+                continue;
+            }
+
+            var xform = _xformQuery.GetComponent(uid);
+            _xformSystem.SetLocalPositionNoLerp(uid, xform.LocalPosition + comp.Direction * frameTime, xform);
+        }
+    }
+
+    private void FinishTransition(EntityUid uid, ClimbingComponent comp)
+    {
+        // TODO: Validate climb here
+        comp.NextTransition = null;
+        _actionBlockerSystem.UpdateCanMove(uid);
+        Dirty(uid, comp);
+
+        // Stop if necessary.
+        if (!_fixturesQuery.TryGetComponent(uid, out var fixtures) ||
+            !IsClimbing(uid, fixtures))
+        {
+            StopClimb(uid, comp);
+            return;
+        }
+    }
+
+    /// <summary>
+    /// Returns true if entity currently has a valid vault.
+    /// </summary>
+    private bool IsClimbing(EntityUid uid, FixturesComponent? fixturesComp = null)
+    {
+        if (!_fixturesQuery.Resolve(uid, ref fixturesComp) || !fixturesComp.Fixtures.TryGetValue(ClimbingFixtureName, out var climbFixture))
+            return false;
+
+        foreach (var contact in climbFixture.Contacts.Values)
+        {
+            var other = uid == contact.EntityA ? contact.EntityB : contact.EntityA;
+
+            if (HasComp<ClimbableComponent>(other))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void OnMoveAttempt(EntityUid uid, ClimbingComponent component, UpdateCanMoveEvent args)
+    {
+        // Can't move when transition.
+        if (component.NextTransition != null)
+            args.Cancel();
+    }
+
+    private void OnParentChange(EntityUid uid, ClimbingComponent component, ref EntParentChangedMessage args)
+    {
+        if (component.NextTransition != null)
+        {
+            StopClimb(uid, component);
+        }
+    }
+
+     private void OnCanDragDropOn(EntityUid uid, ClimbableComponent component, ref CanDropTargetEvent args)
+     {
+         if (args.Handled)
+             return;
+
+         var canVault = args.User == args.Dragged
+             ? CanVault(component, args.User, uid, out _)
+             : CanVault(component, args.User, args.Dragged, uid, out _);
+
+         args.CanDrop = canVault;
+         args.Handled = true;
+     }
+
+     private void AddClimbableVerb(EntityUid uid, ClimbableComponent component, GetVerbsEvent<AlternativeVerb> args)
+     {
+         if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanMove(args.User))
+             return;
+
+         if (!TryComp(args.User, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing)
+             return;
+
+         // TODO VERBS ICON add a climbing icon?
+         args.Verbs.Add(new AlternativeVerb
+         {
+             Act = () => TryClimb(args.User, args.User, args.Target, out _, component),
+             Text = Loc.GetString("comp-climbable-verb-climb")
+         });
+     }
+
+     private void OnClimbableDragDrop(EntityUid uid, ClimbableComponent component, ref DragDropTargetEvent args)
+     {
+         // definitely a better way to check if two entities are equal
+         // but don't have computer access and i have to do this without syntax
+         if (args.Handled || args.User != args.Dragged && !HasComp<HandsComponent>(args.User))
+             return;
+
+         TryClimb(args.User, args.Dragged, uid, out _, component);
+     }
+
+     public bool TryClimb(
+         EntityUid user,
+         EntityUid entityToMove,
+         EntityUid climbable,
+         out DoAfterId? id,
+         ClimbableComponent? comp = null,
+         ClimbingComponent? climbing = null)
+     {
+         id = null;
+
+         if (!Resolve(climbable, ref comp) || !Resolve(entityToMove, ref climbing))
+             return false;
+
+         // Note, IsClimbing does not mean a DoAfter is active, it means the target has already finished a DoAfter and
+         // is currently on top of something..
+         if (climbing.IsClimbing)
+             return true;
+
+         var args = new DoAfterArgs(EntityManager, user, comp.ClimbDelay, new ClimbDoAfterEvent(),
+             entityToMove,
+             target: climbable,
+             used: entityToMove)
+         {
+             BreakOnTargetMove = true,
+             BreakOnUserMove = true,
+             BreakOnDamage = true
+         };
+
+         _audio.PlayPredicted(comp.StartClimbSound, climbable, user);
+         return _doAfterSystem.TryStartDoAfter(args, out id);
+     }
+
+     private void OnDoAfter(EntityUid uid, ClimbingComponent component, ClimbDoAfterEvent args)
+     {
+         if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null)
+             return;
+
+         Climb(uid, args.Args.User, args.Args.Target.Value, climbing: component);
+         args.Handled = true;
+     }
+
+     private void Climb(EntityUid uid, EntityUid user, EntityUid climbable, bool silent = false, ClimbingComponent? climbing = null,
+         PhysicsComponent? physics = null, FixturesComponent? fixtures = null, ClimbableComponent? comp = null)
+     {
+         if (!Resolve(uid, ref climbing, ref physics, ref fixtures, false))
+             return;
+
+         if (!Resolve(climbable, ref comp))
+             return;
+
+         if (!ReplaceFixtures(uid, climbing, fixtures))
+             return;
+
+         var xform = _xformQuery.GetComponent(uid);
+         var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform);
+         var worldDirection = _xformSystem.GetWorldPosition(climbable) - worldPos;
+         var distance = worldDirection.Length();
+         var parentRot = (worldRot - xform.LocalRotation);
+         // Need direction relative to climber's parent.
+         var localDirection = (-parentRot).RotateVec(worldDirection);
+
+         climbing.IsClimbing = true;
+         var climbDuration = TimeSpan.FromSeconds(distance / climbing.TransitionRate);
+         climbing.NextTransition = _timing.CurTime + climbDuration;
+
+         climbing.Direction = localDirection.Normalized() * climbing.TransitionRate;
+         Dirty(uid, climbing);
+
+         _audio.PlayPredicted(comp.FinishClimbSound, climbable, user);
+         _actionBlockerSystem.UpdateCanMove(uid);
+
+         var startEv = new StartClimbEvent(climbable);
+         var climbedEv = new ClimbedOnEvent(uid, user);
+         RaiseLocalEvent(uid, ref startEv);
+         RaiseLocalEvent(climbable, ref climbedEv);
+
+         if (silent)
+             return;
+
+         string selfMessage;
+         string othersMessage;
+
+         if (user == uid)
+         {
+             othersMessage = Loc.GetString("comp-climbable-user-climbs-other",
+                 ("user", Identity.Entity(uid, EntityManager)),
+                 ("climbable", climbable));
+
+             selfMessage = Loc.GetString("comp-climbable-user-climbs", ("climbable", climbable));
+         }
+         else
+         {
+             othersMessage = Loc.GetString("comp-climbable-user-climbs-force-other",
+                 ("user", Identity.Entity(user, EntityManager)),
+                 ("moved-user", Identity.Entity(uid, EntityManager)), ("climbable", climbable));
+
+             selfMessage = Loc.GetString("comp-climbable-user-climbs-force", ("moved-user", Identity.Entity(uid, EntityManager)),
+                 ("climbable", climbable));
+         }
+
+         _popupSystem.PopupEntity(othersMessage, uid, Filter.PvsExcept(user, entityManager: EntityManager), true);
+         _popupSystem.PopupClient(selfMessage, uid, user);
+     }
+
+     /// <summary>
+     /// Replaces the current fixtures with non-climbing collidable versions so that climb end can be detected
+     /// </summary>
+     /// <returns>Returns whether adding the new fixtures was successful</returns>
+     private bool ReplaceFixtures(EntityUid uid, ClimbingComponent climbingComp, FixturesComponent fixturesComp)
+     {
+         // Swap fixtures
+         foreach (var (name, fixture) in fixturesComp.Fixtures)
+         {
+             if (climbingComp.DisabledFixtureMasks.ContainsKey(name)
+                 || fixture.Hard == false
+                 || (fixture.CollisionMask & ClimbingCollisionGroup) == 0)
+             {
+                 continue;
+             }
+
+             climbingComp.DisabledFixtureMasks.Add(name, fixture.CollisionMask & ClimbingCollisionGroup);
+             _physics.SetCollisionMask(uid, name, fixture, fixture.CollisionMask & ~ClimbingCollisionGroup, fixturesComp);
+         }
+
+         if (!_fixtureSystem.TryCreateFixture(
+                 uid,
+                 new PhysShapeCircle(0.35f),
+                 ClimbingFixtureName,
+                 collisionLayer: (int) CollisionGroup.None,
+                 collisionMask: ClimbingCollisionGroup,
+                 hard: false,
+                 manager: fixturesComp))
+         {
+             return false;
+         }
+
+         return true;
+     }
+
+     private void OnClimbEndCollide(EntityUid uid, ClimbingComponent component, ref EndCollideEvent args)
+     {
+         if (args.OurFixtureId != ClimbingFixtureName
+             || !component.IsClimbing
+             || component.NextTransition != null)
+         {
+             return;
+         }
+
+         foreach (var fixture in args.OurFixture.Contacts.Keys)
+         {
+             if (fixture == args.OtherFixture)
+                 continue;
+
+             // If still colliding with a climbable, do not stop climbing
+             if (HasComp<ClimbableComponent>(args.OtherEntity))
+                 return;
+         }
+
+         StopClimb(uid, component);
+     }
+
+     private void StopClimb(EntityUid uid, ClimbingComponent? climbing = null, FixturesComponent? fixtures = null)
+     {
+         if (!Resolve(uid, ref climbing, ref fixtures, false))
+             return;
+
+         foreach (var (name, fixtureMask) in climbing.DisabledFixtureMasks)
+         {
+             if (!fixtures.Fixtures.TryGetValue(name, out var fixture))
+             {
+                 continue;
+             }
+
+             _physics.SetCollisionMask(uid, name, fixture, fixture.CollisionMask | fixtureMask, fixtures);
+         }
+
+         climbing.DisabledFixtureMasks.Clear();
+         _fixtureSystem.DestroyFixture(uid, ClimbingFixtureName, manager: fixtures);
+         climbing.IsClimbing = false;
+         climbing.NextTransition = null;
+         var ev = new EndClimbEvent();
+         RaiseLocalEvent(uid, ref ev);
+         Dirty(uid, climbing);
+     }
+
+     /// <summary>
+     ///     Checks if the user can vault the target
+     /// </summary>
+     /// <param name="component">The component of the entity that is being vaulted</param>
+     /// <param name="user">The entity that wants to vault</param>
+     /// <param name="target">The object that is being vaulted</param>
+     /// <param name="reason">The reason why it cant be dropped</param>
+     public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid target, out string reason)
+     {
+         if (!_actionBlockerSystem.CanInteract(user, target))
+         {
+             reason = Loc.GetString("comp-climbable-cant-interact");
+             return false;
+         }
+
+         if (!HasComp<ClimbingComponent>(user)
+             || !TryComp(user, out BodyComponent? body)
+             || !_bodySystem.BodyHasPartType(user, BodyPartType.Leg, body)
+             || !_bodySystem.BodyHasPartType(user, BodyPartType.Foot, body))
+         {
+             reason = Loc.GetString("comp-climbable-cant-climb");
+             return false;
+         }
+
+         if (!_interactionSystem.InRangeUnobstructed(user, target, component.Range))
+         {
+             reason = Loc.GetString("comp-climbable-cant-reach");
+             return false;
+         }
+
+         reason = string.Empty;
+         return true;
+     }
+
+     /// <summary>
+     ///     Checks if the user can vault the dragged entity onto the the target
+     /// </summary>
+     /// <param name="component">The climbable component of the object being vaulted onto</param>
+     /// <param name="user">The user that wants to vault the entity</param>
+     /// <param name="dragged">The entity that is being vaulted</param>
+     /// <param name="target">The object that is being vaulted onto</param>
+     /// <param name="reason">The reason why it cant be dropped</param>
+     /// <returns></returns>
+     public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid dragged, EntityUid target,
+         out string reason)
+     {
+         if (!_actionBlockerSystem.CanInteract(user, dragged) || !_actionBlockerSystem.CanInteract(user, target))
+         {
+             reason = Loc.GetString("comp-climbable-cant-interact");
+             return false;
+         }
+
+         if (!HasComp<ClimbingComponent>(dragged))
+         {
+             reason = Loc.GetString("comp-climbable-cant-climb");
+             return false;
+         }
+
+         bool Ignored(EntityUid entity) => entity == target || entity == user || entity == dragged;
+
+         if (!_interactionSystem.InRangeUnobstructed(user, target, component.Range, predicate: Ignored)
+             || !_interactionSystem.InRangeUnobstructed(user, dragged, component.Range, predicate: Ignored))
+         {
+             reason = Loc.GetString("comp-climbable-cant-reach");
+             return false;
+         }
+
+         reason = string.Empty;
+         return true;
+     }
+
+     public void ForciblySetClimbing(EntityUid uid, EntityUid climbable, ClimbingComponent? component = null)
+     {
+         Climb(uid, uid, climbable, true, component);
+     }
+
+     private void OnBuckleChange(EntityUid uid, ClimbingComponent component, ref BuckleChangeEvent args)
+     {
+         if (!args.Buckling)
+             return;
+         StopClimb(uid, component);
+     }
+
+     private void OnGlassClimbed(EntityUid uid, GlassTableComponent component, ref ClimbedOnEvent args)
+     {
+         if (TryComp<PhysicsComponent>(args.Climber, out var physics) && physics.Mass <= component.MassLimit)
+             return;
+
+         _damageableSystem.TryChangeDamage(args.Climber, component.ClimberDamage, origin: args.Climber);
+         _damageableSystem.TryChangeDamage(uid, component.TableDamage, origin: args.Climber);
+         _stunSystem.TryParalyze(args.Climber, TimeSpan.FromSeconds(component.StunTime), true);
+
+         // Not shown to the user, since they already get a 'you climb on the glass table' popup
+         _popupSystem.PopupEntity(
+             Loc.GetString("glass-table-shattered-others", ("table", uid), ("climber", Identity.Entity(args.Climber, EntityManager))), args.Climber,
+             Filter.PvsExcept(args.Climber), true);
+     }
+
+    [Serializable, NetSerializable]
+    private sealed partial class ClimbDoAfterEvent : SimpleDoAfterEvent
+    {
+    }
+}
index 691d9a47582459188efc1edffb2f3be9e7695098..382ecb5a9a52c6e8b9b33cd6f80933ebaf172b00 100644 (file)
@@ -43,7 +43,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
                 doAfter.CancelledTime = doAfter.CancelledTime.Value + args.PausedTime;
         }
 
-        Dirty(component);
+        Dirty(uid, component);
     }
 
     private void OnStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args)
@@ -55,7 +55,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
         {
             InternalCancel(doAfter, component);
         }
-        Dirty(component);
+        Dirty(uid, component);
     }
 
     /// <summary>
@@ -63,10 +63,12 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
     /// </summary>
     private void OnDamage(EntityUid uid, DoAfterComponent component, DamageChangedEvent args)
     {
-        if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null)
+        // If we're applying state then let the server state handle the do_after prediction.
+        // This is to avoid scenarios where a do_after is erroneously cancelled on the final tick.
+        if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null || GameTiming.ApplyingState)
             return;
 
-        var delta = args.DamageDelta?.Total;
+        var delta = args.DamageDelta.GetTotal();
 
         var dirty = false;
         foreach (var doAfter in component.DoAfters.Values)
@@ -79,7 +81,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
         }
 
         if (dirty)
-            Dirty(component);
+            Dirty(uid, component);
     }
 
     private void RaiseDoAfterEvents(DoAfter doAfter, DoAfterComponent component)
@@ -254,7 +256,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
 
         comp.DoAfters.Add(doAfter.Index, doAfter);
         EnsureComp<ActiveDoAfterComponent>(args.User);
-        Dirty(comp);
+        Dirty(args.User, comp);
         args.Event.DoAfter = doAfter;
         return true;
     }
index 7f1f6c23f73ddb0798ee185fc3147796b3bce204..24c79015d8221717830fb8106a9dd1c315e16e08 100644 (file)
@@ -1,6 +1,51 @@
-namespace Content.Shared.DragDrop;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Interaction;
+
+namespace Content.Shared.DragDrop;
 
 public abstract class SharedDragDropSystem : EntitySystem
 {
+    [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
+    [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeAllEvent<DragDropRequestEvent>(OnDragDropRequestEvent);
+    }
+
+    private void OnDragDropRequestEvent(DragDropRequestEvent msg, EntitySessionEventArgs args)
+    {
+        var dragged = GetEntity(msg.Dragged);
+        var target = GetEntity(msg.Target);
+
+        if (Deleted(dragged) || Deleted(target))
+            return;
+
+        var user = args.SenderSession.AttachedEntity;
+
+        if (user == null || !_actionBlockerSystem.CanInteract(user.Value, target))
+            return;
+
+        // must be in range of both the target and the object they are drag / dropping
+        // Client also does this check but ya know we gotta validate it.
+        if (!_interaction.InRangeUnobstructed(user.Value, dragged, popup: true)
+            || !_interaction.InRangeUnobstructed(user.Value, target, popup: true))
+        {
+            return;
+        }
+
+        var dragArgs = new DragDropDraggedEvent(user.Value, target);
+
+        // trigger dragdrops on the dropped entity
+        RaiseLocalEvent(dragged, ref dragArgs);
+
+        if (dragArgs.Handled)
+            return;
+
+        var dropArgs = new DragDropTargetEvent(user.Value, dragged);
 
+        // trigger dragdrops on the target entity (what you are dropping onto)
+        RaiseLocalEvent(GetEntity(msg.Target), ref dropArgs);
+    }
 }
index d79a892c71122915c1006f2169dfea0ef62a9946..4a9a43ca2c2b4f9c6b0228fdf9bdc60b61b4820b 100644 (file)
@@ -6,6 +6,7 @@ using Content.Shared.Administration.Logs;
 using Content.Shared.Administration.Managers;
 using Content.Shared.CombatMode;
 using Content.Shared.Database;
+using Content.Shared.DragDrop;
 using Content.Shared.Hands;
 using Content.Shared.Hands.Components;
 using Content.Shared.Input;