using System; using Content.Client.Animations; using Content.Client.UserInterface; using Content.Client.Utility; using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Weapons.Ranged; using Robust.Client.Animations; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Animations; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; using static Content.Client.StaticIoC; namespace Content.Client.GameObjects.Components.Weapons.Ranged { [RegisterComponent] public class BallisticMagazineWeaponComponent : Component, IItemStatus { private static readonly Animation AlarmAnimationSmg = new Animation { Length = TimeSpan.FromSeconds(1.4), AnimationTracks = { new AnimationTrackControlProperty { // These timings match the SMG audio file. Property = nameof(Label.FontColorOverride), InterpolationMode = AnimationInterpolationMode.Previous, KeyFrames = { new AnimationTrackProperty.KeyFrame(Color.Red, 0.1f), new AnimationTrackProperty.KeyFrame(null, 0.3f), new AnimationTrackProperty.KeyFrame(Color.Red, 0.2f), new AnimationTrackProperty.KeyFrame(null, 0.3f), new AnimationTrackProperty.KeyFrame(Color.Red, 0.2f), new AnimationTrackProperty.KeyFrame(null, 0.3f), } } } }; private static readonly Animation AlarmAnimationLmg = new Animation { Length = TimeSpan.FromSeconds(0.75), AnimationTracks = { new AnimationTrackControlProperty { // These timings match the SMG audio file. Property = nameof(Label.FontColorOverride), InterpolationMode = AnimationInterpolationMode.Previous, KeyFrames = { new AnimationTrackProperty.KeyFrame(Color.Red, 0.0f), new AnimationTrackProperty.KeyFrame(null, 0.15f), new AnimationTrackProperty.KeyFrame(Color.Red, 0.15f), new AnimationTrackProperty.KeyFrame(null, 0.15f), new AnimationTrackProperty.KeyFrame(Color.Red, 0.15f), new AnimationTrackProperty.KeyFrame(null, 0.15f), } } } }; public override string Name => "BallisticMagazineWeapon"; public override uint? NetID => ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON; private StatusControl _statusControl; /// /// True if a bullet is chambered. /// [ViewVariables] public bool Chambered { get; private set; } /// /// Count of bullets in the magazine. /// /// /// Null if no magazine is inserted. /// [ViewVariables] public (int count, int max)? MagazineCount { get; private set; } [ViewVariables(VVAccess.ReadWrite)] private bool _isLmgAlarmAnimation; public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); serializer.DataField(ref _isLmgAlarmAnimation, "lmg_alarm_animation", false); } public override void HandleComponentState(ComponentState curState, ComponentState nextState) { var cast = (BallisticMagazineWeaponComponentState) curState; Chambered = cast.Chambered; MagazineCount = cast.MagazineCount; _statusControl?.Update(); } public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null) { switch (message) { case BmwComponentAutoEjectedMessage _: _statusControl?.PlayAlarmAnimation(); return; } base.HandleMessage(message, netChannel, component); } public Control MakeControl() { _statusControl = new StatusControl(this); _statusControl.Update(); return _statusControl; } public void DestroyControl(Control control) { if (_statusControl == control) { _statusControl = null; } } private sealed class StatusControl : Control { private readonly BallisticMagazineWeaponComponent _parent; private readonly HBoxContainer _bulletsListTop; private readonly HBoxContainer _bulletsListBottom; private readonly TextureRect _chamberedBullet; private readonly Label _noMagazineLabel; public StatusControl(BallisticMagazineWeaponComponent parent) { _parent = parent; SizeFlagsHorizontal = SizeFlags.FillExpand; SizeFlagsVertical = SizeFlags.ShrinkCenter; AddChild(new VBoxContainer { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.ShrinkCenter, SeparationOverride = 0, Children = { (_bulletsListTop = new HBoxContainer {SeparationOverride = 0}), new HBoxContainer { SizeFlagsHorizontal = SizeFlags.FillExpand, Children = { new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, Children = { (_bulletsListBottom = new HBoxContainer { SizeFlagsVertical = SizeFlags.ShrinkCenter, SeparationOverride = 0 }), (_noMagazineLabel = new Label { Text = "No Magazine!", StyleClasses = {NanoStyle.StyleClassItemStatus} }) } }, (_chamberedBullet = new TextureRect { Texture = ResC.GetTexture("/Textures/UserInterface/status/bullets/chambered.png"), SizeFlagsVertical = SizeFlags.ShrinkCenter, SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill, }) } } } }); } public void Update() { _chamberedBullet.ModulateSelfOverride = _parent.Chambered ? Color.FromHex("#d7df60") : Color.Black; _bulletsListTop.RemoveAllChildren(); _bulletsListBottom.RemoveAllChildren(); if (_parent.MagazineCount == null) { _noMagazineLabel.Visible = true; return; } var (count, capacity) = _parent.MagazineCount.Value; _noMagazineLabel.Visible = false; string texturePath; if (capacity <= 20) { texturePath = "/Textures/UserInterface/status/bullets/normal.png"; } else if (capacity <= 30) { texturePath = "/Textures/UserInterface/status/bullets/small.png"; } else { texturePath = "/Textures/UserInterface/status/bullets/tiny.png"; } var texture = ResC.GetTexture(texturePath); const int tinyMaxRow = 60; if (capacity > tinyMaxRow) { FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture); FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture); } else { FillBulletRow(_bulletsListBottom, count, capacity, texture); } } private static void FillBulletRow(Control container, int count, int capacity, Texture texture) { var colorA = Color.FromHex("#b68f0e"); var colorB = Color.FromHex("#d7df60"); var colorGoneA = Color.FromHex("#000000"); var colorGoneB = Color.FromHex("#222222"); var altColor = false; for (var i = count; i < capacity; i++) { container.AddChild(new TextureRect { Texture = texture, ModulateSelfOverride = altColor ? colorGoneA : colorGoneB }); altColor ^= true; } for (var i = 0; i < count; i++) { container.AddChild(new TextureRect { Texture = texture, ModulateSelfOverride = altColor ? colorA : colorB }); altColor ^= true; } } protected override Vector2 CalculateMinimumSize() { return Vector2.ComponentMax((0, 15), base.CalculateMinimumSize()); } public void PlayAlarmAnimation() { var animation = _parent._isLmgAlarmAnimation ? AlarmAnimationLmg : AlarmAnimationSmg; _noMagazineLabel.PlayAnimation(animation, "alarm"); } } } }