using System.Numerics; using Content.Client.IoC; using Content.Client.Items; using Content.Client.Resources; using Content.Client.Stylesheets; using Content.Client.Weapons.Ranged.Components; using Content.Client.Weapons.Ranged.ItemStatus; using Robust.Client.Animations; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; namespace Content.Client.Weapons.Ranged.Systems; public sealed partial class GunSystem { private void OnAmmoCounterCollect(EntityUid uid, AmmoCounterComponent component, ItemStatusCollectMessage args) { RefreshControl(uid, component); if (component.Control != null) args.Controls.Add(component.Control); } /// /// Refreshes the control being used to show ammo. Useful if you change the AmmoProvider. /// /// /// private void RefreshControl(EntityUid uid, AmmoCounterComponent? component = null) { if (!Resolve(uid, ref component, false)) return; component.Control?.Dispose(); component.Control = null; var ev = new AmmoCounterControlEvent(); RaiseLocalEvent(uid, ev, false); // Fallback to default if none specified ev.Control ??= new DefaultStatusControl(); component.Control = ev.Control; UpdateAmmoCount(uid, component); } private void UpdateAmmoCount(EntityUid uid, AmmoCounterComponent component) { if (component.Control == null) return; var ev = new UpdateAmmoCounterEvent() { Control = component.Control }; RaiseLocalEvent(uid, ev, false); } protected override void UpdateAmmoCount(EntityUid uid, bool prediction = true) { // Don't use resolves because the method is shared and there's no compref and I'm trying to // share as much code as possible if (prediction && !Timing.IsFirstTimePredicted || !TryComp(uid, out var clientComp)) { return; } UpdateAmmoCount(uid, clientComp); } /// /// Raised when an ammocounter is requesting a control. /// public sealed class AmmoCounterControlEvent : EntityEventArgs { public Control? Control; } /// /// Raised whenever the ammo count / magazine for a control needs updating. /// public sealed class UpdateAmmoCounterEvent : HandledEntityEventArgs { public Control Control = default!; } #region Controls private sealed class DefaultStatusControl : Control { private readonly BulletRender _bulletRender; public DefaultStatusControl() { MinHeight = 15; HorizontalExpand = true; VerticalAlignment = VAlignment.Center; AddChild(_bulletRender = new BulletRender { HorizontalAlignment = HAlignment.Right, VerticalAlignment = VAlignment.Bottom }); } public void Update(int count, int capacity) { _bulletRender.Count = count; _bulletRender.Capacity = capacity; _bulletRender.Type = capacity > 50 ? BulletRender.BulletType.Tiny : BulletRender.BulletType.Normal; } } public sealed class BoxesStatusControl : Control { private readonly BoxContainer _bulletsList; private readonly Label _ammoCount; public BoxesStatusControl() { MinHeight = 15; HorizontalExpand = true; VerticalAlignment = Control.VAlignment.Center; AddChild(new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal, HorizontalExpand = true, Children = { new Control { HorizontalExpand = true, Children = { (_bulletsList = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal, VerticalAlignment = VAlignment.Center, SeparationOverride = 4 }), } }, new Control() { MinSize = new Vector2(5, 0) }, (_ammoCount = new Label { StyleClasses = { StyleNano.StyleClassItemStatus }, HorizontalAlignment = HAlignment.Right, }), } }); } public void Update(int count, int max) { _bulletsList.RemoveAllChildren(); _ammoCount.Visible = true; _ammoCount.Text = $"x{count:00}"; max = Math.Min(max, 8); FillBulletRow(_bulletsList, count, max); } private static void FillBulletRow(Control container, int count, int capacity) { var colorGone = Color.FromHex("#000000"); var color = Color.FromHex("#E00000"); // Draw the empty ones for (var i = count; i < capacity; i++) { container.AddChild(new PanelContainer { PanelOverride = new StyleBoxFlat() { BackgroundColor = colorGone, }, MinSize = new Vector2(10, 15), }); } // Draw the full ones, but limit the count to the capacity count = Math.Min(count, capacity); for (var i = 0; i < count; i++) { container.AddChild(new PanelContainer { PanelOverride = new StyleBoxFlat() { BackgroundColor = color, }, MinSize = new Vector2(10, 15), }); } } } private sealed class ChamberMagazineStatusControl : Control { private readonly BulletRender _bulletRender; private readonly TextureRect _chamberedBullet; private readonly Label _noMagazineLabel; private readonly Label _ammoCount; public ChamberMagazineStatusControl() { MinHeight = 15; HorizontalExpand = true; VerticalAlignment = Control.VAlignment.Center; AddChild(new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal, HorizontalExpand = true, Children = { new Control { HorizontalExpand = true, Margin = new Thickness(0, 0, 5, 0), Children = { (_bulletRender = new BulletRender { HorizontalAlignment = HAlignment.Right, VerticalAlignment = VAlignment.Bottom }), (_noMagazineLabel = new Label { Text = "No Magazine!", StyleClasses = {StyleNano.StyleClassItemStatus} }) } }, new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Vertical, VerticalAlignment = VAlignment.Bottom, Margin = new Thickness(0, 0, 0, 2), Children = { (_ammoCount = new Label { StyleClasses = {StyleNano.StyleClassItemStatus}, HorizontalAlignment = HAlignment.Right, }), (_chamberedBullet = new TextureRect { Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"), HorizontalAlignment = HAlignment.Left, }), } } } }); } public void Update(bool chambered, bool magazine, int count, int capacity) { _chamberedBullet.ModulateSelfOverride = chambered ? Color.FromHex("#d7df60") : Color.Black; if (!magazine) { _bulletRender.Visible = false; _noMagazineLabel.Visible = true; _ammoCount.Visible = false; return; } _bulletRender.Visible = true; _noMagazineLabel.Visible = false; _ammoCount.Visible = true; _bulletRender.Count = count; _bulletRender.Capacity = capacity; _bulletRender.Type = capacity > 50 ? BulletRender.BulletType.Tiny : BulletRender.BulletType.Normal; _ammoCount.Text = $"x{count:00}"; } public void PlayAlarmAnimation(Animation animation) { _noMagazineLabel.PlayAnimation(animation, "alarm"); } } private sealed class RevolverStatusControl : Control { private readonly BoxContainer _bulletsList; public RevolverStatusControl() { MinHeight = 15; HorizontalExpand = true; VerticalAlignment = Control.VAlignment.Center; AddChild((_bulletsList = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal, HorizontalExpand = true, VerticalAlignment = VAlignment.Center, SeparationOverride = 0 })); } public void Update(int currentIndex, bool?[] bullets) { _bulletsList.RemoveAllChildren(); var capacity = bullets.Length; string texturePath; if (capacity <= 20) { texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png"; } else if (capacity <= 30) { texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png"; } else { texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png"; } var texture = StaticIoC.ResC.GetTexture(texturePath); var spentTexture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/empty.png"); FillBulletRow(currentIndex, bullets, _bulletsList, texture, spentTexture); } private void FillBulletRow(int currentIndex, bool?[] bullets, Control container, Texture texture, Texture emptyTexture) { var capacity = bullets.Length; var colorA = Color.FromHex("#b68f0e"); var colorB = Color.FromHex("#d7df60"); var colorSpentA = Color.FromHex("#b50e25"); var colorSpentB = Color.FromHex("#d3745f"); var colorGoneA = Color.FromHex("#000000"); var colorGoneB = Color.FromHex("#222222"); var altColor = false; var scale = 1.3f; for (var i = 0; i < capacity; i++) { var bulletFree = bullets[i]; // Add a outline var box = new Control() { MinSize = texture.Size * scale, }; if (i == currentIndex) { box.AddChild(new TextureRect { Texture = texture, TextureScale = new Vector2(scale, scale), ModulateSelfOverride = Color.LimeGreen, }); } Color color; Texture bulletTexture = texture; if (bulletFree.HasValue) { if (bulletFree.Value) { color = altColor ? colorA : colorB; } else { color = altColor ? colorSpentA : colorSpentB; bulletTexture = emptyTexture; } } else { color = altColor ? colorGoneA : colorGoneB; } box.AddChild(new TextureRect { Stretch = TextureRect.StretchMode.KeepCentered, Texture = bulletTexture, ModulateSelfOverride = color, }); altColor ^= true; container.AddChild(box); } } } #endregion }