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); } }