From 62f6c8dd8e2ee7ee71b90a044280b3f7ed9ee431 Mon Sep 17 00:00:00 2001 From: mirrorcult Date: Fri, 17 Sep 2021 07:16:11 -0700 Subject: [PATCH] Wieldable/two-handed weapons (#4554) * wielding kinda works * rough out all the edges, wielding works nicely * popups + loc * increase damage & extra damage against whitelist * small fixes * forgot to actually do that * reviews * reviews + thing * use resistances and not extradamageagainstwhitelist * slashy * make increasedamageonwield and melee hit events work with modifiersets * Silly individual --- Content.Client/Entry/IgnoredComponents.cs | 2 + ...Status.xaml => HandVirtualItemStatus.xaml} | 2 +- ....xaml.cs => HandVirtualItemStatus.xaml.cs} | 4 +- Content.Client/Hands/HandsGui.xaml.cs | 4 +- ...PullSystem.cs => HandVirtualItemSystem.cs} | 6 +- .../Items/Managers/ItemSlotManager.cs | 4 +- Content.Client/Items/UI/ItemStatusPanel.cs | 6 +- Content.Server/DoAfter/DoAfterEventArgs.cs | 8 +- .../Hands/Systems/HandVirtualItemSystem.cs | 106 +++++++ .../Hands/Systems/HandVirtualPullSystem.cs | 57 ---- Content.Server/Hands/Systems/HandsSystem.cs | 23 +- .../Weapon/Melee/MeleeWeaponSystem.cs | 18 +- .../IncreaseDamageOnWieldComponent.cs | 17 ++ .../Components/WieldableComponent.cs | 59 ++++ Content.Server/Wieldable/WieldableSystem.cs | 289 ++++++++++++++++++ Content.Shared/Damage/DamageSpecifier.cs | 18 ++ .../Components/HandVirtualItemComponent.cs | 53 ++++ .../Components/HandVirtualPullComponent.cs | 50 --- .../Hands/Components/SharedHandsComponent.cs | 19 +- Content.Shared/Hands/SharedHandsSystem.cs | 16 + .../Components/SharedPullableComponent.cs | 9 +- .../Components/SharedPullerComponent.cs | 8 +- .../Pulling/Systems/SharedPullerSystem.cs | 18 +- .../en-US/wieldable/wieldable-component.ftl | 8 + Resources/Prototypes/Damage/modifier_sets.yml | 12 +- .../Objects/Weapons/Melee/fireaxe.yml | 15 +- .../Structures/Furniture/Tables/tables.yml | 10 + .../Structures/Furniture/bookshelf.yml | 4 + .../Entities/Structures/Furniture/seats.yml | 5 + .../Entities/Structures/Walls/barricades.yml | 3 +- .../Entities/Virtual/virtual_item.yml | 8 + .../Entities/Virtual/virtual_pull_item.yml | 8 - Resources/Prototypes/tags.yml | 3 + .../Weapons/Melee/fireaxe.rsi/meta.json | 8 + .../Melee/fireaxe.rsi/wielded-inhand-left.png | Bin 0 -> 906 bytes .../fireaxe.rsi/wielded-inhand-right.png | Bin 0 -> 913 bytes 36 files changed, 719 insertions(+), 161 deletions(-) rename Content.Client/Hands/{HandVirtualPullItemStatus.xaml => HandVirtualItemStatus.xaml} (50%) rename Content.Client/Hands/{HandVirtualPullItemStatus.xaml.cs => HandVirtualItemStatus.xaml.cs} (64%) rename Content.Client/Hands/Systems/{HandVirtualPullSystem.cs => HandVirtualItemSystem.cs} (58%) create mode 100644 Content.Server/Hands/Systems/HandVirtualItemSystem.cs delete mode 100644 Content.Server/Hands/Systems/HandVirtualPullSystem.cs create mode 100644 Content.Server/Wieldable/Components/IncreaseDamageOnWieldComponent.cs create mode 100644 Content.Server/Wieldable/Components/WieldableComponent.cs create mode 100644 Content.Server/Wieldable/WieldableSystem.cs create mode 100644 Content.Shared/Hands/Components/HandVirtualItemComponent.cs delete mode 100644 Content.Shared/Hands/Components/HandVirtualPullComponent.cs create mode 100644 Resources/Locale/en-US/wieldable/wieldable-component.ftl create mode 100644 Resources/Prototypes/Entities/Virtual/virtual_item.yml delete mode 100644 Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml create mode 100644 Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/wielded-inhand-left.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/wielded-inhand-right.png diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 38bcbe940d..b609193342 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -274,6 +274,8 @@ namespace Content.Client.Entry "PowerNetworkBattery", "BatteryCharger", "SpawnItemsOnUse", + "Wieldable", + "IncreaseDamageOnWield", "AmbientOnPowered", "TabletopGame" }; diff --git a/Content.Client/Hands/HandVirtualPullItemStatus.xaml b/Content.Client/Hands/HandVirtualItemStatus.xaml similarity index 50% rename from Content.Client/Hands/HandVirtualPullItemStatus.xaml rename to Content.Client/Hands/HandVirtualItemStatus.xaml index 9fac1e0d08..02258edd8e 100644 --- a/Content.Client/Hands/HandVirtualPullItemStatus.xaml +++ b/Content.Client/Hands/HandVirtualItemStatus.xaml @@ -1,3 +1,3 @@  - diff --git a/Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs b/Content.Client/Hands/HandVirtualItemStatus.xaml.cs similarity index 64% rename from Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs rename to Content.Client/Hands/HandVirtualItemStatus.xaml.cs index f4324f6e41..8fb8519261 100644 --- a/Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs +++ b/Content.Client/Hands/HandVirtualItemStatus.xaml.cs @@ -3,9 +3,9 @@ using Robust.Client.UserInterface.XAML; namespace Content.Client.Hands { - public sealed class HandVirtualPullItemStatus : Control + public sealed class HandVirtualItemStatus : Control { - public HandVirtualPullItemStatus() + public HandVirtualItemStatus() { RobustXamlLoader.Load(this); } diff --git a/Content.Client/Hands/HandsGui.xaml.cs b/Content.Client/Hands/HandsGui.xaml.cs index 63590882bf..ccb679790a 100644 --- a/Content.Client/Hands/HandsGui.xaml.cs +++ b/Content.Client/Hands/HandsGui.xaml.cs @@ -98,9 +98,9 @@ namespace Content.Client.Hands _itemSlotManager.SetItemSlot(newButton, hand.HeldItem); - // Show blocked overlay if hand is pulling. + // Show blocked overlay if hand is blocked. newButton.Blocked.Visible = - hand.HeldItem != null && hand.HeldItem.HasComponent(); + hand.HeldItem != null && hand.HeldItem.HasComponent(); } if (TryGetActiveHand(out var activeHand)) diff --git a/Content.Client/Hands/Systems/HandVirtualPullSystem.cs b/Content.Client/Hands/Systems/HandVirtualItemSystem.cs similarity index 58% rename from Content.Client/Hands/Systems/HandVirtualPullSystem.cs rename to Content.Client/Hands/Systems/HandVirtualItemSystem.cs index 0f47efd9dc..deb9f993a9 100644 --- a/Content.Client/Hands/Systems/HandVirtualPullSystem.cs +++ b/Content.Client/Hands/Systems/HandVirtualItemSystem.cs @@ -3,16 +3,16 @@ using Content.Shared.Hands.Components; using JetBrains.Annotations; using Robust.Shared.GameObjects; -namespace Content.Client.Hands +namespace Content.Client.Hands.Systems { [UsedImplicitly] - public sealed class HandVirtualPullSystem : EntitySystem + public sealed class HandVirtualItemSystem : EntitySystem { public override void Initialize() { base.Initialize(); - Subs.ItemStatus(_ => new HandVirtualPullItemStatus()); + Subs.ItemStatus(_ => new HandVirtualItemStatus()); } } } diff --git a/Content.Client/Items/Managers/ItemSlotManager.cs b/Content.Client/Items/Managers/ItemSlotManager.cs index c274547cda..515eb521ec 100644 --- a/Content.Client/Items/Managers/ItemSlotManager.cs +++ b/Content.Client/Items/Managers/ItemSlotManager.cs @@ -48,8 +48,8 @@ namespace Content.Client.Items.Managers else { ISpriteComponent? sprite; - if (entity.TryGetComponent(out HandVirtualPullComponent? virtPull) - && _componentManager.TryGetComponent(virtPull.PulledEntity, out ISpriteComponent pulledSprite)) + if (entity.TryGetComponent(out HandVirtualItemComponent? virtPull) + && _componentManager.TryGetComponent(virtPull.BlockingEntity, out ISpriteComponent pulledSprite)) { sprite = pulledSprite; } diff --git a/Content.Client/Items/UI/ItemStatusPanel.cs b/Content.Client/Items/UI/ItemStatusPanel.cs index d1dbd714c6..e4d7aa1d41 100644 --- a/Content.Client/Items/UI/ItemStatusPanel.cs +++ b/Content.Client/Items/UI/ItemStatusPanel.cs @@ -156,10 +156,10 @@ namespace Content.Client.Items.UI if (_entity == null) return; - if (_entity.TryGetComponent(out HandVirtualPullComponent? virtualPull) - && _entityManager.TryGetEntity(virtualPull.PulledEntity, out var pulledEnt)) + if (_entity.TryGetComponent(out HandVirtualItemComponent? virtualItem) + && _entityManager.TryGetEntity(virtualItem.BlockingEntity, out var blockEnt)) { - _itemNameLabel.Text = pulledEnt.Name; + _itemNameLabel.Text = blockEnt.Name; } else { diff --git a/Content.Server/DoAfter/DoAfterEventArgs.cs b/Content.Server/DoAfter/DoAfterEventArgs.cs index 8c1e9e4717..83d1b15c00 100644 --- a/Content.Server/DoAfter/DoAfterEventArgs.cs +++ b/Content.Server/DoAfter/DoAfterEventArgs.cs @@ -83,22 +83,22 @@ namespace Content.Server.DoAfter /// /// Event to be raised directed to the entity when the DoAfter is cancelled. /// - public EntityEventArgs? UserCancelledEvent { get; set; } + public object? UserCancelledEvent { get; set; } /// /// Event to be raised directed to the entity when the DoAfter is finished successfully. /// - public EntityEventArgs? UserFinishedEvent { get; set; } + public object? UserFinishedEvent { get; set; } /// /// Event to be raised directed to the entity when the DoAfter is cancelled. /// - public EntityEventArgs? TargetCancelledEvent { get; set; } + public object? TargetCancelledEvent { get; set; } /// /// Event to be raised directed to the entity when the DoAfter is finished successfully. /// - public EntityEventArgs? TargetFinishedEvent { get; set; } + public object? TargetFinishedEvent { get; set; } /// /// Event to be broadcast when the DoAfter is cancelled. diff --git a/Content.Server/Hands/Systems/HandVirtualItemSystem.cs b/Content.Server/Hands/Systems/HandVirtualItemSystem.cs new file mode 100644 index 0000000000..820c409173 --- /dev/null +++ b/Content.Server/Hands/Systems/HandVirtualItemSystem.cs @@ -0,0 +1,106 @@ +using Content.Server.Hands.Components; +using Content.Server.Pulling; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Server.Hands.Systems +{ + [UsedImplicitly] + public sealed class HandVirtualItemSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandleItemDropped); + SubscribeLocalEvent(HandleItemUnequipped); + + SubscribeLocalEvent(HandleBeforeInteract); + } + + public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user) + { + if (ComponentManager.TryGetComponent(user, out var hands)) + { + foreach (var handName in hands.ActivePriorityEnumerable()) + { + var hand = hands.GetHand(handName); + if (!hand.IsEmpty) + continue; + + var pos = hands.Owner.Transform.Coordinates; + var virtualItem = EntityManager.SpawnEntity("HandVirtualItem", pos); + var virtualItemComp = virtualItem.GetComponent(); + virtualItemComp.BlockingEntity = blockingEnt; + hands.PutEntityIntoHand(hand, virtualItem); + return true; + } + } + + return false; + } + + private static void HandleBeforeInteract( + EntityUid uid, + HandVirtualItemComponent component, + BeforeInteractEvent args) + { + // No interactions with a virtual item, please. + args.Handled = true; + } + + // If the virtual item gets removed from the hands for any reason, cancel the pull and delete it. + private void HandleItemUnequipped(EntityUid uid, HandVirtualItemComponent component, UnequippedHandEvent args) + { + Delete(component, args.User.Uid); + } + + private void HandleItemDropped(EntityUid uid, HandVirtualItemComponent component, DroppedEvent args) + { + Delete(component, args.User.Uid); + } + + /// + /// Queues a deletion for a virtual item and notifies the blocking entity and user. + /// + public void Delete(HandVirtualItemComponent comp, EntityUid user) + { + var userEv = new VirtualItemDeletedEvent(comp.BlockingEntity, user); + RaiseLocalEvent(user, userEv, false); + var targEv = new VirtualItemDeletedEvent(comp.BlockingEntity, user); + RaiseLocalEvent(comp.BlockingEntity, targEv, false); + + comp.Owner.QueueDelete(); + } + + /// + /// Deletes all virtual items in a user's hands with + /// the specified blocked entity. + /// + public void DeleteInHandsMatching(EntityUid user, EntityUid matching) + { + if (ComponentManager.TryGetComponent(user, out var hands)) + { + foreach (var handName in hands.ActivePriorityEnumerable()) + { + var hand = hands.GetHand(handName); + if (hand.IsEmpty) + continue; + + if (hand.HeldEntity != null) + { + if (ComponentManager.TryGetComponent(hand.HeldEntity.Uid, + out var virt) + && virt.BlockingEntity == matching) + { + Delete(virt, user); + } + } + } + } + } + } +} diff --git a/Content.Server/Hands/Systems/HandVirtualPullSystem.cs b/Content.Server/Hands/Systems/HandVirtualPullSystem.cs deleted file mode 100644 index f95a4747ca..0000000000 --- a/Content.Server/Hands/Systems/HandVirtualPullSystem.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Content.Server.Pulling; -using Content.Shared.Hands; -using Content.Shared.Hands.Components; -using Content.Shared.Interaction; -using JetBrains.Annotations; -using Robust.Shared.GameObjects; - -namespace Content.Server.Hands -{ - [UsedImplicitly] - public sealed class HandVirtualPullSystem : EntitySystem - { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(HandlePullerDropped); - SubscribeLocalEvent(HandlePullerUnequipped); - - SubscribeLocalEvent(HandleBeforeInteract); - } - - private static void HandleBeforeInteract( - EntityUid uid, - HandVirtualPullComponent component, - BeforeInteractEvent args) - { - // No interactions with a virtual pull, please. - args.Handled = true; - } - - // If the virtual pull gets removed from the hands for any reason, cancel the pull and delete it. - private void HandlePullerUnequipped(EntityUid uid, HandVirtualPullComponent component, UnequippedHandEvent args) - { - MaybeDelete(component, args.User); - } - - private void HandlePullerDropped(EntityUid uid, HandVirtualPullComponent component, DroppedEvent args) - { - MaybeDelete(component, args.User); - } - - private void MaybeDelete(HandVirtualPullComponent comp, IEntity? user) - { - var pulled = comp.PulledEntity; - - if (!ComponentManager.TryGetComponent(pulled, out PullableComponent? pullable)) - return; - - if (pullable.Puller != user) - return; - - pullable.TryStopPull(user); - comp.Owner.QueueDelete(); - } - } -} diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index 6461c65013..13bddd4408 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Hands.Components; +using Content.Server.Hands.Systems; using Content.Server.Interaction; using Content.Server.Inventory.Components; using Content.Server.Items; @@ -26,13 +27,14 @@ using Robust.Shared.Players; using Robust.Shared.Utility; using static Content.Shared.Inventory.EquipmentSlotDefines; -namespace Content.Server.Hands +namespace Content.Server.Hands.Systems { [UsedImplicitly] internal sealed class HandsSystem : SharedHandsSystem { [Dependency] private readonly InteractionSystem _interactionSystem = default!; [Dependency] private readonly StackSystem _stackSystem = default!; + [Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!; public override void Initialize() { @@ -68,21 +70,10 @@ namespace Content.Server.Hands private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args) { - foreach (var handName in component.ActivePriorityEnumerable()) + if (!_virtualItemSystem.TrySpawnVirtualItemInHand(args.Pulled.Owner.Uid, uid)) { - var hand = component.GetHand(handName); - if (!hand.IsEmpty) - continue; - - var pos = component.Owner.Transform.Coordinates; - var virtualPull = EntityManager.SpawnEntity("HandVirtualPull", pos); - var virtualPullComp = virtualPull.GetComponent(); - virtualPullComp.PulledEntity = args.Pulled.Owner.Uid; - component.PutEntityIntoHand(hand, virtualPull); - return; + DebugTools.Assert("Unable to find available hand when starting pulling??"); } - - DebugTools.Assert("Unable to find available hand when starting pulling??"); } private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args) @@ -92,8 +83,8 @@ namespace Content.Server.Hands foreach (var hand in component.Hands) { if (hand.HeldEntity == null - || !hand.HeldEntity.TryGetComponent(out HandVirtualPullComponent? virtualPull) - || virtualPull.PulledEntity != args.Pulled.Owner.Uid) + || !hand.HeldEntity.TryGetComponent(out HandVirtualItemComponent? virtualItem) + || virtualItem.BlockingEntity != args.Pulled.Owner.Uid) continue; hand.HeldEntity.Delete(); diff --git a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs index bfa2649335..a55c7a1581 100644 --- a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs @@ -88,7 +88,9 @@ namespace Content.Server.Weapon.Melee { var targets = new[] { target }; SendAnimation(comp.ClickArc, angle, args.User, owner, targets, comp.ClickAttackEffect, false); - _damageableSystem.TryChangeDamage(target.Uid, comp.Damage); + + _damageableSystem.TryChangeDamage(target.Uid, + DamageSpecifier.ApplyModifierSets(comp.Damage, hitEvent.ModifiersList)); SoundSystem.Play(Filter.Pvs(owner), comp.HitSound.GetSound(), target); } } @@ -153,7 +155,8 @@ namespace Content.Server.Weapon.Melee foreach (var entity in hitEntities) { - _damageableSystem.TryChangeDamage(entity.Uid, comp.Damage); + _damageableSystem.TryChangeDamage(entity.Uid, + DamageSpecifier.ApplyModifierSets(comp.Damage, hitEvent.ModifiersList)); } } @@ -274,6 +277,17 @@ namespace Content.Server.Weapon.Melee /// public class MeleeHitEvent : HandledEntityEventArgs { + /// + /// Modifier sets to apply to the hit event when it's all said and done. + /// This should be modified by adding a new entry to the list. + /// + public List ModifiersList = new(); + + /// + /// A flat amount of damage to add. Same reason as above with Multiplier. + /// + public int FlatDamage = 0; + /// /// A list containing every hit entity. Can be zero. /// diff --git a/Content.Server/Wieldable/Components/IncreaseDamageOnWieldComponent.cs b/Content.Server/Wieldable/Components/IncreaseDamageOnWieldComponent.cs new file mode 100644 index 0000000000..a88eb2d8f3 --- /dev/null +++ b/Content.Server/Wieldable/Components/IncreaseDamageOnWieldComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Damage; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Wieldable.Components +{ + [RegisterComponent, Friend(typeof(WieldableSystem))] + public class IncreaseDamageOnWieldComponent : Component + { + public override string Name { get; } = "IncreaseDamageOnWield"; + + [DataField("modifiers", required: true)] + public DamageModifierSet Modifiers = default!; + } +} diff --git a/Content.Server/Wieldable/Components/WieldableComponent.cs b/Content.Server/Wieldable/Components/WieldableComponent.cs new file mode 100644 index 0000000000..83a2d27c18 --- /dev/null +++ b/Content.Server/Wieldable/Components/WieldableComponent.cs @@ -0,0 +1,59 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Content.Shared.Sound; +using Content.Shared.Verbs; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Wieldable.Components +{ + /// + /// Used for objects that can be wielded in two or more hands, + /// + [RegisterComponent, Friend(typeof(WieldableSystem))] + public class WieldableComponent : Component + { + public override string Name => "Wieldable"; + + [DataField("wieldSound")] + public SoundSpecifier? WieldSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"); + + [DataField("unwieldSound")] + public SoundSpecifier? UnwieldSound = default!; + + /// + /// Number of free hands required (excluding the item itself) required + /// to wield it + /// + [DataField("freeHandsRequired")] + public int FreeHandsRequired = 1; + + public bool Wielded = false; + + public string WieldedInhandPrefix = "wielded"; + + public string? OldInhandPrefix = null; + + [DataField("wieldTime")] + public float WieldTime = 1.5f; + + [Verb] + public sealed class ToggleWieldVerb : Verb + { + protected override void GetData(IEntity user, WieldableComponent component, VerbData data) + { + data.Visibility = VerbVisibility.Visible; + data.Text = component.Wielded ? "Unwield" : "Wield"; + } + + protected override void Activate(IEntity user, WieldableComponent component) + { + if(!component.Wielded) + EntitySystem.Get().AttemptWield(component.Owner.Uid, component, user); + else + EntitySystem.Get().AttemptUnwield(component.Owner.Uid, component, user); + + } + } + } +} diff --git a/Content.Server/Wieldable/WieldableSystem.cs b/Content.Server/Wieldable/WieldableSystem.cs new file mode 100644 index 0000000000..babae5b5ed --- /dev/null +++ b/Content.Server/Wieldable/WieldableSystem.cs @@ -0,0 +1,289 @@ +using Content.Server.DoAfter; +using Content.Server.Hands.Components; +using Content.Server.Hands.Systems; +using Content.Server.Items; +using Content.Server.Weapon.Melee; +using Content.Server.Wieldable.Components; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using Content.Shared.Notification.Managers; +using Content.Shared.Throwing; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Player; + +namespace Content.Server.Wieldable +{ + public class WieldableSystem : EntitySystem + { + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent(OnItemWielded); + SubscribeLocalEvent(OnItemUnwielded); + SubscribeLocalEvent(OnItemLeaveHand); + SubscribeLocalEvent(OnVirtualItemDeleted); + + SubscribeLocalEvent(OnMeleeHit); + } + + 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, IEntity user, bool quiet=false) + { + // Do they have enough hands free? + if (!ComponentManager.TryGetComponent(user.Uid, out var hands)) + { + if(!quiet) + user.PopupMessage(Loc.GetString("wieldable-component-no-hands")); + return false; + } + + if (hands.GetFreeHands() < component.FreeHandsRequired) + { + // TODO FLUENT need function to change 'hands' to 'hand' when there's only 1 required + if (!quiet) + { + user.PopupMessage(Loc.GetString("wieldable-component-not-enough-free-hands", + ("number", component.FreeHandsRequired), + ("item", EntityManager.GetEntity(uid)))); + } + + return false; + } + + // Is it.. actually in one of their hands? + if (!hands.TryGetHandHoldingEntity(EntityManager.GetEntity(uid), out var _)) + { + if (!quiet) + { + user.PopupMessage(Loc.GetString("wieldable-component-not-in-hands", + ("item", EntityManager.GetEntity(uid)))); + } + + return false; + } + + // Seems legit. + return true; + } + + /// + /// Attempts to wield an item, creating a DoAfter.. + /// + public void AttemptWield(EntityUid uid, WieldableComponent component, IEntity user) + { + if (!CanWield(uid, component, user)) + return; + var ev = new BeforeWieldEvent(); + RaiseLocalEvent(uid, ev, false); + var used = EntityManager.GetEntity(uid); + + if (ev.Cancelled) return; + + var doargs = new DoAfterEventArgs( + user, + component.WieldTime, + default, + used + ) + { + BreakOnUserMove = false, + BreakOnDamage = true, + BreakOnStun = true, + BreakOnTargetMove = true, + TargetFinishedEvent = new ItemWieldedEvent(user), + UserFinishedEvent = new WieldedItemEvent(used) + }; + + _doAfter.DoAfter(doargs); + } + + /// + /// Attempts to unwield an item, with no DoAfter. + /// + public void AttemptUnwield(EntityUid uid, WieldableComponent component, IEntity user) + { + var ev = new BeforeUnwieldEvent(); + RaiseLocalEvent(uid, ev, false); + var used = EntityManager.GetEntity(uid); + + if (ev.Cancelled) return; + + var targEv = new ItemUnwieldedEvent(user); + var userEv = new UnwieldedItemEvent(used); + + RaiseLocalEvent(uid, targEv, false); + RaiseLocalEvent(user.Uid, userEv, false); + } + + private void OnItemWielded(EntityUid uid, WieldableComponent component, ItemWieldedEvent args) + { + if (args.User == null) + return; + if (!CanWield(uid, component, args.User) || component.Wielded) + return; + + if (ComponentManager.TryGetComponent(uid, out var item)) + { + component.OldInhandPrefix = item.EquippedPrefix; + item.EquippedPrefix = component.WieldedInhandPrefix; + } + + component.Wielded = true; + + if (component.WieldSound != null) + { + SoundSystem.Play(Filter.Pvs(EntityManager.GetEntity(uid)), component.WieldSound.GetSound()); + } + + for (var i = 0; i < component.FreeHandsRequired; i++) + { + _virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.User.Uid); + } + + args.User.PopupMessage(Loc.GetString("wieldable-component-successful-wield", + ("item", EntityManager.GetEntity(uid)))); + } + + private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUnwieldedEvent args) + { + if (args.User == null) + return; + if (!component.Wielded) + return; + + if (ComponentManager.TryGetComponent(uid, out var item)) + { + item.EquippedPrefix = component.OldInhandPrefix; + } + + component.Wielded = false; + + if (!args.Force) // don't play sound/popup if this was a forced unwield + { + if (component.UnwieldSound != null) + { + SoundSystem.Play(Filter.Pvs(EntityManager.GetEntity(uid)), + component.UnwieldSound.GetSound()); + } + + args.User.PopupMessage(Loc.GetString("wieldable-component-failed-wield", + ("item", EntityManager.GetEntity(uid)))); + } + + _virtualItemSystem.DeleteInHandsMatching(args.User.Uid, uid); + } + + private void OnItemLeaveHand(EntityUid uid, WieldableComponent component, UnequippedHandEvent args) + { + if (!component.Wielded || component.Owner.Uid != args.Unequipped.Uid) + return; + RaiseLocalEvent(uid, new ItemUnwieldedEvent(args.User, force: true)); + } + + private void OnVirtualItemDeleted(EntityUid uid, WieldableComponent component, VirtualItemDeletedEvent args) + { + if(args.BlockingEntity == uid && component.Wielded) + AttemptUnwield(args.BlockingEntity, component, EntityManager.GetEntity(args.User)); + } + + private void OnMeleeHit(EntityUid uid, IncreaseDamageOnWieldComponent component, MeleeHitEvent args) + { + if (ComponentManager.TryGetComponent(uid, out var wield)) + { + if (!wield.Wielded) + return; + } + if (args.Handled) + return; + + args.ModifiersList.Add(component.Modifiers); + } + } + + #region Events + + public class BeforeWieldEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised on the item that has been wielded. + /// + public class ItemWieldedEvent : EntityEventArgs + { + public IEntity? User; + + public ItemWieldedEvent(IEntity? user=null) + { + User = user; + } + } + + /// + /// Raised on the user who wielded the item. + /// + public class WieldedItemEvent : EntityEventArgs + { + public IEntity Item; + + public WieldedItemEvent(IEntity item) + { + Item = item; + } + } + + public class BeforeUnwieldEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised on the item that has been unwielded. + /// + public class ItemUnwieldedEvent : EntityEventArgs + { + public IEntity? User; + /// + /// Whether the item is being forced to be unwielded, or if the player chose to unwield it themselves. + /// + public bool Force; + + public ItemUnwieldedEvent(IEntity? user=null, bool force=false) + { + User = user; + Force = force; + } + } + + /// + /// Raised on the user who unwielded the item. + /// + public class UnwieldedItemEvent : EntityEventArgs + { + public IEntity Item; + + public UnwieldedItemEvent(IEntity item) + { + Item = item; + } + } + + #endregion +} diff --git a/Content.Shared/Damage/DamageSpecifier.cs b/Content.Shared/Damage/DamageSpecifier.cs index 50eb06697b..677caab595 100644 --- a/Content.Shared/Damage/DamageSpecifier.cs +++ b/Content.Shared/Damage/DamageSpecifier.cs @@ -183,6 +183,24 @@ namespace Content.Shared.Damage return newDamage; } + /// + /// Reduce (or increase) damages by applying multiple modifier sets. + /// + /// + /// + /// + public static DamageSpecifier ApplyModifierSets(DamageSpecifier damageSpec, IEnumerable modifierSets) + { + DamageSpecifier newDamage = new(damageSpec); + foreach (var set in modifierSets) + { + // this is probably really inefficient. just don't call this in a hot path I guess. + newDamage = ApplyModifierSet(newDamage, set); + } + + return newDamage; + } + /// /// Remove any damage entries with zero damage. /// diff --git a/Content.Shared/Hands/Components/HandVirtualItemComponent.cs b/Content.Shared/Hands/Components/HandVirtualItemComponent.cs new file mode 100644 index 0000000000..53f3c0aa28 --- /dev/null +++ b/Content.Shared/Hands/Components/HandVirtualItemComponent.cs @@ -0,0 +1,53 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Players; +using Robust.Shared.Serialization; + +namespace Content.Shared.Hands.Components +{ + [RegisterComponent] + [NetworkedComponent] + public sealed class HandVirtualItemComponent : Component + { + private EntityUid _blockingEntity; + public override string Name => "HandVirtualItem"; + + /// + /// The entity blocking this hand. + /// + public EntityUid BlockingEntity + { + get => _blockingEntity; + set + { + _blockingEntity = value; + Dirty(); + } + } + + public override ComponentState GetComponentState(ICommonSession player) + { + return new VirtualItemComponentState(BlockingEntity); + } + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + if (curState is not VirtualItemComponentState pullState) + return; + + _blockingEntity = pullState.BlockingEntity; + } + + [Serializable, NetSerializable] + public sealed class VirtualItemComponentState : ComponentState + { + public readonly EntityUid BlockingEntity; + + public VirtualItemComponentState(EntityUid blockingEntity) + { + BlockingEntity = blockingEntity; + } + } + } +} diff --git a/Content.Shared/Hands/Components/HandVirtualPullComponent.cs b/Content.Shared/Hands/Components/HandVirtualPullComponent.cs deleted file mode 100644 index 42f6bd85f3..0000000000 --- a/Content.Shared/Hands/Components/HandVirtualPullComponent.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; -using Robust.Shared.Players; -using Robust.Shared.Serialization; - -namespace Content.Shared.Hands.Components -{ - [RegisterComponent] - [NetworkedComponent] - public sealed class HandVirtualPullComponent : Component - { - private EntityUid _pulledEntity; - public override string Name => "HandVirtualPull"; - - public EntityUid PulledEntity - { - get => _pulledEntity; - set - { - _pulledEntity = value; - Dirty(); - } - } - - public override ComponentState GetComponentState(ICommonSession player) - { - return new VirtualPullComponentState(_pulledEntity); - } - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - if (curState is not VirtualPullComponentState pullState) - return; - - _pulledEntity = pullState.PulledEntity; - } - - [Serializable, NetSerializable] - public sealed class VirtualPullComponentState : ComponentState - { - public readonly EntityUid PulledEntity; - - public VirtualPullComponentState(EntityUid pulledEntity) - { - PulledEntity = pulledEntity; - } - } - } -} diff --git a/Content.Shared/Hands/Components/SharedHandsComponent.cs b/Content.Shared/Hands/Components/SharedHandsComponent.cs index 0eb370bc22..bdca7b6410 100644 --- a/Content.Shared/Hands/Components/SharedHandsComponent.cs +++ b/Content.Shared/Hands/Components/SharedHandsComponent.cs @@ -228,7 +228,23 @@ namespace Content.Shared.Hands.Components } } - private bool TryGetHandHoldingEntity(IEntity entity, [NotNullWhen(true)] out Hand? handFound) + /// + /// Returns the number of hands that have no items in them. + /// + /// + public int GetFreeHands() + { + int acc = 0; + foreach (var hand in Hands) + { + if (hand.HeldEntity == null) + acc += 1; + } + + return acc; + } + + public bool TryGetHandHoldingEntity(IEntity entity, [NotNullWhen(true)] out Hand? handFound) { handFound = null; @@ -418,6 +434,7 @@ namespace Content.Shared.Hands.Components Logger.Error($"{nameof(SharedHandsComponent)} on {Owner} could not remove {heldEntity} from {handContainer}."); return; } + OnHeldEntityRemovedFromHand(heldEntity, hand.ToHandState()); HandsModified(); diff --git a/Content.Shared/Hands/SharedHandsSystem.cs b/Content.Shared/Hands/SharedHandsSystem.cs index 96bd96d793..130676adc5 100644 --- a/Content.Shared/Hands/SharedHandsSystem.cs +++ b/Content.Shared/Hands/SharedHandsSystem.cs @@ -96,4 +96,20 @@ namespace Content.Shared.Hands HandName = handName; } } + + /// + /// Raised directed on both the blocking entity and user when + /// a virtual hand item is deleted. + /// + public class VirtualItemDeletedEvent : EntityEventArgs + { + public EntityUid BlockingEntity; + public EntityUid User; + + public VirtualItemDeletedEvent(EntityUid blockingEntity, EntityUid user) + { + BlockingEntity = blockingEntity; + User = user; + } + } } diff --git a/Content.Shared/Pulling/Components/SharedPullableComponent.cs b/Content.Shared/Pulling/Components/SharedPullableComponent.cs index a25f3915ff..50d5d9dbf6 100644 --- a/Content.Shared/Pulling/Components/SharedPullableComponent.cs +++ b/Content.Shared/Pulling/Components/SharedPullableComponent.cs @@ -122,6 +122,8 @@ namespace Content.Shared.Pulling.Components } } + valuePuller.Pulling = Owner; + // Continue with pulling process. var pullAttempt = new PullAttemptMessage(pullerPhysics, _physics); @@ -241,7 +243,7 @@ namespace Content.Shared.Pulling.Components Puller = puller; - if (Puller != puller) + if(Puller != puller) { return false; } @@ -266,6 +268,11 @@ namespace Content.Shared.Pulling.Components _physics.RemoveJoint(_pullJoint); } + if (user != null && user.TryGetComponent(out var puller)) + { + puller.Pulling = null; + } + _pullJoint = null; Puller = null; return true; diff --git a/Content.Shared/Pulling/Components/SharedPullerComponent.cs b/Content.Shared/Pulling/Components/SharedPullerComponent.cs index 1579339958..15afacd88e 100644 --- a/Content.Shared/Pulling/Components/SharedPullerComponent.cs +++ b/Content.Shared/Pulling/Components/SharedPullerComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Movement.Components; using Robust.Shared.GameObjects; +using Robust.Shared.ViewVariables; using Component = Robust.Shared.GameObjects.Component; namespace Content.Shared.Pulling.Components @@ -11,14 +12,15 @@ namespace Content.Shared.Pulling.Components private IEntity? _pulling; - public float WalkSpeedModifier => Pulling == null ? 1.0f : 0.75f; + public float WalkSpeedModifier => _pulling == null ? 1.0f : 0.75f; - public float SprintSpeedModifier => Pulling == null ? 1.0f : 0.75f; + public float SprintSpeedModifier => _pulling == null ? 1.0f : 0.75f; + [ViewVariables] public IEntity? Pulling { get => _pulling; - private set + set { if (_pulling == value) { diff --git a/Content.Shared/Pulling/Systems/SharedPullerSystem.cs b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs index d05754c851..172a9102ea 100644 --- a/Content.Shared/Pulling/Systems/SharedPullerSystem.cs +++ b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs @@ -1,10 +1,11 @@ using Content.Shared.Alert; +using Content.Shared.Hands; using Content.Shared.Physics.Pull; using Content.Shared.Pulling.Components; using JetBrains.Annotations; using Robust.Shared.GameObjects; -namespace Content.Shared.Pulling +namespace Content.Shared.Pulling.Systems { [UsedImplicitly] public sealed class SharedPullerSystem : EntitySystem @@ -15,6 +16,21 @@ namespace Content.Shared.Pulling SubscribeLocalEvent(PullerHandlePullStarted); SubscribeLocalEvent(PullerHandlePullStopped); + SubscribeLocalEvent(OnVirtualItemDeleted); + } + + private void OnVirtualItemDeleted(EntityUid uid, SharedPullerComponent component, VirtualItemDeletedEvent args) + { + if (component.Pulling == null) + return; + + if (component.Pulling == EntityManager.GetEntity(args.BlockingEntity)); + { + if (ComponentManager.TryGetComponent(args.BlockingEntity, out var comp)) + { + comp.TryStopPull(EntityManager.GetEntity(uid)); + } + } } private static void PullerHandlePullStarted( diff --git a/Resources/Locale/en-US/wieldable/wieldable-component.ftl b/Resources/Locale/en-US/wieldable/wieldable-component.ftl new file mode 100644 index 0000000000..1e955875cf --- /dev/null +++ b/Resources/Locale/en-US/wieldable/wieldable-component.ftl @@ -0,0 +1,8 @@ +### Locale for wielding items; i.e. two-handing them + +wieldable-component-successful-wield = You wield { THE($item) }. +wieldable-component-failed-wield = You unwield { THE($item) }. +wieldable-component-no-hands = You don't have enough hands! +wieldable-component-not-enough-free-hands = You need { $number } free hands to wield { THE($item) }. +wieldable-component-not-in-hands = { CAPITALIZE(THE($item)) } isn't in your hands! + diff --git a/Resources/Prototypes/Damage/modifier_sets.yml b/Resources/Prototypes/Damage/modifier_sets.yml index 39dd6fceb5..a122aecce0 100644 --- a/Resources/Prototypes/Damage/modifier_sets.yml +++ b/Resources/Prototypes/Damage/modifier_sets.yml @@ -23,8 +23,18 @@ coefficients: Blunt: 0.5 Slash: 0.5 - Piercing: 0.5 + Piercing: 1.5 Heat: 0 Shock: 0 flatReductions: Blunt: 5 + +- type: damageModifierSet + id: Wood + coefficients: + Blunt: 0.5 + Slash: 2.0 + Piercing: 1.0 + Heat: 2.0 + flatReductions: + Blunt: 5 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml index a4b2a73565..11057b854a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml @@ -2,7 +2,7 @@ name: fireaxe parent: BaseItem id: FireAxe - description: A large, robust axe. Can pry open doors and skulls alike. + description: Truly, the weapon of a madman. Who would think to fight fire with an axe? components: - type: Tag tags: @@ -12,10 +12,19 @@ state: icon - type: MeleeWeapon damage: - groups: - Brute: 25 + types: + # axes are kinda like sharp hammers, you know? + Blunt: 7 + Slash: 7 + - type: Wieldable + - type: IncreaseDamageOnWield + modifiers: + flatReductions: + Blunt: -5 # negative reductions = increases + Slash: -5 - type: Clothing size: 20 sprite: Objects/Weapons/Melee/fireaxe.rsi + QuickEquip: false Slots: - back diff --git a/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml b/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml index 4e4d3b2c17..d59766d80e 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml @@ -227,6 +227,8 @@ sprite: Structures/Furniture/Tables/wood.rsi - type: Icon sprite: Structures/Furniture/Tables/wood.rsi + - type: Damageable + damageModifierSet: Wood - type: Destructible thresholds: - trigger: @@ -246,6 +248,9 @@ - type: Construction graph: table node: TableWood + - type: Tag + tags: + - Wooden - type: entity id: TableCarpet @@ -257,6 +262,8 @@ sprite: Structures/Furniture/Tables/carpet.rsi - type: Icon sprite: Structures/Furniture/Tables/carpet.rsi + - type: Damageable + damageModifierSet: Wood - type: Destructible thresholds: - trigger: @@ -279,6 +286,9 @@ - type: Construction graph: table node: TableCarpet + - type: Tag + tags: + - Wooden - type: entity id: TableStone diff --git a/Resources/Prototypes/Entities/Structures/Furniture/bookshelf.yml b/Resources/Prototypes/Entities/Structures/Furniture/bookshelf.yml index 45964eee15..9fcfa50b28 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/bookshelf.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/bookshelf.yml @@ -19,6 +19,7 @@ - book-4 - book-5 - type: Damageable + damageModifierSet: Wood damageContainer: Inorganic - type: Destructible thresholds: @@ -36,3 +37,6 @@ max: 1 - !type:DoActsBehavior acts: ["Destruction"] + - type: Tag + tags: + - Wooden diff --git a/Resources/Prototypes/Entities/Structures/Furniture/seats.yml b/Resources/Prototypes/Entities/Structures/Furniture/seats.yml index 9f2c0178b9..242b1dcbec 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/seats.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/seats.yml @@ -186,6 +186,8 @@ - type: Construction graph: seat node: chairWood + - type: Damageable + damageModifierSet: Wood - type: Destructible thresholds: - trigger: @@ -202,6 +204,9 @@ MaterialWoodPlank: min: 1 max: 1 + - type: Tag + tags: + - Wooden - type: entity name: pilot seat diff --git a/Resources/Prototypes/Entities/Structures/Walls/barricades.yml b/Resources/Prototypes/Entities/Structures/Walls/barricades.yml index 4877f003f7..382c7928ce 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/barricades.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/barricades.yml @@ -23,9 +23,10 @@ - type: Tag tags: - ExplosivePassable + - Wooden - type: Damageable + damageModifierSet: Wood damageContainer: Inorganic - damageModifierSet: Metallic - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Virtual/virtual_item.yml b/Resources/Prototypes/Entities/Virtual/virtual_item.yml new file mode 100644 index 0000000000..8c6a6eabbc --- /dev/null +++ b/Resources/Prototypes/Entities/Virtual/virtual_item.yml @@ -0,0 +1,8 @@ +# This item is stored in the hand slot while it is blocked, to represent what is blocking that hand. +- type: entity + id: HandVirtualItem + name: VIRTUAL ITEM YOU SHOULD NOT SEE THIS + abstract: true + components: + - type: Item + - type: HandVirtualItem diff --git a/Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml b/Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml deleted file mode 100644 index aa5b7bdb22..0000000000 --- a/Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml +++ /dev/null @@ -1,8 +0,0 @@ -# This item is stored in the hand slot while you are pulling something, to represent that and what you are pulling. -- type: entity - id: HandVirtualPull - name: VIRTUAL PULL YOU SHOULD NOT SEE THIS - abstract: true - components: - - type: Item - - type: HandVirtualPull diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 8e6b33c1fd..a535e7775e 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -205,5 +205,8 @@ - type: Tag id: Write +- type: Tag + id: Wooden # just like our atmos + - type: Tag id: EmitterBolt diff --git a/Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/meta.json index 36566042fe..3d83a91ae5 100644 --- a/Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/meta.json @@ -17,6 +17,14 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 }, { "name": "equipped-BACKPACK", diff --git a/Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..f928b92fb128dfb579636731782b52622529bc21 GIT binary patch literal 906 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fA0|T>- zr;B4q#hkaZE_w$C${gQ6`OdW%zBLO|IqjCX3Vsoi+?#PtJ|INE=|_;~mu387UlXPT z*tw`)uR5W`VbW7|Vv!5)BAtcpS=ZS1>P$Epv`uw#)ZNQ}W4D(+zgqhK4%^!~{5`wx zR-ga*w{HICYV#j=MIXp0GR#qCX((=CDCiSlI48(-z|x7q!kvTR`3n{sZN0SLNrv29 zJxgDC30`ScJi!{Tcy3W;lx?DvhBGtn~FHEv^(7>uG7x8`EBUhv_4yXb*Aj*jN>dy zn>$*YZUlt#R;{<4XYu@8vAo~i@6uvbZxckVcWq}|dc1AgUJhq3*`jvwO?M}raktJ7 zI`GonE@zp!t5{BCbSL|J)FD> zn-3-j9j{v|XBoH2EL}vp6 zvGXlEm-L>=o+8&TtbS_Ej5+fhcidPt+gfwM<0A2Fs~V2JySyDWvyV;=KfFWH^Nmw_ z`l*H-wdvXCwrt=0SXZ~>O_qb_L{Z=OagV~|?X9n0TeY9%k@d}q50}4s9_M-J(4+t6 zkA7}`cJb$|cVE9cN83#Nr0u{yRh)}+(t^_(VCk zZFr$C=K_^&!N2dMozLdBonGi$&%EO79!-H0)rVEW3wgO0=&j!Vdi5>4^XfbMvLg;0 z-gu(syT!aKhpttwyKqc(#a`drIa|(g9%j0>cP8`QmK;6vB?l9V>`&eZjGMT%<`i-sogp}p@+5XhOKceF7WRT6dwA-6!&pfX)=f$L* zxt-3lNYpAEp3rc+_V0pqg;%nUa(gnFoITdcS-W#fFK=JFkFDUQGdn!_u1qzKnsBsF zw^fnxis?T?(T0+-L5w)1KfdbhBf&=A-on;Dibd blF#gwZ?jY9%DVRfGZ%xWtDnm{r-UW|bo!T7 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/fireaxe.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..65a944239455a6ea8e2b15548362fe4421f1efce GIT binary patch literal 913 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fA0|T?O zr;B4q#hkaZ;yr=`WsdLPZ1&dbe*&-g!3FI)hD}XMOU2Jty)ay<(DFf5`r>UKomU4n zF|Kj(bed%Zu~WacQdG!(Zm6!Zx&oD*a^VClqQ;m*PEoP+U!%w-mvtx+f6Wn4L= z)hD`)b3=OC!pKP{E+!Z}d^YRzr;gevwnc%Pr=`woV5&AVFkPyzXT!MJz|~>zVaK9P zVK0On9%s}fDATheeszV=ez$pJ=AJ-;Zr}l-X^& z#B%zwhDOf?Uk;sE^sW26`-Zq#FWJ+6g=#KdSg@vEasIvb4dq{@PHcYZZzFLfMk7~$ znYyId6pfGNQwwz#{MLw{y(2#Oz|;0y+l00VhjxYipBiV~CvwB!Q!$U@YTcK2o`+d> z=$tNCrxS3@^+40TeRkgY@sIv(dC%Bs=3*Hc@1J!yujQ21gt>{%rRTl>WM-=`e{Iu! z%w$f!(pC0v&bI;^FZ3J8?SJLceck(sPOpjhemu%(ltR zbB#ByjnnVBXLIyJJmW6Trx!%MRW;bpxY%$t=x{FNfJ#IyTG4t{#uR9{^#*3 zHgorA#xg~DZVfp7sPK8_W9ffuW&SP6e(^ol{q!HxKf;ISu|!_~_Vq;n(UO