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
This commit is contained in:
mirrorcult
2021-09-17 07:16:11 -07:00
committed by GitHub
parent 078a62762f
commit 62f6c8dd8e
36 changed files with 719 additions and 161 deletions

View File

@@ -274,6 +274,8 @@ namespace Content.Client.Entry
"PowerNetworkBattery",
"BatteryCharger",
"SpawnItemsOnUse",
"Wieldable",
"IncreaseDamageOnWield",
"AmbientOnPowered",
"TabletopGame"
};

View File

@@ -1,3 +1,3 @@
<Control xmlns="https://spacestation14.io">
<Label StyleClasses="ItemStatus" Text="Pulling" />
<Label StyleClasses="ItemStatus" Text="Blocked by" />
</Control>

View File

@@ -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);
}

View File

@@ -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<HandVirtualPullComponent>();
hand.HeldItem != null && hand.HeldItem.HasComponent<HandVirtualItemComponent>();
}
if (TryGetActiveHand(out var activeHand))

View File

@@ -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<HandVirtualPullComponent>(_ => new HandVirtualPullItemStatus());
Subs.ItemStatus<HandVirtualItemComponent>(_ => new HandVirtualItemStatus());
}
}
}

View File

@@ -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;
}

View File

@@ -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
{

View File

@@ -83,22 +83,22 @@ namespace Content.Server.DoAfter
/// <summary>
/// Event to be raised directed to the <see cref="User"/> entity when the DoAfter is cancelled.
/// </summary>
public EntityEventArgs? UserCancelledEvent { get; set; }
public object? UserCancelledEvent { get; set; }
/// <summary>
/// Event to be raised directed to the <see cref="User"/> entity when the DoAfter is finished successfully.
/// </summary>
public EntityEventArgs? UserFinishedEvent { get; set; }
public object? UserFinishedEvent { get; set; }
/// <summary>
/// Event to be raised directed to the <see cref="Target"/> entity when the DoAfter is cancelled.
/// </summary>
public EntityEventArgs? TargetCancelledEvent { get; set; }
public object? TargetCancelledEvent { get; set; }
/// <summary>
/// Event to be raised directed to the <see cref="Target"/> entity when the DoAfter is finished successfully.
/// </summary>
public EntityEventArgs? TargetFinishedEvent { get; set; }
public object? TargetFinishedEvent { get; set; }
/// <summary>
/// Event to be broadcast when the DoAfter is cancelled.

View File

@@ -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<HandVirtualItemComponent, DroppedEvent>(HandleItemDropped);
SubscribeLocalEvent<HandVirtualItemComponent, UnequippedHandEvent>(HandleItemUnequipped);
SubscribeLocalEvent<HandVirtualItemComponent, BeforeInteractEvent>(HandleBeforeInteract);
}
public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user)
{
if (ComponentManager.TryGetComponent<HandsComponent>(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<HandVirtualItemComponent>();
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);
}
/// <summary>
/// Queues a deletion for a virtual item and notifies the blocking entity and user.
/// </summary>
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();
}
/// <summary>
/// Deletes all virtual items in a user's hands with
/// the specified blocked entity.
/// </summary>
public void DeleteInHandsMatching(EntityUid user, EntityUid matching)
{
if (ComponentManager.TryGetComponent<HandsComponent>(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<HandVirtualItemComponent>(hand.HeldEntity.Uid,
out var virt)
&& virt.BlockingEntity == matching)
{
Delete(virt, user);
}
}
}
}
}
}
}

View File

@@ -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<HandVirtualPullComponent, DroppedEvent>(HandlePullerDropped);
SubscribeLocalEvent<HandVirtualPullComponent, UnequippedHandEvent>(HandlePullerUnequipped);
SubscribeLocalEvent<HandVirtualPullComponent, BeforeInteractEvent>(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();
}
}
}

View File

@@ -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,22 +70,11 @@ 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<HandVirtualPullComponent>();
virtualPullComp.PulledEntity = args.Pulled.Owner.Uid;
component.PutEntityIntoHand(hand, virtualPull);
return;
}
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();

View File

@@ -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
/// </summary>
public class MeleeHitEvent : HandledEntityEventArgs
{
/// <summary>
/// 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.
/// </summary>
public List<DamageModifierSet> ModifiersList = new();
/// <summary>
/// A flat amount of damage to add. Same reason as above with Multiplier.
/// </summary>
public int FlatDamage = 0;
/// <summary>
/// A list containing every hit entity. Can be zero.
/// </summary>

View File

@@ -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!;
}
}

View File

@@ -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
{
/// <summary>
/// Used for objects that can be wielded in two or more hands,
/// </summary>
[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!;
/// <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;
public string WieldedInhandPrefix = "wielded";
public string? OldInhandPrefix = null;
[DataField("wieldTime")]
public float WieldTime = 1.5f;
[Verb]
public sealed class ToggleWieldVerb : Verb<WieldableComponent>
{
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<WieldableSystem>().AttemptWield(component.Owner.Uid, component, user);
else
EntitySystem.Get<WieldableSystem>().AttemptUnwield(component.Owner.Uid, component, user);
}
}
}
}

