using System.Linq; using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; using Content.Server.Lightning; using Content.Server.Radio.EntitySystems; using Content.Server.Station.Systems; using Content.Server._Sunrise.Messenger; using Content.Shared.Abilities.Goliath; using Content.Shared.Atmos; using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Systems; using Content.Shared.Ghost; using Content.Shared.Interaction; using Content.Shared.Projectiles; using Content.Shared.Radiation.Components; using Content.Shared.Radio; using Content.Shared.Singularity.Components; using Content.Shared.Starlight.Energy.Supermatter; using Robust.Server.Audio; using Robust.Shared.Physics; using Robust.Shared.Physics.Events; using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server.Starlight.Energy.Supermatter; public sealed class SupermatterSystem : AccUpdateEntitySystem { [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly LightningSystem _lightning = default!; [Dependency] private readonly RadioSystem _radioSystem = default!; [Dependency] private readonly SupermatterCascadeSystem _cascade = default!; [Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly MessengerServerSystem _messenger = default!; [Dependency] private readonly StationSystem _station = default!; private readonly Dictionary> _supermatters = []; private DamageGroupPrototype? _brute; private DamageGroupPrototype? _burn; private RadioChannelPrototype? _engi; public override void Initialize() { SubscribeLocalEvent(AddSupermatter); SubscribeLocalEvent(RemoveSupermatter); SubscribeLocalEvent(OnCollide); SubscribeLocalEvent(OnInteract); } private void OnInteract(Entity ent, ref InteractHandEvent args) { if (HasComp(args.User)) return; _audio.PlayPvs(Const.AudioEvaporate, ent.Owner); float damage = 1; if (TryComp(args.User, out var fixture)) damage = fixture.Fixtures.Select(x => x.Value.Density).Aggregate((i, p) => p + i) / 3; _burn ??= _prototypes.Index("Burn"); _damageable.TryChangeDamage(ent.Owner, new(_burn, damage), true); QueueDel(args.User); } private void OnCollide(Entity ent, ref EndCollideEvent args) { ent.Comp.Activated = true; if (HasComp(args.OtherEntity) || HasComp(args.OtherEntity)) return; _audio.PlayPvs(Const.AudioEvaporate, ent.Owner); float damage = 1; if (TryComp(args.OtherEntity, out var fixture)) damage = fixture.Fixtures.Select(x => x.Value.Density).Aggregate((i, p) => p + i) / 3; _burn ??= _prototypes.Index("Burn"); _damageable.TryChangeDamage(ent.Owner, new(_burn, damage), true); QueueDel(args.OtherEntity); } private void AddSupermatter(Entity ent, ref ComponentInit args) => _supermatters.TryAdd(ent.Owner, ent); private void RemoveSupermatter(Entity ent, ref ComponentShutdown args) => _supermatters.Remove(ent.Owner); protected override float Threshold { get; set; } = 1f; protected override void AccUpdate() { foreach (var supermatter in _supermatters) Handle(supermatter.Value); } private void Handle(Entity supermatter) { if (!supermatter.Comp.Activated) return; HandleDamage(supermatter); HandleGas(supermatter); HandleRadiation(supermatter); HandleLighting(supermatter); HandleDestruction(supermatter); NotifyCascad(supermatter); Cascad(supermatter); } private void Cascad(Entity supermatter) { if (supermatter.Comp.Durability > 0.01) return; _cascade.StartCascade(Transform(supermatter.Owner).Coordinates); QueueDel(supermatter.Owner); } private void NotifyCascad(Entity supermatter) { var currentDurability = (int)Math.Floor(supermatter.Comp.Durability.Float()); var lastDurability = (int)Math.Floor(supermatter.Comp.LastSendedDurability.Float()); _engi ??= _prototypes.Index("Engineering"); if (Math.Abs(currentDurability - lastDurability) < 5) return; supermatter.Comp.LastSendedDurability = supermatter.Comp.Durability; if (currentDurability > lastDurability) { // _radioSystem.SendRadioMessage(supermatter.Owner, $"The crystal is regenerating. Durability: {currentDurability}%", _engi, supermatter.Owner); if (_messenger.GetServerEntity(_station.GetOwningStation(supermatter.Owner)) is var (server, _) && _messenger.GetGroupIdByRadioChannel(_engi.ID) is { } groupId) { _messenger.SendSystemMessageToGroup(server, groupId, $"The crystal is regenerating. Durability: {currentDurability}%"); } } else switch (currentDurability) { case > 75: // _radioSystem.SendRadioMessage(supermatter.Owner, $"Attention! The crystal is destabilizing. Durability: {currentDurability}%", _engi, supermatter.Owner); if (_messenger.GetServerEntity(_station.GetOwningStation(supermatter.Owner)) is var (server, _) && _messenger.GetGroupIdByRadioChannel(_engi.ID) is { } groupId) { _messenger.SendSystemMessageToGroup(server, groupId, $"Attention! The crystal is destabilizing. Durability: {currentDurability}%"); } break; case > 50: _chat.DispatchServerAnnouncement($"Attention! The crystal is destabilizing. Durability: {currentDurability}%", Color.Yellow); break; case > 25: _chat.DispatchServerAnnouncement($"Critical state of the crystal! Durability: {currentDurability}%", Color.OrangeRed); break; default: _chat.DispatchServerAnnouncement($"Crystal destruction is inevitable. Current durability: {currentDurability}%", Color.Red); break; } } private void HandleDestruction(Entity supermatter) { var damageToApply = MathHelper.Clamp((supermatter.Comp.AccBreak / 10) - Const.RegenerationPerSecond, -Const.RegenerationPerSecond, Const.MaxDamagePerSecond); supermatter.Comp.AccBreak = 0; supermatter.Comp.Durability = MathHelper.Clamp(supermatter.Comp.Durability - damageToApply, 0f, 100f); } private void HandleLighting(Entity supermatter) { if (supermatter.Comp.AccLighting != 0 && _lightning.ShootRandomLightnings(supermatter.Owner, supermatter.Comp.AccLighting.Float(), 1)) supermatter.Comp.AccLighting = 0; } private void HandleRadiation(Entity supermatter) { var radComp = EnsureComp(supermatter.Owner); radComp.Intensity = supermatter.Comp.AccRadiation.Float(); supermatter.Comp.AccRadiation /= supermatter.Comp.RadiationStability; } private void HandleGas(Entity supermatter) { var gas = _atmosphere.GetTileMixture(supermatter.Owner, true) ?? new(); DamageByPressure(supermatter, gas); DamageByTemperature(supermatter, gas); if (gas.TotalMoles < 1) return; float heatTransfer = 0; float heatModifier = 0; float radiationStability = 0; for (var i = 0; i < Const.GasProperties.Length; i++) { var prop = Const.GasProperties[i]; var percent = Math.Clamp(gas.Moles[i] / gas.TotalMoles, 0, 1); heatTransfer += prop.HeatTransferPerMole * gas.Moles[i]; heatModifier += prop.HeatModifier * percent; radiationStability += prop.RadiationStability * percent; } supermatter.Comp.RadiationStability = MathHelper.Clamp(radiationStability, 1, 10); ProcessHeat(supermatter, gas, heatTransfer, heatModifier); TryCompensateDamage(supermatter, gas); } private static void TryCompensateDamage(Entity supermatter, GasMixture gas) { var breakDelta = supermatter.Comp.AccBreak > Const.EvaporationCompensation ? Const.EvaporationCompensation : supermatter.Comp.AccBreak; if (breakDelta == 0) return; supermatter.Comp.AccBreak -= breakDelta; gas.AdjustMoles((int)Gas.Tritium, breakDelta.Float()/2); gas.AdjustMoles((int)Gas.Oxygen, breakDelta.Float()*4); } private static void ProcessHeat(Entity supermatter, GasMixture gas, float heatTransfer, float heatModifier) { var accHeat = supermatter.Comp.AccHeat.Float(); var heatDelta = heatTransfer <= accHeat ? heatTransfer : accHeat; accHeat = MathHelper.Clamp(accHeat - heatDelta, 0, 9999); ; supermatter.Comp.AccHeat = 0; gas.Temperature += heatDelta * heatModifier; supermatter.Comp.AccBreak += accHeat; } private void DamageByTemperature(Entity supermatter, GasMixture gas) { if (gas.Temperature <= Const.MaxTemperature) return; _audio.PlayPvs(_random.Pick(Const.AudioBurn), supermatter.Owner); _burn ??= _prototypes.Index("Burn"); DamageSpecifier damage = new(_burn, Const.MaxTemperature - gas.Temperature); _damageable.TryChangeDamage(supermatter.Owner, damage, true); } private void DamageByPressure(Entity supermatter, GasMixture gas) { if (gas.Pressure >= Const.MinPressure && gas.Pressure <= Const.MaxPressure) return; _audio.PlayPvs(_random.Pick(Const.AudioCrack), supermatter.Owner); _brute ??= _prototypes.Index("Brute"); DamageSpecifier damage = new(_brute, Math.Max(Const.MinPressure - gas.Pressure, gas.Pressure - Const.MaxPressure) / 100); _damageable.TryChangeDamage(supermatter.Owner, damage, true); } private void HandleDamage(Entity supermatter) { EnsureComp(supermatter.Owner, out var damageable); var trueDamage = damageable.TotalDamage * Const.DamageMultiplayer; _damageable.TryChangeDamage(supermatter.Owner, damageable.Damage.Invert(), true); supermatter.Comp.AccBreak = MathHelper.Clamp(supermatter.Comp.AccBreak + (trueDamage * Const.BreakPercent), 0, 9999); supermatter.Comp.AccHeat = MathHelper.Clamp(supermatter.Comp.AccHeat + (trueDamage * Const.HeatPercent), 0, 9999); supermatter.Comp.AccLighting = MathHelper.Clamp(supermatter.Comp.AccLighting + (trueDamage * Const.LightingPercent), 0, 25); supermatter.Comp.AccRadiation = MathHelper.Clamp(supermatter.Comp.AccRadiation + (trueDamage * Const.RadiationPercent), 0, 100); } }