diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBoltActionBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBoltActionBarrelComponent.cs
new file mode 100644
index 0000000000..778cb19599
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBoltActionBarrelComponent.cs
@@ -0,0 +1,206 @@
+using Content.Client.UserInterface.Stylesheets;
+using Content.Client.Utility;
+using Content.Shared.GameObjects;
+using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Maths;
+using Robust.Shared.ViewVariables;
+using System;
+
+namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
+{
+ [RegisterComponent]
+ public class ClientBoltActionBarrelComponent : Component, IItemStatus
+ {
+ public override string Name => "BoltActionBarrel";
+ public override uint? NetID => ContentNetIDs.BOLTACTION_BARREL;
+
+ private StatusControl _statusControl;
+
+ ///
+ /// chambered is true when a bullet is chambered
+ /// spent is true when the chambered bullet is spent
+ ///
+ [ViewVariables]
+ public (bool chambered, bool spent) Chamber { 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; }
+
+ public override void HandleComponentState(ComponentState curState, ComponentState nextState)
+ {
+ if (!(curState is BoltActionBarrelComponentState cast))
+ return;
+
+ Chamber = cast.Chamber;
+ MagazineCount = cast.Magazine;
+ _statusControl?.Update();
+ }
+
+ 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 ClientBoltActionBarrelComponent _parent;
+ private readonly HBoxContainer _bulletsListTop;
+ private readonly HBoxContainer _bulletsListBottom;
+ private readonly TextureRect _chamberedBullet;
+ private readonly Label _noMagazineLabel;
+
+ public StatusControl(ClientBoltActionBarrelComponent 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 = {StyleNano.StyleClassItemStatus}
+ })
+ }
+ },
+ (_chamberedBullet = new TextureRect
+ {
+ Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"),
+ SizeFlagsVertical = SizeFlags.ShrinkCenter,
+ SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
+ })
+ }
+ }
+ }
+ });
+ }
+
+ public void Update()
+ {
+ _chamberedBullet.ModulateSelfOverride =
+ _parent.Chamber.chambered ?
+ _parent.Chamber.spent ? Color.Red : 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/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);
+
+ 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());
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientMagazineBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientMagazineBarrelComponent.cs
index f879c7c8f0..f02d184cad 100644
--- a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientMagazineBarrelComponent.cs
+++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientMagazineBarrelComponent.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Content.Client.Animations;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
@@ -138,54 +138,52 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
private sealed class StatusControl : Control
{
private readonly ClientMagazineBarrelComponent _parent;
- private readonly HBoxContainer _bulletsListTop;
- private readonly HBoxContainer _bulletsListBottom;
+ private readonly HBoxContainer _bulletsList;
private readonly TextureRect _chamberedBullet;
private readonly Label _noMagazineLabel;
+ private readonly Label _ammoCount;
public StatusControl(ClientMagazineBarrelComponent parent)
{
_parent = parent;
SizeFlagsHorizontal = SizeFlags.FillExpand;
SizeFlagsVertical = SizeFlags.ShrinkCenter;
- AddChild(new VBoxContainer
+
+ AddChild(new HBoxContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
- SizeFlagsVertical = SizeFlags.ShrinkCenter,
- SeparationOverride = 0,
Children =
{
- (_bulletsListTop = new HBoxContainer {SeparationOverride = 0}),
- new HBoxContainer
+ (_chamberedBullet = new TextureRect
+ {
+ Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png"),
+ SizeFlagsVertical = SizeFlags.ShrinkCenter,
+ SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
+ }),
+ new Control() { CustomMinimumSize = (5,0) },
+ new Control
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
Children =
{
- new Control
+ (_bulletsList = new HBoxContainer
{
- SizeFlagsHorizontal = SizeFlags.FillExpand,
- Children =
- {
- (_bulletsListBottom = new HBoxContainer
- {
- SizeFlagsVertical = SizeFlags.ShrinkCenter,
- SeparationOverride = 0
- }),
- (_noMagazineLabel = new Label
- {
- Text = "No Magazine!",
- StyleClasses = {StyleNano.StyleClassItemStatus}
- })
- }
- },
- (_chamberedBullet = new TextureRect
- {
- Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"),
SizeFlagsVertical = SizeFlags.ShrinkCenter,
- SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
+ SeparationOverride = 0
+ }),
+ (_noMagazineLabel = new Label
+ {
+ Text = "No Magazine!",
+ StyleClasses = {StyleNano.StyleClassItemStatus}
})
}
- }
+ },
+ new Control() { CustomMinimumSize = (5,0) },
+ (_ammoCount = new Label
+ {
+ StyleClasses = {StyleNano.StyleClassItemStatus},
+ SizeFlagsHorizontal = SizeFlags.ShrinkEnd,
+ }),
}
});
}
@@ -195,46 +193,26 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
_chamberedBullet.ModulateSelfOverride =
_parent.Chambered ? Color.FromHex("#d7df60") : Color.Black;
- _bulletsListTop.RemoveAllChildren();
- _bulletsListBottom.RemoveAllChildren();
+ _bulletsList.RemoveAllChildren();
if (_parent.MagazineCount == null)
{
_noMagazineLabel.Visible = true;
+ _ammoCount.Visible = false;
return;
}
var (count, capacity) = _parent.MagazineCount.Value;
_noMagazineLabel.Visible = false;
+ _ammoCount.Visible = true;
- 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 texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
var texture = StaticIoC.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);
- }
+ _ammoCount.Text = $"x{count:00}";
+ capacity = Math.Min(capacity, 20);
+ FillBulletRow(_bulletsList, count, capacity, texture);
}
private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
@@ -246,23 +224,32 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
var altColor = false;
+ // Draw the empty ones
for (var i = count; i < capacity; i++)
{
container.AddChild(new TextureRect
{
Texture = texture,
- ModulateSelfOverride = altColor ? colorGoneA : colorGoneB
+ ModulateSelfOverride = altColor ? colorGoneA : colorGoneB,
+ SizeFlagsHorizontal = SizeFlags.Fill,
+ SizeFlagsVertical = SizeFlags.Fill,
+ Stretch = TextureRect.StretchMode.KeepCentered
});
altColor ^= true;
}
+ // 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 TextureRect
{
Texture = texture,
- ModulateSelfOverride = altColor ? colorA : colorB
+ ModulateSelfOverride = altColor ? colorA : colorB,
+ SizeFlagsHorizontal = SizeFlags.Fill,
+ SizeFlagsVertical = SizeFlags.Fill,
+ Stretch = TextureRect.StretchMode.KeepCentered
});
altColor ^= true;
@@ -281,4 +268,4 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientPumpBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientPumpBarrelComponent.cs
new file mode 100644
index 0000000000..a2c58b8bbb
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientPumpBarrelComponent.cs
@@ -0,0 +1,208 @@
+using Content.Client.UserInterface.Stylesheets;
+using Content.Client.Utility;
+using Content.Shared.GameObjects;
+using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Maths;
+using Robust.Shared.ViewVariables;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
+{
+ [RegisterComponent]
+ public class ClientPumpBarrelComponent : Component, IItemStatus
+ {
+ public override string Name => "PumpBarrel";
+ public override uint? NetID => ContentNetIDs.PUMP_BARREL;
+
+ private StatusControl _statusControl;
+
+ ///
+ /// chambered is true when a bullet is chambered
+ /// spent is true when the chambered bullet is spent
+ ///
+ [ViewVariables]
+ public (bool chambered, bool spent) Chamber { 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; }
+
+ public override void HandleComponentState(ComponentState curState, ComponentState nextState)
+ {
+ if (!(curState is PumpBarrelComponentState cast))
+ return;
+
+ Chamber = cast.Chamber;
+ MagazineCount = cast.Magazine;
+ _statusControl?.Update();
+ }
+
+ 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 ClientPumpBarrelComponent _parent;
+ private readonly HBoxContainer _bulletsListTop;
+ private readonly HBoxContainer _bulletsListBottom;
+ private readonly TextureRect _chamberedBullet;
+ private readonly Label _noMagazineLabel;
+
+ public StatusControl(ClientPumpBarrelComponent 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 = {StyleNano.StyleClassItemStatus}
+ })
+ }
+ },
+ (_chamberedBullet = new TextureRect
+ {
+ Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"),
+ SizeFlagsVertical = SizeFlags.ShrinkCenter,
+ SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
+ })
+ }
+ }
+ }
+ });
+ }
+
+ public void Update()
+ {
+ _chamberedBullet.ModulateSelfOverride =
+ _parent.Chamber.chambered ?
+ _parent.Chamber.spent ? Color.Red : 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/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);
+
+ 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());
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientRevolverBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientRevolverBarrelComponent.cs
new file mode 100644
index 0000000000..3d5d94f5a8
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientRevolverBarrelComponent.cs
@@ -0,0 +1,175 @@
+using Content.Client.UserInterface.Stylesheets;
+using Content.Client.Utility;
+using Content.Shared.GameObjects;
+using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Maths;
+using Robust.Shared.ViewVariables;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices.ComTypes;
+using System.Text;
+
+namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
+{
+ [RegisterComponent]
+ public class ClientRevolverBarrelComponent : Component, IItemStatus
+ {
+ public override string Name => "RevolverBarrel";
+ public override uint? NetID => ContentNetIDs.REVOLVER_BARREL;
+
+ private StatusControl _statusControl;
+
+ ///
+ /// A array that lists the bullet states
+ /// true means a spent bullet
+ /// false means a "shootable" bullet
+ /// null means no bullet
+ ///
+ [ViewVariables]
+ public bool?[] Bullets { get; private set; }
+
+ [ViewVariables]
+ public int CurrentSlot { get; private set; }
+
+ public override void HandleComponentState(ComponentState curState, ComponentState nextState)
+ {
+ if (!(curState is RevolverBarrelComponentState cast))
+ return;
+
+ CurrentSlot = cast.CurrentSlot;
+ Bullets = cast.Bullets;
+ _statusControl?.Update();
+ }
+
+ 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 ClientRevolverBarrelComponent _parent;
+ private readonly HBoxContainer _bulletsList;
+
+ public StatusControl(ClientRevolverBarrelComponent parent)
+ {
+ _parent = parent;
+ SizeFlagsHorizontal = SizeFlags.FillExpand;
+ SizeFlagsVertical = SizeFlags.ShrinkCenter;
+ AddChild((_bulletsList = new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ SizeFlagsVertical = SizeFlags.ShrinkCenter,
+ SeparationOverride = 0
+ }));
+ }
+
+ public void Update()
+ {
+ _bulletsList.RemoveAllChildren();
+
+ var capacity = _parent.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(_bulletsList, texture, spentTexture);
+ }
+
+ private void FillBulletRow(Control container, Texture texture, Texture emptyTexture)
+ {
+ 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 < _parent.Bullets.Length; i++)
+ {
+ var bulletSpent = _parent.Bullets[i];
+ // Add a outline
+ var box = new Control()
+ {
+ CustomMinimumSize = texture.Size * scale,
+ };
+ if (i == _parent.CurrentSlot)
+ {
+ box.AddChild(new TextureRect
+ {
+ Texture = texture,
+ TextureScale = (scale, scale),
+ ModulateSelfOverride = Color.Green,
+ });
+ }
+ Color color;
+ Texture bulletTexture = texture;
+
+ if (bulletSpent.HasValue)
+ {
+ if (bulletSpent.Value)
+ {
+ color = altColor ? colorSpentA : colorSpentB;
+ bulletTexture = emptyTexture;
+ }
+ else
+ {
+ color = altColor ? colorA : colorB;
+ }
+ }
+ else
+ {
+ color = altColor ? colorGoneA : colorGoneB;
+ }
+
+ box.AddChild(new TextureRect
+ {
+ SizeFlagsHorizontal = SizeFlags.Fill,
+ SizeFlagsVertical = SizeFlags.Fill,
+ Stretch = TextureRect.StretchMode.KeepCentered,
+ Texture = bulletTexture,
+ ModulateSelfOverride = color,
+ });
+ altColor ^= true;
+ container.AddChild(box);
+ }
+ }
+
+ protected override Vector2 CalculateMinimumSize()
+ {
+ return Vector2.ComponentMax((0, 15), base.CalculateMinimumSize());
+ }
+ }
+ }
+}
diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs
index 72785bce6f..9a72a27e22 100644
--- a/Content.Client/IgnoredComponents.cs
+++ b/Content.Client/IgnoredComponents.cs
@@ -86,9 +86,6 @@
"CanSpill",
"SpeedLoader",
"Hitscan",
- "BoltActionBarrel",
- "PumpBarrel",
- "RevolverBarrel",
"ExplosiveProjectile",
"StunnableProjectile",
"RandomPottedPlant",
diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs
index cc0d0d74b2..3e4b6362fc 100644
--- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs
+++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
+using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
@@ -32,6 +33,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
// but it felt a lot messier to play around with, especially when adding verbs
public override string Name => "BoltActionBarrel";
+ public override uint? NetID => ContentNetIDs.BOLTACTION_BARREL;
public override int ShotsLeft
{
@@ -123,6 +125,24 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
UpdateAppearance();
}
+ public override ComponentState GetComponentState()
+ {
+ (int, int)? count = (ShotsLeft, Capacity);
+ var chamberedExists = _chamberContainer.ContainedEntity != null;
+ // (Is one chambered?, is the bullet spend)
+ var chamber = (chamberedExists, false);
+ if (chamberedExists && _chamberContainer.ContainedEntity.TryGetComponent(out var ammo))
+ {
+ chamber.Item2 = ammo.Spent;
+ }
+
+ return new BoltActionBarrelComponentState(
+ chamber,
+ FireRateSelector,
+ count,
+ SoundGunshot);
+ }
+
public override void Initialize()
{
// TODO: Add existing ammo support on revolvers
@@ -170,6 +190,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
Cycle();
}
+ else
+ {
+ Dirty();
+ }
+
return chamberEntity?.GetComponent().TakeBullet(spawnAtGrid, spawnAtMap);
}
@@ -256,7 +281,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
EntitySystem.Get().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
}
- // Dirty();
+ Dirty();
UpdateAppearance();
return true;
}
@@ -269,7 +294,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
EntitySystem.Get().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
}
- // Dirty();
+ Dirty();
UpdateAppearance();
return true;
}
diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs
index 6859c0c258..9dd3d9c7b5 100644
--- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs
+++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
+using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
@@ -27,6 +28,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
public sealed class PumpBarrelComponent : ServerRangedBarrelComponent, IMapInit, IExamine
{
public override string Name => "PumpBarrel";
+ public override uint? NetID => ContentNetIDs.PUMP_BARREL;
public override int ShotsLeft
{
@@ -81,6 +83,23 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
UpdateAppearance();
}
+ public override ComponentState GetComponentState()
+ {
+ (int, int)? count = (ShotsLeft, Capacity);
+ var chamberedExists = _chamberContainer.ContainedEntity != null;
+ // (Is one chambered?, is the bullet spend)
+ var chamber = (chamberedExists, false);
+ if (chamberedExists && _chamberContainer.ContainedEntity.TryGetComponent(out var ammo))
+ {
+ chamber.Item2 = ammo.Spent;
+ }
+ return new PumpBarrelComponentState(
+ chamber,
+ FireRateSelector,
+ count,
+ SoundGunshot);
+ }
+
public override void Initialize()
{
base.Initialize();
@@ -110,6 +129,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
}
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
+ Dirty();
UpdateAppearance();
}
@@ -131,6 +151,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
Cycle();
}
+ else
+ {
+ Dirty();
+ }
+
return chamberEntity?.GetComponent().TakeBullet(spawnAtGrid, spawnAtMap);
}
@@ -168,7 +193,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
}
}
- // Dirty();
+ Dirty();
UpdateAppearance();
}
@@ -189,7 +214,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
_ammoContainer.Insert(eventArgs.Using);
_spawnedAmmo.Push(eventArgs.Using);
- // Dirty();
+ Dirty();
UpdateAppearance();
if (_soundInsert != null)
{
diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs
index 004e742b71..e976c6aeb3 100644
--- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs
+++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
+using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
@@ -25,6 +26,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
public sealed class RevolverBarrelComponent : ServerRangedBarrelComponent
{
public override string Name => "RevolverBarrel";
+ public override uint? NetID => ContentNetIDs.REVOLVER_BARREL;
+
private BallisticCaliber _caliber;
private Container _ammoContainer;
private int _currentSlot = 0;
@@ -60,6 +63,26 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
serializer.DataField(ref _soundSpin, "soundSpin", "/Audio/Weapons/Guns/Misc/revolver_spin.ogg");
}
+ public override ComponentState GetComponentState()
+ {
+ var slotsSpent = new bool?[Capacity];
+ for (var i = 0; i < Capacity; i++)
+ {
+ slotsSpent[i] = null;
+ if (_ammoSlots[i] != null && _ammoSlots[i].TryGetComponent(out AmmoComponent ammo))
+ {
+ slotsSpent[i] = ammo.Spent;
+ }
+ }
+
+ //TODO: make yaml var to not sent currentSlot/UI? (for russian roulette)
+ return new RevolverBarrelComponentState(
+ _currentSlot,
+ FireRateSelector,
+ slotsSpent,
+ SoundGunshot);
+ }
+
public override void Initialize()
{
base.Initialize();
@@ -90,6 +113,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
}
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
+ Dirty();
}
private void UpdateAppearance()
@@ -129,7 +153,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
EntitySystem.Get().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
}
- // Dirty();
+ Dirty();
UpdateAppearance();
return true;
}
@@ -143,7 +167,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
// Move up a slot
_currentSlot = (_currentSlot + 1) % _ammoSlots.Length;
- // Dirty();
+ Dirty();
UpdateAppearance();
}
@@ -158,6 +182,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
EntitySystem.Get().PlayAtCoords(_soundSpin, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
}
+ Dirty();
}
public override IEntity PeekAmmo()
@@ -227,7 +252,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
public override bool UseEntity(UseEntityEventArgs eventArgs)
{
EjectAllSlots();
- //Dirty();
+ Dirty();
UpdateAppearance();
return true;
}
diff --git a/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBoltActionBarrelComponent.cs b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBoltActionBarrelComponent.cs
new file mode 100644
index 0000000000..f2e8e52b80
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBoltActionBarrelComponent.cs
@@ -0,0 +1,30 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels
+{
+ [Serializable, NetSerializable]
+ public class BoltActionBarrelComponentState : ComponentState
+ {
+ public (bool chambered, bool spent) Chamber { get; }
+ public FireRateSelector FireRateSelector { get; }
+ public (int count, int max)? Magazine { get; }
+ public string SoundGunshot { get; }
+
+ public BoltActionBarrelComponentState(
+ (bool chambered, bool spent) chamber,
+ FireRateSelector fireRateSelector,
+ (int count, int max)? magazine,
+ string soundGunshot) :
+ base(ContentNetIDs.BOLTACTION_BARREL)
+ {
+ Chamber = chamber;
+ FireRateSelector = fireRateSelector;
+ Magazine = magazine;
+ SoundGunshot = soundGunshot;
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedPumpBarrelComponent.cs b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedPumpBarrelComponent.cs
new file mode 100644
index 0000000000..a577e11ad0
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedPumpBarrelComponent.cs
@@ -0,0 +1,30 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels
+{
+ [Serializable, NetSerializable]
+ public class PumpBarrelComponentState : ComponentState
+ {
+ public (bool chambered, bool spent) Chamber { get; }
+ public FireRateSelector FireRateSelector { get; }
+ public (int count, int max)? Magazine { get; }
+ public string SoundGunshot { get; }
+
+ public PumpBarrelComponentState(
+ (bool chambered, bool spent) chamber,
+ FireRateSelector fireRateSelector,
+ (int count, int max)? magazine,
+ string soundGunshot) :
+ base(ContentNetIDs.PUMP_BARREL)
+ {
+ Chamber = chamber;
+ FireRateSelector = fireRateSelector;
+ Magazine = magazine;
+ SoundGunshot = soundGunshot;
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedRevolverBarrelComponent.cs b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedRevolverBarrelComponent.cs
new file mode 100644
index 0000000000..3ed085101b
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedRevolverBarrelComponent.cs
@@ -0,0 +1,30 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels
+{
+ [Serializable, NetSerializable]
+ public class RevolverBarrelComponentState : ComponentState
+ {
+ public int CurrentSlot { get; }
+ public FireRateSelector FireRateSelector { get; }
+ public bool?[] Bullets { get; }
+ public string SoundGunshot { get; }
+
+ public RevolverBarrelComponentState(
+ int currentSlot,
+ FireRateSelector fireRateSelector,
+ bool?[] bullets,
+ string soundGunshot) :
+ base(ContentNetIDs.REVOLVER_BARREL)
+ {
+ CurrentSlot = currentSlot;
+ FireRateSelector = fireRateSelector;
+ Bullets = bullets;
+ SoundGunshot = soundGunshot;
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs
index 7d7bcfdc4f..2eb1e59b58 100644
--- a/Content.Shared/GameObjects/ContentNetIDs.cs
+++ b/Content.Shared/GameObjects/ContentNetIDs.cs
@@ -65,6 +65,9 @@
public const uint RADIATION_PULSE = 1059;
public const uint BODY_MANAGER = 1060;
public const uint CLIMBING = 1061;
+ public const uint BOLTACTION_BARREL = 1062;
+ public const uint PUMP_BARREL = 1063;
+ public const uint REVOLVER_BARREL = 1064;
// Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001;
diff --git a/Resources/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png b/Resources/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png
new file mode 100644
index 0000000000..44d7290469
Binary files /dev/null and b/Resources/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png differ
diff --git a/Resources/Textures/Interface/ItemStatus/Bullets/empty.png b/Resources/Textures/Interface/ItemStatus/Bullets/empty.png
new file mode 100644
index 0000000000..564f5bf851
Binary files /dev/null and b/Resources/Textures/Interface/ItemStatus/Bullets/empty.png differ