Обновление суперматерии (#2732)

This commit is contained in:
Vigers Ray 2025-08-07 04:21:45 +03:00 committed by GitHub
parent c887aad0a4
commit 81f386197b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 828 additions and 960 deletions

View file

@ -50,6 +50,7 @@ public sealed class LightningSystem : SharedLightningSystem
public void ShootLightning(EntityUid user, EntityUid target, string lightningPrototype = "Lightning", bool triggerLightningEvents = true)
{
var spriteState = LightningRandomizer();
_beam.TryCreateBeam(user, target, lightningPrototype, spriteState);
if (triggerLightningEvents) // we don't want certain prototypes to trigger lightning level events
@ -69,7 +70,7 @@ public sealed class LightningSystem : SharedLightningSystem
/// <param name="lightningPrototype">The prototype for the lightning to be created</param>
/// <param name="arcDepth">how many times to recursively fire lightning bolts from the target points of the first shot.</param>
/// <param name="triggerLightningEvents">if the lightnings being fired should trigger lightning events.</param>
public void ShootRandomLightnings(EntityUid user, float range, int boltCount, string lightningPrototype = "Lightning", int arcDepth = 0, bool triggerLightningEvents = true)
public bool ShootRandomLightnings(EntityUid user, float range, int boltCount, string lightningPrototype = "Lightning", int arcDepth = 0, bool triggerLightningEvents = true)
{
//TODO: add support to different priority target tablem for different lightning types
//TODO: Remove Hardcode LightningTargetComponent (this should be a parameter of the SharedLightningComponent)
@ -80,6 +81,7 @@ public sealed class LightningSystem : SharedLightningSystem
_random.Shuffle(targets);
targets.Sort((x, y) => y.Comp.Priority.CompareTo(x.Comp.Priority));
bool shooted = false; //starlight
int shootedCount = 0;
int count = -1;
while(shootedCount < boltCount)
@ -93,12 +95,14 @@ public sealed class LightningSystem : SharedLightningSystem
continue;
ShootLightning(user, targets[count].Owner, lightningPrototype, triggerLightningEvents);
shooted = true;//starlight
if (arcDepth - targets[count].Comp.LightningResistance > 0)
{
ShootRandomLightnings(targets[count].Owner, range, 1, lightningPrototype, arcDepth - targets[count].Comp.LightningResistance, triggerLightningEvents);
}
shootedCount++;
}
return shooted; //starlight
}
}

View file