View File

@@ -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<WieldableComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<WieldableComponent, ItemWieldedEvent>(OnItemWielded);
SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
SubscribeLocalEvent<WieldableComponent, UnequippedHandEvent>(OnItemLeaveHand);
SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
SubscribeLocalEvent<IncreaseDamageOnWieldComponent, MeleeHitEvent>(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<HandsComponent>(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;
}
/// <summary>
/// Attempts to wield an item, creating a DoAfter..
/// </summary>
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);
}
/// <summary>
/// Attempts to unwield an item, with no DoAfter.
/// </summary>
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<ItemComponent>(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<ItemComponent>(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<WieldableComponent>(uid, out var wield))
{
if (!wield.Wielded)
return;
}
if (args.Handled)
return;
args.ModifiersList.Add(component.Modifiers);
}
}
#region Events
public class BeforeWieldEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Raised on the item that has been wielded.
/// </summary>
public class ItemWieldedEvent : EntityEventArgs
{
public IEntity? User;
public ItemWieldedEvent(IEntity? user=null)
{
User = user;
}
}
/// <summary>
/// Raised on the user who wielded the item.
/// </summary>
public class WieldedItemEvent : EntityEventArgs
{
public IEntity Item;
public WieldedItemEvent(IEntity item)
{
Item = item;
}
}
public class BeforeUnwieldEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Raised on the item that has been unwielded.
/// </summary>
public class ItemUnwieldedEvent : EntityEventArgs
{
public IEntity? 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(IEntity? user=null, bool force=false)
{
User = user;
Force = force;
}
}
/// <summary>
/// Raised on the user who unwielded the item.
/// </summary>
public class UnwieldedItemEvent : EntityEventArgs
{
public IEntity Item;
public UnwieldedItemEvent(IEntity item)
{
Item = item;
}
}
#endregion
}

View File

@@ -183,6 +183,24 @@ namespace Content.Shared.Damage
return newDamage;
}
/// <summary>
/// Reduce (or increase) damages by applying multiple modifier sets.
/// </summary>
/// <param name="damageSpec"></param>
/// <param name="modifierSets"></param>
/// <returns></returns>
public static DamageSpecifier ApplyModifierSets(DamageSpecifier damageSpec, IEnumerable<DamageModifierSet> 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;
}
/// <summary>
/// Remove any damage entries with zero damage.
/// </summary>

View File

@@ -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";
/// <summary>
/// The entity blocking this hand.
/// </summary>
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;
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -228,7 +228,23 @@ namespace Content.Shared.Hands.Components
}
}
private bool TryGetHandHoldingEntity(IEntity entity, [NotNullWhen(true)] out Hand? handFound)
/// <summary>
/// Returns the number of hands that have no items in them.
/// </summary>
/// <returns></returns>
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();

View File

@@ -96,4 +96,20 @@ namespace Content.Shared.Hands
HandName = handName;
}
}
/// <summary>
/// Raised directed on both the blocking entity and user when
/// a virtual hand item is deleted.
/// </summary>
public class VirtualItemDeletedEvent : EntityEventArgs
{
public EntityUid BlockingEntity;
public EntityUid User;
public VirtualItemDeletedEvent(EntityUid blockingEntity, EntityUid user)
{
BlockingEntity = blockingEntity;
User = user;
}
}
}

View File

@@ -122,6 +122,8 @@ namespace Content.Shared.Pulling.Components
}
}
valuePuller.Pulling = Owner;
// Continue with pulling process.
var pullAttempt = new PullAttemptMessage(pullerPhysics, _physics);
@@ -266,6 +268,11 @@ namespace Content.Shared.Pulling.Components
_physics.RemoveJoint(_pullJoint);
}
if (user != null && user.TryGetComponent<SharedPullerComponent>(out var puller))
{
puller.Pulling = null;
}
_pullJoint = null;
Puller = null;
return true;

View File

@@ -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)
{

View File

@@ -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<SharedPullerComponent, PullStartedMessage>(PullerHandlePullStarted);
SubscribeLocalEvent<SharedPullerComponent, PullStoppedMessage>(PullerHandlePullStopped);
SubscribeLocalEvent<SharedPullerComponent, VirtualItemDeletedEvent>(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<SharedPullableComponent>(args.BlockingEntity, out var comp))
{
comp.TryStopPull(EntityManager.GetEntity(uid));
}
}
}
private static void PullerHandlePullStarted(

View File

@@ -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!

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -23,9 +23,10 @@
- type: Tag
tags:
- ExplosivePassable
- Wooden
- type: Damageable
damageModifierSet: Wood
damageContainer: Inorganic
damageModifierSet: Metallic
- type: Destructible
thresholds:
- trigger:

View File

@@ -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

View File

@@ -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

View File

@@ -205,5 +205,8 @@
- type: Tag
id: Write
- type: Tag
id: Wooden # just like our atmos
- type: Tag
id: EmitterBolt

View File

@@ -17,6 +17,14 @@
{
"name": "inhand-right",
"directions": 4
},
{
"name": "wielded-inhand-left",
"directions": 4
},
{
"name": "wielded-inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B