diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBatteryBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBatteryBarrelComponent.cs
new file mode 100644
index 0000000000..9de6ba9b71
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBatteryBarrelComponent.cs
@@ -0,0 +1,160 @@
+using Content.Client.UserInterface.Stylesheets;
+using Content.Shared.GameObjects;
+using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
+using Robust.Client.Graphics.Drawing;
+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 ClientBatteryBarrelComponent : Component, IItemStatus
+ {
+ public override string Name => "BatteryBarrel";
+ public override uint? NetID => ContentNetIDs.BATTERY_BARREL;
+
+ private StatusControl _statusControl;
+
+ ///
+ /// 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 BatteryBarrelComponentState cast))
+ return;
+
+ 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 ClientBatteryBarrelComponent _parent;
+ private readonly HBoxContainer _bulletsList;
+ private readonly Label _noBatteryLabel;
+ private readonly Label _ammoCount;
+
+ public StatusControl(ClientBatteryBarrelComponent parent)
+ {
+ _parent = parent;
+ SizeFlagsHorizontal = SizeFlags.FillExpand;
+ SizeFlagsVertical = SizeFlags.ShrinkCenter;
+
+ AddChild(new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ Children =
+ {
+ new Control
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ Children =
+ {
+ (_bulletsList = new HBoxContainer
+ {
+ SizeFlagsVertical = SizeFlags.ShrinkCenter,
+ SeparationOverride = 4
+ }),
+ (_noBatteryLabel = new Label
+ {
+ Text = "No Battery!",
+ StyleClasses = {StyleNano.StyleClassItemStatus}
+ })
+ }
+ },
+ new Control() { CustomMinimumSize = (5,0) },
+ (_ammoCount = new Label
+ {
+ StyleClasses = {StyleNano.StyleClassItemStatus},
+ SizeFlagsHorizontal = SizeFlags.ShrinkEnd,
+ }),
+ }
+ });
+ }
+
+ public void Update()
+ {
+ _bulletsList.RemoveAllChildren();
+
+ if (_parent.MagazineCount == null)
+ {
+ _noBatteryLabel.Visible = true;
+ _ammoCount.Visible = false;
+ return;
+ }
+
+ var (count, capacity) = _parent.MagazineCount.Value;
+
+ _noBatteryLabel.Visible = false;
+ _ammoCount.Visible = true;
+
+ _ammoCount.Text = $"x{count:00}";
+ capacity = Math.Min(capacity, 8);
+ FillBulletRow(_bulletsList, count, capacity);
+ }
+
+ 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,
+ },
+ CustomMinimumSize = (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,
+ },
+ CustomMinimumSize = (10, 15),
+ });
+ }
+ }
+
+ protected override Vector2 CalculateMinimumSize()
+ {
+ return Vector2.ComponentMax((0, 15), base.CalculateMinimumSize());
+ }
+ }
+ }
+}
diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs
index 9a72a27e22..28545a1909 100644
--- a/Content.Client/IgnoredComponents.cs
+++ b/Content.Client/IgnoredComponents.cs
@@ -105,7 +105,6 @@
"SecureEntityStorage",
"PresetIdCard",
"SolarControlConsole",
- "BatteryBarrel",
"FlashExplosive",
"FlashProjectile",
"Utensil",
diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs
index 486d2071b1..d81aa8115e 100644
--- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs
+++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs
@@ -6,7 +6,10 @@ using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Projectiles;
using Content.Shared.Damage;
+using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
+using Content.Shared.GameObjects.EntitySystems;
+using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
@@ -25,6 +28,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
public sealed class ServerBatteryBarrelComponent : ServerRangedBarrelComponent
{
public override string Name => "BatteryBarrel";
+ public override uint? NetID => ContentNetIDs.BATTERY_BARREL;
// The minimum change we need before we can fire
[ViewVariables] private float _lowerChargeLimit;
@@ -88,6 +92,15 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
serializer.DataField(ref _soundPowerCellEject, "soundPowerCellEject", null);
}
+ public override ComponentState GetComponentState()
+ {
+ (int, int)? count = (ShotsLeft, Capacity);
+
+ return new BatteryBarrelComponentState(
+ FireRateSelector,
+ count);
+ }
+
public override void Initialize()
{
base.Initialize();
@@ -108,6 +121,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
_appearanceComponent = appearanceComponent;
}
+ Dirty();
UpdateAppearance();
}
@@ -188,8 +202,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
throw new InvalidOperationException("Ammo doesn't have hitscan or projectile?");
}
+ Dirty();
UpdateAppearance();
- //Dirty();
return entity;
}
@@ -211,30 +225,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
}
_powerCellContainer.Insert(entity);
+
+ Dirty();
UpdateAppearance();
- //Dirty();
return true;
}
- private IEntity RemovePowerCell()
- {
- if (!_powerCellRemovable || _powerCellContainer.ContainedEntity == null)
- {
- return null;
- }
-
- var entity = _powerCellContainer.ContainedEntity;
- _powerCellContainer.Remove(entity);
- if (_soundPowerCellEject != null)
- {
- EntitySystem.Get().PlayAtCoords(_soundPowerCellEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
- }
-
- UpdateAppearance();
- //Dirty();
- return entity;
- }
-
public override bool UseEntity(UseEntityEventArgs eventArgs)
{
if (!_powerCellRemovable)
@@ -242,22 +238,44 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
return false;
}
- if (!eventArgs.User.TryGetComponent(out HandsComponent handsComponent) ||
- PowerCellEntity == null)
+ if (PowerCellEntity == null)
{
return false;
}
- var itemComponent = PowerCellEntity.GetComponent();
- if (!handsComponent.CanPutInHand(itemComponent))
+ return TryEjectCell(eventArgs.User);
+ }
+
+ private bool TryEjectCell(IEntity user)
+ {
+ if (PowerCell == null || !_powerCellRemovable)
{
return false;
}
- var powerCell = RemovePowerCell();
- handsComponent.PutInHand(itemComponent);
- powerCell.Transform.GridPosition = eventArgs.User.Transform.GridPosition;
+ if (!user.TryGetComponent(out HandsComponent hands))
+ {
+ return false;
+ }
+ var cell = PowerCell;
+ if (!_powerCellContainer.Remove(cell.Owner))
+ {
+ return false;
+ }
+
+ Dirty();
+ UpdateAppearance();
+
+ if (!hands.PutInHand(cell.Owner.GetComponent()))
+ {
+ cell.Owner.Transform.GridPosition = user.Transform.GridPosition;
+ }
+
+ if (_soundPowerCellEject != null)
+ {
+ EntitySystem.Get().PlayAtCoords(_soundPowerCellEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
+ }
return true;
}
@@ -270,5 +288,33 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
return TryInsertPowerCell(eventArgs.Using);
}
+
+ [Verb]
+ public sealed class EjectCellVerb : Verb
+ {
+ protected override void GetData(IEntity user, ServerBatteryBarrelComponent component, VerbData data)
+ {
+ if (!ActionBlockerSystem.CanInteract(user) || !component._powerCellRemovable)
+ {
+ data.Visibility = VerbVisibility.Invisible;
+ return;
+ }
+
+ if (component.PowerCell == null)
+ {
+ data.Text = "Eject cell (cell missing)";
+ data.Visibility = VerbVisibility.Disabled;
+ }
+ else
+ {
+ data.Text = "Eject cell";
+ }
+ }
+
+ protected override void Activate(IEntity user, ServerBatteryBarrelComponent component)
+ {
+ component.TryEjectCell(user);
+ }
+ }
}
}
diff --git a/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBatteryBarrelComponent.cs b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBatteryBarrelComponent.cs
new file mode 100644
index 0000000000..40ef9b47e1
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBatteryBarrelComponent.cs
@@ -0,0 +1,24 @@
+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 BatteryBarrelComponentState : ComponentState
+ {
+ public FireRateSelector FireRateSelector { get; }
+ public (int count, int max)? Magazine { get; }
+
+ public BatteryBarrelComponentState(
+ FireRateSelector fireRateSelector,
+ (int count, int max)? magazine) :
+ base(ContentNetIDs.BATTERY_BARREL)
+ {
+ FireRateSelector = fireRateSelector;
+ Magazine = magazine;
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs
index f375525110..32ede76f3d 100644
--- a/Content.Shared/GameObjects/ContentNetIDs.cs
+++ b/Content.Shared/GameObjects/ContentNetIDs.cs
@@ -70,6 +70,7 @@
public const uint REVOLVER_BARREL = 1064;
public const uint CUFFED = 1065;
public const uint HANDCUFFS = 1066;
+ public const uint BATTERY_BARREL = 1067;
// Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001;
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
index ef5ae605f1..f57ce3d57f 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
@@ -30,6 +30,7 @@
- Single
fireRate: 2
powerCellPrototype: PowerCellSmallStandard
+ powerCellRemovable: true
ammoPrototype: RedLaser
soundGunshot: /Audio/Weapons/Guns/Gunshots/laser.ogg
- type: Appearance
@@ -71,6 +72,7 @@
angleIncrease: 15
angleDecay: 45
powerCellPrototype: PowerCellSmallSuper
+ powerCellRemovable: true
ammoPrototype: RedHeavyLaser
soundGunshot: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg
- type: Appearance
@@ -112,6 +114,7 @@
angleIncrease: 15
angleDecay: 45
powerCellPrototype: PowerCellSmallSuper
+ powerCellRemovable: true
base_fire_cost: 600
ammoPrototype: XrayLaser
soundGunshot: /Audio/Weapons/Guns/Gunshots/laser3.ogg
@@ -155,6 +158,7 @@
angleIncrease: 20
angleDecay: 15
powerCellPrototype: PowerCellSmallStandard
+ powerCellRemovable: false
ammoPrototype: BulletTaser
soundGunshot: /Audio/Weapons/Guns/Gunshots/taser.ogg
- type: Appearance