@ -4,10 +4,11 @@ using Content.Server.Singularity.Events;
using Content.Shared.Singularity.Components;
using Content.Shared.Singularity.EntitySystems;
using Content.Shared.Singularity.Events;
using Content.Server.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;
@ -36,11 +37,6 @@ public sealed class SingularitySystem : SharedSingularitySystem
/// </summary>
public const float BaseEntityEnergy = 1f;
/// <summary>
/// Whether or not the singuloo has eaten the supermatter crystal
/// </summary>
public bool HasEatenSM = false;
public override void Initialize()
{
base.Initialize();
@ -107,7 +103,7 @@ public sealed class SingularitySystem : SharedSingularitySystem
{
// 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 when HasEatenSM => 6,
>= 5000 => 6,
>= 2000 => 5,
>= 1000 => 4,
>= 500 => 3,
@ -220,8 +216,7 @@ public sealed class SingularitySystem : SharedSingularitySystem
// Don't double count singulo food
if (HasComp<SinguloFoodComponent>(args.Entity))
return;
if (HasComp<SupermatterComponent>(uid))
HasEatenSM = true;
AdjustEnergy(uid, BaseEntityEnergy, singularity: comp);
}

View file

@ -1,276 +0,0 @@
using Content.Shared.Atmos;
using Robust.Shared.Audio;
namespace Content.Server.Supermatter.Components;
[RegisterComponent]
public sealed partial class SupermatterComponent : Component
{
/// <summary>
/// The damage taken from direct hits, e.g. laser weapons
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float AVExternalDamage = 0f;
//
[ViewVariables(VVAccess.ReadOnly)]
public float HeatAccumulatorRate = 0.5f;
[ViewVariables(VVAccess.ReadOnly)]
public float AVHeatAccumulator = 0f;
[ViewVariables(VVAccess.ReadOnly)]
public float RadiationAccumulatorRate = 0.3f;
[ViewVariables(VVAccess.ReadOnly)]
public float AVRadiationAccumulator = 0f;
[ViewVariables(VVAccess.ReadOnly)]
public float LightingAccumulatorThreshold = 2f;
[ViewVariables(VVAccess.ReadOnly)]
public float LightingAccumulatorRate = 0.1f;
[ViewVariables(VVAccess.ReadOnly)]
public float AVLightingAccumulator = 0f;
[ViewVariables(VVAccess.ReadOnly)]
public float InternalEnergyAccumulatorRate = 0.1f;
/// <summary>
/// Lightning prototype IDs that the supermatter should spit out.
/// </summary>
public readonly string[] LightningPrototypeIDs =
{
"Lightning",
"ChargedLightning",
"SuperchargedLightning",
"HyperchargedLightning"
};
public readonly string SliverPrototype = "SupermatterSliver";
[DataField("zapSound")]
public SoundSpecifier SupermatterZapSound = new SoundPathSpecifier("/Audio/Weapons/emitter2.ogg");
[DataField("calmAmbienceSound")]
public SoundSpecifier CalmAmbienceSound = new SoundPathSpecifier("/Audio/Ambience/Objects/supermatter_calm.ogg");
[DataField("delamAmbienceSound")]
public SoundSpecifier DelamAmbienceSound = new SoundPathSpecifier("/Audio/Ambience/Objects/supermatter_delam.ogg");
[ViewVariables]
public SoundSpecifier CurrentAmbience = new SoundPathSpecifier("/Audio/Ambience/Objects/supermatter_calm.ogg");
[DataField("teslaSpawnPrototype")]
public string TeslaPrototype = "TeslaEnergyBall";
[DataField("singularitySpawnPrototype")]
public string SingularityPrototype = "Singularity";
[DataField("supermatterKudzuSpawnPrototype")]
public string SupermatterKudzuPrototype = "SupermatterKudzu";
/// <summary>
/// If a supermatter sliver has been removed. Lowers the delamination countdown time.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public bool SliverRemoved = false;
/// <summary>
/// Indicates whether supermatter crystal is active or not.
/// </summary>
[DataField("activated")]
public bool Activated = false;
[ViewVariables]
public GasMixture AbsorbedGasMix = new();
/// <summary>
/// Delta time between Update() calls storage.
/// </summary>
public float DeltaTime = 0f;
public float UpdateTimerAccumulator = 0f;
public float AnnouncementTimerAccumulator = 0f;
/// <summary>
/// Amount of seconds to pass before another SM cycle.
/// </summary>
[DataField("updateTimer")]
public float UpdateTimer = 1f;
/// <summary>
/// Amount of seconds to pass before makes an announcement.
/// </summary>
[DataField("announcementTimer")]
public float AnnouncementTimer = 60f;
[ViewVariables(VVAccess.ReadWrite)]
public int? PreferredDelamType = 0;
/// <summary>
/// The time in seconds for crystal to delaminate.
/// </summary>
[DataField("countdownTimer")]
public float DelamCountdownTimerRaw = 120f;
public float DelamCountdownTimer => SliverRemoved ? DelamCountdownTimerRaw / 2 : DelamCountdownTimerRaw;
public bool DelamAnnouncementHappened = false;
[ViewVariables(VVAccess.ReadWrite)]
public float DelamCountdownAccumulator = 0f;
/// <summary>
/// The portion of gasmix we should absorb.
/// </summary>
[DataField("gasAbsorptionRatio")]
public float AbsorptionRatio = .15f;
/// <summary>
/// This value effects gas output, damage and power generation.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float InternalEnergy = 0f;
/// <summary>
/// The amount of damage the SM currently has.
/// </summary>
[DataField("damage")]
public float Damage = 0f;
/// <summary>
/// The amount of damage SM had before the cycle.
/// </summary>
public float DamageArchive = 0f;
/// <summary>
/// The temperature at which the supermatter crystal will begin to take damage.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float TempLimit = Atmospherics.T0C + HeatPenaltyThreshold;
/// <summary>
/// Multiplies our gas waste amount and temperature.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float WasteMultiplier = 0f;
[DataField("damageDangerPoint")]
public float DamageDangerPoint = 50f;
[DataField("damageEmergencyPoint")]
public float DamageEmergencyPoint = 75f;
[DataField("damageDelaminationPoint")]
public float DelaminationPoint = 100f;
[ViewVariables(VVAccess.ReadOnly)]
public bool AreWeDelaming = false;
/// <summary>
/// Affects the heat SM makes.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float GasHeatModifier = 0f;
/// <summary>
/// Affters the minimum point at which SM takes damage.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float GasHeatResistance = 0f;
/// <summary>
/// How much power decay is negated. Complete power decay negation at 1.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float GasPowerlossInhibition = 0f;
[ViewVariables(VVAccess.ReadOnly)]
public float ThermalСonductivity = 0f;
/// <summary>
/// Affects the power gain the SM experiences from heat.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float HeatPowerGeneration = 0f;
/// <summary>
/// Lesser than that and it's not worth processing.
/// </summary>
public const float MinimumMoleCount = .01f;
public const float HeatPenaltyThreshold = 40f;
public const float PowerPenaltyThreshold = 3f;
public const float MolePenaltyThreshold = 900f;
public const float ThermalReleaseModifier = 4f;
public const float PlasmaReleaseModifier = 1.5f;
public const float OxygenReleaseModifier = 2.5f;
public const float GasHeatPowerScaling = 1f / 6f;
/// <summary>
/// Stores gas properties used for the supermatter.
/// Array values should alwayd match gas enum values.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public GasFact[] GasFacts =
{
new (thermalConductivity: 2.4f, heatPowerGeneration: 1f), // o2
new (thermalConductivity: 2.0f, heatModifier: -2.5f, heatPowerGeneration: -1), // n2
new (thermalConductivity: 1.2f, heatModifier: 1f, heatPowerGeneration: 1f, powerlossInhibition: 1f), // co2
new (thermalConductivity: 6.0f, heatModifier: 14f, heatPowerGeneration: 1f), // plasma
new (thermalConductivity: 3.0f, heatModifier: 9f, heatPowerGeneration: 1f), // tritium
new (thermalConductivity: 1.4f, heatModifier: 11f, heatPowerGeneration: 1f), // vapor
new (thermalConductivity: 1.6f, heatPowerGeneration: .5f), // ommonium
new (thermalConductivity: 1.3f, heatResistance: 5f), // n2o
new (thermalConductivity: 9.9f, heatModifier: 9f, heatResistance: 1f, heatPowerGeneration: 1f), // frezon
};
}
/// <summary>
/// Stores gas properties used for the supermatter.
/// </summary>
[Serializable]
[DataDefinition]
public sealed partial class GasFact
{
/// <summary>
/// Affects the amount of power the main SM zap makes.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float ThermalСonductivity;
/// <summary>
/// Affects the heat SM makes.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float HeatModifier;
/// <summary>
/// Affters the minimum point at which SM takes damage.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float HeatResistance;
/// <summary>
/// Affects the power gain the SM experiences from heat.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float HeatPowerGeneration;
/// <summary>
/// How much power decay is negated. Complete power decay negation at 1.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float PowerlossInhibition;
public GasFact(float? thermalConductivity = null, float? heatModifier = null, float? heatResistance = null, float? heatPowerGeneration = null, float? powerlossInhibition = null)
{
ThermalСonductivity = thermalConductivity ?? 1;
HeatModifier = heatModifier ?? 1;
HeatResistance = heatResistance ?? 0;
HeatPowerGeneration = heatPowerGeneration ?? 0;
PowerlossInhibition = powerlossInhibition ?? 0;
}
}
/// <summary>
/// Type of delamination that should occur.
/// </summary>
public enum DelamType : sbyte
{
Explosion = 0,
Tesla = 1,
Singularity = 2,
ResonanceCascade = 3,
}

View file

@ -1,504 +0,0 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Supermatter.Components;
using Content.Shared.Atmos;
using Content.Server.Lightning;
using Content.Shared.Radiation.Components;
using Content.Shared.Interaction;
using Content.Server.Audio;
using Content.Shared.Audio;
using Content.Server.Station.Systems;
using Content.Server.Station.Components;
using Content.Server.Anomaly;
using Content.Shared.Damage;
using Content.Shared.Tag;
using Content.Shared.DoAfter;
using Content.Server.Popups;
using Content.Shared.Supermatter;
using Content.Server.Administration.Logs;
using Robust.Shared.Physics.Events;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
using Content.Shared.Database;
using Content.Server.Chat.Systems;
using System.Text;
using Content.Server.AlertLevel;
using Content.Shared.Examine;
using Content.Server.DoAfter;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Kitchen.Components;
using Content.Shared.Weapons.Melee.EnergySword;
using Content.Shared.Singularity.Components;
using System;
using Robust.Shared.Audio;
namespace Content.Server.Supermatter.EntitySystems;
public sealed class SupermatterSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmos = default!;
[Dependency] private readonly SharedAudioSystem _sound = default!;
[Dependency] private readonly LightningSystem _lightning = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly AlertLevelSystem _alert = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly AmbientSoundSystem _ambience = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly AnomalySystem _anomaly = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly ExplosionSystem _explosion = default!;
public static SoundSpecifier VaporizeSound =
new SoundPathSpecifier("/Audio/Effects/Grenades/Supermatter/supermatter_start.ogg");
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SupermatterComponent, StartCollideEvent>(OnCollide);
SubscribeLocalEvent<SupermatterComponent, InteractUsingEvent>(OnClick);
SubscribeLocalEvent<SupermatterComponent, SupermatterDoAfterEvent>(OnGetSliver);
SubscribeLocalEvent<SupermatterComponent, DamageChangedEvent>(OnGetHit);
SubscribeLocalEvent<SupermatterComponent, ExaminedEvent>(OnExamine);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var sm in EntityQuery<SupermatterComponent>())
{
if (!sm.Activated)
return;
var uid = sm.Owner;
sm.DeltaTime = frameTime;
sm.UpdateTimerAccumulator += frameTime;
if (sm.UpdateTimerAccumulator >= sm.UpdateTimer)
{
sm.UpdateTimerAccumulator = 0f;
Cycle(uid, sm);
}
}
}
public void Cycle(EntityUid uid, SupermatterComponent sm)
{
sm.AnnouncementTimerAccumulator++;
ProcessAtmos(uid, sm);
ProcessPower(uid, sm);
ProcessDamage(uid, sm);
// due to how damage calculation works, it will do the announcement only if sm is consistently taking damage
if (sm.Damage > sm.DamageArchive)
{
if (sm.AnnouncementTimerAccumulator > sm.AnnouncementTimer)
{
sm.AnnouncementTimerAccumulator = 0f;
var loc = "danger";
if (sm.Damage > sm.DamageEmergencyPoint)
loc = "critical";
SupermatterAlert(uid, Loc.GetString($"supermatter-announcement-{loc}", ("integrity", (int)(sm.DelaminationPoint - sm.Damage))));
}
}
sm.DamageArchive = sm.Damage;
if (sm.AreWeDelaming)
DelamCountdown(uid, sm);
ProcessWaste(uid, sm);
HandleSound(uid, sm);
}
/// <summary>
/// Calculate power based on gases absorbed.
/// </summary>
private void ProcessAtmos(EntityUid uid, SupermatterComponent sm)
{
var mix = _atmos.GetContainingMixture((uid, Transform(uid)), true, true) ?? new();
var absorbedMix = mix?.RemoveRatio(sm.AbsorptionRatio) ?? new();
// calculate gases
var gasPercentages = new float[Enum.GetValues(typeof(Gas)).Length];
var thermalСonductivity = 0f;
var heatModifier = 0f;
var heatResistance = 0f;
var heatPowerGeneration = 0f;
var powerlossInhibition = 0f;
var moles = absorbedMix.TotalMoles;
if (moles <= SupermatterComponent.MinimumMoleCount)
return;
for (int i = 0; i < gasPercentages.Length; i++)
{
var moleCount = absorbedMix.GetMoles(i);
if (moleCount <= SupermatterComponent.MinimumMoleCount)
continue;
gasPercentages[i] = moleCount / moles;
var smGas = sm.GasFacts[i];
thermalСonductivity += smGas.ThermalСonductivity * gasPercentages[i];
heatModifier += smGas.HeatModifier * gasPercentages[i];
heatResistance += smGas.HeatResistance * gasPercentages[i];
heatPowerGeneration += smGas.HeatPowerGeneration * gasPercentages[i];
powerlossInhibition += smGas.PowerlossInhibition * gasPercentages[i];
}
heatPowerGeneration = Math.Clamp(heatPowerGeneration, 0, 1);
powerlossInhibition = Math.Clamp(powerlossInhibition, 0, 1);
sm.AbsorbedGasMix = absorbedMix;
sm.ThermalСonductivity = thermalСonductivity;
sm.GasHeatModifier = heatModifier;
sm.GasHeatResistance = heatResistance;
sm.HeatPowerGeneration = heatPowerGeneration;
sm.GasPowerlossInhibition = powerlossInhibition;
}
/// <summary>
/// Shoot lightning and radiate everything based on whatever power there is.
/// </summary>
private void ProcessPower(EntityUid uid, SupermatterComponent sm)
{
var powerHeat = sm.HeatPowerGeneration * sm.AbsorbedGasMix.Temperature * SupermatterComponent.GasHeatPowerScaling;
var powerloss = -1 * sm.GasPowerlossInhibition;
var atmosStrength = Math.Clamp((powerHeat + powerloss) * 0.2f, 0, 2);
var damageStrength = Math.Clamp(sm.Damage / sm.DelaminationPoint, 0, 1);
var strength = Math.Clamp(atmosStrength + damageStrength, 0, 4);
var damageExternal = sm.AVExternalDamage + strength;
sm.AVExternalDamage = 0f;
sm.AVHeatAccumulator = Math.Clamp(sm.AVHeatAccumulator + (damageExternal * sm.HeatAccumulatorRate), 0, 10);
sm.AVRadiationAccumulator = Math.Clamp(sm.AVRadiationAccumulator + (damageExternal * sm.RadiationAccumulatorRate), 0, 10);
sm.AVLightingAccumulator = Math.Clamp(sm.AVLightingAccumulator + (damageExternal * sm.LightingAccumulatorRate), 0, 10);
sm.InternalEnergy = Math.Clamp(sm.InternalEnergy + (damageExternal * sm.InternalEnergyAccumulatorRate), 0, 10);
if (sm.AVLightingAccumulator > sm.LightingAccumulatorThreshold)
{
var lightningProto = sm.LightningPrototypeIDs[(int)Math.Clamp(sm.AVLightingAccumulator, 0, 3)];
_lightning.ShootRandomLightnings(uid, 3, (int)sm.AVLightingAccumulator, lightningProto);
sm.AVLightingAccumulator = 0;
}
Comp<RadiationSourceComponent>(uid).Intensity = 1 + sm.AVRadiationAccumulator;
sm.AVRadiationAccumulator /= 2;
sm.InternalEnergy /= 2;
var mix = _atmos.GetContainingMixture((uid, Transform(uid)), true, true) ?? new();
mix.Temperature += sm.AVHeatAccumulator * 4;
sm.AVHeatAccumulator /= sm.ThermalСonductivity;
}
/// <summary>
/// React to damage dealt by all doodads.
/// </summary>
private void ProcessDamage(EntityUid uid, SupermatterComponent sm)
{
var additiveTempBase = Atmospherics.T0C + SupermatterComponent.HeatPenaltyThreshold;
var tempLimitBase = additiveTempBase;
var tempLimitGas = sm.GasHeatResistance * additiveTempBase;
var tempLimitMoles = Math.Clamp(2 - sm.AbsorbedGasMix.TotalMoles / 100, 0, 1) * additiveTempBase;
sm.TempLimit = Math.Max(tempLimitBase + tempLimitGas + tempLimitMoles, Atmospherics.TCMB);
var damageHeat = Math.Clamp((sm.AbsorbedGasMix.Temperature - sm.TempLimit) / 24000, 0, .15f);
var damagePower = Math.Clamp((sm.InternalEnergy - SupermatterComponent.PowerPenaltyThreshold), 0, .1f);
var damageMoles = Math.Clamp((sm.AbsorbedGasMix.TotalMoles - SupermatterComponent.MolePenaltyThreshold) / 3200, 0, .1f);
var damageHealHeat = 0f;
if (sm.AbsorbedGasMix.TotalMoles > 0)
damageHealHeat = Math.Clamp((sm.AbsorbedGasMix.TotalMoles - sm.TempLimit) / 6000, -.1f, 0);
var totalDamage = damageHeat + damagePower + damageMoles + damageHealHeat;
var oldDamage = sm.Damage;
sm.Damage += Math.Max(totalDamage, 0);
if (sm.Damage >= sm.DelaminationPoint && !sm.AreWeDelaming)
{
sm.AreWeDelaming = true;
SupermatterAlert(uid, Loc.GetString("supermatter-announcement-delam"));
}
if (sm.Damage > sm.DamageDangerPoint && _random.Prob(.0001f))
GenerateAnomaly(uid);
}
/// <summary>
/// Process waste, release hot plasma and oxygen.
/// </summary>
private void ProcessWaste(EntityUid uid, SupermatterComponent sm)
{
sm.WasteMultiplier = Math.Clamp(1f + sm.GasHeatModifier, .5f, float.PositiveInfinity);
var mix = _atmos.GetContainingMixture((uid, Transform(uid)), true, true) ?? new();
var mergeMix = sm.AbsorbedGasMix;
mergeMix.Temperature += .65f * sm.WasteMultiplier * SupermatterComponent.ThermalReleaseModifier;
mergeMix.Temperature = Math.Clamp(mergeMix.Temperature, Atmospherics.TCMB, 2500 * sm.WasteMultiplier);
mergeMix.AdjustMoles(Gas.Plasma, Math.Max(.65f * sm.InternalEnergy * sm.WasteMultiplier * SupermatterComponent.PlasmaReleaseModifier, 0));
mergeMix.AdjustMoles(Gas.Oxygen, Math.Max(.65f * sm.InternalEnergy * sm.WasteMultiplier * SupermatterComponent.OxygenReleaseModifier, 0));
_atmos.Merge(mix, mergeMix);
}
/// <summary>
/// Swaps out ambience sounds whether the SM is delamming or not.
/// </summary>
private void HandleSound(EntityUid uid, SupermatterComponent sm)
{
var ambienceComp = Comp<AmbientSoundComponent>(uid);
if (sm.AreWeDelaming)
{
if (sm.CurrentAmbience != sm.DelamAmbienceSound)
sm.CurrentAmbience = sm.DelamAmbienceSound;
}
else if (sm.CurrentAmbience != sm.CalmAmbienceSound)
sm.CurrentAmbience = sm.CalmAmbienceSound;
if (ambienceComp.Sound != sm.CurrentAmbience)
_ambience.SetSound(uid, sm.CurrentAmbience, ambienceComp);
}
/// <summary>
/// Make console alerts, set station codes, etc. etc.
/// </summary>
/// <param name="customSender"> If true, the message will be sent from Central Command </param>
public void SupermatterAlert(EntityUid uid, string message, bool customSender = false)
{
_chat.DispatchStationAnnouncement(uid, message, customSender ? "Central Command" : Loc.GetString("supermatter-announcement-sender"), playDefault: false, playTts: true, colorOverride: Color.Yellow);
}
private void GenerateAnomaly(EntityUid uid, float amount = 1)
{
var stationUid = _station.GetOwningStation(uid);
if (stationUid == null || !TryComp<StationDataComponent>(stationUid, out var data))
return;
var grid = _station.GetLargestGrid(data);
if (grid == null)
return;
for (var i = 0; i < amount; i++)
{
_anomaly.SpawnOnRandomGridLocation((EntityUid)grid, "RandomAnomalySpawner");
_adminLogger.Add(LogType.Anomaly, LogImpact.Medium, $"An anomaly has been spawned by the supermatter crystal.");
}
}
/// <summary>
/// Vaporizes the targeted uid.
/// </summary>
private void Vaporize(EntityUid uid, EntityUid smUid)
{
if (EntityManager.IsQueuedForDeletion(uid))
return;
if (_tagSystem.HasTag(uid, "EmitterBolt")
|| HasComp<SingularityComponent>(uid))
return;
if (TryComp<SupermatterComponent>(smUid, out var sm))
sm.AVExternalDamage += 1f;
_sound.PlayPvs(VaporizeSound, smUid);
EntityManager.QueueDeleteEntity(uid);
// getting discombobulated by the SM is the same as permanent round removal so why not log that
_adminLogger.Add(LogType.Action, LogImpact.High, $"{EntityManager.ToPrettyString(uid):player} has been vaporized by the supermatter.");
}
/// <summary>
/// Handle supermatter delamination and the end of the station.
/// </summary>
private void Delaminate(EntityUid uid, SupermatterComponent sm)
{
sm.PreferredDelamType = sm.PreferredDelamType ?? (int)ChooseDelam(sm);
Delaminate(uid, sm, (DelamType)sm.PreferredDelamType);
}
/// <summary>
/// Handle supermatter delamination based on it's type.
/// </summary>
private void Delaminate(EntityUid uid, SupermatterComponent sm, DelamType type)
{
GenerateAnomaly(uid, _random.Next(2, 4));
var prototypeId = string.Empty;
switch (type)
{
case DelamType.Explosion:
default:
_explosion.TriggerExplosive(uid); return;
case DelamType.Tesla:
prototypeId = sm.TeslaPrototype; break;
case DelamType.Singularity:
prototypeId = sm.SingularityPrototype; break;
case DelamType.ResonanceCascade:
prototypeId = sm.SupermatterKudzuPrototype; break;
}
if (string.IsNullOrWhiteSpace(prototypeId))
return;
EntityManager.SpawnEntity(prototypeId, Transform(uid).Coordinates);
}
/// <summary>
/// Choose a prefered delamination type. The supermatter is picky.
/// </summary>
private DelamType ChooseDelam(SupermatterComponent sm)
{
if (sm.AbsorbedGasMix.TotalMoles >= SupermatterComponent.MolePenaltyThreshold)
return DelamType.Singularity;
if (sm.InternalEnergy > SupermatterComponent.PowerPenaltyThreshold)
return DelamType.Tesla;
// todo: add resonance cascade when hypernob and antinob gases get added
// or a destabilizing crystal. bet it will take years :godo:
return DelamType.Explosion;
}
/// <summary>
/// Handle the delamination countdown, alerts, etc.
/// </summary>
private void DelamCountdown(EntityUid uid, SupermatterComponent sm)
{
if (!sm.DelamAnnouncementHappened)
{
var delamType = ChooseDelam(sm);
var stationUid = _station.GetStationInMap(Transform(uid).MapID);
var alertLevel = string.Empty;
var announcementLoc = string.Empty;
var sb = new StringBuilder();
sb.Append(Loc.GetString("supermatter-announcement-delam"));
switch (delamType)
{
case DelamType.Explosion:
default:
announcementLoc = "supermatter-announcement-delam-explosion";
alertLevel = "yellow";
break;
case DelamType.Tesla:
announcementLoc = "supermatter-announcement-delam-tesla";
alertLevel = "delta";
break;
case DelamType.Singularity:
announcementLoc = "supermatter-announcement-delam-singuloose";
alertLevel = "delta";
break;
case DelamType.ResonanceCascade:
announcementLoc = "supermatter-announcement-delam-cascade";
alertLevel = "delta";
break;
}
sb.AppendLine(" " + Loc.GetString(announcementLoc));
sb.Append(Loc.GetString("supermatter-announcement-delam-countdown", ("seconds", sm.DelamCountdownTimer)));
// make it cancellable in case there are crazy engineers that managed to contain the delam
if (stationUid != null)
_alert.SetLevel(stationUid.Value, alertLevel, true, true, true, false);
SupermatterAlert(uid, sb.ToString());
sm.DelamAnnouncementHappened = true;
}
if (sm.Damage < sm.DelaminationPoint)
{
// yay!
sm.DelamCountdownAccumulator = 0;
sm.DelamAnnouncementHappened = false;
sm.AreWeDelaming = false;
SupermatterAlert(uid, Loc.GetString("supermatter-announcement-safe"));
return;
}
if (sm.DelamCountdownAccumulator >= sm.DelamCountdownTimer) // uh oh
Delaminate(uid, sm);
sm.DelamCountdownAccumulator++;
}
private void OnCollide(EntityUid uid, SupermatterComponent sm, StartCollideEvent args)
{
if (!sm.Activated)
sm.Activated = true;
Vaporize(args.OtherEntity, uid);
}
private void OnClick(EntityUid uid, SupermatterComponent sm, InteractUsingEvent args)
{
if (HasComp<SharpComponent>(args.Used))
{
if (sm.Damage >= 100)
{
_popup.PopupEntity(Loc.GetString("supermatter-tamper-fail"), args.User, args.User);
return;
}
if (HasComp<SMCuttableComponent>(args.Used))
{
_adminLogger.Add(LogType.Action, LogImpact.High, $"{EntityManager.ToPrettyString(args.User):player} is trying to extract a sliver from the supermatter crystal.");
_popup.PopupEntity(Loc.GetString("supermatter-tamper-begin"), args.User, args.User);
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, 30, new SupermatterDoAfterEvent(), uid, target: uid, used: args.Used)
{
BreakOnDamage = true,
BreakOnHandChange = true,
BreakOnMove = true,
BreakOnWeightlessMove = false,
NeedHand = true,
RequireCanInteract = true,
};
_doAfter.TryStartDoAfter(doAfterArgs);
}
else
{
Vaporize(args.Used, uid);
}
}
}
private void OnGetSliver(EntityUid uid, SupermatterComponent sm, SupermatterDoAfterEvent args)
{
if (args.Cancelled)
return;
sm.Damage += 10; // your criminal actions will not go unnoticed
SupermatterAlert(uid, Loc.GetString("supermatter-announcement-tamper", ("integrity", (int)(100 - sm.Damage))));
Spawn(sm.SliverPrototype, _transform.GetMapCoordinates(args.User));
_popup.PopupEntity(Loc.GetString("supermatter-tamper-end"), args.User, args.User);
}
private void OnGetHit(EntityUid uid, SupermatterComponent sm, DamageChangedEvent args)
{
if (!sm.Activated)
sm.Activated = true;
sm.AVExternalDamage += args.DamageDelta?.GetTotal().Value / 100 ?? 0;
}
private void OnExamine(EntityUid uid, SupermatterComponent sm, ExaminedEvent args)
{
if (args.IsInDetailsRange) // get all close to it
{
args.PushMarkup(Loc.GetString("supermatter-examine-integrity", ("integrity", (int)(100 - sm.Damage))));
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Shared.Atmos;
using Content.Shared.FixedPoint;
using Robust.Shared.Physics;
namespace Content.Server.Starlight.Energy.Supermatter;
internal static class Const
{
public static FixedPoint2 HeatPercent = 0.82f;
public static FixedPoint2 BreakPercent = 0.04f;
public static FixedPoint2 LightingPercent = 0.03f;
public static FixedPoint2 RadiationPercent = 0.11f;
public static FixedPoint2 DamageMultiplayer = 3.14f;
public static GasProperties[] GasProperties =
[
new (0.24f, 1.20f, 1.4f), // oxygen
new (0.20f, 1.46f, 1.5f), // nitrogen
new (0.12f, 2.21f, 3.5f), // carbon dioxide
new (0.60f, 0.61f, 1.3f), // plasma
new (0.30f, 0.45f, 1.2f), // tritium
new (0.14f, 2.31f, 2.5f), // vapor
new (0.16f, 2.11f, 4.4f), // ommonium
new (0.13f, 2.19f, 2.2f), // nitrous oxide
new (1.00f, 0.01f, 1.1f), // frezon
];
public static float MinPressure = 33f;
public static float MaxPressure = 303.9f;
public static float MaxTemperature = Atmospherics.T0C + 150;
public static float EvaporationCompensation = 10;
public static FixedPoint2 MaxDamagePerSecond = (100f / 120f) + RegenerationPerSecond; // Ensures it takes at least 2 minutes to deplete
public static FixedPoint2 RegenerationPerSecond = 0.3f;
public static string[] AudioCrack = ["/Audio/_Starlight/Effects/supermatter/crystal_crack_1.ogg", "/Audio/_Starlight/Effects/supermatter/crystal_crack_2.ogg"];
public static string[] AudioBurn = ["/Audio/_Starlight/Effects/supermatter/burning_1.ogg", "/Audio/_Starlight/Effects/supermatter/burning_2.ogg", "/Audio/_Starlight/Effects/supermatter/burning_3.ogg"];
public static string AudioEvaporate = "/Audio/_Starlight/Effects/supermatter/emitter2.ogg";
}
public record struct GasProperties(float HeatTransferPerMole, float HeatModifier, float RadiationStability);

View file

@ -0,0 +1,146 @@
using System;
using System.Linq;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Chat.Managers;
using Content.Server.Lightning;
using Content.Server.Radio.EntitySystems;
using Content.Server.Starlight.Energy.Supermatter;
using Content.Shared.Abilities.Goliath;
using Content.Shared.Atmos;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Directions;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Content.Shared.Random.Helpers;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Random;
namespace Content.Server.Starlight.Energy.Supermatter;
public sealed class SupermatterCascadeSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
private readonly LinkedList<Branch> _branches = [];
private LinkedListNode<Branch>? node;
private readonly string[] _prototypes = ["Cascad1", "Cascad2", "Cascad3", "Cascad4", "Cascad5", "Cascad6"];
public override void Initialize()
{
}
public override void Update(float frameTime)
{
node ??= _branches.First;
if (node == null) return;
var branch = node.Value;
var nextNode = node.Next;
branch.Lifetime--;
if (branch.Lifetime <= 0)
{
_branches.Remove(node);
node = nextNode;
return;
}
var rand = _random.Next(0, 100);
if (rand < 10)
{
branch.Direction = branch.RotateLeft();
}
else if (rand < 20)
{
branch.Direction = branch.RotateRight();
}
else if (rand < 25 && _branches.Count < 10)
{
var leftBranch = new Branch
{
Coordinates = branch.Coordinates,
Direction = branch.RotateLeft(),
Lifetime = branch.Lifetime
};
var rightBranch = new Branch
{
Coordinates = branch.Coordinates,
Direction = branch.RotateRight(),
Lifetime = branch.Lifetime
};
_branches.AddLast(leftBranch);
_branches.AddLast(rightBranch);
_branches.Remove(node);
node = nextNode;
return;
}
branch.Coordinates = branch.Coordinates.Offset(branch.Direction);
if (_transform.GetGrid(branch.Coordinates) is not { } grid
|| !TryComp<MapGridComponent>(grid, out var gridComp)
|| !_map.TryGetTileRef(grid, gridComp, branch.Coordinates, out var tileRef)
|| tileRef.IsSpace())
{
_branches.Remove(node);
node = nextNode;
return;
}
branch.Coordinates = branch.Coordinates.SnapToGrid(gridComp);
foreach (var entity in _lookup.GetEntitiesIntersecting(branch.Coordinates))
QueueDel(entity);
SpawnAttachedTo(_random.Pick(_prototypes), branch.Coordinates);
node = nextNode;
}
public void StartCascade(EntityCoordinates coordinates)
{
for (int i = 0; i < 8; i += 2)
_branches.AddLast(new Branch
{
Coordinates = coordinates,
Direction = (Direction)i,
Lifetime = 100
});
}
private sealed class Branch
{
public EntityCoordinates Coordinates { get; set; }
public Direction Direction { get; set; }
public int Lifetime { get; set; }
public Direction RotateLeft() => Direction switch
{
Direction.North => Direction.NorthWest,
Direction.NorthWest => Direction.West,
Direction.West => Direction.SouthWest,
Direction.SouthWest => Direction.South,
Direction.South => Direction.SouthEast,
Direction.SouthEast => Direction.East,
Direction.East => Direction.NorthEast,
Direction.NorthEast => Direction.North,
_ => Direction,
};
public Direction RotateRight() => Direction switch
{
Direction.North => Direction.NorthEast,
Direction.NorthEast => Direction.East,
Direction.East => Direction.SouthEast,
Direction.SouthEast => Direction.South,
Direction.South => Direction.SouthWest,
Direction.SouthWest => Direction.West,
Direction.West => Direction.NorthWest,
Direction.NorthWest => Direction.North,
_ => Direction,
};
}
}

View file

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

View file

@ -19,7 +19,7 @@ namespace Content.Shared.Atmos
public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true};
// No access, to ensure immutable mixtures are never accidentally mutated.
[Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), typeof(GasEnumerator), Other = AccessPermissions.None)]
[Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), typeof(GasEnumerator), Other = AccessPermissions.Read)] // Sunrise-Edit
[DataField]
public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases];

