diff --git a/Content.Client/White/Supermatter/Systems/SupermatterSystem.cs b/Content.Client/White/Supermatter/Systems/SupermatterSystem.cs new file mode 100644 index 0000000000..36d4ded82c --- /dev/null +++ b/Content.Client/White/Supermatter/Systems/SupermatterSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared.White.Supermatter.Components; +using Content.Shared.White.Supermatter.Systems; +using Robust.Shared.GameStates; + +namespace Content.Client.White.Supermatter.Systems; + +public sealed class SupermatterSystem : SharedSupermatterSystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandleSupermatterState); + } + + private void HandleSupermatterState(EntityUid uid, SupermatterComponent comp, ref ComponentHandleState args) + { + if (args.Current is not SupermatterComponentState state) + return; + } +} diff --git a/Content.Server/White/Supermatter/Systems/SupermatterSystem.cs b/Content.Server/White/Supermatter/Systems/SupermatterSystem.cs new file mode 100644 index 0000000000..1b0fee2610 --- /dev/null +++ b/Content.Server/White/Supermatter/Systems/SupermatterSystem.cs @@ -0,0 +1,582 @@ +using System.Linq; +using Content.Server.Alert.Click; +using JetBrains.Annotations; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +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.Server.Explosion.Components; +using Content.Shared.Singularity.Components; +using Content.Shared.Singularity.EntitySystems; +using Content.Shared.White.Supermatter.Components; +using Content.Shared.White.Supermatter.Systems; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Random; + +namespace Content.Server.White.Supermatter.Systems +{ + [UsedImplicitly] + public sealed class SupermatterSystem : SharedSupermatterSystem + { + [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly TagSystem _tag = 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 IRobustRandom _random = default!; + [Dependency] private readonly SharedSingularitySystem _singularity = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnCollideEvent); + SubscribeLocalEvent(OnHandInteract); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(HandleSupermatterState); + SubscribeLocalEvent(OnComponentRemove); + } + + private void OnComponentInit(EntityUid uid, SupermatterComponent component, ComponentInit args) + { + if (_random.Prob(0.2f)) + { + component.DelamType = DelamType.Singulo; + } + } + + private void OnComponentRemove(EntityUid uid, SupermatterComponent component, ComponentRemove args) + { + // turn off any ambient if component is removed (ex. entity deleted) + _ambient.SetAmbience(uid, false); + _audio.Stop(component.AudioEntity); + } + + 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 mixture = _atmosphere.GetContainingMixture(uid, true, true); + mixture?.AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard); + mixture?.AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard); + } + + private void HandleSupermatterState(EntityUid uid, SupermatterComponent comp, ref ComponentGetState args) + { + args.State = new SupermatterComponentState(comp); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_gameTiming.IsFirstTimePredicted) + return; + + foreach (var (supermatter, xplode, rads) in EntityManager + .EntityQuery()) + { + var mixture = _atmosphere.GetContainingMixture(supermatter.Owner, true, true); + HandleOutput(supermatter.Owner, frameTime, supermatter, rads, mixture); + HandleDamage(supermatter.Owner, frameTime, supermatter, xplode, mixture); + } + } + + /// + /// Handle outputting based off enery, damage, gas mix and radiation + /// + private void HandleOutput( + EntityUid uid, + float frameTime, + SupermatterComponent? sMcomponent = null, + RadiationSourceComponent? radcomponent = null, + Atmos.GasMixture? mixture = null) + { + if (!Resolve(uid, ref sMcomponent, ref radcomponent)) + { + return; + } + + sMcomponent.AtmosUpdateAccumulator += frameTime; + + if (!(sMcomponent.AtmosUpdateAccumulator > sMcomponent.AtmosUpdateTimer) || mixture is null) + return; + + sMcomponent.AtmosUpdateAccumulator -= sMcomponent.AtmosUpdateTimer; + + //Absorbed gas from surrounding area + var absorbedGas = mixture.Remove(sMcomponent.GasEfficiency * mixture.TotalMoles); + var absorbedTotalMoles = absorbedGas.TotalMoles; + + if (!(absorbedTotalMoles > 0f)) + return; + + var gasStorage = sMcomponent.GasStorage; + var gasEffect = sMcomponent.GasDataFields; + + //Lets get the proportions of the gasses in the mix for scaling stuff later + //They range between 0 and 1 + gasStorage = gasStorage.ToDictionary( + gas => gas.Key, + gas => Math.Clamp(absorbedGas.GetMoles(gas.Key) / absorbedTotalMoles, 0, 1) + ); + + //No less then zero, and no greater then one, we use this to do explosions + //and heat to power transfer + var gasmixPowerRatio = gasStorage.Sum(gas => gasStorage[gas.Key] * gasEffect[gas.Key].PowerMixRatio); + + //Minimum value of -10, maximum value of 23. Effects plasma and o2 output + //and the output heat + var dynamicHeatModifier = gasStorage.Sum(gas => gasStorage[gas.Key] * gasEffect[gas.Key].HeatPenalty); + + //Minimum value of -10, maximum value of 23. Effects plasma and o2 output + // and the output heat + var powerTransmissionBonus = + gasStorage.Sum(gas => gasStorage[gas.Key] * gasEffect[gas.Key].TransmitModifier); + + var h2OBonus = 1 - gasStorage[Gas.WaterVapor] * 0.25f; + + gasmixPowerRatio = Math.Clamp(gasmixPowerRatio, 0, 1); + dynamicHeatModifier = Math.Max(dynamicHeatModifier, 0.5f); + powerTransmissionBonus *= h2OBonus; + + //Effects the damage heat does to the crystal + sMcomponent.DynamicHeatResistance = 1f; + + //more moles of gases are harder to heat than fewer, + //so let's scale heat damage around them + sMcomponent.MoleHeatPenaltyThreshold = + (float) Math.Max(absorbedTotalMoles / sMcomponent.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 (absorbedTotalMoles > sMcomponent.PowerlossInhibitionMoleThreshold && + gasStorage[Gas.CarbonDioxide] > sMcomponent.PowerlossInhibitionGasThreshold) + { + sMcomponent.PowerlossDynamicScaling = + Math.Clamp( + sMcomponent.PowerlossDynamicScaling + Math.Clamp( + gasStorage[Gas.CarbonDioxide] - sMcomponent.PowerlossDynamicScaling, -0.02f, 0.02f), 0f, + 1f); + } + else + { + sMcomponent.PowerlossDynamicScaling = Math.Clamp(sMcomponent.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 - sMcomponent.PowerlossDynamicScaling * + Math.Clamp(absorbedTotalMoles / sMcomponent.PowerlossInhibitionMoleBoostThreshold, 1f, 1.5f), + 0f, 1f); + + if (sMcomponent.MatterPower != 0) //We base our removed power off one 10th of the matter_power. + { + var removedMatter = Math.Max(sMcomponent.MatterPower / sMcomponent.MatterPowerConversion, 40); + //Adds at least 40 power + sMcomponent.Power = Math.Max(sMcomponent.Power + removedMatter, 0); + //Removes at least 40 matter power + sMcomponent.MatterPower = Math.Max(sMcomponent.MatterPower - removedMatter, 0); + } + + //based on gas mix, makes the power more based on heat or less effected by heat + var tempFactor = gasmixPowerRatio > 0.8 ? 50f : 30f; + + //if there is more pluox and n2 then anything else, we receive no power increase from heat + sMcomponent.Power = + Math.Max( + absorbedGas.Temperature * tempFactor / Atmospherics.T0C * gasmixPowerRatio + sMcomponent.Power, + 0); + + //Rad Pulse Calculation + radcomponent.Intensity = sMcomponent.Power * Math.Max(0, 1f + powerTransmissionBonus / 10f) * 0.003f; + + //Power * 0.55 * a value between 1 and 0.8 + var energy = sMcomponent.Power * sMcomponent.ReactionPowerModefier; + + //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 * dynamicHeatModifier / sMcomponent.ThermalReleaseModifier; + absorbedGas.Temperature = Math.Max(0, + Math.Min(absorbedGas.Temperature, sMcomponent.HeatThreshold * dynamicHeatModifier)); + + //Calculate how much gas to release + //Varies based on power and gas content + + absorbedGas.AdjustMoles(Gas.Plasma, + Math.Max(energy * dynamicHeatModifier / sMcomponent.PlasmaReleaseModifier, 0f)); + + absorbedGas.AdjustMoles(Gas.Oxygen, + Math.Max( + (energy + absorbedGas.Temperature * dynamicHeatModifier - Atmospherics.T0C) / + sMcomponent.OxygenReleaseModifier, 0f)); + + _atmosphere.Merge(mixture, absorbedGas); + + var powerReduction = (float) Math.Pow(sMcomponent.Power / 500, 3); + + //After this point power is lowered + //This wraps around to the begining of the function + sMcomponent.Power = + Math.Max( + sMcomponent.Power - Math.Min(powerReduction * powerlossInhibitor, + sMcomponent.Power * 0.83f * powerlossInhibitor), 0f); + } + + /// + /// Handles environmental damage and dispatching damage warning + /// + private void HandleDamage( + EntityUid uid, + float frameTime, + SupermatterComponent? sMcomponent = null, + ExplosiveComponent? xplode = null, + Atmos.GasMixture? mixture = null) + { + if (!Resolve(uid, ref sMcomponent, ref xplode)) + { + return; + } + + var xform = Transform(uid); + var indices = _xform.GetGridOrMapTilePosition(uid, xform); + + sMcomponent.DamageUpdateAccumulator += frameTime; + sMcomponent.YellAccumulator += frameTime; + + if (!(sMcomponent.DamageUpdateAccumulator > sMcomponent.DamageUpdateTimer)) + return; + + sMcomponent.DamageArchived = sMcomponent.Damage; + //we're in space or there is no gas to process + if (!xform.GridUid.HasValue || mixture is null || mixture.TotalMoles == 0f) + { + sMcomponent.Damage += Math.Max(sMcomponent.Power / 1000 * sMcomponent.DamageIncreaseMultiplier, 0.1f); + } + else + { + //Absorbed gas from surrounding area + var absorbedGas = mixture.Remove(sMcomponent.GasEfficiency * mixture.TotalMoles); + var absorbedTotalMoles = absorbedGas.TotalMoles; + + //Mols start to have a positive effect on damage after 350 + sMcomponent.Damage = (float) Math.Max( + sMcomponent.Damage + Math.Max( + Math.Clamp(absorbedTotalMoles / 200, 0.5, 1) * absorbedGas.Temperature - + (Atmospherics.T0C + sMcomponent.HeatPenaltyThreshold) * sMcomponent.DynamicHeatResistance, + 0) + * sMcomponent.MoleHeatPenalty / 150 * sMcomponent.DamageIncreaseMultiplier, + 0); + + //Power only starts affecting damage when it is above 5000 + sMcomponent.Damage = + Math.Max( + sMcomponent.Damage + + Math.Max(sMcomponent.Power - sMcomponent.PowerPenaltyThreshold, 0) / 500 * + sMcomponent.DamageIncreaseMultiplier, 0); + + //Molar count only starts affecting damage when it is above 1800 + sMcomponent.Damage = + Math.Max( + sMcomponent.Damage + Math.Max(absorbedTotalMoles - sMcomponent.MolePenaltyThreshold, 0) / 80 * + sMcomponent.DamageIncreaseMultiplier, 0); + + //There might be a way to integrate healing and hurting via heat + //healing damage + if (absorbedTotalMoles < sMcomponent.MolePenaltyThreshold) + { + //Only has a net positive effect when the temp is below 313.15, heals up to 2 damage. Psycologists increase this temp min by up to 45 + sMcomponent.Damage = + Math.Max( + sMcomponent.Damage + + Math.Min(absorbedGas.Temperature - (Atmospherics.T0C + sMcomponent.HeatPenaltyThreshold), + 0) / 150, + 0); + } + + //if there are space tiles next to SM + //TODO: change moles out for checking if adjacent tiles exist + foreach (var ind in _atmosphere.GetAdjacentTileMixtures(xform.GridUid.Value, indices)) + { + if (ind.TotalMoles != 0) + continue; + + var integrity = GetIntegrity(sMcomponent.Damage, sMcomponent.ExplosionPoint); + + var factor = integrity switch + { + < 10 => 0.0005f, + < 25 => 0.0009f, + < 45 => 0.005f, + < 75 => 0.002f, + _ => 0f + }; + + sMcomponent.Damage += Math.Clamp(sMcomponent.Power * factor * sMcomponent.DamageIncreaseMultiplier, + 0, sMcomponent.MaxSpaceExposureDamage); + + break; + } + + sMcomponent.Damage = + Math.Min(sMcomponent.DamageArchived + sMcomponent.DamageHardcap * sMcomponent.ExplosionPoint, + sMcomponent.Damage); + } + + HandleSoundLoop(uid, sMcomponent); + + if (sMcomponent.Damage > sMcomponent.ExplosionPoint) + { + Delamination(uid, frameTime, sMcomponent, xplode, mixture); + return; + } + + if (sMcomponent.Damage > sMcomponent.WarningPoint) + { + var integrity = GetIntegrity(sMcomponent.Damage, sMcomponent.ExplosionPoint); + if (sMcomponent.YellAccumulator >= sMcomponent.YellTimer) + { + if (sMcomponent.Damage > sMcomponent.EmergencyPoint) + { + _chat.TrySendInGameICMessage(uid, + Loc.GetString("supermatter-danger-message", ("integrity", integrity.ToString("0.00"))), + InGameICChatType.Speak, hideChat: true); + } + else if (sMcomponent.Damage >= sMcomponent.DamageArchived) + { + _chat.TrySendInGameICMessage(uid, + Loc.GetString("supermatter-warning-message", ("integrity", integrity.ToString("0.00"))), + InGameICChatType.Speak, hideChat: true); + } + else + { + _chat.TrySendInGameICMessage(uid, + Loc.GetString("supermatter-safe-alert", ("integrity", integrity.ToString("0.00"))), + InGameICChatType.Speak, hideChat: true); + } + + sMcomponent.YellAccumulator = 0; + } + } + + sMcomponent.DamageUpdateAccumulator -= sMcomponent.DamageUpdateTimer; + } + + private float GetIntegrity(float damage, float explosionPoint) + { + var integrity = damage / explosionPoint; + integrity = (float) Math.Round(100 - integrity * 100, 2); + integrity = integrity < 0 ? 0 : integrity; + return integrity; + } + + /// + /// Runs the logic and timers for Delamination + /// + private void Delamination( + EntityUid uid, + float frameTime, + SupermatterComponent? sMcomponent = null, + ExplosiveComponent? xplode = null, + Atmos.GasMixture? mixture = null) + { + if (!Resolve(uid, ref sMcomponent, ref xplode)) + { + return; + } + + var xform = Transform(uid); + + //before we actually start counting down, check to see what delam type we're doing. + if (!sMcomponent.FinalCountdown) + { + //if we're in atmos + if (mixture is { }) + { + //Absorbed gas from surrounding area + var absorbedGas = mixture.Remove(sMcomponent.GasEfficiency * mixture.TotalMoles); + var absorbedTotalMoles = absorbedGas.TotalMoles; + //if the moles on the sm's tile are above MolePenaltyThreshold + if (absorbedTotalMoles >= sMcomponent.MolePenaltyThreshold) + { + sMcomponent.DelamType = DelamType.Singulo; + _chat.TrySendInGameICMessage(uid, Loc.GetString("supermatter-delamination-overmass"), + InGameICChatType.Speak, hideChat: true); + } + } + else + { + sMcomponent.DelamType = DelamType.Explosion; + _chat.TrySendInGameICMessage(uid, Loc.GetString("supermatter-delamination-default"), + InGameICChatType.Speak, hideChat: true); + } + } + + sMcomponent.FinalCountdown = true; + + sMcomponent.DelamTimerAccumulator += frameTime; + sMcomponent.SpeakAccumulator += frameTime; + var roundSeconds = sMcomponent.DelamTimerTimer - (int) Math.Floor(sMcomponent.DelamTimerAccumulator); + + //we're more than 5 seconds from delam, only yell every 5 seconds. + if (roundSeconds >= sMcomponent.YellDelam && sMcomponent.SpeakAccumulator >= sMcomponent.YellDelam) + { + sMcomponent.SpeakAccumulator -= sMcomponent.YellDelam; + _chat.TrySendInGameICMessage(uid, + Loc.GetString("supermatter-seconds-before-delam", ("Seconds", roundSeconds)), + InGameICChatType.Speak, hideChat: true); + } + //less than 5 seconds to delam, count every second. + else if (roundSeconds < sMcomponent.YellDelam && sMcomponent.SpeakAccumulator >= 1) + { + sMcomponent.SpeakAccumulator -= 1; + _chat.TrySendInGameICMessage(uid, + Loc.GetString("supermatter-seconds-before-delam", ("Seconds", roundSeconds)), + InGameICChatType.Speak, hideChat: true); + } + + //TODO: make tesla(?) spawn at SupermatterComponent.PowerPenaltyThreshold and think up other delam types + //times up, explode or make a singulo + if (!(sMcomponent.DelamTimerAccumulator >= sMcomponent.DelamTimerTimer)) + return; + + if (sMcomponent.DelamType == DelamType.Singulo) + { + //spawn a singulo :) + var singuloEntity = EntityManager.SpawnEntity("Singularity", xform.Coordinates); + _singularity.SetLevel(singuloEntity, 4); + + _explosion.TriggerExplosive( + uid, + explosive: xplode, + totalIntensity: sMcomponent.TotalIntensity / 50, + radius: sMcomponent.Radius / 50, + user: uid + ); + + _audio.Stop(sMcomponent.AudioEntity); + } + else + { + //explosion!!!!! + _explosion.TriggerExplosive( + uid, + explosive: xplode, + totalIntensity: sMcomponent.TotalIntensity, + radius: sMcomponent.Radius, + user: uid + ); + + _audio.Stop(sMcomponent.AudioEntity); + _ambient.SetAmbience(uid, false); + } + + sMcomponent.FinalCountdown = false; + } + + private void HandleSoundLoop(EntityUid uid, SupermatterComponent sMcomponent) + { + var isAggressive = sMcomponent.Damage > sMcomponent.WarningPoint; + var isDelamming = sMcomponent.Damage > sMcomponent.ExplosionPoint; + + if (!isAggressive && !isDelamming) + { + _audio.Stop(sMcomponent.AudioEntity); + return; + } + + var smSound = isDelamming ? SuperMatterSound.Delam : SuperMatterSound.Aggressive; + + if (sMcomponent.SmSound == smSound) + return; + + _audio.Stop(sMcomponent.AudioEntity); + + var sounds = isDelamming ? sMcomponent.DelamAlarm : sMcomponent.DelamSound; + var sound = _audio.GetSound(sounds); + var param = sounds.Params.WithLoop(true).WithVolume(5f).WithMaxDistance(20f); + sMcomponent.AudioEntity = _audio.PlayPvs(sound, uid, param).Value.Entity; + sMcomponent.SmSound = smSound; + } + + /// + /// Determines if an entity can be dusted + /// + private bool CannotDestroy(EntityUid uid) + { + var @static = false; + + var tag = _tag.HasTag(uid, "SMImmune"); + + if (EntityManager.TryGetComponent(uid, out var physicsComp)) + { + @static = physicsComp.BodyType == BodyType.Static; + } + + return tag || @static; + } + + private void OnCollideEvent(EntityUid uid, SupermatterComponent supermatter, ref StartCollideEvent args) + { + var target = args.OtherBody.Owner; + + if (!supermatter.Whitelist.IsValid(target) || CannotDestroy(target) || _container.IsEntityInContainer(uid)) + return; + + if (EntityManager.TryGetComponent(target, out var supermatterFood)) + supermatter.Power += supermatterFood.Energy; + else if (EntityManager.TryGetComponent(target, out var projectile)) + supermatter.Power += (float) projectile.Damage.Total; + else + supermatter.Power++; + + supermatter.MatterPower += EntityManager.HasComponent(target) ? 200 : 0; + if (!EntityManager.HasComponent(target)) + { + EntityManager.SpawnEntity("Ash", Transform(target).Coordinates); + _audio.PlayPvs(supermatter.DustSound, uid); + } + + EntityManager.QueueDeleteEntity(target); + } + + private void OnHandInteract(EntityUid uid, SupermatterComponent supermatter, InteractHandEvent args) + { + var target = args.User; + if (_tag.HasTag(target, "SMImmune")) + { + return; + } + + supermatter.MatterPower += 200; + EntityManager.SpawnEntity("Ash", Transform(target).Coordinates); + _audio.PlayPvs(supermatter.DustSound, uid); + EntityManager.QueueDeleteEntity(target); + } + } +} diff --git a/Content.Shared/White/Supermatter/Components/SupermatterComponent.cs b/Content.Shared/White/Supermatter/Components/SupermatterComponent.cs new file mode 100644 index 0000000000..c00b4d6413 --- /dev/null +++ b/Content.Shared/White/Supermatter/Components/SupermatterComponent.cs @@ -0,0 +1,372 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Audio; +using Content.Shared.Atmos; +using Content.Shared.White.Supermatter.Systems; +using Content.Shared.Whitelist; + +namespace Content.Shared.White.Supermatter.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SupermatterComponent : Component +{ + #region SM Base + + [DataField("whitelist")] public EntityWhitelist Whitelist = new(); + public string IdTag = "EmitterBolt"; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("power")] + public float Power; + + /// + /// The amount of damage we have currently + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("damage")] + public float Damage = 0f; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("matterPower")] + public float MatterPower; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("matterPowerConversion")] + public float MatterPowerConversion = 10f; + + [ViewVariables(VVAccess.ReadWrite)] + public SharedSupermatterSystem.DelamType DelamType; + + /// + /// The portion of the gasmix we're on + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("gasEfficiency")] + public float GasEfficiency = 0.15f; + + /// + /// The amount of heat we apply scaled + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("heatThreshold")] + public float HeatThreshold = 2500f; + + #endregion SM Base + + #region SM Sound + + /// + /// Current stream of SM audio. + /// + public EntityUid? AudioEntity; + + public SharedSupermatterSystem.SuperMatterSound? SmSound; + + [DataField("dustSound")] + public SoundSpecifier DustSound = new SoundPathSpecifier("/Audio/White/Supermatter/dust.ogg"); + + [DataField("delamSound")] + public SoundSpecifier DelamSound = new SoundPathSpecifier("/Audio/White/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)] + [DataField("powerlossdynamicScaling")] + public float PowerlossDynamicScaling; + + /// + /// Affects the amount of damage and minimum point + /// at which the sm takes heat damage + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("dynamicheatResistance")] + public float DynamicHeatResistance = 1; + + /// + /// Used to increase or lessen the amount of damage the sm takes + /// from heat based on molar counts. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("moleheatPenalty")] + public float MoleHeatPenalty = 350f; + + /// + /// Higher == more overall power + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("reactionpowerModefier")] + public float ReactionPowerModefier = 0.55f; + + /// + /// Higher == less heat released during reaction + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("thermalreleaseModifier")] + public float ThermalReleaseModifier = 5f; + + /// + /// Higher == less plasma released by reaction + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("plasmareleaseModifier")] + public float PlasmaReleaseModifier = 750f; + + /// + /// Higher == less oxygen released at high temperature/power + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("oxygenreleaseModifier")] + public float OxygenReleaseModifier = 325f; + + #endregion SM Calculation + + #region SM Timer + + /// + /// The point at which we should start sending messeges + /// about the damage to the engi channels. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("WarningPoint")] + public float WarningPoint = 50; + + /// + /// The point at which we start sending messages to the common channel + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("emergencyPoint")] + public float EmergencyPoint = 500; + + /// + /// we yell if over 50 damage every YellTimer Seconds + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("yellTimer")] + public float YellTimer = 30f; + + /// + /// set to YellTimer at first so it doesnt yell a minute after being hit + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("yellAccumulator")] + public float YellAccumulator = 30f; + + /// + /// YellTimer before the SM is about the delam + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("yellDelam")] + public float YellDelam = 5f; + + /// + /// Timer for Damage + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("damageupdateAccumulator")] + public float DamageUpdateAccumulator; + + /// + /// update environment damage every 1 second + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("damageupdateTimer")] + public float DamageUpdateTimer = 1f; + + /// + /// Timer for delam + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("delamtimerAccumulator")] + public float DelamTimerAccumulator; + + /// + /// updates delam + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("delamtimerTimer")] + public int DelamTimerTimer = 30; + + /// + /// The message timer + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("speakaccumulator")] + public float SpeakAccumulator = 5f; + + /// + /// Atmos update timer + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("atmosupdateAccumulator")] + public float AtmosUpdateAccumulator; + + /// + /// update atmos every 1 second + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("atmosupdateTimer")] + public float AtmosUpdateTimer = 1f; + + #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 = 1800f; + + /// + /// 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 = 5000f; + + /// + /// Higher == Crystal safe operational temperature is higher. + /// + [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)] + [DataField("damagearchived")] + public float DamageArchived = 0f; + + /// + /// is multiplied by ExplosionPoint to cap + /// evironmental damage per cycle + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("damageHardcap")] + 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 + + /// + /// The point at which we delamm + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("explosionPoint")] + public int ExplosionPoint = 900; + + //Are we delamming? + [ViewVariables(VVAccess.ReadOnly)] public bool Delamming = false; + + //it's the final countdown + [ViewVariables(VVAccess.ReadOnly)] public bool FinalCountdown = false; + + //Explosion totalIntensity value + [ViewVariables(VVAccess.ReadOnly)] + [DataField("totalIntensity")] + public float TotalIntensity= 500000f; + + //Explosion radius value + [ViewVariables(VVAccess.ReadOnly)] + [DataField("radius")] + public float Radius = 500f; + + /// + /// 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() + { + {Gas.Oxygen, 0f}, + {Gas.Nitrogen, 0f}, + {Gas.CarbonDioxide, 0f}, + {Gas.Plasma, 0f}, + {Gas.Tritium, 0f}, + {Gas.WaterVapor, 0f} + }; + + /// + /// Stores each gases calculation + /// + 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) + }; + + #endregion SM Gas +} diff --git a/Content.Shared/White/Supermatter/Components/SupermatterFoodComponent.cs b/Content.Shared/White/Supermatter/Components/SupermatterFoodComponent.cs new file mode 100644 index 0000000000..17a7ad5174 --- /dev/null +++ b/Content.Shared/White/Supermatter/Components/SupermatterFoodComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.White.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/White/Supermatter/Systems/SharedSupermatterSystem.cs b/Content.Shared/White/Supermatter/Systems/SharedSupermatterSystem.cs new file mode 100644 index 0000000000..002d5a5377 --- /dev/null +++ b/Content.Shared/White/Supermatter/Systems/SharedSupermatterSystem.cs @@ -0,0 +1,40 @@ +using Content.Shared.White.Supermatter.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.White.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, + } + + public void OnSupermatterStartup(EntityUid uid, SupermatterComponent comp, ComponentStartup args) + { + } + + /// + /// A state wrapper used to sync the supermatter between the server and client. + /// + [Serializable, NetSerializable] + protected sealed class SupermatterComponentState : ComponentState + { + public SupermatterComponentState(SupermatterComponent supermatter) + { + } + } +} diff --git a/Resources/Audio/White/Supermatter/calm.ogg b/Resources/Audio/White/Supermatter/calm.ogg new file mode 100644 index 0000000000..cee14fcd13 Binary files /dev/null and b/Resources/Audio/White/Supermatter/calm.ogg differ diff --git a/Resources/Audio/White/Supermatter/delamming.ogg b/Resources/Audio/White/Supermatter/delamming.ogg new file mode 100644 index 0000000000..7d79f0e3c4 Binary files /dev/null and b/Resources/Audio/White/Supermatter/delamming.ogg differ diff --git a/Resources/Audio/White/Supermatter/dust.ogg b/Resources/Audio/White/Supermatter/dust.ogg new file mode 100644 index 0000000000..c6e87b61e6 Binary files /dev/null and b/Resources/Audio/White/Supermatter/dust.ogg differ diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 552ed02129..d4448f387b 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -12,6 +12,7 @@ - CanPilot - BypassInteractionRangeChecks - BypassDropChecks + - SMImmune - type: Input context: "aghost" - type: Ghost diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml index 5a3b739355..60f60fd5c2 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml @@ -245,7 +245,6 @@ - type: Sprite sprite: Objects/Tanks/plasma.rsi - type: Item - size: 25 sprite: Objects/Tanks/plasma.rsi - type: GasTank outputPressure: 101.3 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml index 78f3eff959..5f909d221f 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml @@ -306,7 +306,7 @@ # soundHit: Waiting on serv3 damage: types: - Heat: 14 + Heat: 50 # mining laser real - type: GatheringProjectile - type: Tag diff --git a/Resources/Prototypes/White/Entities/Structures/Supermatter/supermatter.yml b/Resources/Prototypes/White/Entities/Structures/Supermatter/supermatter.yml new file mode 100644 index 0000000000..d5e3f3ae05 --- /dev/null +++ b/Resources/Prototypes/White/Entities/Structures/Supermatter/supermatter.yml @@ -0,0 +1,66 @@ +- type: entity + id: supermatter + name: Supermatter + description: A strangely translucent and iridescent crystal. + placement: + mode: SnapgridCenter + components: + - type: Supermatter + whitelist: + tags: + - EmitterBolt + components: + - Body + - Item + - type: RadiationSource + - type: AmbientSound + range: 5 + volume: 50 + sound: + path: /Audio/White/Supermatter/calm.ogg + - type: Physics + bodyType: Dynamic + - 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: White/Structures/supermatter.rsi + state: supermatter + - type: Icon + sprite: White/Structures/supermatter.rsi + state: supermatter + - type: PointLight + enabled: true + radius: 10 + energy: 5 + color: "#d9ce00" + - type: Explosive + explosionType: Supermatter + maxIntensity: 10000 + intensitySlope: 10 + totalIntensity: 10000 diff --git a/Resources/Prototypes/White/tags.yml b/Resources/Prototypes/White/tags.yml index 776329923e..479aa6cde3 100644 --- a/Resources/Prototypes/White/tags.yml +++ b/Resources/Prototypes/White/tags.yml @@ -6,3 +6,6 @@ - type: Tag id: CrossbowBolt + +- type: Tag + id: SMImmune diff --git a/Resources/Prototypes/explosion.yml b/Resources/Prototypes/explosion.yml index ff0c78d86e..69148893b3 100644 --- a/Resources/Prototypes/explosion.yml +++ b/Resources/Prototypes/explosion.yml @@ -117,3 +117,20 @@ lightColor: Orange texturePath: /Textures/Effects/fire.rsi fireStates: 6 + +# WD ADDED +- type: explosion + 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/Textures/White/Structures/supermatter.rsi/meta.json b/Resources/Textures/White/Structures/supermatter.rsi/meta.json new file mode 100644 index 0000000000..3c25e1a830 --- /dev/null +++ b/Resources/Textures/White/Structures/supermatter.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "copyright": "Taken from https://github.com/tgstation/tgstation/blob/master/icons/obj/supermatter.dmi", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 48 + }, + "states": [ + { + "name": "supermatter" + } + ] +} diff --git a/Resources/Textures/White/Structures/supermatter.rsi/supermatter.png b/Resources/Textures/White/Structures/supermatter.rsi/supermatter.png new file mode 100644 index 0000000000..b8fa4defeb Binary files /dev/null and b/Resources/Textures/White/Structures/supermatter.rsi/supermatter.png differ