]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Predict wielding (#16275)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Wed, 10 May 2023 00:01:23 +0000 (10:01 +1000)
committerGitHub <noreply@github.com>
Wed, 10 May 2023 00:01:23 +0000 (10:01 +1000)
12 files changed:
Content.Server/Actions/Events/DisarmAttemptEvent.cs [deleted file]
Content.Server/Weapons/Melee/MeleeWeaponSystem.cs
Content.Server/Wieldable/Components/IncreaseDamageOnWieldComponent.cs [deleted file]
Content.Server/Wieldable/Components/WieldableComponent.cs [deleted file]
Content.Server/Wieldable/WieldableSystem.cs [deleted file]
Content.Shared/Actions/Events/DisarmAttemptEvent.cs [new file with mode: 0644]
Content.Shared/Wieldable/BeforeUnwieldEvent.cs [new file with mode: 0644]
Content.Shared/Wieldable/BeforeWieldEvent.cs [new file with mode: 0644]
Content.Shared/Wieldable/Components/IncreaseDamageOnWieldComponent.cs [new file with mode: 0644]
Content.Shared/Wieldable/Components/WieldableComponent.cs [new file with mode: 0644]
Content.Shared/Wieldable/ItemUnwieldedEvent.cs [new file with mode: 0644]
Content.Shared/Wieldable/WieldableSystem.cs [new file with mode: 0644]

diff --git a/Content.Server/Actions/Events/DisarmAttemptEvent.cs b/Content.Server/Actions/Events/DisarmAttemptEvent.cs
deleted file mode 100644 (file)
index 162731d..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace Content.Server.Actions.Events
-{
-    public sealed class DisarmAttemptEvent : CancellableEntityEventArgs
-    {
-        public readonly EntityUid TargetUid;
-        public readonly EntityUid DisarmerUid;
-        public readonly EntityUid? TargetItemInHandUid;
-
-        public DisarmAttemptEvent(EntityUid targetUid, EntityUid disarmerUid, EntityUid? targetItemInHandUid = null)
-        {
-            TargetUid = targetUid;
-            DisarmerUid = disarmerUid;
-            TargetItemInHandUid = targetItemInHandUid;
-        }
-    }
-}
index 0fcaaa6f4fce450bdc5b8778541afbb97b856905..60fa69649e820057ab0da98f1ba99d52141ce271 100644 (file)
@@ -1,5 +1,4 @@
 using System.Linq;
-using Content.Server.Actions.Events;
 using Content.Server.Body.Components;
 using Content.Server.Body.Systems;
 using Content.Server.Chemistry.Components;
@@ -8,6 +7,7 @@ using Content.Server.CombatMode.Disarm;
 using Content.Server.Contests;
 using Content.Server.Examine;
 using Content.Server.Movement.Systems;
+using Content.Shared.Actions.Events;
 using Content.Shared.Administration.Components;
 using Content.Shared.CombatMode;
 using Content.Shared.Damage;
@@ -25,7 +25,6 @@ using Content.Shared.Weapons.Melee.Events;
 using Robust.Server.Player;
 using Robust.Shared.Audio;
 using Robust.Shared.Map;
-using Robust.Shared.Physics;
 using Robust.Shared.Player;
 using Robust.Shared.Players;
 using Robust.Shared.Random;
diff --git a/Content.Server/Wieldable/Components/IncreaseDamageOnWieldComponent.cs b/Content.Server/Wieldable/Components/IncreaseDamageOnWieldComponent.cs
deleted file mode 100644 (file)
index 083bd51..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-using Content.Shared.Damage;
-
-namespace Content.Server.Wieldable.Components
-{
-    [RegisterComponent, Access(typeof(WieldableSystem))]
-    public sealed class IncreaseDamageOnWieldComponent : Component
-    {
-        [DataField("damage", required: true)]
-        public DamageSpecifier BonusDamage = default!;
-    }
-}
diff --git a/Content.Server/Wieldable/Components/WieldableComponent.cs b/Content.Server/Wieldable/Components/WieldableComponent.cs
deleted file mode 100644 (file)
index f54d87e..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-using Robust.Shared.Audio;
-
-namespace Content.Server.Wieldable.Components
-{
-    /// <summary>
-    ///     Used for objects that can be wielded in two or more hands,
-    /// </summary>
-    [RegisterComponent, Access(typeof(WieldableSystem))]
-    public sealed class WieldableComponent : Component
-    {
-        [DataField("wieldSound")]
-        public SoundSpecifier? WieldSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
-
-        [DataField("unwieldSound")]
-        public SoundSpecifier? UnwieldSound;
-
-        /// <summary>
-        ///     Number of free hands required (excluding the item itself) required
-        ///     to wield it
-        /// </summary>
-        [DataField("freeHandsRequired")]
-        public int FreeHandsRequired = 1;
-
-        public bool Wielded = false;
-
-        [DataField("wieldedInhandPrefix")]
-        public string WieldedInhandPrefix = "wielded";
-
-        public string? OldInhandPrefix = null;
-
-        [DataField("wieldTime")]
-        public float WieldTime = 1.5f;
-    }
-}
diff --git a/Content.Server/Wieldable/WieldableSystem.cs b/Content.Server/Wieldable/WieldableSystem.cs
deleted file mode 100644 (file)
index f158655..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-using Content.Server.Actions.Events;
-using Content.Server.Hands.Systems;
-using Content.Server.Wieldable.Components;
-using Content.Shared.DoAfter;
-using Content.Shared.Hands;
-using Content.Shared.Hands.Components;
-using Content.Shared.Hands.EntitySystems;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Item;
-using Content.Shared.Popups;
-using Content.Shared.Verbs;
-using Content.Shared.Weapons.Melee.Events;
-using Content.Shared.Wieldable;
-using Robust.Shared.Player;
-
-namespace Content.Server.Wieldable
-{
-    public sealed class WieldableSystem : EntitySystem
-    {
-        [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
-        [Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!;
-        [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
-        [Dependency] private readonly SharedItemSystem _itemSystem = default!;
-        [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
-        [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
-
-        public override void Initialize()
-        {
-            base.Initialize();
-
-            SubscribeLocalEvent<WieldableComponent, UseInHandEvent>(OnUseInHand);
-            SubscribeLocalEvent<WieldableComponent, WieldableDoAfterEvent>(OnDoAfter);
-            SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
-            SubscribeLocalEvent<WieldableComponent, GotUnequippedHandEvent>(OnItemLeaveHand);
-            SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
-            SubscribeLocalEvent<WieldableComponent, GetVerbsEvent<InteractionVerb>>(AddToggleWieldVerb);
-            SubscribeLocalEvent<WieldableComponent, DisarmAttemptEvent>(OnDisarmAttemptEvent);
-
-            SubscribeLocalEvent<IncreaseDamageOnWieldComponent, MeleeHitEvent>(OnMeleeHit);
-        }
-
-        private void OnDisarmAttemptEvent(EntityUid uid, WieldableComponent component, DisarmAttemptEvent args)
-        {
-            if (component.Wielded)
-                args.Cancel();
-        }
-
-        private void AddToggleWieldVerb(EntityUid uid, WieldableComponent component, GetVerbsEvent<InteractionVerb> args)
-        {
-            if (args.Hands == null || !args.CanAccess || !args.CanInteract)
-                return;
-
-            if (!_handsSystem.IsHolding(args.User, uid, out _, args.Hands))
-                return;
-
-            // TODO VERB TOOLTIPS Make CanWield or some other function return string, set as verb tooltip and disable
-            // verb. Or just don't add it to the list if the action is not executable.
-
-            // TODO VERBS ICON + localization
-            InteractionVerb verb = new()
-            {
-                Text = component.Wielded ? Loc.GetString("wieldable-verb-text-unwield") : Loc.GetString("wieldable-verb-text-wield"),
-                Act = component.Wielded
-                    ? () => AttemptUnwield(component.Owner, component, args.User)
-                    : () => AttemptWield(component.Owner, component, args.User)
-            };
-
-            args.Verbs.Add(verb);
-        }
-
-        private void OnUseInHand(EntityUid uid, WieldableComponent component, UseInHandEvent args)
-        {
-            if (args.Handled)
-                return;
-            if(!component.Wielded)
-                AttemptWield(uid, component, args.User);
-            else
-                AttemptUnwield(uid, component, args.User);
-        }
-
-        public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user, bool quiet=false)
-        {
-            // Do they have enough hands free?
-            if (!EntityManager.TryGetComponent<HandsComponent>(user, out var hands))
-            {
-                if(!quiet)
-                    _popupSystem.PopupEntity(Loc.GetString("wieldable-component-no-hands"), user, user);
-                return false;
-            }
-
-            // Is it.. actually in one of their hands?
-            if (!_handsSystem.IsHolding(user, uid, out _, hands))
-            {
-                if (!quiet)
-                    _popupSystem.PopupEntity(Loc.GetString("wieldable-component-not-in-hands", ("item", uid)), user, user);
-                return false;
-            }
-
-            if (hands.CountFreeHands() < component.FreeHandsRequired)
-            {
-                if (!quiet)
-                {
-                    var message = Loc.GetString("wieldable-component-not-enough-free-hands",
-                        ("number", component.FreeHandsRequired), ("item", uid));
-                    _popupSystem.PopupEntity(message, user, user);
-                }
-                return false;
-            }
-
-            // Seems legit.
-            return true;
-        }
-
-        /// <summary>
-        ///     Attempts to wield an item, creating a DoAfter..
-        /// </summary>
-        public void AttemptWield(EntityUid used, WieldableComponent component, EntityUid user)
-        {
-            if (!CanWield(used, component, user))
-                return;
-            var ev = new BeforeWieldEvent();
-            RaiseLocalEvent(used, ev);
-
-            if (ev.Cancelled)
-                return;
-
-            var doargs = new DoAfterArgs(user, component.WieldTime, new WieldableDoAfterEvent(), used, used: used)
-            {
-                BreakOnUserMove = false,
-                BreakOnDamage = true
-            };
-
-            _doAfter.TryStartDoAfter(doargs);
-        }
-
-        /// <summary>
-        ///     Attempts to unwield an item, with no DoAfter.
-        /// </summary>
-        public void AttemptUnwield(EntityUid used, WieldableComponent component, EntityUid user)
-        {
-            var ev = new BeforeUnwieldEvent();
-            RaiseLocalEvent(used, ev);
-
-            if (ev.Cancelled)
-                return;
-
-            var targEv = new ItemUnwieldedEvent(user);
-
-            RaiseLocalEvent(used, targEv);
-        }
-
-        private void OnDoAfter(EntityUid uid, WieldableComponent component, DoAfterEvent args)
-        {
-            if (args.Handled || args.Cancelled || !CanWield(uid, component, args.Args.User) || component.Wielded)
-                return;
-
-            if (TryComp<ItemComponent>(uid, out var item))
-            {
-                component.OldInhandPrefix = item.HeldPrefix;
-                _itemSystem.SetHeldPrefix(uid, component.WieldedInhandPrefix, item);
-            }
-
-            component.Wielded = true;
-
-            if (component.WieldSound != null)
-                _audioSystem.PlayPvs(component.WieldSound, uid);
-
-            for (int i = 0; i < component.FreeHandsRequired; i++)
-            {
-                _virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.Args.User);
-            }
-
-            _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield", ("item", uid)), args.Args.User, args.Args.User);
-            _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield-other", ("user", args.Args.User),("item", uid)), args.Args.User, Filter.PvsExcept(args.Args.User), true);
-
-            args.Handled = true;
-        }
-
-        private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUnwieldedEvent args)
-        {
-            if (args.User == null)
-                return;
-            if (!component.Wielded)
-                return;
-
-            if (TryComp<ItemComponent>(uid, out var item))
-            {
-                _itemSystem.SetHeldPrefix(uid, component.OldInhandPrefix, item);
-            }
-
-            component.Wielded = false;
-
-            if (!args.Force) // don't play sound/popup if this was a forced unwield
-            {
-                if (component.UnwieldSound != null)
-                    _audioSystem.PlayPvs(component.UnwieldSound, uid);
-
-                _popupSystem.PopupEntity(Loc.GetString("wieldable-component-failed-wield",
-                    ("item", uid)), args.User.Value, args.User.Value);
-                _popupSystem.PopupEntity(Loc.GetString("wieldable-component-failed-wield-other",
-                    ("user", args.User.Value), ("item", uid)), args.User.Value, Filter.PvsExcept(args.User.Value), true);
-            }
-
-            _virtualItemSystem.DeleteInHandsMatching(args.User.Value, uid);
-        }
-
-        private void OnItemLeaveHand(EntityUid uid, WieldableComponent component, GotUnequippedHandEvent args)
-        {
-            if (!component.Wielded || component.Owner != args.Unequipped)
-                return;
-            RaiseLocalEvent(uid, new ItemUnwieldedEvent(args.User, force: true), true);
-        }
-
-        private void OnVirtualItemDeleted(EntityUid uid, WieldableComponent component, VirtualItemDeletedEvent args)
-        {
-            if (args.BlockingEntity == uid && component.Wielded)
-                AttemptUnwield(args.BlockingEntity, component, args.User);
-        }
-
-        private void OnMeleeHit(EntityUid uid, IncreaseDamageOnWieldComponent component, MeleeHitEvent args)
-        {
-            if (EntityManager.TryGetComponent<WieldableComponent>(uid, out var wield))
-            {
-                if (!wield.Wielded)
-                    return;
-            }
-            if (args.Handled)
-                return;
-
-            args.BonusDamage += component.BonusDamage;
-        }
-    }
-
-    #region Events
-
-    public sealed class BeforeWieldEvent : CancellableEntityEventArgs
-    {
-    }
-
-    public sealed class BeforeUnwieldEvent : CancellableEntityEventArgs
-    {
-    }
-
-    /// <summary>
-    ///     Raised on the item that has been unwielded.
-    /// </summary>
-    public sealed class ItemUnwieldedEvent : EntityEventArgs
-    {
-        public EntityUid? User;
-        /// <summary>
-        ///     Whether the item is being forced to be unwielded, or if the player chose to unwield it themselves.
-        /// </summary>
-        public bool Force;
-
-        public ItemUnwieldedEvent(EntityUid? user = null, bool force=false)
-        {
-            User = user;
-            Force = force;
-        }
-    }
-
-    #endregion
-}
diff --git a/Content.Shared/Actions/Events/DisarmAttemptEvent.cs b/Content.Shared/Actions/Events/DisarmAttemptEvent.cs
new file mode 100644 (file)
index 0000000..8252205
--- /dev/null
@@ -0,0 +1,15 @@
+namespace Content.Shared.Actions.Events;
+
+public sealed class DisarmAttemptEvent : CancellableEntityEventArgs
+{
+    public readonly EntityUid TargetUid;
+    public readonly EntityUid DisarmerUid;
+    public readonly EntityUid? TargetItemInHandUid;
+
+    public DisarmAttemptEvent(EntityUid targetUid, EntityUid disarmerUid, EntityUid? targetItemInHandUid = null)
+    {
+        TargetUid = targetUid;
+        DisarmerUid = disarmerUid;
+        TargetItemInHandUid = targetItemInHandUid;
+    }
+}
\ No newline at end of file
diff --git a/Content.Shared/Wieldable/BeforeUnwieldEvent.cs b/Content.Shared/Wieldable/BeforeUnwieldEvent.cs
new file mode 100644 (file)
index 0000000..4328ba5
--- /dev/null
@@ -0,0 +1,5 @@
+namespace Content.Shared.Wieldable;
+
+public sealed class BeforeUnwieldEvent : CancellableEntityEventArgs
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Wieldable/BeforeWieldEvent.cs b/Content.Shared/Wieldable/BeforeWieldEvent.cs
new file mode 100644 (file)
index 0000000..744a009
--- /dev/null
@@ -0,0 +1,5 @@
+namespace Content.Shared.Wieldable;
+
+public sealed class BeforeWieldEvent : CancellableEntityEventArgs
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Wieldable/Components/IncreaseDamageOnWieldComponent.cs b/Content.Shared/Wieldable/Components/IncreaseDamageOnWieldComponent.cs
new file mode 100644 (file)
index 0000000..4d453d5
--- /dev/null
@@ -0,0 +1,10 @@
+using Content.Shared.Damage;
+
+namespace Content.Shared.Wieldable.Components;
+
+[RegisterComponent, Access(typeof(WieldableSystem))]
+public sealed class IncreaseDamageOnWieldComponent : Component
+{
+    [DataField("damage", required: true)]
+    public DamageSpecifier BonusDamage = default!;
+}
\ No newline at end of file
diff --git a/Content.Shared/Wieldable/Components/WieldableComponent.cs b/Content.Shared/Wieldable/Components/WieldableComponent.cs
new file mode 100644 (file)
index 0000000..541a5a6
--- /dev/null
@@ -0,0 +1,35 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Wieldable.Components;
+
+/// <summary>
+///     Used for objects that can be wielded in two or more hands,
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(WieldableSystem)), AutoGenerateComponentState]
+public sealed partial class WieldableComponent : Component
+{
+    [DataField("wieldSound")]
+    public SoundSpecifier? WieldSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
+
+    [DataField("unwieldSound")]
+    public SoundSpecifier? UnwieldSound;
+
+    /// <summary>
+    ///     Number of free hands required (excluding the item itself) required
+    ///     to wield it
+    /// </summary>
+    [DataField("freeHandsRequired")]
+    public int FreeHandsRequired = 1;
+
+    [AutoNetworkedField, DataField("wielded")]
+    public bool Wielded = false;
+
+    [DataField("wieldedInhandPrefix")]
+    public string WieldedInhandPrefix = "wielded";
+
+    public string? OldInhandPrefix = null;
+
+    [DataField("wieldTime")]
+    public float WieldTime = 1.5f;
+}
diff --git a/Content.Shared/Wieldable/ItemUnwieldedEvent.cs b/Content.Shared/Wieldable/ItemUnwieldedEvent.cs
new file mode 100644 (file)
index 0000000..f980d45
--- /dev/null
@@ -0,0 +1,23 @@
+namespace Content.Shared.Wieldable;
+
+#region Events
+
+/// <summary>
+///     Raised on the item that has been unwielded.
+/// </summary>
+public sealed class ItemUnwieldedEvent : EntityEventArgs
+{
+    public EntityUid? User;
+    /// <summary>
+    ///     Whether the item is being forced to be unwielded, or if the player chose to unwield it themselves.
+    /// </summary>
+    public bool Force;
+
+    public ItemUnwieldedEvent(EntityUid? user = null, bool force=false)
+    {
+        User = user;
+        Force = force;
+    }
+}
+
+#endregion
diff --git a/Content.Shared/Wieldable/WieldableSystem.cs b/Content.Shared/Wieldable/WieldableSystem.cs
new file mode 100644 (file)
index 0000000..9298563
--- /dev/null
@@ -0,0 +1,232 @@
+using Content.Shared.Actions.Events;
+using Content.Shared.DoAfter;
+using Content.Shared.Hands;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Item;
+using Content.Shared.Popups;
+using Content.Shared.Verbs;
+using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.Wieldable.Components;
+using Robust.Shared.Player;
+
+namespace Content.Shared.Wieldable;
+
+public sealed class WieldableSystem : EntitySystem
+{
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedHandVirtualItemSystem _virtualItemSystem = default!;
+    [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+    [Dependency] private readonly SharedItemSystem _itemSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<WieldableComponent, UseInHandEvent>(OnUseInHand);
+        SubscribeLocalEvent<WieldableComponent, WieldableDoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
+        SubscribeLocalEvent<WieldableComponent, GotUnequippedHandEvent>(OnItemLeaveHand);
+        SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
+        SubscribeLocalEvent<WieldableComponent, GetVerbsEvent<InteractionVerb>>(AddToggleWieldVerb);
+        SubscribeLocalEvent<WieldableComponent, DisarmAttemptEvent>(OnDisarmAttemptEvent);
+
+        SubscribeLocalEvent<IncreaseDamageOnWieldComponent, MeleeHitEvent>(OnMeleeHit);
+    }
+
+    private void OnDisarmAttemptEvent(EntityUid uid, WieldableComponent component, DisarmAttemptEvent args)
+    {
+        if (component.Wielded)
+            args.Cancel();
+    }
+
+    private void AddToggleWieldVerb(EntityUid uid, WieldableComponent component, GetVerbsEvent<InteractionVerb> args)
+    {
+        if (args.Hands == null || !args.CanAccess || !args.CanInteract)
+            return;
+
+        if (!_handsSystem.IsHolding(args.User, uid, out _, args.Hands))
+            return;
+
+        // TODO VERB TOOLTIPS Make CanWield or some other function return string, set as verb tooltip and disable
+        // verb. Or just don't add it to the list if the action is not executable.
+
+        // TODO VERBS ICON
+        InteractionVerb verb = new()
+        {
+            Text = component.Wielded ? Loc.GetString("wieldable-verb-text-unwield") : Loc.GetString("wieldable-verb-text-wield"),
+            Act = component.Wielded
+                ? () => AttemptUnwield(uid, component, args.User)
+                : () => AttemptWield(uid, component, args.User)
+        };
+
+        args.Verbs.Add(verb);
+    }
+
+    private void OnUseInHand(EntityUid uid, WieldableComponent component, UseInHandEvent args)
+    {
+        if (args.Handled)
+            return;
+        if(!component.Wielded)
+            AttemptWield(uid, component, args.User);
+        else
+            AttemptUnwield(uid, component, args.User);
+    }
+
+    public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user, bool quiet=false)
+    {
+        // Do they have enough hands free?
+        if (!EntityManager.TryGetComponent<HandsComponent>(user, out var hands))
+        {
+            if(!quiet)
+                _popupSystem.PopupClient(Loc.GetString("wieldable-component-no-hands"), user, user);
+            return false;
+        }
+
+        // Is it.. actually in one of their hands?
+        if (!_handsSystem.IsHolding(user, uid, out _, hands))
+        {
+            if (!quiet)
+                _popupSystem.PopupClient(Loc.GetString("wieldable-component-not-in-hands", ("item", uid)), user, user);
+            return false;
+        }
+
+        if (hands.CountFreeHands() < component.FreeHandsRequired)
+        {
+            if (!quiet)
+            {
+                var message = Loc.GetString("wieldable-component-not-enough-free-hands",
+                    ("number", component.FreeHandsRequired), ("item", uid));
+                _popupSystem.PopupClient(message, user, user);
+            }
+            return false;
+        }
+
+        // Seems legit.
+        return true;
+    }
+
+    /// <summary>
+    ///     Attempts to wield an item, creating a DoAfter..
+    /// </summary>
+    public void AttemptWield(EntityUid used, WieldableComponent component, EntityUid user)
+    {
+        if (!CanWield(used, component, user))
+            return;
+        var ev = new BeforeWieldEvent();
+        RaiseLocalEvent(used, ev);
+
+        if (ev.Cancelled)
+            return;
+
+        var doargs = new DoAfterArgs(user, component.WieldTime, new WieldableDoAfterEvent(), used, used: used)
+        {
+            BreakOnUserMove = false,
+            BreakOnDamage = true
+        };
+
+        _doAfter.TryStartDoAfter(doargs);
+    }
+
+    /// <summary>
+    ///     Attempts to unwield an item, with no DoAfter.
+    /// </summary>
+    public void AttemptUnwield(EntityUid used, WieldableComponent component, EntityUid user)
+    {
+        var ev = new BeforeUnwieldEvent();
+        RaiseLocalEvent(used, ev);
+
+        if (ev.Cancelled)
+            return;
+
+        var targEv = new ItemUnwieldedEvent(user);
+
+        RaiseLocalEvent(used, targEv);
+    }
+
+    private void OnDoAfter(EntityUid uid, WieldableComponent component, DoAfterEvent args)
+    {
+        if (args.Handled || args.Cancelled || !CanWield(uid, component, args.Args.User) || component.Wielded)
+            return;
+
+        if (TryComp<ItemComponent>(uid, out var item))
+        {
+            component.OldInhandPrefix = item.HeldPrefix;
+            _itemSystem.SetHeldPrefix(uid, component.WieldedInhandPrefix, item);
+        }
+
+        component.Wielded = true;
+
+        if (component.WieldSound != null)
+            _audioSystem.PlayPredicted(component.WieldSound, uid, args.User);
+
+        for (var i = 0; i < component.FreeHandsRequired; i++)
+        {
+            _virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.Args.User);
+        }
+
+        _popupSystem.PopupClient(Loc.GetString("wieldable-component-successful-wield", ("item", uid)), args.Args.User, args.Args.User);
+        _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield-other", ("user", args.Args.User),("item", uid)), args.Args.User, Filter.PvsExcept(args.Args.User), true);
+
+        Dirty(component);
+        args.Handled = true;
+    }
+
+    private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUnwieldedEvent args)
+    {
+        if (args.User == null)
+            return;
+        if (!component.Wielded)
+            return;
+
+        if (TryComp<ItemComponent>(uid, out var item))
+        {
+            _itemSystem.SetHeldPrefix(uid, component.OldInhandPrefix, item);
+        }
+
+        component.Wielded = false;
+
+        if (!args.Force) // don't play sound/popup if this was a forced unwield
+        {
+            if (component.UnwieldSound != null)
+                _audioSystem.PlayPredicted(component.UnwieldSound, uid, args.User);
+
+            _popupSystem.PopupClient(Loc.GetString("wieldable-component-failed-wield",
+                ("item", uid)), args.User.Value, args.User.Value);
+            _popupSystem.PopupEntity(Loc.GetString("wieldable-component-failed-wield-other",
+                ("user", args.User.Value), ("item", uid)), args.User.Value, Filter.PvsExcept(args.User.Value), true);
+        }
+
+        Dirty(component);
+        _virtualItemSystem.DeleteInHandsMatching(args.User.Value, uid);
+    }
+
+    private void OnItemLeaveHand(EntityUid uid, WieldableComponent component, GotUnequippedHandEvent args)
+    {
+        if (!component.Wielded || uid != args.Unequipped)
+            return;
+        RaiseLocalEvent(uid, new ItemUnwieldedEvent(args.User, force: true), true);
+    }
+
+    private void OnVirtualItemDeleted(EntityUid uid, WieldableComponent component, VirtualItemDeletedEvent args)
+    {
+        if (args.BlockingEntity == uid && component.Wielded)
+            AttemptUnwield(args.BlockingEntity, component, args.User);
+    }
+
+    private void OnMeleeHit(EntityUid uid, IncreaseDamageOnWieldComponent component, MeleeHitEvent args)
+    {
+        if (EntityManager.TryGetComponent<WieldableComponent>(uid, out var wield))
+        {
+            if (!wield.Wielded)
+                return;
+        }
+        if (args.Handled)
+            return;
+
+        args.BonusDamage += component.BonusDamage;
+    }
+}