diff --git a/Content.Shared/Weapons/Ranged/Components/BallisticAmmoProviderComponent.cs b/Content.Shared/Weapons/Ranged/Components/BallisticAmmoProviderComponent.cs index 679b0df782..d5fb2e7dc8 100644 --- a/Content.Shared/Weapons/Ranged/Components/BallisticAmmoProviderComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/BallisticAmmoProviderComponent.cs @@ -53,4 +53,10 @@ public sealed partial class BallisticAmmoProviderComponent : Component /// [ViewVariables(VVAccess.ReadWrite), DataField("mayTransfer")] public bool MayTransfer = false; + + /// + /// DoAfter delay for filling a bullet into another ballistic ammo provider. + /// + [DataField("fillDelay")] + public TimeSpan FillDelay = TimeSpan.FromSeconds(0.5); } diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index a6a31bc992..aea7fb4e8b 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -1,3 +1,4 @@ +using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; @@ -13,6 +14,8 @@ namespace Content.Shared.Weapons.Ranged.Systems; public abstract partial class SharedGunSystem { + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + protected virtual void InitializeBallistic() { SubscribeLocalEvent(OnBallisticInit); @@ -24,6 +27,7 @@ public abstract partial class SharedGunSystem SubscribeLocalEvent>(OnBallisticVerb); SubscribeLocalEvent(OnBallisticInteractUsing); SubscribeLocalEvent(OnBallisticAfterInteract); + SubscribeLocalEvent(OnBallisticAmmoFillDoAfter); SubscribeLocalEvent(OnBallisticUse); } @@ -61,7 +65,7 @@ public abstract partial class SharedGunSystem args.Target == null || args.Used == args.Target || Deleted(args.Target) || - !TryComp(args.Target, out BallisticAmmoProviderComponent? targetComponent) || + !TryComp(args.Target, out var targetComponent) || targetComponent.Whitelist == null) { return; @@ -69,7 +73,23 @@ public abstract partial class SharedGunSystem args.Handled = true; - if (targetComponent.Entities.Count + targetComponent.UnspawnedCount == targetComponent.Capacity) + _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.FillDelay, new AmmoFillDoAfterEvent(), used: uid, target: args.Target, eventTarget: uid) + { + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnDamage = false, + NeedHand = true + }); + } + + private void OnBallisticAmmoFillDoAfter(EntityUid uid, BallisticAmmoProviderComponent component, AmmoFillDoAfterEvent args) + { + if (Deleted(args.Target) || + !TryComp(args.Target, out var target) || + target.Whitelist == null) + return; + + if (target.Entities.Count + target.UnspawnedCount == target.Capacity) { Popup( Loc.GetString("gun-ballistic-transfer-target-full", @@ -83,8 +103,8 @@ public abstract partial class SharedGunSystem { Popup( Loc.GetString("gun-ballistic-transfer-empty", - ("entity", args.Used)), - args.Used, + ("entity", uid)), + uid, args.User); return; } @@ -96,27 +116,25 @@ public abstract partial class SharedGunSystem } List<(EntityUid? Entity, IShootable Shootable)> ammo = new(); - var evTakeAmmo = new TakeAmmoEvent(1, ammo, Transform(args.Used).Coordinates, args.User); - RaiseLocalEvent(args.Used, evTakeAmmo); + var evTakeAmmo = new TakeAmmoEvent(1, ammo, Transform(uid).Coordinates, args.User); + RaiseLocalEvent(uid, evTakeAmmo); foreach (var (ent, _) in ammo) { if (ent == null) continue; - if (!targetComponent.Whitelist.IsValid(ent.Value)) + if (!target.Whitelist.IsValid(ent.Value)) { Popup( Loc.GetString("gun-ballistic-transfer-invalid", ("ammoEntity", ent.Value), ("targetEntity", args.Target.Value)), - args.Used, + uid, args.User); - // TODO: For better or worse, this will play a sound, but it's the - // more future-proof thing to do than copying the same code - // that OnBallisticInteractUsing has, sans sound. - SimulateInsertAmmo(ent.Value, args.Used, Transform(args.Used).Coordinates); + // play sound to be cool + SimulateInsertAmmo(ent.Value, uid, Transform(uid).Coordinates); } else { @@ -126,6 +144,11 @@ public abstract partial class SharedGunSystem if (ent.Value.IsClientSide()) Del(ent.Value); } + + // repeat if there is more space in the target and more ammo to fill it + var moreSpace = target.Entities.Count + target.UnspawnedCount < target.Capacity; + var moreAmmo = component.Entities.Count + component.UnspawnedCount > 0; + args.Repeat = moreSpace && moreAmmo; } private void OnBallisticVerb(EntityUid uid, BallisticAmmoProviderComponent component, GetVerbsEvent args) @@ -247,3 +270,11 @@ public abstract partial class SharedGunSystem Appearance.SetData(uid, AmmoVisuals.AmmoMax, component.Capacity, appearance); } } + +/// +/// DoAfter event for filling one ballistic ammo provider from another. +/// +[Serializable, NetSerializable] +public sealed partial class AmmoFillDoAfterEvent : SimpleDoAfterEvent +{ +}