diff --git a/Content.Server/Ganimed/SM/SupermatterSystem.cs b/Content.Server/Ganimed/SM/SupermatterSystem.cs new file mode 100644 index 00000000000000..6b4c54d86da2e6 --- /dev/null +++ b/Content.Server/Ganimed/SM/SupermatterSystem.cs @@ -0,0 +1,629 @@ +using System.Linq; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Events; +using Robust.Shared.Timing; +using Robust.Server.GameObjects; +using Content.Shared.Atmos; +using Content.Shared.Interaction; +using Content.Shared.Projectiles; +using Content.Shared.Tag; +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; +using Content.Shared.Supermatter.Components; +using Content.Shared.Supermatter.Systems; +using Content.Server.Lightning; +using Content.Server.AlertLevel; +using Content.Server.Station.Systems; +using System.Text; +using Content.Server.Kitchen.Components; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Server.DoAfter; +using Content.Server.Popups; + +namespace Content.Server.Supermatter.Systems; + +public sealed class SupermatterSystem : SharedSupermatterSystem +{ + [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 IGameTiming _gameTiming = default!; + [Dependency] private readonly AmbientSoundSystem _ambient = default!; + [Dependency] private readonly TagSystem _tagSystem = 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!; + + private DelamType _delamType = DelamType.Explosion; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentRemove); + SubscribeLocalEvent(OnMapInit); + + SubscribeLocalEvent(OnCollideEvent); + SubscribeLocalEvent(OnHandInteract); + SubscribeLocalEvent(OnItemInteract); + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnGetSliver); + } + + private void OnComponentRemove(EntityUid uid, SupermatterComponent component, ComponentRemove args) + { + // turn off any ambient if component is removed (ex. entity deleted) + _ambient.SetAmbience(uid, false); + component.AudioStream = _audio.Stop(component.AudioStream); + } + + private void OnMapInit(EntityUid uid, SupermatterComponent component, MapInitEvent args) + { + // Set the Sound + _ambient.SetAmbience(uid, true); + + //Add Air to the initialized SM in the Map so it doesnt delam on default + var mix = _atmosphere.GetContainingMixture(uid, true, true); + mix?.AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard); + mix?.AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_gameTiming.IsFirstTimePredicted) + return; + + 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.DelaminationPoint || 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; + HandleAnnouncements(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 between 0 and 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 to 1(1-(value between 0 and 1 * ranges from 1 to 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 one 10th of 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); + + //Radiate stuff + if (TryComp(uid, out var rad)) + rad.Intensity = sm.Power * Math.Max(0, 1f + transmissionBonus / 10f) * 0.003f; + + //Power * 0.55 * a value between 1 and 0.8 + 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 @ 25% efficiency here results in an increase of 1*C / (#tilesincore) overall. + // Power * 0.55 * (some value between 1.5 and 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 it's threshold to get a value from 0 to 1, then multiply by the amount of possible lightnings + // Makes it pretty obvious that if SM is shooting out red lightnings something is wrong. + // And if it shoots too weak lightnings it means that it's underfed. Feed the SM :godo: + 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; + + // Molar 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) + { + // left there a very small float value so that it doesn't eventually 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); + + // this is some magic number shit + 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; + } + + sm.Damage = Math.Min(sm.DamageArchived + sm.DamageHardcap * sm.DelaminationPoint, totalDamage); + } + + /// + /// Handles 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; + var alertLevel = "yellow"; + + switch (_delamType) + { + case DelamType.Explosion: + default: + loc = "supermatter-delam-explosion"; + break; + + case DelamType.Singulo: + loc = "supermatter-delam-overmass"; + alertLevel = "delta"; + break; + + case DelamType.Tesla: + loc = "supermatter-delam-tesla"; + alertLevel = "delta"; + break; + + case DelamType.Cascade: + loc = "supermatter-delam-cascade"; + alertLevel = "delta"; + break; + } + + var station = _station.GetOwningStation(uid); + if (station != null) + _alert.SetLevel((EntityUid) station, alertLevel, 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; + } + + // We are not taking consistent damage. Engis not 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); + } + + /// + /// Help the SM announce something. + /// + /// If true, does the station announcement. + /// If true, sends the announcement from Central Command. + public void SupermatterAnnouncement(EntityUid uid, string message, bool global = false, string? customSender = null) + { + if (global) + { + var sender = customSender != null ? customSender : Loc.GetString("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) + { + var mix = _atmosphere.GetContainingMixture(uid, true, true); + + if (mix is { }) + { + var absorbedGas = mix.Remove(sm.GasEfficiency * mix.TotalMoles); + var moles = absorbedGas.TotalMoles; + + if (moles >= sm.MolePenaltyThreshold) + return DelamType.Singulo; + } + if (sm.Power >= sm.PowerPenaltyThreshold) + return DelamType.Tesla; + + // TODO: add resonance cascade when there's crazy conditions, or a destabilizing crystal :godo: + + return DelamType.Explosion; + } + + /// + /// Handle the end of the station. + /// + private void HandleDelamination(EntityUid uid, SupermatterComponent sm) + { + var xform = Transform(uid); + + _delamType = 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 (_delamType) + { + case DelamType.Explosion: + default: + _explosion.TriggerExplosive(uid); + break; + + case DelamType.Singulo: + Spawn(sm.SingularityPrototypeId, xform.Coordinates); + break; + + case DelamType.Tesla: + Spawn(sm.TeslaPrototypeId, xform.Coordinates); + break; + + case DelamType.Cascade: + Spawn(sm.SupermatterKudzuPrototypeId, xform.Coordinates); + break; + } + } + + private void HandleSoundLoop(EntityUid uid, SupermatterComponent sm) + { + var isAggressive = sm.Damage > sm.WarningPoint; + var isDelamming = sm.Damage > sm.DelaminationPoint; + + if (!isAggressive && !isDelamming) + { + sm.AudioStream = _audio.Stop(sm.AudioStream); + return; + } + + var smSound = isDelamming ? SuperMatterSound.Delam : SuperMatterSound.Aggressive; + + if (sm.SmSound == smSound) + return; + + sm.AudioStream = _audio.Stop(sm.AudioStream); + sm.SmSound = smSound; + } + + #endregion + + + #region Event Handlers + + 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 (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; + + if (!HasComp(target)) + { + EntityManager.SpawnEntity("Ash", Transform(target).Coordinates); + _audio.PlayPvs(sm.DustSound, uid); + } + + EntityManager.QueueDeleteEntity(target); + } + + 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("Ash", 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(), uid) + { + BreakOnDamage = true, + BreakOnHandChange = false, + BreakOnMove = true, + BreakOnWeightlessMove = false, + NeedHand = true, + RequireCanInteract = true, + }; + + _doAfter.TryStartDoAfter(dae); + } + + private void OnGetSliver(EntityUid uid, SupermatterComponent sm, ref SupermatterDoAfterEvent args) + { + if (args.Cancelled) + return; + + // your criminal actions will not go unnoticed + sm.Damage += sm.DelaminationPoint / 10; + sm.DamageArchived += sm.DelaminationPoint / 10; + + var integrity = GetIntegrity(sm).ToString("0.00"); + SupermatterAnnouncement(uid, Loc.GetString("supermatter-announcement-cc-tamper", ("integrity", integrity)), true, "Central Command"); + + Spawn(sm.SliverPrototypeId, _transform.GetMapCoordinates(args.User)); + + sm.DelamTimer /= 2; + } + + private void OnExamine(EntityUid uid, SupermatterComponent sm, ref ExaminedEvent args) + { + // get all close and personal to it + if (args.IsInDetailsRange) + { + args.PushMarkup(Loc.GetString("supermatter-examine-integrity", ("integrity", GetIntegrity(sm).ToString("0.00")))); + } + } + + #endregion +} \ No newline at end of file diff --git a/Content.Server/Singularity/EntitySystems/SingularitySystem.cs b/Content.Server/Singularity/EntitySystems/SingularitySystem.cs index 6b9182fb4dfbd6..19821bb15952b2 100644 --- a/Content.Server/Singularity/EntitySystems/SingularitySystem.cs +++ b/Content.Server/Singularity/EntitySystems/SingularitySystem.cs @@ -4,11 +4,10 @@ using Content.Shared.Singularity.Components; using Content.Shared.Singularity.EntitySystems; using Content.Shared.Singularity.Events; +using Content.Shared.Supermatter.Components; using Robust.Server.GameStates; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.GameStates; -using Robust.Shared.Player; using Robust.Shared.Timing; namespace Content.Server.Singularity.EntitySystems; @@ -37,6 +36,11 @@ public sealed class SingularitySystem : SharedSingularitySystem /// public const float BaseEntityEnergy = 1f; + /// + /// Whether or not the singuloo has eaten the supermatter crystal + /// + public bool HasEatenSM = false; + public override void Initialize() { base.Initialize(); @@ -103,7 +107,7 @@ public void SetEnergy(EntityUid uid, float value, SingularityComponent? singular { // Normally, a level 6 singularity requires the supermatter + 3000 energy. // The required amount of energy has been bumped up to compensate for the lack of the supermatter. - >= 5000 => 6, + >= 5000 when HasEatenSM => 6, >= 2000 => 5, >= 1000 => 4, >= 500 => 3, @@ -216,7 +220,8 @@ public void OnConsumedEntity(EntityUid uid, SingularityComponent comp, ref Entit // Don't double count singulo food if (HasComp(args.Entity)) return; - + if (HasComp(uid)) + HasEatenSM = true; AdjustEnergy(uid, BaseEntityEnergy, singularity: comp); } @@ -306,4 +311,4 @@ private void UpdateGravityWell(EntityUid uid, GravityWellComponent comp, Singula } #endregion Event Handlers -} +} \ No newline at end of file diff --git a/Content.Shared/Ganimed/SM/SharedSupermatterSystem.cs b/Content.Shared/Ganimed/SM/SharedSupermatterSystem.cs new file mode 100644 index 00000000000000..b6b04b6ebbfb55 --- /dev/null +++ b/Content.Shared/Ganimed/SM/SharedSupermatterSystem.cs @@ -0,0 +1,49 @@ +using Content.Shared.Supermatter.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Supermatter.Systems; + +public abstract class SharedSupermatterSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnSupermatterStartup); + } + + public enum SuperMatterSound : sbyte + { + Aggressive = 0, + Delam = 1 + } + + public enum DelamType : sbyte + { + Explosion = 0, + Singulo = 1, + Tesla = 2, + Cascade = 3 // save for later + } + #region Getters/Setters + + public void OnSupermatterStartup(EntityUid uid, SupermatterComponent comp, ComponentStartup args) + { + } + + #endregion Getters/Setters + + #region Serialization + /// + /// A state wrapper used to sync the supermatter between the server and client. + /// + [Serializable, NetSerializable] + protected sealed class SupermatterComponentState : ComponentState + { + public SupermatterComponentState(SupermatterComponent supermatter) + { + } + } + + #endregion Serialization + +} diff --git a/Content.Shared/Ganimed/SM/SupermatterComponent.cs b/Content.Shared/Ganimed/SM/SupermatterComponent.cs new file mode 100644 index 00000000000000..9729663b337743 --- /dev/null +++ b/Content.Shared/Ganimed/SM/SupermatterComponent.cs @@ -0,0 +1,383 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Audio; +using Content.Shared.Atmos; +using Content.Shared.Supermatter.Systems; +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 SM Base + + /// + /// The SM will only cycle if activated. + /// + [DataField("activated")] + [ViewVariables(VVAccess.ReadWrite)] + public bool Activated = false; + + [DataField("supermatterSliverPrototype")] + public string SliverPrototypeId = "SupermatterSliver"; + + /// + /// Affects delamination timer. If removed - delamination timer is divided by 2. + /// + [DataField("sliverRemoved")] + [ViewVariables(VVAccess.ReadWrite)] + public bool SliverRemoved = false; + + [DataField("whitelist")] + public EntityWhitelist Whitelist = new(); + public string IdTag = "EmitterBolt"; + + public string[] LightningPrototypes = + { + "Lightning", + "ChargedLightning", + "SuperchargedLightning", + "HyperchargedLightning" + }; + + [DataField("singularitySpawnPrototype")] + public string SingularityPrototypeId = "Singularity"; + + [DataField("teslaSpawnPrototype")] + public string TeslaPrototypeId = "TeslaEnergyBall"; + + [DataField("supermatterKudzuSpawnPrototype")] + public string SupermatterKudzuPrototypeId = "SupermatterKudzu"; + + [ViewVariables(VVAccess.ReadWrite)] + public float Power; + + /// + /// The amount of damage we have currently + /// + [ViewVariables(VVAccess.ReadWrite)] + public float Damage = 0f; + + [ViewVariables(VVAccess.ReadWrite)] + public float MatterPower; + + [ViewVariables(VVAccess.ReadWrite)] + public float MatterPowerConversion = 10f; + + /// + /// The portion of the gasmix we're on + /// + [ViewVariables(VVAccess.ReadWrite)] + public float GasEfficiency = 0.15f; + + /// + /// The amount of heat we apply scaled + /// + [ViewVariables(VVAccess.ReadWrite)] + public float HeatThreshold = 2500f; + + #endregion SM Base + + #region SM Sound + /// + /// Current stream of SM audio. + /// + public EntityUid? AudioStream; + + public SharedSupermatterSystem.SuperMatterSound? SmSound; + + [DataField("dustSound")] + public SoundSpecifier DustSound = new SoundPathSpecifier("/Audio/Effects/Grenades/Supermatter/supermatter_start.ogg"); + + [DataField("delamSound")] + public SoundSpecifier DelamSound = new SoundPathSpecifier("/Audio/Goobstation/Supermatter/delamming.ogg"); + + [DataField("delamAlarm")] + public SoundSpecifier DelamAlarm = new SoundPathSpecifier("/Audio/Machines/alarm.ogg"); + + #endregion SM Sound + + #region SM Calculation + + /// + /// Based on co2 percentage, slowly moves between + /// 0 and 1. We use it to calc the powerloss_inhibitor + /// + [ViewVariables(VVAccess.ReadOnly)] + public float PowerlossDynamicScaling; + + /// + /// Affects the amount of damage and minimum point + /// at which the sm takes heat damage + /// + [ViewVariables(VVAccess.ReadOnly)] + public float DynamicHeatResistance = 1; + + /// + /// Multiplier on damage the core takes from absorbing hot gas + /// Default is ~1/350 + /// + [ViewVariables(VVAccess.ReadOnly)] + public float MoleHeatPenalty = 0.00286f; + + /// + /// Inverse of MoleHeatPenalty + /// + [ViewVariables(VVAccess.ReadOnly)] + public float MoleHeatThreshold = 350f; + + /// + /// Multiplier on power generated by nuclear reactions + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("reactionpowerModifier")] + public float ReactionPowerModifier = 0.55f; + + /// + /// Acts as a multiplier on the amount that nuclear reactions increase the supermatter core temperature + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("thermalreleaseModifier")] + public float ThermalReleaseModifier = 0.2f; + + /// + /// Multiplier on how much plasma is released during supermatter reactions + /// Default is ~1/750 + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("plasmareleaseModifier")] + public float PlasmaReleaseModifier = 0.001333f; + + /// + /// Multiplier on how much oxygen is released during supermatter reactions. + /// Default is ~1/325 + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("oxygenreleaseModifier")] + public float OxygenReleaseEfficiencyModifier = 0.0031f; + + #endregion SM Calculation + + #region SM Timer + + /// + /// The point at which we should start sending messeges + /// about the damage to the engi channels. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("WarningPoint")] + public float WarningPoint = 50; + + /// + /// The point at which we start sending messages to the common channel + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("emergencyPoint")] + public float EmergencyPoint = 500; + + /// + /// we yell if over 50 damage every YellTimer Seconds + /// + [ViewVariables(VVAccess.ReadWrite)] + public float YellTimer = 60f; + + /// + /// set to YellTimer at first so it doesnt yell a minute after being hit + /// + [ViewVariables(VVAccess.ReadOnly)] + public float YellAccumulator = 60f; + + /// + /// Timer for delam + /// + [ViewVariables(VVAccess.ReadOnly)] + public float DelamTimerAccumulator; + + /// + /// Time until delam + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("delamTimer")] + public float DelamTimer = 120f; + + /// + /// The message timer + /// + [ViewVariables(VVAccess.ReadWrite)] + public float SpeakAccumulator = 60f; + + [ViewVariables(VVAccess.ReadOnly)] + public float UpdateAccumulator = 0f; + + [ViewVariables(VVAccess.ReadWrite)] + public float UpdateTimer = 1f; + + [ViewVariables(VVAccess.ReadOnly)] + public float ZapAccumulator = 0f; + + [ViewVariables(VVAccess.ReadWrite)] + public float ZapTimer = 10f; + #endregion SM Timer + + #region SM Threshold + + /// + /// Higher == Higher percentage of inhibitor gas needed + /// before the charge inertia chain reaction effect starts. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("powerlossinhibitiongasThreshold")] + public float PowerlossInhibitionGasThreshold = 0.20f; + + /// + /// Higher == More moles of the gas are needed before the charge + /// inertia chain reaction effect starts. + /// Scales powerloss inhibition down until this amount of moles is reached + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("powerlossinhibitionmoleThreshold")] + public float PowerlossInhibitionMoleThreshold = 20f; + + /// + /// bonus powerloss inhibition boost if this amount of moles is reached + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("powerlossinhibitionmoleboostThreshold")] + public float PowerlossInhibitionMoleBoostThreshold = 500f; + + /// + /// Above this value we can get lord singulo and independent mol damage, + /// below it we can heal damage + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("molepenaltyThreshold")] + public float MolePenaltyThreshold = 900f; + + /// + /// more moles of gases are harder to heat than fewer, + /// so let's scale heat damage around them + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("moleheatpenaltyThreshold")] + 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 + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("powerPenaltyThreshold")] + public float PowerPenaltyThreshold = 2500f; + + /// + /// Maximum safe operational temperature in degrees Celsius. Supermatter begins taking damage above this temperature. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("heatpenaltyThreshold")] + public float HeatPenaltyThreshold = 40f; + + /// + /// The damage we had before this cycle. Used to limit the damage we can take each cycle, and for safe alert + /// + [ViewVariables(VVAccess.ReadWrite)] + public float DamageArchived = 0f; + + /// + /// is multiplied by ExplosionPoint to cap + /// evironmental damage per cycle + /// + [ViewVariables(VVAccess.ReadOnly)] + public float DamageHardcap = 0.002f; + + /// + /// environmental damage is scaled by this + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("damageincreaseMultiplier")] + public float DamageIncreaseMultiplier = 0.25f; + + /// + /// if spaced sm wont take more than 2 damage per cycle + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("maxspaceexposureDamage")] + public float MaxSpaceExposureDamage = 2; + + #endregion SM Threshold + + #region SM Delamm + + public bool DelamAnnounced = false; + + /// + /// The point at which we delamm + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("explosionPoint")] + public int DelaminationPoint = 900; + + //Are we delamming? + [ViewVariables(VVAccess.ReadOnly)] + public bool Delamming = false; + + //Explosion totalIntensity value + [ViewVariables(VVAccess.ReadOnly)] + [DataField("totalIntensity")] + public float TotalIntensity = 50000f; + + //Explosion radius value + [ViewVariables(VVAccess.ReadOnly)] + [DataField("radius")] + public float Radius = 50f; + + /// + /// These would be what you would get at point blank, decreases with distance + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("detonationRads")] + public float DetonationRads = 200f; + + #endregion SM Delamm + + #region SM Gas + /// + /// Is used to store gas + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("gasStorage")] + public Dictionary GasStorage = new Dictionary() + { + {Gas.Oxygen, 0f}, + {Gas.Nitrogen, 0f}, + {Gas.CarbonDioxide, 0f}, + {Gas.Plasma, 0f}, + {Gas.Tritium, 0f}, + {Gas.WaterVapor, 0f} + }; + + /// + /// Stores each gas facts + /// + 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), + }; + + #endregion SM Gas +} + +[Serializable, NetSerializable] +public sealed partial class SupermatterDoAfterEvent : SimpleDoAfterEvent +{ + +} diff --git a/Content.Shared/Ganimed/SM/SupermatterFoodComponent.cs b/Content.Shared/Ganimed/SM/SupermatterFoodComponent.cs new file mode 100644 index 00000000000000..16ee486bfc0fd2 --- /dev/null +++ b/Content.Shared/Ganimed/SM/SupermatterFoodComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.Supermatter.Components; + +[RegisterComponent] +public sealed partial class SupermatterFoodComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + [DataField("energy")] + public int Energy { get; set; } = 1; +} diff --git a/Content.Shared/Ganimed/SM/SupermatterImmuneComponent.cs b/Content.Shared/Ganimed/SM/SupermatterImmuneComponent.cs new file mode 100644 index 00000000000000..b517115eca7492 --- /dev/null +++ b/Content.Shared/Ganimed/SM/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/Ambience/Objects/attributions.yml b/Resources/Audio/Ambience/Objects/attributions.yml index e5cd81a3725ae4..1e1d03d4f090e5 100644 --- a/Resources/Audio/Ambience/Objects/attributions.yml +++ b/Resources/Audio/Ambience/Objects/attributions.yml @@ -62,3 +62,8 @@ license: "CC-BY-4.0" copyright: "Taken and edited from source" source: "https://freesound.org/people/juskiddink/sounds/215658/" + +- files: ["supermatter_calm.ogg", "supermatter_delam.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from source" + source: "https://github.com/tgstation/tgstation/blob/master/sound/machines/sm/loops" \ No newline at end of file diff --git a/Resources/Audio/Ambience/Objects/supermatter_calm.ogg b/Resources/Audio/Ambience/Objects/supermatter_calm.ogg new file mode 100644 index 00000000000000..cee14fcd13d665 Binary files /dev/null and b/Resources/Audio/Ambience/Objects/supermatter_calm.ogg differ diff --git a/Resources/Audio/Ambience/Objects/supermatter_delam.ogg b/Resources/Audio/Ambience/Objects/supermatter_delam.ogg new file mode 100644 index 00000000000000..7d79f0e3c4acd0 Binary files /dev/null and b/Resources/Audio/Ambience/Objects/supermatter_delam.ogg differ diff --git a/Resources/Audio/Weapons/attributions.yml b/Resources/Audio/Weapons/attributions.yml index 310b01b7280a9f..09e8f703ffcdfa 100644 --- a/Resources/Audio/Weapons/attributions.yml +++ b/Resources/Audio/Weapons/attributions.yml @@ -87,3 +87,8 @@ license: "CC0-1.0" copyright: "Taken from 0ne_one111yt on freesound.org" source: "https://freesound.org/people/0ne_one111yt/sounds/478213/" + +- files: ["emitter2.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from tgstation" + source: "https://github.com/tgstation/tgstation/blob/master/sound/weapons/emitter2.ogg" \ No newline at end of file diff --git a/Resources/Audio/Weapons/emitter2.ogg b/Resources/Audio/Weapons/emitter2.ogg new file mode 100644 index 00000000000000..72b6a1c572549b Binary files /dev/null and b/Resources/Audio/Weapons/emitter2.ogg differ diff --git a/Resources/Locale/ru-RU/ganimed/supermatter.ftl b/Resources/Locale/ru-RU/ganimed/supermatter.ftl new file mode 100644 index 00000000000000..e14b76b9f5195b --- /dev/null +++ b/Resources/Locale/ru-RU/ganimed/supermatter.ftl @@ -0,0 +1,44 @@ +supermatter-examine-integrity = + Целостность [color=yellow]{$integrity}%[/color]. + +supermatter-announcer = Система оповещений Суперматерии + +supermatter-warning = + Внимание! Расслоение кристалла суперматерии: {$integrity}% + +supermatter-emergency = + ОПАСНОСТЬ! Уровень расслоения кристалла суперматерии достиг критического уровня! Целостность кристалла: {$integrity}%!! + +supermatter-announcement-cc-tamper = + Наша автоматическая аварийная система обнаружила, что структурная целостность кристалла суперматерии была нарушена в результате воздействия внешней силы. + Инженерному отделу необходимо проверить кристалл суперматерии. + +supermatter-announcement-delam = + НЕИЗБЕЖНОЕ РАССЛОЕНИЕ КРИСТАЛЛА. Целостность кристалла из сверхматерии достигла критической точки!! + +supermatter-seconds-before-delam = + Расчетное время до расслаивания: {$seconds} секунд. + +supermatter-delam-explosion = + Задействуем поле дестабилизационных щитов. + +supermatter-delam-overmass = + Формирование сингулярности неизбежно. + +supermatter-delam-tesla = + Неизбежен выброс сжатой энергии. + +supermatter-delam-cascade = + Превышены пределы частоты гармоник. Не удалось задействовать поле дестабилизации в случае аварии. + +supermatter-delam-cancel = + Гиперструктура кристалла вернулась к своему безопасному значению. Расслоение предотвращенно. Целостность: {$integrity}%. + +supermatter-tamper-begin = Вы начинаете осторожно откалывать кусочек от кристалла сверхматерии... + +supermatter-tamper-end = Ты чувствуешь себя богом, держащим в руках частичку сверхматерии. Или это все из-за радиации? + +ent-SupermatterCrystal = суперматерия + .desc = Странно прозрачный и переливающийся кристалл. +ent-SupermatterSliver = срез суперматерии + .desc = Осколок от двигателя станции, работающего на сверхматерии. Высокорадиоактивный. \ No newline at end of file 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 00000000000000..ab7e1d6815bae5 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/supermatter_sliver.yml @@ -0,0 +1,24 @@ +- type: entity + parent: BaseItem + id: SupermatterSliver + name: supermatter sliver + description: A shard from the station's supermatter engine. Highly radioactive. + components: + - type: PointLight + enabled: true + radius: 3 + energy: 2 + color: "#fff633" + - type: RadiationSource + intensity: 2.5 + - type: Icon + sprite: Objects/Misc/supermatter_sliver.rsi + state: icon + - type: Sprite + sprite: Objects/Misc/supermatter_sliver.rsi + state: icon + - type: StealTarget + stealGroup: SupermatterSliver + - type: Tag + tags: + - HighRiskItem diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml index 065662146515e3..fda0e1b7070940 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/Objects/Weapons/Guns/Projectiles/hitscan.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml index 55672cc170ee57..c0d89d6eed8abf 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml @@ -14,6 +14,7 @@ - type: Tag tags: - HideContextMenu + - Laser - type: AnimationPlayer - type: hitscan diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml index 25d219ab9459fe..b8d66c61e38292 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/Tesla/energyball.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/energyball.yml index ea41ba3a20df7c..1cfdb9256a5eb8 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/Entities/Structures/Power/Generation/supermatter.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/supermatter.yml new file mode 100644 index 00000000000000..7d52c69e2b1c9d --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/supermatter.yml @@ -0,0 +1,68 @@ +- type: entity + id: SupermatterCrystal + name: supermatter crystal + description: A strangely translucent and iridescent crystal. + placement: + mode: SnapgridCenter + components: + - type: Supermatter + - type: SupermatterImmune + - type: Transform + anchored: true + noRot: true + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Glass + - type: RadiationSource + intensity: 0 # will be set somewhere else + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.45,-0.45,0.45,0.45" + density: 250 + mask: + - FullTileMask + layer: + - WallLayer + - type: CollisionWake + enabled: false + - type: Clickable + - type: InteractionOutline + - type: Anchorable + - type: Pullable + - type: Sprite + drawdepth: 2 + sprite: Structures/Power/Generation/supermatter.rsi + state: supermatter + - type: Icon + sprite: Structures/Power/Generation/supermatter.rsi + state: supermatter + - type: PointLight + enabled: true + radius: 10 + energy: 5 + color: "#fff633" + - type: AmbientSound + range: 4 + volume: -2 + sound: + path: /Audio/Ambience/Objects/supermatter_calm.ogg + - type: SinguloFood + energy: 7500 + - type: WarpPoint + follow: true + location: supermatter + - type: Explosive + explosionType: HardBomb + totalIntensity: 10000.0 + intensitySlope: 4 + maxIntensity: 1000 + - type: GuideHelp + guides: [ Supermatter, Power ] + - type: WarpPoint + follow: true + location: supermatter \ No newline at end of file diff --git a/Resources/Prototypes/Ganimed/Catalog/Loadout/Head/hoods.yml b/Resources/Prototypes/Ganimed/Catalog/Loadout/Head/hoods.yml index e5808f5de138ea..da4e46440871ed 100644 --- a/Resources/Prototypes/Ganimed/Catalog/Loadout/Head/hoods.yml +++ b/Resources/Prototypes/Ganimed/Catalog/Loadout/Head/hoods.yml @@ -18,4 +18,3 @@ cost: 3 exclusive: true prototype: ClothingHeadHatHoodGoliathCloak - diff --git a/Resources/Prototypes/Guidebook/engineering.yml b/Resources/Prototypes/Guidebook/engineering.yml index 21d17f02279f1c..ef502d8ab4c13f 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" \ No newline at end of file diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 76bc18569c6421..8f81593574c66f 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -12,6 +12,9 @@ - type: Tag id: AirSensor +- type: Tag + id: Laser + - type: Tag id: Table diff --git a/Resources/ServerInfo/Guidebook/Engineering/Supermatter.xml b/Resources/ServerInfo/Guidebook/Engineering/Supermatter.xml new file mode 100644 index 00000000000000..61bbcade6dcc51 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Engineering/Supermatter.xml @@ -0,0 +1,63 @@ + + + + + Итак, вы решили принять вызов и настроить двигатель суперматерии? Прежде всего, давайте дадим вам краткий обзор кристалла суперматерии. + + Ее основной особенностью является излучение радиоактивного фона, который используется для питания станции с помощью радиационных коллекторов. + + Побочные эффекты включают удары молний, выделение горячего кислорода и плазмы, нагрев воздуха вокруг, взрыв, превращение в черную дыру или энергетический шар и поглощение всей станции, если вы достаточно сильно облажаетесь. + + Вначале он инертен, но попадание в него предмета или снаряда активирует его, и он начнет проявлять почти все вышеупомянутые свойства. + + ## Предупреждения + + 1. Кристалл сверхматерии [color=#ff0000] ОЧЕНЬ ОПАСЕН[/color]. Активация кристалла должна быть последним шагом в создании любой формы энергии, основанной на Сверхматерии! + + 2. [color=#ff0000]НАДЕНЬ СВОЙ ЧЕРТОВ ПРОТИВОРАДИАЦИОННЫЙ КОСТЮМ[/color]!! + + 3. Большая часть работы по установке сверхматерии связана с газовым контуром, который предназначен для охлаждения камеры со сверхматерией. Пожалуйста, обладайте хотя бы некоторыми знаниями о газах и их атмосферных свойствах. + + 4. Все, что соприкасается со Сверхматерией, [color=#ff0000] уничтожается в корне[/color]. [color=#ff0000]Не прикасайтесь к этому[/color]. Это означает, что дверь камеры нужно завараить и заболтировать. + + ## Взаимодействие газов + + Вот список всех газов от наименее опасных до наиболее опасных. + + 1. [color=#bffffe]Фрезон[/color]. Помимо охлаждения Сверхматерии, это в основном останавливает выработку электроэнергии и отходов, что может пригодиться, если сверхматерия близка к расслаиванию и вам нужно быстро ее отключить. + + 2. [color=#c20000]Азот[/color]. N2 это основной газ, на котором работает большинство установок сверхматерии, поскольку его очень просто настроить. Он снижает выработку энергии за счет тепла и уменьшает количество плазмы, выделяемой SM, что делает его полезным, когда вы не пытаетесь делать что-то глупое. + + 3. [color=#b16d6d]Оксид азота[/color]. Повышает термостойкость кристалла, позволяя использовать его при гораздо более высоких температурах, чем обычно. Однако при высоких температурах он разлагается на азот и кислород. В то время как N2 - это хорошо, O2, безусловно, не подходит. Этот O2 также вступит в реакцию с плазмой, образуя тритий, а затем... Тритиевый пожар. + + 4. [color=#62d5ca]Кислород[/color]. Обеспечивает повышение мощности при передаче без существенного увеличения количества отходящих газов или температуры. Довольно рискованно использовать, так как любое нарушение работы контура охлаждения вскоре приведет к возгоранию плазмы в камере кристалла. Даже высокая концентрация O2 активирует кристалл и обеспечивает его непрерывным питанием. + + 5. [color=#19b348]Аммиак[/color]. Незначительно увеличивает выработку электроэнергии при незначительных затратах на утилизацию тепла. + + 6. [color=#979797]Оксид углерода[/color]. В низких концентрациях он увеличивает выработку энергии кристаллом. В высоких концентрациях он повышает энергию кристалла до чрезвычайно высокого уровня. При плохом управлении и недостаточной или совершенно плохой подготовке он в конечном итоге превысит безопасный уровень энергии и начнет расслаиваться, создавая электрические дуги и аномалии, пока в конце концов не превратится в шар Теслы. + + [color=#ff0000]7[/color]. [color=#ff9d00]Плазма[/color]. Очень похож на кислород, но обеспечивает более высокую мощность, а также значительно меньшее количество отходов и тепла. Экстремальные давления и объемы газа, образующегося при использовании этого газа, с большой вероятностью могут привести к закупорке труб и перегреву камеры. + + [color=#ff0000]8[/color]. [color=#08a800]Тритий[/color]. Увеличивает выработку энергии сверхматерией до 3 раз, но с этим есть одна небольшая проблема. Это опасно. Это очень опасно. Тритий - ужасно раздражительный и нервный газ. Хотя он не так вреден для тепловыделения, как плазма (совсем немного), он также обладает второй по величине теплоемкостью из всех газов, в то время как плазма занимает второе место по величине. Это означает, что плазму можно поддерживать в хорошем состоянии при достаточном охлаждении, в то время как тритий из безопасного космического пространства превращается в пылающий адский огонь. Добавьте к этому побочный продукт производства большого количества кислорода (не только трития. Проблема и в плазменном двигателе), и у вас будет тритиевый огонь и очень горячий кристалл. Не используйте этот газ, если вы не очень хорошо разбираетесь в атмосфере и сверхматерии и не готовы к творческому подходу. + + ## Практическое руководство по сверхматерии + + Теперь забудьте обо всем, что вы только что прочитали, и приступайте к настройке самого простого из существующих контуров: азотного контура. + + Атмосферная обстановка в ее самом простом виде должна выглядеть следующим образом: + + (У нас не было достаточного бюджета на изображения, вот текстовое представление) + + 1. Азот подается в камеру через пассивные вентиляционные отверстия с одной стороны + + 2. Каждый газ откачивается из камеры с помощью скрубберов, установленных на сифоне с другой стороны. + + 3. Выход охлаждается, фильтруется, а избыток азота либо выбрасывается в космос, либо перенаправляется на вход. + + Вот, в общем-то, и все. Надеюсь, вы хоть что-то поняли из этого примера. А теперь приступайте к делу! + + ## Эксперимент + + Вы ведь не настоящий инженер, если не придумали наиболее эффективный способ производства электроэнергии с помощью кристалла сверхматерии, не так ли? + + diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json b/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json index 5cb5998b1bcdf5..eeba72122df5dc 100644 --- a/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json +++ b/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json @@ -228,11 +228,11 @@ { "name": "paper_stamp-warden" }, - { - "name": "paper_stamp-generic" - }, { "name": "paper_stamp-iaa" }, + { + "name": "paper_stamp-generic" + } ] } diff --git a/Resources/Textures/Objects/Misc/supermatter_sliver.rsi/icon.png b/Resources/Textures/Objects/Misc/supermatter_sliver.rsi/icon.png new file mode 100644 index 00000000000000..2187706b107afd Binary files /dev/null and b/Resources/Textures/Objects/Misc/supermatter_sliver.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Misc/supermatter_sliver.rsi/meta.json b/Resources/Textures/Objects/Misc/supermatter_sliver.rsi/meta.json new file mode 100644 index 00000000000000..744651bea08072 --- /dev/null +++ b/Resources/Textures/Objects/Misc/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" + } + ] +} diff --git a/Resources/Textures/Structures/Power/Generation/supermatter.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/supermatter.rsi/meta.json new file mode 100644 index 00000000000000..d0a000ae2b5e54 --- /dev/null +++ b/Resources/Textures/Structures/Power/Generation/supermatter.rsi/meta.json @@ -0,0 +1,21 @@ +{ + "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 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Power/Generation/supermatter.rsi/supermatter.png b/Resources/Textures/Structures/Power/Generation/supermatter.rsi/supermatter.png new file mode 100644 index 00000000000000..0c5747a315fec5 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/supermatter.rsi/supermatter.png differ