View file

@ -19,6 +19,12 @@ public sealed partial class FlatpackComponent : Component
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public ProtoId<ToolQualityPrototype> QualityNeeded = "Pulsing";
/// <summary>
/// Is Flatpack allowed to unpuck on tables? (For microwaves, etc.)
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public bool AllowUnpackOnTables = false; // Starlight-edit
/// <summary>
/// The entity that is spawned when this object is unpacked.
/// </summary>

View file

@ -1,4 +1,5 @@
using Content.Shared._Sunrise.Economy;
using System.Linq;
using Content.Shared.Construction.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.Containers.ItemSlots;
@ -6,6 +7,7 @@ using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Materials;
using Content.Shared.Tag;
using Content.Shared.Popups;
using Content.Shared.Tools.Systems;
using Robust.Shared.Audio.Systems;
@ -31,6 +33,7 @@ public abstract class SharedFlatpackSystem : EntitySystem
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedToolSystem _tool = default!;
[Dependency] private readonly TagSystem _tag = default!;
/// <inheritdoc/>
public override void Initialize()
@ -80,10 +83,12 @@ public abstract class SharedFlatpackSystem : EntitySystem
var buildPos = _map.TileIndicesFor(grid, gridComp, xform.Coordinates);
var coords = _map.ToCenterCoordinates(grid, buildPos);
// TODO FLATPAK
// Make this logic smarter. This should eventually allow for shit like building microwaves on tables and such.
// Also: make it ignore ghosts
if (_entityLookup.AnyEntitiesIntersecting(coords, LookupFlags.Dynamic | LookupFlags.Static))
// TODO FLATPACK
// make it ignore ghosts
// Starlight-start
if (_entityLookup.GetEntitiesIntersecting(coords, LookupFlags.Dynamic | LookupFlags.Static)
.Any(entity => entity != uid && (!_tag.HasTag(entity, "Table") || !ent.Comp.AllowUnpackOnTables)))
// Starlight-end
{
// this popup is on the server because the predicts on the intersection is crazy
if (_net.IsServer)

View file

@ -1,10 +0,0 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.Supermatter;
[Serializable, NetSerializable]
public sealed partial class SupermatterDoAfterEvent : SimpleDoAfterEvent
{
}

View file

@ -0,0 +1,19 @@
namespace Content.Shared.Abilities.Goliath;
public abstract class AccUpdateEntitySystem : EntitySystem
{
public override void Update(float frameTime)
{
_accumulator += frameTime;
if (_accumulator > Threshold)
{
AccUpdate();
_accumulator = 0;
}
}
protected virtual void AccUpdate()
{
}
protected virtual float Threshold { get; set; } = 0.35f;
private float _accumulator = 0f;
}

View file

@ -0,0 +1,33 @@
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Starlight.Energy.Supermatter;
[RegisterComponent, NetworkedComponent]
public sealed partial class SupermatterComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
public bool Activated = false;
[ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 AccHeat = 0f;
[ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 AccRadiation = 0f;
[ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 AccLighting = 0f;
[ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 AccBreak = 0f;
[ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 RadiationStability = 1f;
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 Durability = 100f;
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 LastSendedDurability = 100f;
}

View file

@ -62,8 +62,3 @@
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"

View file

@ -0,0 +1,9 @@
- files: ["crystal_crack_1.ogg","crystal_crack_2.ogg", "burning_1.ogg", "burning_2.ogg", "burning_3.ogg"]
license: "CC-BY-4.0"
copyright: 'Darkrell'
source: "https://github.com/ss14Starlight/space-station-14"
- 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"

View file

@ -4,7 +4,5 @@ ent-CMOAdvancedDefibrillatorStealObjective = { ent-BaseCMOStealObjective }
.desc = { ent-BaseCMOStealObjective.desc }
ent-PlutoniumCoreStealObjective = { ent-BaseTraitorStealObjective }
.desc = { ent-BaseTraitorStealObjective.desc }
ent-StealSupermatterSliverObjective = { ent-BaseTraitorStealObjective }
.desc = { ent-BaseTraitorStealObjective.desc }
ent-HandheldFaxStealObjective = { ent-BaseTraitorStealObjective }
.desc = { ent-BaseTraitorStealObjective.desc }

View file

@ -1,2 +0,0 @@
ent-SupermatterSliver = supermatter sliver
.desc = A shard from the station's supermatter engine. Highly radioactive.

View file

@ -1,5 +1,4 @@
objective-condition-steal-nuclear-bomb = nuclear bomb
objective-condition-steal-supermatter-sliver = Отрежьте кусочек от кристалла суперматерии.
objective-description-steal-supermatter-sliver = Используйте любой подходящий режущий инструмент. Лучше всего подойдёт скальпель. И постарайтесь не умереть от радиационного отравления.
objective-description-steal-handheld-fax = You need to steal a portable fax machine from a corporate representative.
objective-condition-steal-handheld-fax = Portable fax

View file

@ -4,7 +4,5 @@ ent-CMOAdvancedDefibrillatorStealObjective = { ent-BaseCMOStealObjective }
.desc = { ent-BaseCMOStealObjective.desc }
ent-PlutoniumCoreStealObjective = { ent-BaseTraitorStealObjective }
.desc = { ent-BaseTraitorStealObjective.desc }
ent-StealSupermatterSliverObjective = { ent-BaseTraitorStealObjective }
.desc = { ent-BaseTraitorStealObjective.desc }
ent-HandheldFaxStealObjective = { ent-BaseTraitorStealObjective }
.desc = { ent-BaseTraitorStealObjective.desc }

View file

@ -1,2 +0,0 @@
ent-SupermatterSliver = осколок суперматерии
.desc = Осколок от кристалла суперматерии станции. Сильно радиоактивен.

View file

@ -1,3 +1,2 @@
objective-condition-steal-nuclear-bomb = ядерную бомбу
objective-condition-steal-supermatter-sliver = Отрежьте кусочек от кристалла суперматерии.
objective-description-steal-supermatter-sliver = Используйте любой подходящий режущий инструмент. Лучше всего подойдёт скальпель. И постарайтесь не умереть от радиационного отравления.

View file

@ -1,5 +1,4 @@
# Traitor single items
steal-target-groups-supermatter-sliver = осколок суперматерии
steal-target-groups-hypospray = гипоспрей
steal-target-groups-handheld-crew-monitor = портативный монитор экипажа
steal-target-groups-clothing-outer-hardsuit-rd = экспериментальный исследовательский скафандр

View file

@ -231,6 +231,7 @@
layers:
- state: fax-machine
- type: Flatpack
allowUnpackOnTables: true
entity: FaxMachineBase
- type: entity
@ -251,6 +252,32 @@
- type: Flatpack
entity: ComputerCrewMonitoring
- type: entity
parent: BaseFlatpack
id: MicrowaveFlatpack
name: microwave flatpack
description: A flatpack used for constructing a microwave.
components:
- type: Sprite
layers:
- state: microwave
- type: Flatpack
allowUnpackOnTables: true
entity: KitchenMicrowave
- type: entity
parent: BaseFlatpack
id: SyndicateMicrowaveFlatpack
name: microwave flatpack
description: A flatpack used for constructing a microwave.
components:
- type: Sprite
layers:
- state: microwave
- type: Flatpack
allowUnpackOnTables: true
entity: SyndicateMicrowave
- type: entity
parent: BaseFlatpack
id: HydroponicsTrayFlatpack

View file

@ -1,24 +0,0 @@
- 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

View file

@ -40,6 +40,7 @@
- type: Tag
tags:
- ForceFixRotations
- Table
- type: entity
id: CounterBase

View file

@ -1,60 +0,0 @@
- type: entity
id: SupermatterCrystal
name: supermatter crystal
description: A strangely translucent and iridescent crystal.
placement:
mode: SnapgridCenter
components:
- type: Supermatter
- 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: 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

View file

@ -26,7 +26,6 @@
PlutoniumCoreStealObjective: 0.5
#MultiphaseEnergygunStealObjective: 1 # Sunrise-Edit: ОСЩ больше не появляется на станции
CMOAdvancedDefibrillatorStealObjective: 1
StealSupermatterSliverObjective: 1
HandheldFaxStealObjective: 1
# Sunrise-End

View file

@ -1,12 +1,5 @@
# Traitor single items
- type: stealTargetGroup
id: SupermatterSliver
name: steal-target-groups-supermatter-sliver
sprite:
sprite: Objects/Misc/supermatter_sliver.rsi
state: icon
- type: stealTargetGroup
id: Hypospray
name: steal-target-groups-hypospray

View file

@ -0,0 +1,9 @@
- type: cargoProduct
id: SupermatterFlatpackPurchase
icon:
sprite: _Starlight/Objects/Specific/supermatter.rsi
state: supermatter
product: SupermatterFlatpack
cost: 8000
category: cargoproduct-category-name-engineering
group: market

View file

@ -0,0 +1,67 @@
- type: entity
parent: BaseStructureDynamic
id: BaseStructureFlatpack
name: base structure flatpack
description: A flatpack used for constructing something.
categories:
- HideSpawnMenu
components:
- type: Animateable
- type: Transform
noRot: true
- type: Icon
sprite: Structures/Storage/Crates/generic.rsi
state: base
- type: Sprite
sprite: Objects/Devices/flatpack.rsi
layers:
- state: base
- state: overlay
color: "#cec8ac"
map: ["enum.FlatpackVisualLayers.Overlay"]
- state: icon-default
- type: InteractionOutline
- type: Physics
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.4,-0.4,0.4,0.29"
density: 50
mask:
- CrateMask #this is so they can go under plastic flaps
layer:
- MachineLayer
- type: Appearance
- type: Flatpack
boardColors:
command: "#334E6D"
medical: "#52B4E9"
service: "#9FED58"
engineering: "#EFB341"
security: "#DE3A3A"
science: "#D381C9"
supply: "#A46106"
cpu_command: "#334E6D"
cpu_medical: "#52B4E9"
cpu_service: "#9FED58"
cpu_engineering: "#EFB341"
cpu_security: "#DE3A3A"
cpu_science: "#D381C9"
cpu_supply: "#A46106"
- type: StaticPrice
price: 250
- type: entity
parent: [BaseStructureFlatpack, BaseEngineeringContraband]
id: SupermatterFlatpack
name: supermatter flatpack
description: A flatpack used for constructing a Supermatter Crystal.
components:
- type: Sprite
sprite: Objects/Devices/flatpack.rsi
layers:
- state: large
- type: Flatpack
entity: SupermatterCrystal

View file

@ -0,0 +1,141 @@
- type: entity
id: SupermatterCrystal
name: supermatter
description: A glowing supermatter crystal, with half an ID card lying next to it, and it says "Clown pira..." on it.
placement:
mode: SnapgridCenter
components:
- type: Supermatter
- type: Transform
noRot: true
anchored: true
- type: InteractionOutline
- type: Clickable
- type: Anchorable
delay: 2
- type: Physics
bodyType: Static
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.40,-0.40,0.40,0.40"
density: 190
friction: 0.8
mask:
- FullTileMask
layer:
- FullTileLayer
- type: Pullable
- type: Sprite
sprite: _Starlight/Objects/Specific/supermatter.rsi
state: supermatter
shader: unshaded
- type: Icon
sprite: _Starlight/Objects/Specific/supermatter.rsi
state: supermatter
- type: PointLight
enabled: true
color: "#FFFB97"
- type: Damageable
damageModifierSet: RGlass
damageContainer: StructuralInorganic
# - type: LightningTarget Tesla needs to interact too.
# priority: 1
- type: entity
id: Cascad1
name: cascad
description: Consequences of the supermatter crystal's cascade failure.
placement:
mode: SnapgridCenter
components:
- type: Damageable
damageContainer: StructuralInorganic
damageModifierSet: Glass
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 80
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]
- !type:PlaySoundBehavior
sound:
collection: GlassBreak
- type: Sprite
drawdepth: Walls
sprite: _Starlight/Objects/Specific/supermatter_cascade.rsi
layers:
- state: cascade_1
- type: Icon
sprite: _Starlight/Objects/Specific/supermatter_cascade.rsi
state: cascade_1
- type: Physics
bodyType: Static
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.5,-0.5,0.5,0.5"
layer:
- GlassLayer
- type: entity
id: Cascad2
parent: Cascad1
components:
- type: Sprite
layers:
- state: cascade_2
- type: Icon
sprite: _Starlight/Objects/Specific/supermatter_cascade.rsi
state: cascade_2
- type: entity
id: Cascad3
parent: Cascad1
components:
- type: Sprite
layers:
- state: cascade_3
- type: Icon
sprite: _Starlight/Objects/Specific/supermatter_cascade.rsi
state: cascade_3
- type: entity
id: Cascad4
parent: Cascad1
components:
- type: Sprite
layers:
- state: cascade_4
- type: Icon
sprite: _Starlight/Objects/Specific/supermatter_cascade.rsi
state: cascade_4
- type: entity
id: Cascad5
parent: Cascad1
components:
- type: Sprite
layers:
- state: cascade_5
- type: Icon
sprite: _Starlight/Objects/Specific/supermatter_cascade.rsi
state: cascade_5
- type: entity
id: Cascad6
parent: Cascad1
components:
- type: Sprite
layers:
- state: cascade_6
- type: Icon
sprite: _Starlight/Objects/Specific/supermatter_cascade.rsi
state: cascade_6

View file

@ -33,18 +33,6 @@
listings:
- UplinkCoreExtractionToolbox
- type: entity
categories: [ HideSpawnMenu ]
parent: BaseTraitorStealObjective
id: StealSupermatterSliverObjective
components:
- type: Objective
difficulty: 2.5
- type: StealCondition
stealGroup: SupermatterSliver
objectiveNoOwnerText: objective-condition-steal-supermatter-sliver
descriptionText: objective-description-steal-supermatter-sliver
- type: entity
parent: BaseTraitorStealObjective
id: HandheldFaxStealObjective

View file

@ -456,3 +456,6 @@
- type: Tag
id: SnapPop
- type: Tag
id: Table

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

View file

@ -48,6 +48,12 @@
},
{
"name": "hydroponics-tray"
},
{
"name": "large"
},
{
"name": "microwave"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

View file

@ -1,14 +0,0 @@
{
"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"
}
]
}

View file

@ -1,21 +0,0 @@
{
"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
]
]
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,24 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://tgstation13.org/wiki/images/a/a4/Supermatter-bg.gif",
"size": {
"x": 32,
"y": 48
},
"states": [
{
"name": "supermatter",
"delays": [
[
0.2,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,29 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://tgstation13.org/wiki/images/a/a4/Supermatter-bg.gif",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "cascade_1"
},
{
"name": "cascade_2"
},
{
"name": "cascade_3"
},
{
"name": "cascade_4"
},
{
"name": "cascade_5"
},
{
"name": "cascade_6"
}
]
}