diff --git a/Content.Server/Supermatter/Systems/SupermatterSystem.Processing.cs b/Content.Server/Supermatter/Systems/SupermatterSystem.Processing.cs new file mode 100644 index 0000000000..7a62eba6f7 --- /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 new file mode 100644 index 0000000000..3d86f57fb8 --- /dev/null +++ b/Content.Server/Supermatter/Systems/SupermatterSystem.cs @@ -0,0 +1,212 @@ +using Robust.Shared.Audio.Systems; +using Robust.Shared.Configuration; +using Robust.Shared.Containers; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Events; +using Robust.Server.GameObjects; +using Content.Shared.Atmos; +using Content.Shared.Interaction; +using Content.Shared.Projectiles; +using Content.Shared.Mobs.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Chat.Systems; +using Content.Server.Explosion.EntitySystems; +using Content.Shared.Supermatter.Components; +using Content.Server.Lightning; +using Content.Server.AlertLevel; +using Content.Server.Station.Systems; +using Content.Server.Kitchen.Components; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Server.DoAfter; +using Content.Server.Popups; +using Content.Shared.Audio; + +namespace Content.Server.Supermatter.Systems; + +public sealed partial class SupermatterSystem : EntitySystem +{ + [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly ExplosionSystem _explosion = default!; + [Dependency] private readonly TransformSystem _xform = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambient = default!; + [Dependency] private readonly LightningSystem _lightning = default!; + [Dependency] private readonly AlertLevelSystem _alert = default!; + [Dependency] private readonly StationSystem _station = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + + SubscribeLocalEvent(OnCollideEvent); + SubscribeLocalEvent(OnHandInteract); + SubscribeLocalEvent(OnItemInteract); + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnGetSliver); + } + + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var sm in EntityManager.EntityQuery()) + { + if (!sm.Activated) + return; + + var uid = sm.Owner; + sm.UpdateAccumulator += frameTime; + + if (sm.UpdateAccumulator >= sm.UpdateTimer) + { + sm.UpdateAccumulator -= sm.UpdateTimer; + Cycle(uid, sm); + } + } + } + + + public void Cycle(EntityUid uid, SupermatterComponent sm) + { + sm.ZapAccumulator++; + sm.YellAccumulator++; + + ProcessAtmos(uid, sm); + HandleDamage(uid, sm); + + if (sm.Damage >= sm.DamageDelaminationPoint || sm.Delamming) + HandleDelamination(uid, sm); + + HandleSoundLoop(uid, sm); + + if (sm.ZapAccumulator >= sm.ZapTimer) + { + sm.ZapAccumulator -= sm.ZapTimer; + SupermatterZap(uid, sm); + } + + if (sm.YellAccumulator >= sm.YellTimer) + { + sm.YellAccumulator -= sm.YellTimer; + AnnounceCoreDamage(uid, sm); + } + } + + private void OnMapInit(EntityUid uid, SupermatterComponent sm, MapInitEvent args) + { + // Set the Sound + _ambient.SetAmbience(uid, true); + + // Add Air to the initialized SM in the Map so it doesn't delam on its' own + var mix = _atmosphere.GetContainingMixture(uid, true, true); + mix?.AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard); + mix?.AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard); + } + + private void OnCollideEvent(EntityUid uid, SupermatterComponent sm, ref StartCollideEvent args) + { + if (!sm.Activated) + sm.Activated = true; + + var target = args.OtherEntity; + if (args.OtherBody.BodyType == BodyType.Static + || HasComp(target) + || _container.IsEntityInContainer(uid)) + return; + + if (!HasComp(target)) + { + EntityManager.SpawnEntity(sm.CollisionResultPrototype, Transform(target).Coordinates); + _audio.PlayPvs(sm.DustSound, uid); + sm.Power += args.OtherBody.Mass; + } + + EntityManager.QueueDeleteEntity(target); + + if (TryComp(target, out var food)) + sm.Power += food.Energy; + else if (TryComp(target, out var projectile)) + sm.Power += (float) projectile.Damage.GetTotal(); + else + sm.Power++; + + sm.MatterPower += HasComp(target) ? 200 : 0; + } + + private void OnHandInteract(EntityUid uid, SupermatterComponent sm, ref InteractHandEvent args) + { + if (!sm.Activated) + sm.Activated = true; + + var target = args.User; + + if (HasComp(target)) + return; + + sm.MatterPower += 200; + + EntityManager.SpawnEntity(sm.CollisionResultPrototype, Transform(target).Coordinates); + _audio.PlayPvs(sm.DustSound, uid); + EntityManager.QueueDeleteEntity(target); + } + + private void OnItemInteract(EntityUid uid, SupermatterComponent sm, ref InteractUsingEvent args) + { + if (!sm.Activated) + sm.Activated = true; + + if (sm.SliverRemoved) + return; + + if (!HasComp(args.Used)) + return; + + var dae = new DoAfterArgs(EntityManager, args.User, 30f, new SupermatterDoAfterEvent(), args.Target) + { + BreakOnDamage = true, + BreakOnHandChange = false, + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnWeightlessMove = false, + NeedHand = true, + RequireCanInteract = true, + }; + + _doAfter.TryStartDoAfter(dae); + _popup.PopupClient(Loc.GetString("supermatter-tamper-begin"), uid, args.User); + } + + private void OnGetSliver(EntityUid uid, SupermatterComponent sm, ref SupermatterDoAfterEvent args) + { + if (args.Cancelled) + return; + + // Your criminal actions will not go unnoticed + sm.Damage += sm.DamageDelaminationPoint / 10; + + var integrity = GetIntegrity(sm).ToString("0.00"); + SendSupermatterAnnouncement(uid, Loc.GetString("supermatter-announcement-cc-tamper", ("integrity", integrity)), true, "Central Command"); + + Spawn(sm.SliverPrototype, _transform.GetMapCoordinates(args.User)); + _popup.PopupClient(Loc.GetString("supermatter-tamper-end"), uid, args.User); + + sm.DelamTimer /= 2; + } + + private void OnExamine(EntityUid uid, SupermatterComponent sm, ref ExaminedEvent args) + { + if (args.IsInDetailsRange) + args.PushMarkup(Loc.GetString("supermatter-examine-integrity", ("integrity", GetIntegrity(sm).ToString("0.00")))); + } +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 95fb7bd692..cb79426116 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1,5 +1,6 @@ using Content.Shared.Maps; using Content.Shared.Roles; +using Content.Shared.Supermatter.Components; using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; @@ -2201,5 +2202,54 @@ public static readonly CVarDef /// public static readonly CVarDef DebugPow3rDisableParallel = CVarDef.Create("debug.pow3r_disable_parallel", true, CVar.SERVERONLY); + + #region Supermatter System + + /// + /// With completely default supermatter values, Singuloose delamination will occur if engineers inject at least 900 moles of coolant per tile + /// in the crystal chamber. For reference, a gas canister contains 1800 moles of air. This Cvar directly multiplies the amount of moles required to singuloose. + /// + public static readonly CVarDef SupermatterSingulooseMolesModifier = + CVarDef.Create("supermatter.singuloose_moles_modifier", 1f, CVar.SERVER); + + /// + /// Toggles whether or not Singuloose delaminations can occur. If both Singuloose and Tesloose are disabled, it will always delam into a Nuke. + /// + public static readonly CVarDef SupermatterDoSingulooseDelam = + CVarDef.Create("supermatter.do_singuloose", true, CVar.SERVER); + + /// + /// By default, Supermatter will "Tesloose" if the conditions for Singuloose are not met, and the core's power is at least 4000. + /// The actual reasons for being at least this amount vary by how the core was screwed up, but traditionally it's caused by "The core is on fire". + /// This Cvar multiplies said power threshold for the purpose of determining if the delam is a Tesloose. + /// + public static readonly CVarDef SupermatterTesloosePowerModifier = + CVarDef.Create("supermatter.tesloose_power_modifier", 1f, CVar.SERVER); + + /// + /// Toggles whether or not Tesloose delaminations can occur. If both Singuloose and Tesloose are disabled, it will always delam into a Nuke. + /// + public static readonly CVarDef SupermatterDoTeslooseDelam = + CVarDef.Create("supermatter.do_tesloose", true, CVar.SERVER); + + /// + /// When true, bypass the normal checks to determine delam type, and instead use the type chosen by supermatter.forced_delam_type + /// + public static readonly CVarDef SupermatterDoForceDelam = + CVarDef.Create("supermatter.do_force_delam", false, CVar.SERVER); + + /// + /// If supermatter.do_force_delam is true, this determines the delamination type, bypassing the normal checks. + /// + public static readonly CVarDef SupermatterForcedDelamType = + CVarDef.Create("supermatter.forced_delam_type", DelamType.Singulo, CVar.SERVER); + + /// + /// Directly multiplies the amount of rads put out by the supermatter. Be VERY conservative with this. + /// + public static readonly CVarDef SupermatterRadsModifier = + CVarDef.Create("supermatter.rads_modifier", 1f, CVar.SERVER); + + #endregion } } diff --git a/Content.Shared/Supermatter/Components/SupermatterComponent.cs b/Content.Shared/Supermatter/Components/SupermatterComponent.cs new file mode 100644 index 0000000000..ad7604f5ba --- /dev/null +++ b/Content.Shared/Supermatter/Components/SupermatterComponent.cs @@ -0,0 +1,390 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Audio; +using Content.Shared.Atmos; +using Content.Shared.Whitelist; +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Supermatter.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SupermatterComponent : Component +{ + #region Base + + /// + /// The SM will only cycle if activated. + /// + [DataField] + public bool Activated = false; + + [DataField] + public string SliverPrototype = "SupermatterSliver"; + + /// + /// Affects delamination timer. + /// If removed - delamination timer is divided by 2. + /// + [DataField] + public bool SliverRemoved = false; + + public string[] LightningPrototypes = + { + "Lightning", + "ChargedLightning", + "SuperchargedLightning", + "HyperchargedLightning" + }; + + [DataField] + public string SingularitySpawnPrototype = "Singularity"; + + [DataField] + public string TeslaSpawnPrototype = "TeslaEnergyBall"; + + [DataField] + public string KudzuSpawnPrototype = "SupermatterKudzu"; + + /// + /// What spawns in the place of an unfortunate entity that got removed by the SM. + /// + [DataField] + public string CollisionResultPrototype = "Ash"; + + [DataField] + public SoundSpecifier DustSound = new SoundPathSpecifier("/Audio/Effects/Grenades/Supermatter/supermatter_start.ogg"); + + [DataField] + public SoundSpecifier CalmSound = new SoundPathSpecifier("/Audio/Supermatter/calm.ogg"); + + [DataField] + public SoundSpecifier DelamSound = new SoundPathSpecifier("/Audio/Supermatter/delamming.ogg"); + + [DataField] + public SoundSpecifier CurrentSoundLoop = new SoundPathSpecifier("/Audio/Supermatter/calm.ogg"); + + #endregion + + #region Processing + + [DataField] + public float Power; + + [DataField] + public float MatterPower; + + [DataField] + public float MatterPowerConversion = 10f; + + /// + /// The portion of the gasmix we're on + /// + [DataField] + public float GasEfficiency = 0.15f; + + /// + /// Based on CO2 percentage, this slowly moves between 0 and 1. + /// We use it to calculate the powerloss_inhibitor. + /// + [DataField] + public float PowerlossDynamicScaling; + + /// + /// Affects the amount of damage and minimum point at which the SM takes heat damage + /// + [DataField] + public float DynamicHeatResistance = 1; + + /// + /// Multiplier on damage the core takes from absorbing hot gas. + /// Default is ~1/350. + /// + [DataField] + public float MoleHeatPenalty = 0.00286f; + + /// + /// Inverse of + /// + [DataField] + public float MoleHeatThreshold = 350f; + + /// + /// Multiplier on power generated by nuclear reactions + /// + [DataField] + public float ReactionPowerModifier = 0.55f; + + /// + /// Acts as a multiplier on the amount that nuclear reactions increase the supermatter core temperature + /// + [DataField] + public float ThermalReleaseModifier = 0.2f; + + /// + /// Multiplier on how much plasma is released during supermatter reactions + /// Default is ~1/750 + /// + [DataField] + public float PlasmaReleaseModifier = 0.001333f; + + /// + /// Multiplier on how much oxygen is released during supermatter reactions. + /// Default is ~1/325 + /// + [DataField] + public float OxygenReleaseEfficiencyModifier = 0.0031f; + + #endregion + + #region Timing + + /// + /// We yell if over 50 damage every YellTimer Seconds + /// + [DataField] + public float YellTimer = 60f; + + /// + /// Set to YellTimer at first so it doesnt yell a minute after being hit + /// + [DataField] + public float YellAccumulator = 60f; + + /// + /// Timer for delam + /// + [DataField] + public float DelamTimerAccumulator; + + /// + /// Time until delam + /// + [DataField] + public float DelamTimer = 120f; + + /// + /// The message timer + /// + [DataField] + public float SpeakAccumulator = 60f; + + [DataField] + public float UpdateAccumulator = 0f; + + [DataField] + public float UpdateTimer = 1f; + + [DataField] + public float ZapAccumulator = 0f; + + [DataField] + public float ZapTimer = 10f; + + #endregion + + #region Thresholds + + /// + /// The heat threshold in Kelvin, after which the supermatter begins taking damage. + /// + [DataField] + public float HeatThreshold = 2500f; + + /// + /// Percentage of inhibitor gas needed before the charge inertia chain reaction effect starts. + /// + [DataField] + public float PowerlossInhibitionGasThreshold = 0.20f; + + /// + /// Moles of the gas needed before the charge inertia chain reaction effect starts. + /// Scales powerloss inhibition down until this amount of moles is reached. + /// + [DataField] + public float PowerlossInhibitionMoleThreshold = 20f; + + /// + /// Bonus powerloss inhibition boost if this amount of moles is reached + /// + [DataField] + public float PowerlossInhibitionMoleBoostThreshold = 500f; + + /// + /// Above this value we can get lord singulo and independent mol damage, below it we can heal damage + /// + [DataField] + public float MolePenaltyThreshold = 900f; + + /// + /// More moles of gases are harder to heat than fewer, so let's scale heat damage around them + /// + [DataField] + public float MoleHeatPenaltyThreshold; + + /// + /// The cutoff on power properly doing damage, pulling shit around, + /// and delamming into a tesla. Low chance of pyro anomalies, +2 bolts of electricity + /// + [DataField] + public float PowerPenaltyThreshold = 4000f; + + /// + /// Maximum safe operational temperature in degrees Celsius. + /// Supermatter begins taking damage above this temperature. + /// + [DataField] + public float HeatPenaltyThreshold = 40f; + + #endregion + + #region Damage + + /// + /// The amount of damage taken + /// + [DataField] + public float Damage = 0f; + + /// + /// The damage from before this cycle. + /// Used to limit the damage we can take each cycle, and for safe alert. + /// + [DataField] + public float DamageArchived = 0f; + + /// + /// Is multiplied by ExplosionPoint to cap evironmental damage per cycle + /// + [DataField] + public float DamageHardcap = 0.002f; + + /// + /// Environmental damage is scaled by this + /// + [DataField] + public float DamageIncreaseMultiplier = 0.25f; + + /// + /// Max space damage the SM will take per cycle + /// + [DataField] + public float MaxSpaceExposureDamage = 2; + + /// + /// The point at which we should start sending radio messages about the damage. + /// + [DataField] + public float DamageWarningThreshold = 50; + + /// + /// The point at which we start sending station announcements about the damage. + /// + [DataField] + public float DamageEmergencyThreshold = 500; + + /// + /// The point at which the SM begins delaminating. + /// + [DataField] + public int DamageDelaminationPoint = 900; + + [DataField] + public bool Delamming = false; + + [DataField] + public DelamType PreferredDelamType = DelamType.Explosion; + + #endregion + + #region Announcements + + [DataField] + public string AlertCodeYellowId = "yellow"; + + [DataField] + public string AlertCodeDeltaId = "delta"; + + [DataField] + public bool DelamAnnounced = false; + + #endregion + + #region Gases + + /// + /// How much gas is in the SM + /// + [DataField] + public Dictionary GasStorage = new Dictionary() + { + { Gas.Oxygen, 0f }, + { Gas.Nitrogen, 0f }, + { Gas.CarbonDioxide, 0f }, + { Gas.Plasma, 0f }, + { Gas.Tritium, 0f }, + { Gas.WaterVapor, 0f }, + { Gas.Frezon, 0f }, + { Gas.Ammonia, 0f }, + { Gas.NitrousOxide, 0f }, + }; + + /// + /// Stores information about how every gas interacts with the SM + /// + //TODO: Replace this with serializable GasFact array something + public readonly Dictionary GasDataFields = new() + { + { 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 +} + + +public enum SupermatterSound : sbyte +{ + Aggressive = 0, + Delam = 1 +} + +public enum DelamType : int +{ + Explosion = 0, + Singulo = 1, + Tesla = 2, + Cascade = 3 +} + +[Serializable, DataDefinition] +public sealed partial class GasFact +{ + [DataField] + public float TransmitModifier; + + [DataField] + public float HeatPenalty; + + [DataField] + public float PowerMixRatio; + + public GasFact(float transmitModifier, float heatPenalty, float powerMixRatio) + { + TransmitModifier = transmitModifier; + HeatPenalty = heatPenalty; + PowerMixRatio = powerMixRatio; + } +} + +[Serializable, NetSerializable] +public sealed partial class SupermatterDoAfterEvent : SimpleDoAfterEvent +{ + +} diff --git a/Content.Shared/Supermatter/Components/SupermatterFoodComponent.cs b/Content.Shared/Supermatter/Components/SupermatterFoodComponent.cs new file mode 100644 index 0000000000..9d235a4b4d --- /dev/null +++ b/Content.Shared/Supermatter/Components/SupermatterFoodComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Supermatter.Components; + +[RegisterComponent] +public sealed partial class SupermatterFoodComponent : Component +{ + [DataField] + public int Energy { get; set; } = 1; +} diff --git a/Content.Shared/Supermatter/Components/SupermatterImmuneComponent.cs b/Content.Shared/Supermatter/Components/SupermatterImmuneComponent.cs new file mode 100644 index 0000000000..b517115eca --- /dev/null +++ b/Content.Shared/Supermatter/Components/SupermatterImmuneComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Supermatter.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SupermatterImmuneComponent : Component +{ + +} diff --git a/Resources/Audio/Supermatter/calm.ogg b/Resources/Audio/Supermatter/calm.ogg new file mode 100644 index 0000000000..dc3102e578 Binary files /dev/null and b/Resources/Audio/Supermatter/calm.ogg differ diff --git a/Resources/Audio/Supermatter/delamming.ogg b/Resources/Audio/Supermatter/delamming.ogg new file mode 100644 index 0000000000..a48878ec42 Binary files /dev/null and b/Resources/Audio/Supermatter/delamming.ogg differ diff --git a/Resources/Locale/en-US/objectives/conditions/steal.ftl b/Resources/Locale/en-US/objectives/conditions/steal.ftl index 00c8e0fdaf..0709bf6e5e 100644 --- a/Resources/Locale/en-US/objectives/conditions/steal.ftl +++ b/Resources/Locale/en-US/objectives/conditions/steal.ftl @@ -9,3 +9,6 @@ objective-condition-steal-Ian = head of personnel's corgi objective-condition-thief-description = The {$itemName} would be a great addition to my collection! objective-condition-thief-animal-description = The {$itemName} would be a great addition to my collection! Most importantly, alive. objective-condition-thief-multiply-description = I need to get {$count} {MAKEPLURAL($itemName)} and take them with me. + +objective-condition-steal-smsliver-title = Cut off a sliver from the supermatter crystal. +objective-condition-steal-smsliver-description = Use any cutting tool that comes in handy. A scalpel is more recommended. Also, don't die of radiation poisoning. diff --git a/Resources/Locale/en-US/supermatter/supermatter.ftl b/Resources/Locale/en-US/supermatter/supermatter.ftl new file mode 100644 index 0000000000..52593f5524 --- /dev/null +++ b/Resources/Locale/en-US/supermatter/supermatter.ftl @@ -0,0 +1,26 @@ +supermatter-announcer = Automatic Supermatter Engine +supermatter-examine-integrity = + Its' integrity is [color=yellow]{$integrity}%[/color]. +supermatter-announcement-warning = + Warning! Crystal hyperstructure integrity faltering! Integrity: {$integrity}%. +supermatter-announcement-emergency = + DANGER! Crystal hyperstructure integrity reaching critical levels! Integrity: {$integrity}%. +supermatter-announcement-delam-explosion = + CRYSTAL DELAMINATION IMMINENT! The crystal has reached critical integrity failure! Emergency causality destabilization field has been engaged. +supermatter-announcement-delam-overmass = + CRYSTAL DELAMINATION IMMINENT! Crystal hyperstructure integrity has reached critical mass failure! Singularity formation imminent! +supermatter-announcement-delam-tesla = + CRYSTAL DELAMINATION IMMINENT! Crystal hyperstructure integrity has reached critical power surge failure! Energy ball formation imminent! +supermatter-announcement-delam-cascade = + CRYSTAL DELAMINATION IMMINENT! Harmonic frequency limits exceeded, casualty destabilization field could not be engaged! +supermatter-announcement-delam-cancel = + Crystalline hyperstructure returning to safe operating parameters. Failsafe has been Disengaged. Integrity: {$integrity}%. +supermatter-seconds-before-delam = + Estimated time before delamination: {$seconds} seconds. +supermatter-tamper-begin = + You begin carefully cutting a piece off the supermatter crystal... +supermatter-tamper-end = + You feel the power of a thousand suns laying on your palms. Or is it all the radiation? +supermatter-announcement-cc-tamper = + Our automatic casualty system has detected that the supermatter crystal structural integrity was compromised by an external force. + Engineering department, report to the supermatter engine immediately. diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index 314d6b3eae..dcb8ae9053 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -338,6 +338,9 @@ - type: HeldSpeedModifier - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitEngineeringWhite + - type: ClothingGrantComponent + component: + - type: SupermatterImmune #Chief Medical Officer's Hardsuit - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 0b60ed447f..b52128bc09 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -91,6 +91,7 @@ - type: Loadout prototypes: [ MobAghostGear ] - type: BypassInteractionChecks + - type: SupermatterImmune - type: entity id: ActionAGhostShowSolar diff --git a/Resources/Prototypes/Entities/Objects/Misc/supermatter_sliver.yml b/Resources/Prototypes/Entities/Objects/Misc/supermatter_sliver.yml new file mode 100644 index 0000000000..d62935523d --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/supermatter_sliver.yml @@ -0,0 +1,25 @@ +- type: entity + parent: BaseItem + id: SupermatterSliver + name: supermatter sliver + description: A shard from the station's Supermatter crystal. Highly radioactive. + components: + - type: PointLight + enabled: true + radius: 3 + energy: 2 + color: "#fff633" + - type: RadiationSource + intensity: .75 + - type: Icon + sprite: Supermatter/supermatter_sliver.rsi + state: icon + - type: Sprite + sprite: Supermatter/supermatter_sliver.rsi + state: icon + - type: StealTarget + stealGroup: SupermatterSliver + - type: Tag + tags: + - HighRiskItem + - type: SupermatterImmune diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml index 578e071587..0d96de1297 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml @@ -130,6 +130,7 @@ solution: food - type: Extractable grindableSolutionName: food + - type: SupermatterImmune - type: entity parent: Ash diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml index 25d219ab94..b8d66c61e3 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml @@ -3,6 +3,7 @@ name: gravitational singularity description: A mesmerizing swirl of darkness that sucks in everything. If it's moving towards you, run. components: + - type: SupermatterImmune - type: Clickable - type: AmbientSound volume: -4 diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Supermatter/supermatter.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Supermatter/supermatter.yml new file mode 100644 index 0000000000..6fc3429600 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Supermatter/supermatter.yml @@ -0,0 +1,66 @@ +- type: entity + id: Supermatter + name: supermatter crystal + description: A strangely translucent and iridescent crystal. + placement: + mode: SnapgridCenter + components: + - type: Supermatter + - type: RadiationSource + - type: AmbientSound + range: 5 + volume: 0 + sound: + path: /Audio/Supermatter/calm.ogg + - type: Physics + - type: Speech + speechSounds: Pai + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + mask: + - Impassable + - MidImpassable + - HighImpassable + - LowImpassable + - InteractImpassable + - Opaque + layer: + - MidImpassable + - HighImpassable + - BulletImpassable + - InteractImpassable + - type: Transform + anchored: true + noRot: true + - type: CollisionWake + enabled: false + - type: Clickable + - type: InteractionOutline + - type: Sprite + drawdepth: WallMountedItems + sprite: Supermatter/supermatter.rsi + state: supermatter + - type: Icon + sprite: Supermatter/supermatter.rsi + state: supermatter + - type: PointLight + enabled: true + radius: 10 + energy: 5 + color: "#ffe000" + - type: Explosive + explosionType: Supermatter + maxIntensity: 25000 + intensitySlope: 5 + totalIntensity: 25000 + - type: GuideHelp + guides: [ Supermatter, Power ] + - type: WarpPoint + follow: true + location: supermatter + - type: SinguloFood + energy: 10000 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml index dc2ad1687a..ae5731d6bb 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml @@ -5,6 +5,7 @@ placement: mode: SnapgridCenter components: + - type: SupermatterImmune - type: Transform anchored: true - type: Physics @@ -110,6 +111,7 @@ placement: mode: SnapgridCenter components: + - type: SupermatterImmune - type: Transform anchored: true - type: Physics diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/energyball.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/energyball.yml index ea41ba3a20..1cfdb9256a 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/energyball.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/energyball.yml @@ -2,6 +2,7 @@ id: BaseEnergyBall abstract: true components: + - type: SupermatterImmune - type: Clickable - type: Physics bodyType: KinematicController diff --git a/Resources/Prototypes/Guidebook/engineering.yml b/Resources/Prototypes/Guidebook/engineering.yml index 21d17f0227..e08d46276c 100644 --- a/Resources/Prototypes/Guidebook/engineering.yml +++ b/Resources/Prototypes/Guidebook/engineering.yml @@ -66,6 +66,7 @@ - Singularity - TEG - RTG + - Supermatter - type: guideEntry id: AME @@ -91,3 +92,8 @@ id: PortableGenerator name: guide-entry-portable-generator text: "/ServerInfo/Guidebook/Engineering/PortableGenerator.xml" + +- type: guideEntry + id: Supermatter + name: guide-entry-sm + text: "/ServerInfo/Guidebook/Engineering/Supermatter.xml" diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 481ca0f93d..74ddd65adb 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -22,6 +22,7 @@ CaptainJetpackStealObjective: 0.5 HandTeleporterStealObjective: 0.5 EnergyShotgunStealObjective: 0.5 + StealSupermatterSliverObjective: 0.5 - type: weightedRandom id: TraitorObjectiveGroupKill diff --git a/Resources/Prototypes/Objectives/stealTargetGroups.yml b/Resources/Prototypes/Objectives/stealTargetGroups.yml index 5b57a95503..feb92d87b3 100644 --- a/Resources/Prototypes/Objectives/stealTargetGroups.yml +++ b/Resources/Prototypes/Objectives/stealTargetGroups.yml @@ -1,5 +1,12 @@ # Traitor single items +- type: stealTargetGroup + id: SupermatterSliver + name: supermatter sliver + sprite: + sprite: Supermatter/supermatter_sliver.rsi + state: icon + - type: stealTargetGroup id: Hypospray name: hypospray diff --git a/Resources/Prototypes/Objectives/traitor.yml b/Resources/Prototypes/Objectives/traitor.yml index 419e7ac109..8ffb1cd480 100644 --- a/Resources/Prototypes/Objectives/traitor.yml +++ b/Resources/Prototypes/Objectives/traitor.yml @@ -294,3 +294,15 @@ - type: StealCondition stealGroup: NukeDisk owner: objective-condition-steal-station + +- type: entity + noSpawn: true + parent: BaseTraitorStealObjective + id: StealSupermatterSliverObjective + components: + - type: Objective + difficulty: 3.5 + - type: StealCondition + stealGroup: SupermatterSliver + objectiveNoOwnerText: objective-condition-steal-smsliver-title + descriptionText: objective-condition-steal-smsliver-description \ No newline at end of file diff --git a/Resources/Prototypes/explosion.yml b/Resources/Prototypes/explosion.yml index 4f72067376..bb9a445f45 100644 --- a/Resources/Prototypes/explosion.yml +++ b/Resources/Prototypes/explosion.yml @@ -133,3 +133,17 @@ # STOP # BEFORE YOU ADD MORE EXPLOSION TYPES CONSIDER IF AN EXISTING ONE IS SUITABLE # ADDING NEW ONES IS PROHIBITIVELY EXPENSIVE + id: Supermatter + damagePerIntensity: + types: + Radiation: 5 + Heat: 4 + Blunt: 3 + Piercing: 3 + tileBreakChance: [0, 0.5, 1] + tileBreakIntensity: [0, 10, 30] + tileBreakRerollReduction: 20 + lightColor: Yellow + fireColor: Green + texturePath: /Textures/Effects/fire_greyscale.rsi + fireStates: 3 diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index d823f93d9d..73c6bf6480 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -1174,6 +1174,9 @@ - type: Tag id: Smokable +- type: Tag + id: SnapPop + - type: Tag id: SnowyLabs diff --git a/Resources/ServerInfo/Guidebook/Engineering/Supermatter.xml b/Resources/ServerInfo/Guidebook/Engineering/Supermatter.xml new file mode 100644 index 0000000000..6e89df4432 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Engineering/Supermatter.xml @@ -0,0 +1,65 @@ + + + + + # The Supermatter Engine + + So you've decided to take on the challenge and set up the Supermatter Engine? First, let's give you a short overview of the main Supermatter crystal beforehand. + + Its primary features are emitting electrical arcs that are harnessed to power the station through tesla coils. + + Side effects include radiation emission, releasing hot oxygen and plasma, heating the air around, and exploding, transforming into a black hole or an energy ball and eating the entire station if you screw up hard enough. + + It begins inert but being hit by an object or a projectile will activate it and it'll start exhibiting nearly all of the aforementioned properties. + + ## Words of Warning + + 1. The Supermatter crystal is [color=red]VERY DANGEROUS[/color]. Activating the crystal should be the last step in setting up any form of Supermatter based power! + + 2. [color=red]PUT YOUR RADIATION SUIT ON[/color]. + + 3. Most the Supermatter setup involves a gas loop that is designed to cool down the Supermatter chamber. Please have at least some knowledge of gases and their atmospheric properties. + + 4. Anything that bumps into the Supermatter is [color=red]fundamentally annihilated[/color]. [color=red]Do not touch it[/color]. This means weld and bolt the door to the chamber. + + ## Gas Interactions + + Here's a list of all gases from least dangerous to most dangerous. + + 1. [color=#bffffe]Frezon[/color]. Aside from cooling down the Supermatter, it basically stops power and waste production, which may come handy if the Supermatter is close to delaminating and you need to shut it down fast. + + 2. [color=#c20000]Nitrogen[/color]. N2 is the basic gas most Supermatter setups will run exclusively, being very simple to set up for. It dampens the power generation from heat, and reduces the amount of plasma the SM belches out, making it good for when you aren't trying to do something silly. + + 3. [color=#b16d6d]Nitrous oxide[/color]. Reinforces the heat resistance of the crystal, allowing for much hotter setups than usual. However, at high temperatures it will decompose into Nitrogen and Oxygen. While N2 is good, O2 certainly is not. This O2 will also react with the Plasma to create Tritium and then a Tritium fire. + + 4. [color=#62d5ca]Oxygen[/color]. Provides a boost to power transmission without actively increasing the waste gas amount or temperature. Pretty risky to use, as any disruption of the cooling loop will soon cause a plasma fire in the crystal chamber. Even just a high concentration of O2 will activate and continuously power the crystal. + + 5. [color=#19b348]Ammonia[/color]. Increases the power generation slightly at a minor cost to the heat penalty. + + 6. [color=#979797]Carbon Dioxide[/color]. In low concentrations, it will increase the crystal's power generation. In high concentrations it will raise the crystal's energy to extremely high levels. With poor management and insufficient or downright bad preparation, it will eventually exceed safe energy levels and begin a charge delamination, producing electric arcs and anomalies until it eventually explodes into a Tesla ball. + + [color=red]7[/color]. [color=#ff9d00]Plasma[/color]. Very similar to Oxygen but provides a higher power boost as well as a much higher waste and heat penalty. The extreme pressures and volumes of gas produced by this gas are very likely to clog pipes and overheat the chamber. + + [color=red]8[/color]. [color=#08a800]Tritium[/color]. Increases the power production of the Supermatter by up to 3 times, there is one slight issue with it. It is dangerous. It is very dangerous. Tritium is a horrifyingly irritable and jumpy gas. While it isn't as harmful to the heat level as Plasma is (just barely), it also has the second worst heat capacity of all gasses while Plasma has the second highest. This means that Plasma can be kept happy with enough cooling, whereas Tritium eagerly goes from a safe space loop into a burning hellfire. Add to this the byproduct of large amounts of Oxygen production (not exclusive to Tritium, an issue in a Plasma engine too), and you have a tritium fire and a very hot crystal. Do not use this gas unless you have a very strong understanding of atmospherics and the Supermatter, and are willing to get creative. + + ## Practical guide to the Supermatter + + Now, forget about everything you've just read and get to setting up the most basic loop there is: the Nitrogen loop. + + The atmospheric setup in its' most basic form should look like this: + + (We did not have enough budget for images, here is a text representation) + + 1. Nitrogen gets pumped into the chamber by passive vents from one side + + 2. Every gas gets pumped out of the chamber by using scrubbers set on Siphon on the other side. + + 3. The output gets filtered, cooled down, and excess nitrogen gets either routed into space or rerouted into the input. + + That's basically it. I hope you understand at least something in this example. Now get to it! + + ## Experiment + + You're not a real engineer if you haven't figured out the most efficient way to produce electricity using the Supermatter crystal, are you? + + \ No newline at end of file diff --git a/Resources/Textures/Supermatter/supermatter.rsi/meta.json b/Resources/Textures/Supermatter/supermatter.rsi/meta.json new file mode 100644 index 0000000000..6bca0558a8 --- /dev/null +++ b/Resources/Textures/Supermatter/supermatter.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "copyright": "Taken and edited from https://tgstation13.org/wiki/images/a/a4/Supermatter-bg.gif", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 48 + }, + "states": [ + { + "name": "supermatter", + "delays": [ [ 0.08, 0.08, 0.08 ] ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Supermatter/supermatter.rsi/supermatter.png b/Resources/Textures/Supermatter/supermatter.rsi/supermatter.png new file mode 100644 index 0000000000..0c5747a315 Binary files /dev/null and b/Resources/Textures/Supermatter/supermatter.rsi/supermatter.png differ diff --git a/Resources/Textures/Supermatter/supermatter_sliver.rsi/icon.png b/Resources/Textures/Supermatter/supermatter_sliver.rsi/icon.png new file mode 100644 index 0000000000..2187706b10 Binary files /dev/null and b/Resources/Textures/Supermatter/supermatter_sliver.rsi/icon.png differ diff --git a/Resources/Textures/Supermatter/supermatter_sliver.rsi/meta.json b/Resources/Textures/Supermatter/supermatter_sliver.rsi/meta.json new file mode 100644 index 0000000000..f157223291 --- /dev/null +++ b/Resources/Textures/Supermatter/supermatter_sliver.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "copyright": "Taken and edited from https://github.com/tgstation/tgstation/blob/master/icons/obj/antags/syndicate_tools.dmi", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + } + ] +} \ No newline at end of file