diff --git a/Content.Server/Weapon/Ranged/Ammunition/AmmunitionSystem.cs b/Content.Server/Weapon/Ranged/Ammunition/AmmunitionSystem.cs deleted file mode 100644 index 85e0db8e92..0000000000 --- a/Content.Server/Weapon/Ranged/Ammunition/AmmunitionSystem.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Content.Server.Weapon.Ranged.Ammunition.Components; -using Content.Shared.Verbs; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; - -namespace Content.Server.Weapon.Ranged.Ammunition -{ - public sealed class AmmunitionSystem : EntitySystem - { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(AddDumpVerb); - } - - private void AddDumpVerb(EntityUid uid, AmmoBoxComponent component, GetAlternativeVerbsEvent args) - { - if (args.Hands == null || !args.CanAccess || !args.CanInteract) - return; - - if (component.AmmoLeft == 0) - return; - - Verb verb = new(); - verb.Text = Loc.GetString("dump-vert-get-data-text"); - verb.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - verb.Act = () => component.EjectContents(10); - args.Verbs.Add(verb); - } - } -} diff --git a/Content.Server/Weapon/Ranged/Ammunition/Components/AmmoBoxComponent.cs b/Content.Server/Weapon/Ranged/Ammunition/Components/AmmoBoxComponent.cs index 85944d15e3..7c1c35a3cf 100644 --- a/Content.Server/Weapon/Ranged/Ammunition/Components/AmmoBoxComponent.cs +++ b/Content.Server/Weapon/Ranged/Ammunition/Components/AmmoBoxComponent.cs @@ -1,34 +1,23 @@ -using System; using System.Collections.Generic; -using System.Threading.Tasks; -using Content.Server.Hands.Components; -using Content.Server.Items; -using Content.Server.Weapon.Ranged.Barrels.Components; -using Content.Shared.Examine; -using Content.Shared.Interaction; -using Content.Shared.Popups; -using Content.Shared.Weapons.Ranged.Barrels.Components; +using Robust.Shared.Analyzers; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Utility; -using Robust.Shared.Utility.Markup; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Weapon.Ranged.Ammunition.Components { + /// + /// Stores ammo and can quickly transfer ammo into a magazine. + /// [RegisterComponent] -#pragma warning disable 618 - public sealed class AmmoBoxComponent : Component, IInteractUsing, IUse, IInteractHand, IMapInit, IExamine -#pragma warning restore 618 + [ComponentProtoName("AmmoBox")] + [Friend(typeof(GunSystem))] + public sealed class AmmoBoxComponent : Component { - [Dependency] private readonly IEntityManager _entities = default!; - - public override string Name => "AmmoBox"; - [DataField("caliber")] - private BallisticCaliber _caliber = BallisticCaliber.Unspecified; + public BallisticCaliber Caliber = BallisticCaliber.Unspecified; [DataField("capacity")] public int Capacity @@ -37,188 +26,29 @@ namespace Content.Server.Weapon.Ranged.Ammunition.Components set { _capacity = value; - _spawnedAmmo = new Stack(value); + SpawnedAmmo = new Stack(value); } } private int _capacity = 30; - public int AmmoLeft => _spawnedAmmo.Count + _unspawnedCount; - private Stack _spawnedAmmo = new(); - private Container _ammoContainer = default!; - private int _unspawnedCount; + public int AmmoLeft => SpawnedAmmo.Count + UnspawnedCount; + public Stack SpawnedAmmo = new(); - [DataField("fillPrototype")] - private string? _fillPrototype; + /// + /// Container that holds any instantiated ammo. + /// + public Container AmmoContainer = default!; - protected override void Initialize() - { - base.Initialize(); - _ammoContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-container", out var existing); + /// + /// How many more deferred entities can be spawned. We defer these to avoid instantiating the entities until needed for performance reasons. + /// + public int UnspawnedCount; - if (existing) - { - foreach (var entity in _ammoContainer.ContainedEntities) - { - _unspawnedCount--; - _spawnedAmmo.Push(entity); - _ammoContainer.Insert(entity); - } - } - - } - - void IMapInit.MapInit() - { - _unspawnedCount += _capacity; - UpdateAppearance(); - } - - private void UpdateAppearance() - { - if (_entities.TryGetComponent(Owner, out AppearanceComponent? appearanceComponent)) - { - appearanceComponent.SetData(MagazineBarrelVisuals.MagLoaded, true); - appearanceComponent.SetData(AmmoVisuals.AmmoCount, AmmoLeft); - appearanceComponent.SetData(AmmoVisuals.AmmoMax, _capacity); - } - } - - public EntityUid? TakeAmmo() - { - if (_spawnedAmmo.TryPop(out var ammo)) - { - _ammoContainer.Remove(ammo); - return ammo; - } - - if (_unspawnedCount > 0) - { - ammo = _entities.SpawnEntity(_fillPrototype, _entities.GetComponent(Owner).Coordinates); - - // when dumping from held ammo box, this detaches the spawned ammo from the player. - _entities.GetComponent(ammo).AttachParentToContainerOrGrid(); - - _unspawnedCount--; - } - - return ammo; - } - - public bool TryInsertAmmo(EntityUid user, EntityUid entity) - { - if (!_entities.TryGetComponent(entity, out AmmoComponent? ammoComponent)) - { - return false; - } - - if (ammoComponent.Caliber != _caliber) - { - Owner.PopupMessage(user, Loc.GetString("ammo-box-component-try-insert-ammo-wrong-caliber")); - return false; - } - - if (AmmoLeft >= Capacity) - { - Owner.PopupMessage(user, Loc.GetString("ammo-box-component-try-insert-ammo-no-room")); - return false; - } - - _spawnedAmmo.Push(entity); - _ammoContainer.Insert(entity); - UpdateAppearance(); - return true; - } - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - if (_entities.HasComponent(eventArgs.Using)) - { - return TryInsertAmmo(eventArgs.User, eventArgs.Using); - } - - if (_entities.TryGetComponent(eventArgs.Using, out RangedMagazineComponent? rangedMagazine)) - { - for (var i = 0; i < Math.Max(10, rangedMagazine.ShotsLeft); i++) - { - if (rangedMagazine.TakeAmmo() is not {Valid: true} ammo) - { - continue; - } - - if (!TryInsertAmmo(eventArgs.User, ammo)) - { - rangedMagazine.TryInsertAmmo(eventArgs.User, ammo); - return true; - } - } - - return true; - } - - return false; - } - - private bool TryUse(EntityUid user) - { - if (!_entities.TryGetComponent(user, out HandsComponent? handsComponent)) - { - return false; - } - - if (TakeAmmo() is not { } ammo) - { - return false; - } - - if (_entities.TryGetComponent(ammo, out ItemComponent? item)) - { - if (!handsComponent.CanPutInHand(item)) - { - TryInsertAmmo(user, ammo); - return false; - } - - handsComponent.PutInHand(item); - } - - UpdateAppearance(); - return true; - } - - public void EjectContents(int count) - { - var ejectCount = Math.Min(count, Capacity); - var ejectAmmo = new List(ejectCount); - - for (var i = 0; i < Math.Min(count, Capacity); i++) - { - if (TakeAmmo() is not { } ammo) - { - break; - } - - ejectAmmo.Add(ammo); - } - - ServerRangedBarrelComponent.EjectCasings(ejectAmmo); - UpdateAppearance(); - } - - bool IUse.UseEntity(UseEntityEventArgs eventArgs) - { - return TryUse(eventArgs.User); - } - - bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) - { - return TryUse(eventArgs.User); - } - - public void Examine(FormattedMessage.Builder message, bool inDetailsRange) - { - message.AddMarkup("\n" + Loc.GetString("ammo-box-component-on-examine-caliber-description", ("caliber", _caliber))); - message.AddMarkup("\n" + Loc.GetString("ammo-box-component-on-examine-remaining-ammo-description", ("ammoLeft",AmmoLeft),("capacity", _capacity))); - } + /// + /// The prototype of the ammo to be retrieved when getting ammo. + /// + [DataField("fillPrototype", customTypeSerializer:typeof(PrototypeIdSerializer))] + public string? FillPrototype; } } diff --git a/Content.Server/Weapon/Ranged/GunSystem.AmmoBox.cs b/Content.Server/Weapon/Ranged/GunSystem.AmmoBox.cs new file mode 100644 index 0000000000..d9c5024f41 --- /dev/null +++ b/Content.Server/Weapon/Ranged/GunSystem.AmmoBox.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using Content.Server.Hands.Components; +using Content.Server.Items; +using Content.Server.Weapon.Ranged.Ammunition.Components; +using Content.Server.Weapon.Ranged.Barrels.Components; +using Content.Shared.Examine; +using Content.Shared.Interaction; +using Content.Shared.Verbs; +using Content.Shared.Weapons.Ranged.Barrels.Components; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Player; + +namespace Content.Server.Weapon.Ranged; + +public sealed partial class GunSystem +{ + // Probably needs combining with magazines in future given the common functionality. + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAmmoBoxInit); + SubscribeLocalEvent(OnAmmoBoxMapInit); + SubscribeLocalEvent(OnAmmoBoxExamine); + + SubscribeLocalEvent(OnAmmoBoxInteractUsing); + SubscribeLocalEvent(OnAmmoBoxUse); + SubscribeLocalEvent(OnAmmoBoxInteractHand); + SubscribeLocalEvent(OnAmmoBoxAltVerbs); + } + + private void OnAmmoBoxAltVerbs(EntityUid uid, AmmoBoxComponent component, GetAlternativeVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + if (component.AmmoLeft == 0) + return; + + Verb verb = new() + { + Text = Loc.GetString("dump-vert-get-data-text"), + IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png", + Act = () => AmmoBoxEjectContents(component, 10) + }; + args.Verbs.Add(verb); + } + + private void OnAmmoBoxInteractHand(EntityUid uid, AmmoBoxComponent component, InteractHandEvent args) + { + if (args.Handled) return; + + TryUse(args.User, component); + } + + private void OnAmmoBoxUse(EntityUid uid, AmmoBoxComponent component, UseInHandEvent args) + { + if (args.Handled) return; + + TryUse(args.User, component); + } + + private void OnAmmoBoxInteractUsing(EntityUid uid, AmmoBoxComponent component, InteractUsingEvent args) + { + if (args.Handled) return; + + if (EntityManager.TryGetComponent(args.Used, out AmmoComponent? ammoComponent)) + { + if (TryInsertAmmo(args.User, args.Used, component, ammoComponent)) + { + args.Handled = true; + } + + return; + } + + if (!EntityManager.TryGetComponent(args.Used, out RangedMagazineComponent? rangedMagazine)) return; + + for (var i = 0; i < Math.Max(10, rangedMagazine.ShotsLeft); i++) + { + if (rangedMagazine.TakeAmmo() is not {Valid: true} ammo) + { + continue; + } + + if (!TryInsertAmmo(args.User, ammo, component)) + { + rangedMagazine.TryInsertAmmo(args.User, ammo); + args.Handled = true; + return; + } + } + + args.Handled = true; + } + + private void OnAmmoBoxInit(EntityUid uid, AmmoBoxComponent component, ComponentInit args) + { + component.AmmoContainer = uid.EnsureContainer($"{component.Name}-container", out var existing); + + if (existing) + { + foreach (var entity in component.AmmoContainer.ContainedEntities) + { + component.UnspawnedCount--; + component.SpawnedAmmo.Push(entity); + component.AmmoContainer.Insert(entity); + } + } + } + + private void OnAmmoBoxExamine(EntityUid uid, AmmoBoxComponent component, ExaminedEvent args) + { + args.PushMarkup(Loc.GetString("ammo-box-component-on-examine-caliber-description", ("caliber", component.Caliber))); + args.PushMarkup(Loc.GetString("ammo-box-component-on-examine-remaining-ammo-description", ("ammoLeft", component.AmmoLeft),("capacity", component.Capacity))); + } + + private void OnAmmoBoxMapInit(EntityUid uid, AmmoBoxComponent component, MapInitEvent args) + { + component.UnspawnedCount += component.Capacity; + UpdateAmmoBoxAppearance(uid, component); + } + + private void UpdateAmmoBoxAppearance(EntityUid uid, AmmoBoxComponent ammoBox, AppearanceComponent? appearanceComponent = null) + { + if (!Resolve(uid, ref appearanceComponent, false)) return; + + appearanceComponent.SetData(MagazineBarrelVisuals.MagLoaded, true); + appearanceComponent.SetData(AmmoVisuals.AmmoCount, ammoBox.AmmoLeft); + appearanceComponent.SetData(AmmoVisuals.AmmoMax, ammoBox.Capacity); + } + + private void AmmoBoxEjectContents(AmmoBoxComponent ammoBox, int count) + { + var ejectCount = Math.Min(count, ammoBox.Capacity); + var ejectAmmo = new List(ejectCount); + + for (var i = 0; i < Math.Min(count, ammoBox.Capacity); i++) + { + if (TakeAmmo(ammoBox) is not { } ammo) + { + break; + } + + ejectAmmo.Add(ammo); + } + + ServerRangedBarrelComponent.EjectCasings(ejectAmmo); + UpdateAmmoBoxAppearance(ammoBox.Owner, ammoBox); + } + + private bool TryUse(EntityUid user, AmmoBoxComponent ammoBox) + { + if (!EntityManager.TryGetComponent(user, out HandsComponent? handsComponent)) + { + return false; + } + + if (TakeAmmo(ammoBox) is not { } ammo) + { + return false; + } + + if (EntityManager.TryGetComponent(ammo, out ItemComponent? item)) + { + if (!handsComponent.CanPutInHand(item)) + { + TryInsertAmmo(user, ammo, ammoBox); + return false; + } + + handsComponent.PutInHand(item); + } + + UpdateAmmoBoxAppearance(ammoBox.Owner, ammoBox); + return true; + } + + public bool TryInsertAmmo(EntityUid user, EntityUid ammo, AmmoBoxComponent ammoBox, AmmoComponent? ammoComponent = null) + { + if (!Resolve(ammo, ref ammoComponent, false)) + { + return false; + } + + if (ammoComponent.Caliber != ammoBox.Caliber) + { + _popup.PopupEntity(Loc.GetString("ammo-box-component-try-insert-ammo-wrong-caliber"), ammo, Filter.Entities(user)); + return false; + } + + if (ammoBox.AmmoLeft >= ammoBox.Capacity) + { + _popup.PopupEntity(Loc.GetString("ammo-box-component-try-insert-ammo-no-room"), ammo, Filter.Entities(user)); + return false; + } + + ammoBox.SpawnedAmmo.Push(ammo); + ammoBox.AmmoContainer.Insert(ammo); + UpdateAmmoBoxAppearance(ammoBox.Owner, ammoBox); + return true; + } + + public EntityUid? TakeAmmo(AmmoBoxComponent ammoBox, TransformComponent? xform = null) + { + if (!Resolve(ammoBox.Owner, ref xform)) return null; + + if (ammoBox.SpawnedAmmo.TryPop(out var ammo)) + { + ammoBox.AmmoContainer.Remove(ammo); + return ammo; + } + + if (ammoBox.UnspawnedCount > 0) + { + ammo = EntityManager.SpawnEntity(ammoBox.FillPrototype, xform.Coordinates); + + // when dumping from held ammo box, this detaches the spawned ammo from the player. + EntityManager.GetComponent(ammo).AttachParentToContainerOrGrid(); + + ammoBox.UnspawnedCount--; + } + + return ammo; + } +} diff --git a/Content.Server/Weapon/Ranged/GunSystem.cs b/Content.Server/Weapon/Ranged/GunSystem.cs new file mode 100644 index 0000000000..872a37ff1d --- /dev/null +++ b/Content.Server/Weapon/Ranged/GunSystem.cs @@ -0,0 +1,10 @@ +using Content.Shared.Popups; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Server.Weapon.Ranged; + +public sealed partial class GunSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; +}