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