From 1c76a1f634688d456afc77a560968f4681631170 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 2 Aug 2024 19:56:44 -0400 Subject: [PATCH] Final Update --- .../Systems/SupermatterSystem.Processing.cs | 412 +++++++++++++++++ .../Supermatter/Systems/SupermatterSystem.cs | 426 +----------------- .../Components/SupermatterComponent.cs | 31 +- 3 files changed, 429 insertions(+), 440 deletions(-) create mode 100644 Content.Server/Supermatter/Systems/SupermatterSystem.Processing.cs diff --git a/Content.Server/Supermatter/Systems/SupermatterSystem.Processing.cs b/Content.Server/Supermatter/Systems/SupermatterSystem.Processing.cs new file mode 100644 index 00000000000..7a62eba6f7d --- /dev/null +++ b/Content.Server/Supermatter/Systems/SupermatterSystem.Processing.cs @@ -0,0 +1,412 @@ +using Content.Shared.Atmos; +using Content.Shared.Radiation.Components; +using Content.Shared.Supermatter.Components; +using System.Text; +using Content.Shared.Chat; +using System.Linq; +using Content.Shared.Audio; +using Content.Shared.CCVar; + +namespace Content.Server.Supermatter.Systems; + +public sealed partial class SupermatterSystem +{ + /// + /// Handle power and radiation output depending on atmospheric things. + /// + private void ProcessAtmos(EntityUid uid, SupermatterComponent sm) + { + var mix = _atmosphere.GetContainingMixture(uid, true, true); + + if (mix is not { }) + return; + + var absorbedGas = mix.Remove(sm.GasEfficiency * mix.TotalMoles); + var moles = absorbedGas.TotalMoles; + + if (!(moles > 0f)) + return; + + var gases = sm.GasStorage; + var facts = sm.GasDataFields; + + // Lets get the proportions of the gasses in the mix for scaling stuff later + // They range between 0 and 1 + gases = gases.ToDictionary( + gas => gas.Key, + gas => Math.Clamp(absorbedGas.GetMoles(gas.Key) / moles, 0, 1) + ); + + // No less then zero, and no greater then one, we use this to do explosions and heat to power transfer. + var powerRatio = gases.Sum(gas => gases[gas.Key] * facts[gas.Key].PowerMixRatio); + + // Minimum value of -10, maximum value of 23. Affects plasma, o2 and heat output. + var heatModifier = gases.Sum(gas => gases[gas.Key] * facts[gas.Key].HeatPenalty); + + // Minimum value of -10, maximum value of 23. Affects plasma, o2 and heat output. + var transmissionBonus = gases.Sum(gas => gases[gas.Key] * facts[gas.Key].TransmitModifier); + + var h2OBonus = 1 - gases[Gas.WaterVapor] * 0.25f; + + powerRatio = Math.Clamp(powerRatio, 0, 1); + heatModifier = Math.Max(heatModifier, 0.5f); + transmissionBonus *= h2OBonus; + + // Effects the damage heat does to the crystal + sm.DynamicHeatResistance = 1f; + + // More moles of gases are harder to heat than fewer, so let's scale heat damage around them + sm.MoleHeatPenaltyThreshold = (float) Math.Max(moles * sm.MoleHeatPenalty, 0.25); + + // Ramps up or down in increments of 0.02 up to the proportion of CO2 + // Given infinite time, powerloss_dynamic_scaling = co2comp + // Some value from 0-1 + if (moles > sm.PowerlossInhibitionMoleThreshold && gases[Gas.CarbonDioxide] > sm.PowerlossInhibitionGasThreshold) + { + var co2powerloss = Math.Clamp(gases[Gas.CarbonDioxide] - sm.PowerlossDynamicScaling, -0.02f, 0.02f); + sm.PowerlossDynamicScaling = Math.Clamp(sm.PowerlossDynamicScaling + co2powerloss, 0f, 1f); + } + else + sm.PowerlossDynamicScaling = Math.Clamp(sm.PowerlossDynamicScaling - 0.05f, 0f, 1f); + + // Ranges from 0~1(1 - (0~1 * 1~(1.5 * (mol / 500)))) + // We take the mol count, and scale it to be our inhibitor + var powerlossInhibitor = + Math.Clamp( + 1 + - sm.PowerlossDynamicScaling + * Math.Clamp( + moles / sm.PowerlossInhibitionMoleBoostThreshold, + 1f, 1.5f), + 0f, 1f); + + if (sm.MatterPower != 0) // We base our removed power off 1/10 the matter_power. + { + var removedMatter = Math.Max(sm.MatterPower / sm.MatterPowerConversion, 40); + // Adds at least 40 power + sm.Power = Math.Max(sm.Power + removedMatter, 0); + // Removes at least 40 matter power + sm.MatterPower = Math.Max(sm.MatterPower - removedMatter, 0); + } + + // Based on gas mix, makes the power more based on heat or less effected by heat + var tempFactor = powerRatio > 0.8 ? 50f : 30f; + + // If there is more pluox and N2 then anything else, we receive no power increase from heat + sm.Power = Math.Max(absorbedGas.Temperature * tempFactor / Atmospherics.T0C * powerRatio + sm.Power, 0); + + // Irradiate stuff + if (TryComp(uid, out var rad)) + rad.Intensity = + sm.Power + * Math.Max(0, 1f + transmissionBonus / 10f) + * 0.003f + * _config.GetCVar(CCVars.SupermatterRadsModifier); + + // Power * 0.55 * 0.8~1 + var energy = sm.Power * sm.ReactionPowerModifier; + + // Keep in mind we are only adding this temperature to (efficiency)% of the one tile the rock is on. + // An increase of 4°C at 25% efficiency here results in an increase of 1°C / (#tilesincore) overall. + // Power * 0.55 * 1.5~23 / 5 + absorbedGas.Temperature += energy * heatModifier * sm.ThermalReleaseModifier; + absorbedGas.Temperature = Math.Max(0, + Math.Min(absorbedGas.Temperature, sm.HeatThreshold * heatModifier)); + + // Release the waste + absorbedGas.AdjustMoles(Gas.Plasma, Math.Max(energy * heatModifier * sm.PlasmaReleaseModifier, 0f)); + absorbedGas.AdjustMoles(Gas.Oxygen, Math.Max((energy + absorbedGas.Temperature * heatModifier - Atmospherics.T0C) * sm.OxygenReleaseEfficiencyModifier, 0f)); + + _atmosphere.Merge(mix, absorbedGas); + + var powerReduction = (float) Math.Pow(sm.Power / 500, 3); + + // After this point power is lowered + // This wraps around to the begining of the function + sm.Power = Math.Max(sm.Power - Math.Min(powerReduction * powerlossInhibitor, sm.Power * 0.83f * powerlossInhibitor), 0f); + } + + /// + /// Shoot lightning bolts depensing on accumulated power. + /// + private void SupermatterZap(EntityUid uid, SupermatterComponent sm) + { + // Divide power by its' threshold to get a value from 0-1, then multiply by the amount of possible lightnings + var zapPower = sm.Power / sm.PowerPenaltyThreshold * sm.LightningPrototypes.Length; + var zapPowerNorm = (int) Math.Clamp(zapPower, 0, sm.LightningPrototypes.Length - 1); + _lightning.ShootRandomLightnings(uid, 3.5f, sm.Power > sm.PowerPenaltyThreshold ? 3 : 1, sm.LightningPrototypes[zapPowerNorm]); + } + + /// + /// Handles environmental damage. + /// + private void HandleDamage(EntityUid uid, SupermatterComponent sm) + { + var xform = Transform(uid); + var indices = _xform.GetGridOrMapTilePosition(uid, xform); + + sm.DamageArchived = sm.Damage; + + var mix = _atmosphere.GetContainingMixture(uid, true, true); + + // We're in space or there is no gas to process + if (!xform.GridUid.HasValue || mix is not { } || mix.TotalMoles == 0f) + { + sm.Damage += Math.Max(sm.Power / 1000 * sm.DamageIncreaseMultiplier, 0.1f); + return; + } + + // Absorbed gas from surrounding area + var absorbedGas = mix.Remove(sm.GasEfficiency * mix.TotalMoles); + var moles = absorbedGas.TotalMoles; + + var totalDamage = 0f; + + var tempThreshold = Atmospherics.T0C + sm.HeatPenaltyThreshold; + + // Temperature start to have a positive effect on damage after 350 + var tempDamage = + Math.Max( + Math.Clamp(moles / 200f, .5f, 1f) + * absorbedGas.Temperature + - tempThreshold + * sm.DynamicHeatResistance, + 0f) + * sm.MoleHeatThreshold + / 150f + * sm.DamageIncreaseMultiplier; + totalDamage += tempDamage; + + // Power only starts affecting damage when it is above 5000 + var powerDamage = Math.Max(sm.Power - sm.PowerPenaltyThreshold, 0f) / 500f * sm.DamageIncreaseMultiplier; + totalDamage += powerDamage; + + // Mol count only starts affecting damage when it is above 1800 + var moleDamage = Math.Max(moles - sm.MolePenaltyThreshold, 0) / 80 * sm.DamageIncreaseMultiplier; + totalDamage += moleDamage; + + // Healing damage + if (moles < sm.MolePenaltyThreshold) + { + // There's a very small float so that it doesn't divide by 0 + var healHeatDamage = Math.Min(absorbedGas.Temperature - tempThreshold, 0.001f) / 150; + totalDamage += healHeatDamage; + } + + // Check for space tiles next to SM + //TODO: Change moles out for checking if adjacent tiles exist + var enumerator = _atmosphere.GetAdjacentTileMixtures(xform.GridUid.Value, indices, false, false); + while (enumerator.MoveNext(out var ind)) + { + if (ind.TotalMoles != 0) + continue; + + var integrity = GetIntegrity(sm); + + var factor = integrity switch + { + < 10 => 0.0005f, + < 25 => 0.0009f, + < 45 => 0.005f, + < 75 => 0.002f, + _ => 0f + }; + + totalDamage += Math.Clamp(sm.Power * factor * sm.DamageIncreaseMultiplier, 0, sm.MaxSpaceExposureDamage); + + break; + } + + var damage = Math.Min(sm.DamageArchived + sm.DamageHardcap * sm.DamageDelaminationPoint, totalDamage); + + // Prevent it from going negative + sm.Damage = Math.Clamp(damage, 0, float.PositiveInfinity); + } + + /// + /// Handles core damage announcements + /// + private void AnnounceCoreDamage(EntityUid uid, SupermatterComponent sm) + { + var message = string.Empty; + var global = false; + + var integrity = GetIntegrity(sm).ToString("0.00"); + + // Special cases + if (sm.Damage < sm.DamageDelaminationPoint && sm.Delamming) + { + message = Loc.GetString("supermatter-delam-cancel", ("integrity", integrity)); + sm.DelamAnnounced = false; + global = true; + } + + if (sm.Delamming && !sm.DelamAnnounced) + { + var sb = new StringBuilder(); + var loc = string.Empty; + + switch (sm.PreferredDelamType) + { + case DelamType.Cascade: loc = "supermatter-delam-cascade"; break; + case DelamType.Singulo: loc = "supermatter-delam-overmass"; break; + case DelamType.Tesla: loc = "supermatter-delam-tesla"; break; + default: loc = "supermatter-delam-explosion"; break; + } + + var station = _station.GetOwningStation(uid); + if (station != null) + _alert.SetLevel((EntityUid) station, sm.AlertCodeDeltaId, true, true, true, false); + + sb.AppendLine(Loc.GetString(loc)); + sb.AppendLine(Loc.GetString("supermatter-seconds-before-delam", ("seconds", sm.DelamTimer))); + + message = sb.ToString(); + global = true; + sm.DelamAnnounced = true; + + SendSupermatterAnnouncement(uid, message, global); + return; + } + + // Ignore the 0% integrity alarm + if (sm.Delamming) + return; + + // We are not taking consistent damage, Engineers aren't needed + if (sm.Damage <= sm.DamageArchived) + return; + + if (sm.Damage >= sm.DamageWarningThreshold) + { + message = Loc.GetString("supermatter-warning", ("integrity", integrity)); + if (sm.Damage >= sm.DamageEmergencyThreshold) + { + message = Loc.GetString("supermatter-emergency", ("integrity", integrity)); + global = true; + } + } + + SendSupermatterAnnouncement(uid, message, global); + } + + /// If true, sends a station announcement + /// Localisation string for a custom announcer name + public void SendSupermatterAnnouncement(EntityUid uid, string message, bool global = false, string? customSender = null) + { + if (global) + { + var sender = Loc.GetString(customSender != null ? customSender : "supermatter-announcer"); + _chat.DispatchStationAnnouncement(uid, message, sender, colorOverride: Color.Yellow); + return; + } + + _chat.TrySendInGameICMessage(uid, message, InGameICChatType.Speak, hideChat: false, checkRadioPrefix: true); + } + + /// + /// Returns the integrity rounded to hundreds, e.g. 100.00% + /// + public float GetIntegrity(SupermatterComponent sm) + { + var integrity = sm.Damage / sm.DamageDelaminationPoint; + integrity = (float) Math.Round(100 - integrity * 100, 2); + integrity = integrity < 0 ? 0 : integrity; + return integrity; + } + + /// + /// Decide on how to delaminate. + /// + public DelamType ChooseDelamType(EntityUid uid, SupermatterComponent sm) + { + if (_config.GetCVar(CCVars.SupermatterDoForceDelam)) + return _config.GetCVar(CCVars.SupermatterForcedDelamType); + + var mix = _atmosphere.GetContainingMixture(uid, true, true); + + if (mix is { }) + { + var absorbedGas = mix.Remove(sm.GasEfficiency * mix.TotalMoles); + var moles = absorbedGas.TotalMoles; + + if (_config.GetCVar(CCVars.SupermatterDoSingulooseDelam) + && moles >= sm.MolePenaltyThreshold * _config.GetCVar(CCVars.SupermatterSingulooseMolesModifier)) + return DelamType.Singulo; + } + + if (_config.GetCVar(CCVars.SupermatterDoTeslooseDelam) + && sm.Power >= sm.PowerPenaltyThreshold * _config.GetCVar(CCVars.SupermatterTesloosePowerModifier)) + return DelamType.Tesla; + + //TODO: Add resonance cascade when there's crazy conditions or a destabilizing crystal + + return DelamType.Explosion; + } + + /// + /// Handle the end of the station. + /// + private void HandleDelamination(EntityUid uid, SupermatterComponent sm) + { + var xform = Transform(uid); + + sm.PreferredDelamType = ChooseDelamType(uid, sm); + + if (!sm.Delamming) + { + sm.Delamming = true; + AnnounceCoreDamage(uid, sm); + } + + if (sm.Damage < sm.DamageDelaminationPoint && sm.Delamming) + { + sm.Delamming = false; + AnnounceCoreDamage(uid, sm); + } + + sm.DelamTimerAccumulator++; + + if (sm.DelamTimerAccumulator < sm.DelamTimer) + return; + + switch (sm.PreferredDelamType) + { + case DelamType.Cascade: + Spawn(sm.KudzuSpawnPrototype, xform.Coordinates); + break; + + case DelamType.Singulo: + Spawn(sm.SingularitySpawnPrototype, xform.Coordinates); + break; + + case DelamType.Tesla: + Spawn(sm.TeslaSpawnPrototype, xform.Coordinates); + break; + + default: + _explosion.TriggerExplosive(uid); + break; + } + } + + /// + /// Swaps out ambience sounds when the SM is delamming or not. + /// + private void HandleSoundLoop(EntityUid uid, SupermatterComponent sm) + { + var ambient = Comp(uid); + + if (ambient == null) + return; + + if (sm.Delamming && sm.CurrentSoundLoop != sm.DelamSound) + sm.CurrentSoundLoop = sm.DelamSound; + + else if (!sm.Delamming && sm.CurrentSoundLoop != sm.CalmSound) + sm.CurrentSoundLoop = sm.CalmSound; + + if (ambient.Sound != sm.CurrentSoundLoop) + _ambient.SetSound(uid, sm.CurrentSoundLoop, ambient); + } +} \ No newline at end of file diff --git a/Content.Server/Supermatter/Systems/SupermatterSystem.cs b/Content.Server/Supermatter/Systems/SupermatterSystem.cs index 571a5d3568b..3d86f57fb84 100644 --- a/Content.Server/Supermatter/Systems/SupermatterSystem.cs +++ b/Content.Server/Supermatter/Systems/SupermatterSystem.cs @@ -8,8 +8,6 @@ using Content.Shared.Interaction; using Content.Shared.Projectiles; using Content.Shared.Mobs.Components; -using Content.Shared.Radiation.Components; -using Content.Server.Audio; using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Systems; using Content.Server.Explosion.EntitySystems; @@ -17,21 +15,16 @@ using Content.Server.Lightning; using Content.Server.AlertLevel; using Content.Server.Station.Systems; -using System.Text; using Content.Server.Kitchen.Components; -using Content.Shared.Chat; using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Server.DoAfter; using Content.Server.Popups; -using System.Linq; using Content.Shared.Audio; -using System.Configuration; -using Content.Shared.CCVar; namespace Content.Server.Supermatter.Systems; -public sealed class SupermatterSystem : EntitySystem +public sealed partial class SupermatterSystem : EntitySystem { [Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly ChatSystem _chat = default!; @@ -92,7 +85,7 @@ public void Cycle(EntityUid uid, SupermatterComponent sm) ProcessAtmos(uid, sm); HandleDamage(uid, sm); - if (sm.Damage >= sm.DelaminationPoint || sm.Delamming) + if (sm.Damage >= sm.DamageDelaminationPoint || sm.Delamming) HandleDelamination(uid, sm); HandleSoundLoop(uid, sm); @@ -106,415 +99,10 @@ public void Cycle(EntityUid uid, SupermatterComponent sm) if (sm.YellAccumulator >= sm.YellTimer) { sm.YellAccumulator -= sm.YellTimer; - HandleAnnouncements(uid, sm); + AnnounceCoreDamage(uid, sm); } } - #region Processing - - /// - /// Handle power and radiation output depending on atmospheric things. - /// - private void ProcessAtmos(EntityUid uid, SupermatterComponent sm) - { - var mix = _atmosphere.GetContainingMixture(uid, true, true); - - if (mix is not { }) - return; - - var absorbedGas = mix.Remove(sm.GasEfficiency * mix.TotalMoles); - var moles = absorbedGas.TotalMoles; - - if (!(moles > 0f)) - return; - - var gases = sm.GasStorage; - var facts = sm.GasDataFields; - - // Lets get the proportions of the gasses in the mix for scaling stuff later - // They range between 0 and 1 - gases = gases.ToDictionary( - gas => gas.Key, - gas => Math.Clamp(absorbedGas.GetMoles(gas.Key) / moles, 0, 1) - ); - - // No less then zero, and no greater then one, we use this to do explosions and heat to power transfer. - var powerRatio = gases.Sum(gas => gases[gas.Key] * facts[gas.Key].PowerMixRatio); - - // Minimum value of -10, maximum value of 23. Affects plasma, o2 and heat output. - var heatModifier = gases.Sum(gas => gases[gas.Key] * facts[gas.Key].HeatPenalty); - - // Minimum value of -10, maximum value of 23. Affects plasma, o2 and heat output. - var transmissionBonus = gases.Sum(gas => gases[gas.Key] * facts[gas.Key].TransmitModifier); - - var h2OBonus = 1 - gases[Gas.WaterVapor] * 0.25f; - - powerRatio = Math.Clamp(powerRatio, 0, 1); - heatModifier = Math.Max(heatModifier, 0.5f); - transmissionBonus *= h2OBonus; - - // Effects the damage heat does to the crystal - sm.DynamicHeatResistance = 1f; - - // More moles of gases are harder to heat than fewer, so let's scale heat damage around them - sm.MoleHeatPenaltyThreshold = (float) Math.Max(moles * sm.MoleHeatPenalty, 0.25); - - // Ramps up or down in increments of 0.02 up to the proportion of CO2 - // Given infinite time, powerloss_dynamic_scaling = co2comp - // Some value from 0-1 - if (moles > sm.PowerlossInhibitionMoleThreshold && gases[Gas.CarbonDioxide] > sm.PowerlossInhibitionGasThreshold) - { - var co2powerloss = Math.Clamp(gases[Gas.CarbonDioxide] - sm.PowerlossDynamicScaling, -0.02f, 0.02f); - sm.PowerlossDynamicScaling = Math.Clamp(sm.PowerlossDynamicScaling + co2powerloss, 0f, 1f); - } - else - sm.PowerlossDynamicScaling = Math.Clamp(sm.PowerlossDynamicScaling - 0.05f, 0f, 1f); - - // Ranges from 0~1(1 - (0~1 * 1~(1.5 * (mol / 500)))) - // We take the mol count, and scale it to be our inhibitor - var powerlossInhibitor = - Math.Clamp( - 1 - - sm.PowerlossDynamicScaling - * Math.Clamp( - moles / sm.PowerlossInhibitionMoleBoostThreshold, - 1f, 1.5f), - 0f, 1f); - - if (sm.MatterPower != 0) // We base our removed power off 1/10 the matter_power. - { - var removedMatter = Math.Max(sm.MatterPower / sm.MatterPowerConversion, 40); - // Adds at least 40 power - sm.Power = Math.Max(sm.Power + removedMatter, 0); - // Removes at least 40 matter power - sm.MatterPower = Math.Max(sm.MatterPower - removedMatter, 0); - } - - // Based on gas mix, makes the power more based on heat or less effected by heat - var tempFactor = powerRatio > 0.8 ? 50f : 30f; - - // If there is more pluox and N2 then anything else, we receive no power increase from heat - sm.Power = Math.Max(absorbedGas.Temperature * tempFactor / Atmospherics.T0C * powerRatio + sm.Power, 0); - - // Irradiate stuff - if (TryComp(uid, out var rad)) - rad.Intensity = - sm.Power - * Math.Max(0, 1f + transmissionBonus / 10f) - * 0.003f - * _config.GetCVar(CCVars.SupermatterRadsModifier); - - // Power * 0.55 * 0.8~1 - var energy = sm.Power * sm.ReactionPowerModifier; - - // Keep in mind we are only adding this temperature to (efficiency)% of the one tile the rock is on. - // An increase of 4°C at 25% efficiency here results in an increase of 1°C / (#tilesincore) overall. - // Power * 0.55 * 1.5~23 / 5 - absorbedGas.Temperature += energy * heatModifier * sm.ThermalReleaseModifier; - absorbedGas.Temperature = Math.Max(0, - Math.Min(absorbedGas.Temperature, sm.HeatThreshold * heatModifier)); - - // Release the waste - absorbedGas.AdjustMoles(Gas.Plasma, Math.Max(energy * heatModifier * sm.PlasmaReleaseModifier, 0f)); - absorbedGas.AdjustMoles(Gas.Oxygen, Math.Max((energy + absorbedGas.Temperature * heatModifier - Atmospherics.T0C) * sm.OxygenReleaseEfficiencyModifier, 0f)); - - _atmosphere.Merge(mix, absorbedGas); - - var powerReduction = (float) Math.Pow(sm.Power / 500, 3); - - // After this point power is lowered - // This wraps around to the begining of the function - sm.Power = Math.Max(sm.Power - Math.Min(powerReduction * powerlossInhibitor, sm.Power * 0.83f * powerlossInhibitor), 0f); - } - - /// - /// Shoot lightning bolts depensing on accumulated power. - /// - private void SupermatterZap(EntityUid uid, SupermatterComponent sm) - { - // Divide power by its' threshold to get a value from 0-1, then multiply by the amount of possible lightnings - var zapPower = sm.Power / sm.PowerPenaltyThreshold * sm.LightningPrototypes.Length; - var zapPowerNorm = (int) Math.Clamp(zapPower, 0, sm.LightningPrototypes.Length - 1); - _lightning.ShootRandomLightnings(uid, 3.5f, sm.Power > sm.PowerPenaltyThreshold ? 3 : 1, sm.LightningPrototypes[zapPowerNorm]); - } - - /// - /// Handles environmental damage. - /// - private void HandleDamage(EntityUid uid, SupermatterComponent sm) - { - var xform = Transform(uid); - var indices = _xform.GetGridOrMapTilePosition(uid, xform); - - sm.DamageArchived = sm.Damage; - - var mix = _atmosphere.GetContainingMixture(uid, true, true); - - // We're in space or there is no gas to process - if (!xform.GridUid.HasValue || mix is not { } || mix.TotalMoles == 0f) - { - sm.Damage += Math.Max(sm.Power / 1000 * sm.DamageIncreaseMultiplier, 0.1f); - return; - } - - // Absorbed gas from surrounding area - var absorbedGas = mix.Remove(sm.GasEfficiency * mix.TotalMoles); - var moles = absorbedGas.TotalMoles; - - var totalDamage = 0f; - - var tempThreshold = Atmospherics.T0C + sm.HeatPenaltyThreshold; - - // Temperature start to have a positive effect on damage after 350 - var tempDamage = - Math.Max( - Math.Clamp(moles / 200f, .5f, 1f) - * absorbedGas.Temperature - - tempThreshold - * sm.DynamicHeatResistance, - 0f) - * sm.MoleHeatThreshold - / 150f - * sm.DamageIncreaseMultiplier; - totalDamage += tempDamage; - - // Power only starts affecting damage when it is above 5000 - var powerDamage = Math.Max(sm.Power - sm.PowerPenaltyThreshold, 0f) / 500f * sm.DamageIncreaseMultiplier; - totalDamage += powerDamage; - - // Mol count only starts affecting damage when it is above 1800 - var moleDamage = Math.Max(moles - sm.MolePenaltyThreshold, 0) / 80 * sm.DamageIncreaseMultiplier; - totalDamage += moleDamage; - - // Healing damage - if (moles < sm.MolePenaltyThreshold) - { - // There's a very small float so that it doesn't divide by 0 - var healHeatDamage = Math.Min(absorbedGas.Temperature - tempThreshold, 0.001f) / 150; - totalDamage += healHeatDamage; - } - - // Check for space tiles next to SM - //TODO: Change moles out for checking if adjacent tiles exist - var enumerator = _atmosphere.GetAdjacentTileMixtures(xform.GridUid.Value, indices, false, false); - while (enumerator.MoveNext(out var ind)) - { - if (ind.TotalMoles != 0) - continue; - - var integrity = GetIntegrity(sm); - - var factor = integrity switch - { - < 10 => 0.0005f, - < 25 => 0.0009f, - < 45 => 0.005f, - < 75 => 0.002f, - _ => 0f - }; - - totalDamage += Math.Clamp(sm.Power * factor * sm.DamageIncreaseMultiplier, 0, sm.MaxSpaceExposureDamage); - - break; - } - - var damage = Math.Min(sm.DamageArchived + sm.DamageHardcap * sm.DelaminationPoint, totalDamage); - - // Prevent it from going negative - sm.Damage = Math.Clamp(damage, 0, float.PositiveInfinity); - } - - /// - /// Handles core damage announcements - /// - private void HandleAnnouncements(EntityUid uid, SupermatterComponent sm) - { - var message = string.Empty; - var global = false; - - var integrity = GetIntegrity(sm).ToString("0.00"); - - // Special cases - if (sm.Damage < sm.DelaminationPoint && sm.Delamming) - { - message = Loc.GetString("supermatter-delam-cancel", ("integrity", integrity)); - sm.DelamAnnounced = false; - global = true; - } - - if (sm.Delamming && !sm.DelamAnnounced) - { - var sb = new StringBuilder(); - var loc = string.Empty; - - switch (sm.PreferredDelamType) - { - case DelamType.Cascade: loc = "supermatter-delam-cascade"; break; - case DelamType.Singulo: loc = "supermatter-delam-overmass"; break; - case DelamType.Tesla: loc = "supermatter-delam-tesla"; break; - default: loc = "supermatter-delam-explosion"; break; - } - - var station = _station.GetOwningStation(uid); - if (station != null) - _alert.SetLevel((EntityUid) station, sm.AlertCodeDeltaId, true, true, true, false); - - sb.AppendLine(Loc.GetString(loc)); - sb.AppendLine(Loc.GetString("supermatter-seconds-before-delam", ("seconds", sm.DelamTimer))); - - message = sb.ToString(); - global = true; - sm.DelamAnnounced = true; - - SupermatterAnnouncement(uid, message, global); - return; - } - - // Ignore the 0% integrity alarm - if (sm.Delamming) - return; - - // We are not taking consistent damage, Engineers aren't needed - if (sm.Damage <= sm.DamageArchived) - return; - - if (sm.Damage >= sm.WarningPoint) - { - message = Loc.GetString("supermatter-warning", ("integrity", integrity)); - if (sm.Damage >= sm.EmergencyPoint) - { - message = Loc.GetString("supermatter-emergency", ("integrity", integrity)); - global = true; - } - } - - SupermatterAnnouncement(uid, message, global); - } - - /// If true, sends a station announcement - /// Localisation string for a custom announcer name - public void SendSupermatterAnnouncement(EntityUid uid, string message, bool global = false, string? customSender = null) - { - if (global) - { - var sender = Loc.GetString(customSender != null ? customSender : "supermatter-announcer"); - _chat.DispatchStationAnnouncement(uid, message, sender, colorOverride: Color.Yellow); - return; - } - - _chat.TrySendInGameICMessage(uid, message, InGameICChatType.Speak, hideChat: false, checkRadioPrefix: true); - } - - /// - /// Returns the integrity rounded to hundreds, e.g. 100.00% - /// - public float GetIntegrity(SupermatterComponent sm) - { - var integrity = sm.Damage / sm.DelaminationPoint; - integrity = (float) Math.Round(100 - integrity * 100, 2); - integrity = integrity < 0 ? 0 : integrity; - return integrity; - } - - /// - /// Decide on how to delaminate. - /// - public DelamType ChooseDelamType(EntityUid uid, SupermatterComponent sm) - { - if (_config.GetCVar(CCVars.DoForceDelam)) - return _config.GetCVar(CCVars.ForcedDelamType); - - var mix = _atmosphere.GetContainingMixture(uid, true, true); - - if (mix is { }) - { - var absorbedGas = mix.Remove(sm.GasEfficiency * mix.TotalMoles); - var moles = absorbedGas.TotalMoles; - - if (_config.GetCVar(CCVars.DoSingulooseDelam) - && moles >= sm.MolePenaltyThreshold * _config.GetCVar(CCVars.SingulooseMolesModifier)) - return DelamType.Singulo; - } - - if (_config.GetCVar(CCVars.DoTeslooseDelam) - && sm.Power >= sm.PowerPenaltyThreshold * _config.GetCVar(CCVars.TesloosePowerModifier)) - return DelamType.Tesla; - - //TODO: Add resonance cascade when there's crazy conditions or a destabilizing crystal - - return DelamType.Explosion; - } - - /// - /// Handle the end of the station. - /// - private void HandleDelamination(EntityUid uid, SupermatterComponent sm) - { - var xform = Transform(uid); - - sm.PreferredDelamType = ChooseDelamType(uid, sm); - - if (!sm.Delamming) - { - sm.Delamming = true; - HandleAnnouncements(uid, sm); - } - - if (sm.Damage < sm.DelaminationPoint && sm.Delamming) - { - sm.Delamming = false; - HandleAnnouncements(uid, sm); - } - - sm.DelamTimerAccumulator++; - - if (sm.DelamTimerAccumulator < sm.DelamTimer) - return; - - switch (sm.PreferredDelamType) - { - case DelamType.Cascade: - Spawn(sm.SupermatterKudzuSpawnPrototype, xform.Coordinates); - break; - - case DelamType.Singulo: - Spawn(sm.SingularitySpawnPrototype, xform.Coordinates); - break; - - case DelamType.Tesla: - Spawn(sm.TeslaSpawnPrototype, xform.Coordinates); - break; - - default: - _explosion.TriggerExplosive(uid); - break; - } - } - - /// - /// Swaps out ambience sounds when the SM is delamming or not. - /// - private void HandleSoundLoop(EntityUid uid, SupermatterComponent sm) - { - var ambient = Comp(uid); - - if (ambient == null) - return; - - if (sm.Delamming && sm.CurrentSoundLoop != sm.DelamSound) - sm.CurrentSoundLoop = sm.DelamSound; - - else if (!sm.Delamming && sm.CurrentSoundLoop != sm.CalmSound) - sm.CurrentSoundLoop = sm.CalmSound; - - if (ambient.Sound != sm.CurrentSoundLoop) - _ambient.SetSound(uid, sm.CurrentSoundLoop, ambient); - } - - #endregion - - #region Event Handlers - private void OnMapInit(EntityUid uid, SupermatterComponent sm, MapInitEvent args) { // Set the Sound @@ -605,12 +193,12 @@ private void OnGetSliver(EntityUid uid, SupermatterComponent sm, ref Supermatter return; // Your criminal actions will not go unnoticed - sm.Damage += sm.DelaminationPoint / 10; + sm.Damage += sm.DamageDelaminationPoint / 10; var integrity = GetIntegrity(sm).ToString("0.00"); - SupermatterAnnouncement(uid, Loc.GetString("supermatter-announcement-cc-tamper", ("integrity", integrity)), true, "Central Command"); + SendSupermatterAnnouncement(uid, Loc.GetString("supermatter-announcement-cc-tamper", ("integrity", integrity)), true, "Central Command"); - Spawn(sm.SupermatterSliverPrototype, _transform.GetMapCoordinates(args.User)); + Spawn(sm.SliverPrototype, _transform.GetMapCoordinates(args.User)); _popup.PopupClient(Loc.GetString("supermatter-tamper-end"), uid, args.User); sm.DelamTimer /= 2; @@ -621,6 +209,4 @@ private void OnExamine(EntityUid uid, SupermatterComponent sm, ref ExaminedEvent if (args.IsInDetailsRange) args.PushMarkup(Loc.GetString("supermatter-examine-integrity", ("integrity", GetIntegrity(sm).ToString("0.00")))); } - - #endregion } diff --git a/Content.Shared/Supermatter/Components/SupermatterComponent.cs b/Content.Shared/Supermatter/Components/SupermatterComponent.cs index 9a181cdda56..ad7604f5ba6 100644 --- a/Content.Shared/Supermatter/Components/SupermatterComponent.cs +++ b/Content.Shared/Supermatter/Components/SupermatterComponent.cs @@ -28,15 +28,6 @@ public sealed partial class SupermatterComponent : Component [DataField] public bool SliverRemoved = false; - [DataField] - public EntityWhitelist Whitelist = new(); - - /// - /// The ID of the projectile fired by Emitter machines. - /// - [DataField] - public string IdTag = "EmitterBolt"; - public string[] LightningPrototypes = { "Lightning", @@ -157,7 +148,7 @@ public sealed partial class SupermatterComponent : Component /// Set to YellTimer at first so it doesnt yell a minute after being hit /// [DataField] - public float YellAccumulator = YellTimer; + public float YellAccumulator = 60f; /// /// Timer for delam @@ -194,7 +185,7 @@ public sealed partial class SupermatterComponent : Component #region Thresholds /// - /// The amount of heat we apply scaled + /// The heat threshold in Kelvin, after which the supermatter begins taking damage. /// [DataField] public float HeatThreshold = 2500f; @@ -343,15 +334,15 @@ public sealed partial class SupermatterComponent : Component //TODO: Replace this with serializable GasFact array something public readonly Dictionary GasDataFields = new() { - [Gas.Oxygen] = (TransmitModifier: 1.5f, HeatPenalty: 1f, PowerMixRatio: 1f), - [Gas.Nitrogen] = (TransmitModifier: 0f, HeatPenalty: -1.5f, PowerMixRatio: -1f), - [Gas.CarbonDioxide] = (TransmitModifier: 0f, HeatPenalty: 0.1f, PowerMixRatio: 1f), - [Gas.Plasma] = (TransmitModifier: 4f, HeatPenalty: 15f, PowerMixRatio: 1f), - [Gas.Tritium] = (TransmitModifier: 30f, HeatPenalty: 10f, PowerMixRatio: 1f), - [Gas.WaterVapor] = (TransmitModifier: 2f, HeatPenalty: 12f, PowerMixRatio: 1f), - [Gas.Frezon] = (TransmitModifier: 3f, HeatPenalty: -10f, PowerMixRatio: -1f), - [Gas.Ammonia] = (TransmitModifier: 0f, HeatPenalty: .5f, PowerMixRatio: 1f), - [Gas.NitrousOxide] = (TransmitModifier: 0f, HeatPenalty: -5f, PowerMixRatio: -1f), + { Gas.Oxygen, (1.5f, 1f, 1f) }, + { Gas.Nitrogen, (0f, -1.5f, -1f) }, + { Gas.CarbonDioxide, (0f, 0.1f, 1f) }, + { Gas.Plasma, (4f, 15f, 1f) }, + { Gas.Tritium, (30f, 10f, 1f) }, + { Gas.WaterVapor, (2f, 12f, 1f) }, + { Gas.Frezon, (3f, -10f, -1f) }, + { Gas.Ammonia, (0f, .5f, 1f) }, + { Gas.NitrousOxide, (0f, -5f, -1f) }, }; #endregion