From 80727f7fa4a4f34b9fb8bc2f1d07c7bafab3cbf9 Mon Sep 17 00:00:00 2001
From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Date: Wed, 2 Feb 2022 18:04:38 +1100
Subject: [PATCH] ECS cluster nades (#6368)
---
Content.Client/Entry/IgnoredComponents.cs | 2 +-
...ualizer.cs => ClusterGrenadeVisualizer.cs} | 4 +-
.../Components/ClusterFlashComponent.cs | 164 ------------------
.../Components/ClusterGrenadeComponent.cs | 51 ++++++
.../EntitySystems/ClusterGrenadeSystem.cs | 133 ++++++++++++++
...nt.cs => SharedClusterGrenadeComponent.cs} | 2 +-
.../Weapons/Guns/Explosives/clusterbang.yml | 6 +-
7 files changed, 191 insertions(+), 171 deletions(-)
rename Content.Client/Explosion/{ClusterFlashVisualizer.cs => ClusterGrenadeVisualizer.cs} (83%)
delete mode 100644 Content.Server/Explosion/Components/ClusterFlashComponent.cs
create mode 100644 Content.Server/Explosion/Components/ClusterGrenadeComponent.cs
create mode 100644 Content.Server/Explosion/EntitySystems/ClusterGrenadeSystem.cs
rename Content.Shared/Explosion/{SharedClusterFlashComponent.cs => SharedClusterGrenadeComponent.cs} (78%)
diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs
index 134bf7c003..4bd03bc2f0 100644
--- a/Content.Client/Entry/IgnoredComponents.cs
+++ b/Content.Client/Entry/IgnoredComponents.cs
@@ -235,7 +235,7 @@ namespace Content.Client.Entry
"LightReplacer",
"SecretStash",
"Toilet",
- "ClusterFlash",
+ "ClusterGrenade",
"Repairable",
"SolutionTransfer",
"Evaporation",
diff --git a/Content.Client/Explosion/ClusterFlashVisualizer.cs b/Content.Client/Explosion/ClusterGrenadeVisualizer.cs
similarity index 83%
rename from Content.Client/Explosion/ClusterFlashVisualizer.cs
rename to Content.Client/Explosion/ClusterGrenadeVisualizer.cs
index 406ddcedc5..2ece5cb2b5 100644
--- a/Content.Client/Explosion/ClusterFlashVisualizer.cs
+++ b/Content.Client/Explosion/ClusterGrenadeVisualizer.cs
@@ -9,7 +9,7 @@ namespace Content.Client.Explosion
{
[UsedImplicitly]
// ReSharper disable once InconsistentNaming
- public class ClusterFlashVisualizer : AppearanceVisualizer
+ public class ClusterGrenadeVisualizer : AppearanceVisualizer
{
[DataField("state")]
private string? _state;
@@ -24,7 +24,7 @@ namespace Content.Client.Explosion
return;
}
- if (component.TryGetData(ClusterFlashVisuals.GrenadesCounter, out int grenadesCounter))
+ if (component.TryGetData(ClusterGrenadeVisuals.GrenadesCounter, out int grenadesCounter))
{
sprite.LayerSetState(0, $"{_state}-{grenadesCounter}");
}
diff --git a/Content.Server/Explosion/Components/ClusterFlashComponent.cs b/Content.Server/Explosion/Components/ClusterFlashComponent.cs
deleted file mode 100644
index d01f076b7a..0000000000
--- a/Content.Server/Explosion/Components/ClusterFlashComponent.cs
+++ /dev/null
@@ -1,164 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using Content.Server.Explosion.EntitySystems;
-using Content.Server.Flash.Components;
-using Content.Server.Throwing;
-using Content.Shared.Explosion;
-using Content.Shared.Interaction;
-using Robust.Shared.Containers;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Serialization.Manager.Attributes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Robust.Shared.ViewVariables;
-
-namespace Content.Server.Explosion.Components
-{
- [RegisterComponent]
- public sealed class ClusterFlashComponent : Component, IInteractUsing, IUse
- {
- [Dependency] private readonly IEntityManager _entMan = default!;
-
- private Container _grenadesContainer = default!;
-
- ///
- /// What we fill our prototype with if we want to pre-spawn with grenades.
- ///
- [ViewVariables] [DataField("fillPrototype", customTypeSerializer:typeof(PrototypeIdSerializer))]
- private string? _fillPrototype;
-
- ///
- /// If we have a pre-fill how many more can we spawn.
- ///
- private int _unspawnedCount;
-
- ///
- /// Maximum grenades in the container.
- ///
- [ViewVariables] [DataField("maxGrenadesCount")]
- private int _maxGrenades = 3;
-
- ///
- /// How long until our grenades are shot out and armed.
- ///
- [ViewVariables(VVAccess.ReadWrite)] [DataField("delay")]
- private float _delay = 1;
-
- ///
- /// Max distance grenades can be thrown.
- ///
- [ViewVariables(VVAccess.ReadWrite)] [DataField("distance")]
- private float _throwDistance = 50;
-
- ///
- /// This is the end.
- ///
- private bool _countDown;
-
- async Task IInteractUsing.InteractUsing(InteractUsingEventArgs args)
- {
- if (_grenadesContainer.ContainedEntities.Count >= _maxGrenades ||
- !_entMan.HasComponent(args.Using))
- return false;
-
- _grenadesContainer.Insert(args.Using);
- UpdateAppearance();
- return true;
- }
-
- protected override void Initialize()
- {
- base.Initialize();
-
- _grenadesContainer = Owner.EnsureContainer("cluster-flash");
-
- }
-
- protected override void Startup()
- {
- base.Startup();
-
- if (_fillPrototype != null)
- {
- _unspawnedCount = Math.Max(0, _maxGrenades - _grenadesContainer.ContainedEntities.Count);
- UpdateAppearance();
- }
- }
-
- bool IUse.UseEntity(UseEntityEventArgs eventArgs)
- {
- if (_countDown || (_grenadesContainer.ContainedEntities.Count + _unspawnedCount) <= 0)
- return false;
- Owner.SpawnTimer((int) (_delay * 1000), () =>
- {
- if (_entMan.Deleted(Owner))
- return;
- _countDown = true;
- var random = IoCManager.Resolve();
- var delay = 20;
- var grenadesInserted = _grenadesContainer.ContainedEntities.Count + _unspawnedCount;
- var thrownCount = 0;
- var segmentAngle = 360 / grenadesInserted;
- while (TryGetGrenade(out var grenade))
- {
- var angleMin = segmentAngle * thrownCount;
- var angleMax = segmentAngle * (thrownCount + 1);
- var angle = Angle.FromDegrees(random.Next(angleMin, angleMax));
- // var distance = random.NextFloat() * _throwDistance;
-
- delay += random.Next(550, 900);
- thrownCount++;
-
- // TODO: Suss out throw strength
- grenade.TryThrow(angle.ToVec().Normalized * _throwDistance);
-
- grenade.SpawnTimer(delay, () =>
- {
- if ((!_entMan.EntityExists(grenade) ? EntityLifeStage.Deleted : _entMan.GetComponent(grenade).EntityLifeStage) >= EntityLifeStage.Deleted)
- return;
-
- EntitySystem.Get().Trigger(grenade, eventArgs.User);
- });
- }
-
- _entMan.DeleteEntity(Owner);
- });
- return true;
- }
-
- private bool TryGetGrenade(out EntityUid grenade)
- {
- grenade = default;
-
- if (_unspawnedCount > 0)
- {
- _unspawnedCount--;
- grenade = _entMan.SpawnEntity(_fillPrototype, _entMan.GetComponent(Owner).MapPosition);
- return true;
- }
-
- if (_grenadesContainer.ContainedEntities.Count > 0)
- {
- grenade = _grenadesContainer.ContainedEntities[0];
-
- // This shouldn't happen but you never know.
- if (!_grenadesContainer.Remove(grenade))
- return false;
-
- return true;
- }
-
- return false;
- }
-
- private void UpdateAppearance()
- {
- if (!_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance)) return;
-
- appearance.SetData(ClusterFlashVisuals.GrenadesCounter, _grenadesContainer.ContainedEntities.Count + _unspawnedCount);
- }
- }
-}
diff --git a/Content.Server/Explosion/Components/ClusterGrenadeComponent.cs b/Content.Server/Explosion/Components/ClusterGrenadeComponent.cs
new file mode 100644
index 0000000000..c7c6abe241
--- /dev/null
+++ b/Content.Server/Explosion/Components/ClusterGrenadeComponent.cs
@@ -0,0 +1,51 @@
+using Content.Server.Explosion.EntitySystems;
+using Robust.Shared.Analyzers;
+using Robust.Shared.Containers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager.Attributes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.ViewVariables;
+
+namespace Content.Server.Explosion.Components
+{
+ [RegisterComponent, ComponentProtoName("ClusterGrenade"), Friend(typeof(ClusterGrenadeSystem))]
+ public sealed class ClusterGrenadeComponent : Component
+ {
+ public Container GrenadesContainer = default!;
+
+ ///
+ /// What we fill our prototype with if we want to pre-spawn with grenades.
+ ///
+ [ViewVariables] [DataField("fillPrototype", customTypeSerializer:typeof(PrototypeIdSerializer))]
+ public string? FillPrototype;
+
+ ///
+ /// If we have a pre-fill how many more can we spawn.
+ ///
+ public int UnspawnedCount;
+
+ ///
+ /// Maximum grenades in the container.
+ ///
+ [ViewVariables] [DataField("maxGrenadesCount")]
+ public int MaxGrenades = 3;
+
+ ///
+ /// How long until our grenades are shot out and armed.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)] [DataField("delay")]
+ public float Delay = 1;
+
+ ///
+ /// Max distance grenades can be thrown.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)] [DataField("distance")]
+ public float ThrowDistance = 50;
+
+ ///
+ /// This is the end.
+ ///
+ public bool CountDown;
+ }
+}
diff --git a/Content.Server/Explosion/EntitySystems/ClusterGrenadeSystem.cs b/Content.Server/Explosion/EntitySystems/ClusterGrenadeSystem.cs
new file mode 100644
index 0000000000..7edefb4b8c
--- /dev/null
+++ b/Content.Server/Explosion/EntitySystems/ClusterGrenadeSystem.cs
@@ -0,0 +1,133 @@
+using System;
+using Content.Server.Explosion.Components;
+using Content.Server.Flash.Components;
+using Content.Server.Throwing;
+using Content.Shared.Explosion;
+using Content.Shared.Interaction;
+using Robust.Shared.Containers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Maths;
+using Robust.Shared.Random;
+
+namespace Content.Server.Explosion.EntitySystems;
+
+public sealed class ClusterGrenadeSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly TriggerSystem _trigger = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnClugInit);
+ SubscribeLocalEvent(OnClugStartup);
+ SubscribeLocalEvent(OnClugUsing);
+ SubscribeLocalEvent(OnClugUse);
+ }
+
+ private void OnClugInit(EntityUid uid, ClusterGrenadeComponent component, ComponentInit args)
+ {
+ component.GrenadesContainer = _container.EnsureContainer(uid, "cluster-flash");
+ }
+
+ private void OnClugStartup(EntityUid uid, ClusterGrenadeComponent component, ComponentStartup args)
+ {
+ if (component.FillPrototype != null)
+ {
+ component.UnspawnedCount = Math.Max(0, component.MaxGrenades - component.GrenadesContainer.ContainedEntities.Count);
+ UpdateAppearance(component);
+ }
+ }
+
+ private void OnClugUsing(EntityUid uid, ClusterGrenadeComponent component, InteractUsingEvent args)
+ {
+ if (args.Handled) return;
+
+ // TODO: Should use whitelist.
+ if (component.GrenadesContainer.ContainedEntities.Count >= component.MaxGrenades ||
+ !HasComp(args.Used))
+ return;
+
+ component.GrenadesContainer.Insert(args.Used);
+ UpdateAppearance(component);
+ args.Handled = true;
+ }
+
+ private void OnClugUse(EntityUid uid, ClusterGrenadeComponent component, UseInHandEvent args)
+ {
+ if (component.CountDown || (component.GrenadesContainer.ContainedEntities.Count + component.UnspawnedCount) <= 0)
+ return;
+
+ // TODO: Should be an Update loop
+ uid.SpawnTimer((int) (component.Delay * 1000), () =>
+ {
+ if (Deleted(component.Owner))
+ return;
+
+ component.CountDown = true;
+ var delay = 20;
+ var grenadesInserted = component.GrenadesContainer.ContainedEntities.Count + component.UnspawnedCount;
+ var thrownCount = 0;
+ var segmentAngle = 360 / grenadesInserted;
+ while (TryGetGrenade(component, out var grenade))
+ {
+ var angleMin = segmentAngle * thrownCount;
+ var angleMax = segmentAngle * (thrownCount + 1);
+ var angle = Angle.FromDegrees(_random.Next(angleMin, angleMax));
+ // var distance = random.NextFloat() * _throwDistance;
+
+ delay += _random.Next(550, 900);
+ thrownCount++;
+
+ // TODO: Suss out throw strength
+ grenade.TryThrow(angle.ToVec().Normalized * component.ThrowDistance);
+
+ grenade.SpawnTimer(delay, () =>
+ {
+ if ((!EntityManager.EntityExists(grenade) ? EntityLifeStage.Deleted : MetaData(grenade).EntityLifeStage) >= EntityLifeStage.Deleted)
+ return;
+
+ _trigger.Trigger(grenade, args.User);
+ });
+ }
+
+ EntityManager.DeleteEntity(uid);
+ });
+
+ args.Handled = true;
+ }
+
+ private bool TryGetGrenade(ClusterGrenadeComponent component, out EntityUid grenade)
+ {
+ grenade = default;
+
+ if (component.UnspawnedCount > 0)
+ {
+ component.UnspawnedCount--;
+ grenade = EntityManager.SpawnEntity(component.FillPrototype, Transform(component.Owner).MapPosition);
+ return true;
+ }
+
+ if (component.GrenadesContainer.ContainedEntities.Count > 0)
+ {
+ grenade = component.GrenadesContainer.ContainedEntities[0];
+
+ // This shouldn't happen but you never know.
+ if (!component.GrenadesContainer.Remove(grenade))
+ return false;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private void UpdateAppearance(ClusterGrenadeComponent component)
+ {
+ if (!TryComp(component.Owner, out var appearance)) return;
+
+ appearance.SetData(ClusterGrenadeVisuals.GrenadesCounter, component.GrenadesContainer.ContainedEntities.Count + component.UnspawnedCount);
+ }
+}
diff --git a/Content.Shared/Explosion/SharedClusterFlashComponent.cs b/Content.Shared/Explosion/SharedClusterGrenadeComponent.cs
similarity index 78%
rename from Content.Shared/Explosion/SharedClusterFlashComponent.cs
rename to Content.Shared/Explosion/SharedClusterGrenadeComponent.cs
index d58b1f16e6..cd58fba5a1 100644
--- a/Content.Shared/Explosion/SharedClusterFlashComponent.cs
+++ b/Content.Shared/Explosion/SharedClusterGrenadeComponent.cs
@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Explosion
{
[Serializable, NetSerializable]
- public enum ClusterFlashVisuals : byte
+ public enum ClusterGrenadeVisuals : byte
{
GrenadesCounter
}
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/clusterbang.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/clusterbang.yml
index 80937d0f3e..94fb4066d1 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/clusterbang.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/clusterbang.yml
@@ -10,9 +10,9 @@
state: base-0
- type: Appearance
visuals:
- - type: ClusterFlashVisualizer
+ - type: ClusterGrenadeVisualizer
state: base
- - type: ClusterFlash
+ - type: ClusterGrenade
- type: entity
parent: ClusterBang
@@ -21,5 +21,5 @@
components:
- type: Sprite
state: base-3
- - type: ClusterFlash
+ - type: ClusterGrenade
fillPrototype: GrenadeFlashBang