# Conflicts: # Content.Client/Clothing/ClientClothingSystem.cs # Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs # Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs # Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml # Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs # Content.Server/Administration/Commands/AdminWhoCommand.cs # Content.Server/Bed/Sleep/SleepingSystem.cs # Content.Server/Body/Components/BloodstreamComponent.cs # Content.Server/Body/Components/RespiratorComponent.cs # Content.Server/Body/Systems/InternalsSystem.cs # Content.Server/Body/Systems/RespiratorSystem.cs # Content.Server/Chat/Managers/IChatManager.cs # Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs # Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs # Content.Server/Electrocution/ElectrocutionSystem.cs # Content.Server/Holosign/HolosignProjectorComponent.cs # Content.Server/Holosign/HolosignSystem.cs # Content.Server/Remotes/DoorRemoteSystem.cs # Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs # Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs # Content.Server/Store/Systems/StoreSystem.Ui.cs # Content.Server/VendingMachines/VendingMachineSystem.cs # Content.Server/VoiceMask/VoiceMaskSystem.Equip.cs # Content.Server/VoiceMask/VoiceMaskSystem.cs # Content.Server/VoiceMask/VoiceMaskerComponent.cs # Content.Server/Zombies/ZombieSystem.cs # Content.Shared/Bed/Sleep/SleepEmitSoundComponent.cs # Content.Shared/Cuffs/SharedCuffableSystem.cs # Content.Shared/VoiceMask/SharedVoiceMaskSystem.cs # Resources/Locale/en-US/escape-menu/ui/options-menu.ftl # Resources/Maps/bagel.yml # Resources/Maps/box.yml # Resources/Maps/centcomm.yml # Resources/Maps/cluster.yml # Resources/Maps/europa.yml # Resources/Maps/marathon.yml # Resources/Maps/meta.yml # Resources/Maps/omega.yml # Resources/Maps/origin.yml # Resources/Maps/packed.yml # Resources/Maps/reach.yml # Resources/Prototypes/Catalog/Fills/Boxes/syndicate.yml # Resources/Prototypes/Catalog/Fills/Lockers/security.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/medidrobe.yml # Resources/Prototypes/Entities/Clothing/Hands/colored.yml # Resources/Prototypes/Entities/Clothing/Neck/mantles.yml # Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml # Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/packs.yml # Resources/Prototypes/Entities/Objects/Misc/land_mine.yml # Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml # Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml # Resources/Prototypes/Entities/Structures/Decoration/curtains.yml # Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml # Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml # Resources/Prototypes/Entities/Structures/Holographic/projections.yml # Resources/Prototypes/Entities/Structures/Machines/lathe.yml # Resources/Prototypes/Maps/marathon.yml # Resources/Prototypes/Maps/packed.yml # Resources/Prototypes/Recipes/Lathes/security.yml # Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml # Resources/Prototypes/Roles/Jobs/Security/detective.yml # Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml # Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml # Resources/Prototypes/Roles/Jobs/Security/security_officer.yml # Resources/Prototypes/Roles/Jobs/Security/warden.yml # Resources/Textures/Clothing/OuterClothing/Armor/lingarmor.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/icon-open.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/open-equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/open-inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/open-inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/icon-open.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/open-equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/open-inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/open-inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/icon-open.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/open-equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_sci.rsi/equipped-OUTERCLOTHING-body-slim.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_sci.rsi/open-equipped-OUTERCLOTHING-body-slim.png # Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/lingspacesuit.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Misc/nunrobe.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Misc/plaguedoctorsuit.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Suits/atmos_firesuit.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatbar.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcentcom.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatchem.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatclown.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcmo.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatgen.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coathop.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coathos.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatjani.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatnomi.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatqm.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatrd.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatrobo.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatviro.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/meta.json # Resources/Textures/Clothing/Shoes/Boots/combatboots.rsi/meta.json # Resources/Textures/Clothing/Shoes/Specific/bling.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpskirt/atmosf.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpskirt/centcomformaldress.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpskirt/hosformaldress.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpskirt/operative_s.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/atmos.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/centcomformal.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/hosformal.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/journalist.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/operative.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/psychologist.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/reporter.rsi/meta.json # Resources/Textures/Interface/Alerts/essence_counter.rsi/essence0.png # Resources/Textures/Interface/Alerts/essence_counter.rsi/essence16.png # Resources/Textures/Objects/Storage/boxes.rsi/meta.json # Resources/Textures/Structures/Doors/Airlocks/Glass/atmospherics.rsi/meta.json # Resources/Textures/Structures/Doors/Airlocks/Standard/atmospherics.rsi/meta.json # Resources/Textures/Structures/Doors/Airlocks/highsec/highsec.rsi/meta.json
447 lines
18 KiB
C#
447 lines
18 KiB
C#
using System.Linq;
|
|
using Content.Server.Administration.Logs;
|
|
using Content.Server.Atmos.Components;
|
|
using Content.Server.Atmos.EntitySystems;
|
|
using Content.Server.Body.Components;
|
|
using Content.Server.Temperature.Components;
|
|
using Content.Shared.Alert;
|
|
using Content.Shared.Atmos;
|
|
using Content.Shared.Changeling;
|
|
using Content.Shared.Damage;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.Rejuvenate;
|
|
using Content.Shared.Temperature;
|
|
using Robust.Shared.Physics.Components;
|
|
|
|
namespace Content.Server.Temperature.Systems;
|
|
|
|
public sealed class TemperatureSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly AlertsSystem _alerts = default!;
|
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
|
[Dependency] private readonly DamageableSystem _damageable = default!;
|
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
|
|
|
/// <summary>
|
|
/// All the components that will have their damage updated at the end of the tick.
|
|
/// This is done because both AtmosExposed and Flammable call ChangeHeat in the same tick, meaning
|
|
/// that we need some mechanism to ensure it doesn't double dip on damage for both calls.
|
|
/// </summary>
|
|
public HashSet<Entity<TemperatureComponent>> ShouldUpdateDamage = new();
|
|
|
|
public float UpdateInterval = 1.0f;
|
|
|
|
private float _accumulatedFrametime;
|
|
|
|
public override void Initialize()
|
|
{
|
|
SubscribeLocalEvent<TemperatureComponent, OnTemperatureChangeEvent>(EnqueueDamage);
|
|
SubscribeLocalEvent<TemperatureComponent, AtmosExposedUpdateEvent>(OnAtmosExposedUpdate);
|
|
SubscribeLocalEvent<TemperatureComponent, RejuvenateEvent>(OnRejuvenate);
|
|
SubscribeLocalEvent<AlertsComponent, OnTemperatureChangeEvent>(ServerAlert);
|
|
SubscribeLocalEvent<TemperatureProtectionComponent, InventoryRelayedEvent<ModifyChangedTemperatureEvent>>(
|
|
OnTemperatureChangeAttempt);
|
|
|
|
SubscribeLocalEvent<InternalTemperatureComponent, MapInitEvent>(OnInit);
|
|
|
|
// Allows overriding thresholds based on the parent's thresholds.
|
|
SubscribeLocalEvent<TemperatureComponent, EntParentChangedMessage>(OnParentChange);
|
|
SubscribeLocalEvent<ContainerTemperatureDamageThresholdsComponent, ComponentStartup>(
|
|
OnParentThresholdStartup);
|
|
SubscribeLocalEvent<ContainerTemperatureDamageThresholdsComponent, ComponentShutdown>(
|
|
OnParentThresholdShutdown);
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
|
|
// conduct heat from the surface to the inside of entities with internal temperatures
|
|
var query = EntityQueryEnumerator<InternalTemperatureComponent, TemperatureComponent>();
|
|
while (query.MoveNext(out var uid, out var comp, out var temp))
|
|
{
|
|
// don't do anything if they equalised
|
|
var diff = Math.Abs(temp.CurrentTemperature - comp.Temperature);
|
|
if (diff < 0.1f)
|
|
continue;
|
|
|
|
// heat flow in W/m^2 as per fourier's law in 1D.
|
|
var q = comp.Conductivity * diff / comp.Thickness;
|
|
|
|
// convert to J then K
|
|
var joules = q * comp.Area * frameTime;
|
|
var degrees = joules / GetHeatCapacity(uid, temp);
|
|
if (temp.CurrentTemperature < comp.Temperature)
|
|
degrees *= -1;
|
|
|
|
// exchange heat between inside and surface
|
|
comp.Temperature += degrees;
|
|
ForceChangeTemperature(uid, temp.CurrentTemperature - degrees, temp);
|
|
}
|
|
|
|
UpdateDamage(frameTime);
|
|
}
|
|
|
|
private void UpdateDamage(float frameTime)
|
|
{
|
|
_accumulatedFrametime += frameTime;
|
|
|
|
if (_accumulatedFrametime < UpdateInterval)
|
|
return;
|
|
_accumulatedFrametime -= UpdateInterval;
|
|
|
|
if (!ShouldUpdateDamage.Any())
|
|
return;
|
|
|
|
foreach (var comp in ShouldUpdateDamage)
|
|
{
|
|
MetaDataComponent? metaData = null;
|
|
|
|
var uid = comp.Owner;
|
|
if (Deleted(uid, metaData) || Paused(uid, metaData))
|
|
continue;
|
|
|
|
ChangeDamage(uid, comp);
|
|
}
|
|
|
|
ShouldUpdateDamage.Clear();
|
|
}
|
|
|
|
public void ForceChangeTemperature(EntityUid uid, float temp, TemperatureComponent? temperature = null)
|
|
{
|
|
if (!Resolve(uid, ref temperature))
|
|
return;
|
|
|
|
float lastTemp = temperature.CurrentTemperature;
|
|
float delta = temperature.CurrentTemperature - temp;
|
|
temperature.CurrentTemperature = temp;
|
|
RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta),
|
|
true);
|
|
}
|
|
|
|
public void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false,
|
|
TemperatureComponent? temperature = null)
|
|
{
|
|
if (!Resolve(uid, ref temperature))
|
|
return;
|
|
|
|
if (!ignoreHeatResistance)
|
|
{
|
|
var ev = new ModifyChangedTemperatureEvent(heatAmount);
|
|
RaiseLocalEvent(uid, ev);
|
|
heatAmount = ev.TemperatureDelta;
|
|
}
|
|
|
|
float lastTemp = temperature.CurrentTemperature;
|
|
temperature.CurrentTemperature += heatAmount / GetHeatCapacity(uid, temperature);
|
|
float delta = temperature.CurrentTemperature - lastTemp;
|
|
|
|
RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta), true);
|
|
}
|
|
|
|
private void OnAtmosExposedUpdate(EntityUid uid, TemperatureComponent temperature,
|
|
ref AtmosExposedUpdateEvent args)
|
|
{
|
|
var transform = args.Transform;
|
|
|
|
if (transform.MapUid == null)
|
|
return;
|
|
|
|
var temperatureDelta = args.GasMixture.Temperature - temperature.CurrentTemperature;
|
|
var airHeatCapacity = _atmosphere.GetHeatCapacity(args.GasMixture, false);
|
|
var heatCapacity = GetHeatCapacity(uid, temperature);
|
|
var heat = temperatureDelta * (airHeatCapacity * heatCapacity /
|
|
(airHeatCapacity + heatCapacity));
|
|
ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature);
|
|
|
|
// WD START
|
|
var adjEv = new AdjustTemperatureEvent(temperature.CurrentTemperature);
|
|
RaiseLocalEvent(uid, adjEv);
|
|
if (!MathHelper.CloseTo(adjEv.Temperature, temperature.CurrentTemperature))
|
|
ForceChangeTemperature(uid, adjEv.Temperature, temperature);
|
|
// WD END
|
|
}
|
|
|
|
public float GetHeatCapacity(EntityUid uid, TemperatureComponent? comp = null, PhysicsComponent? physics = null)
|
|
{
|
|
if (!Resolve(uid, ref comp) || !Resolve(uid, ref physics, false) || physics.FixturesMass <= 0)
|
|
{
|
|
return Atmospherics.MinimumHeatCapacity;
|
|
}
|
|
|
|
return comp.SpecificHeat * physics.FixturesMass;
|
|
}
|
|
|
|
private void OnInit(EntityUid uid, InternalTemperatureComponent comp, MapInitEvent args)
|
|
{
|
|
if (!TryComp<TemperatureComponent>(uid, out var temp))
|
|
return;
|
|
|
|
comp.Temperature = temp.CurrentTemperature;
|
|
}
|
|
|
|
private void OnRejuvenate(EntityUid uid, TemperatureComponent comp, RejuvenateEvent args)
|
|
{
|
|
ForceChangeTemperature(uid, Atmospherics.T20C, comp);
|
|
}
|
|
|
|
private void ServerAlert(EntityUid uid, AlertsComponent status, OnTemperatureChangeEvent args)
|
|
{
|
|
AlertType type;
|
|
float threshold;
|
|
float idealTemp;
|
|
|
|
if (!TryComp<TemperatureComponent>(uid, out var temperature))
|
|
{
|
|
_alerts.ClearAlertCategory(uid, AlertCategory.Temperature);
|
|
return;
|
|
}
|
|
|
|
if (TryComp<ThermalRegulatorComponent>(uid, out var regulator) &&
|
|
regulator.NormalBodyTemperature > temperature.ColdDamageThreshold &&
|
|
regulator.NormalBodyTemperature < temperature.HeatDamageThreshold)
|
|
{
|
|
idealTemp = regulator.NormalBodyTemperature;
|
|
}
|
|
else
|
|
{
|
|
idealTemp = (temperature.ColdDamageThreshold + temperature.HeatDamageThreshold) / 2;
|
|
}
|
|
|
|
if (args.CurrentTemperature <= idealTemp)
|
|
{
|
|
type = AlertType.Cold;
|
|
threshold = temperature.ColdDamageThreshold;
|
|
}
|
|
else
|
|
{
|
|
type = AlertType.Hot;
|
|
threshold = temperature.HeatDamageThreshold;
|
|
}
|
|
|
|
// Calculates a scale where 1.0 is the ideal temperature and 0.0 is where temperature damage begins
|
|
// The cold and hot scales will differ in their range if the ideal temperature is not exactly halfway between the thresholds
|
|
var tempScale = (args.CurrentTemperature - threshold) / (idealTemp - threshold);
|
|
switch (tempScale)
|
|
{
|
|
case <= 0f:
|
|
_alerts.ShowAlert(uid, type, 3);
|
|
break;
|
|
|
|
case <= 0.4f:
|
|
_alerts.ShowAlert(uid, type, 2);
|
|
break;
|
|
|
|
case <= 0.66f:
|
|
_alerts.ShowAlert(uid, type, 1);
|
|
break;
|
|
|
|
case > 0.66f:
|
|
_alerts.ClearAlertCategory(uid, AlertCategory.Temperature);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void EnqueueDamage(Entity<TemperatureComponent> temperature, ref OnTemperatureChangeEvent args)
|
|
{
|
|
ShouldUpdateDamage.Add(temperature);
|
|
}
|
|
|
|
private void ChangeDamage(EntityUid uid, TemperatureComponent temperature)
|
|
{
|
|
if (!HasComp<DamageableComponent>(uid))
|
|
return;
|
|
|
|
// See this link for where the scaling func comes from:
|
|
// https://www.desmos.com/calculator/0vknqtdvq9
|
|
// Based on a logistic curve, which caps out at MaxDamage
|
|
var heatK = 0.005;
|
|
var a = 1;
|
|
var y = temperature.DamageCap;
|
|
var c = y * 2;
|
|
|
|
var heatDamageThreshold = temperature.ParentHeatDamageThreshold ?? temperature.HeatDamageThreshold;
|
|
var coldDamageThreshold = temperature.ParentColdDamageThreshold ?? temperature.ColdDamageThreshold;
|
|
|
|
if (temperature.CurrentTemperature >= heatDamageThreshold)
|
|
{
|
|
if (!temperature.TakingDamage)
|
|
{
|
|
_adminLogger.Add(LogType.Temperature, $"{ToPrettyString(uid):entity} started taking high temperature damage");
|
|
temperature.TakingDamage = true;
|
|
}
|
|
|
|
var diff = Math.Abs(temperature.CurrentTemperature - heatDamageThreshold);
|
|
var tempDamage = c / (1 + a * Math.Pow(Math.E, -heatK * diff)) - y;
|
|
_damageable.TryChangeDamage(uid, temperature.HeatDamage * tempDamage, ignoreResistances: true, interruptsDoAfters: false);
|
|
}
|
|
else if (temperature.CurrentTemperature <= coldDamageThreshold)
|
|
{
|
|
if (TryComp(uid, out VoidAdaptationComponent? voidAdaptation)) // WD
|
|
{
|
|
if (temperature.TakingDamage)
|
|
{
|
|
_adminLogger.Add(LogType.Temperature,
|
|
$"{ToPrettyString(uid):entity} stopped taking temperature damage");
|
|
temperature.TakingDamage = false;
|
|
}
|
|
|
|
voidAdaptation.ChemMultiplier = 0.75f;
|
|
return;
|
|
}
|
|
|
|
if (!temperature.TakingDamage)
|
|
{
|
|
_adminLogger.Add(LogType.Temperature, $"{ToPrettyString(uid):entity} started taking low temperature damage");
|
|
temperature.TakingDamage = true;
|
|
}
|
|
|
|
var diff = Math.Abs(temperature.CurrentTemperature - coldDamageThreshold);
|
|
var tempDamage =
|
|
Math.Sqrt(diff * (Math.Pow(temperature.DamageCap.Double(), 2) / coldDamageThreshold));
|
|
_damageable.TryChangeDamage(uid, temperature.ColdDamage * tempDamage, ignoreResistances: true, interruptsDoAfters: false);
|
|
}
|
|
else if (temperature.TakingDamage)
|
|
{
|
|
_adminLogger.Add(LogType.Temperature, $"{ToPrettyString(uid):entity} stopped taking temperature damage");
|
|
temperature.TakingDamage = false;
|
|
}
|
|
}
|
|
|
|
private void OnTemperatureChangeAttempt(EntityUid uid, TemperatureProtectionComponent component,
|
|
InventoryRelayedEvent<ModifyChangedTemperatureEvent> args)
|
|
{
|
|
var ev = new GetTemperatureProtectionEvent(component.Coefficient);
|
|
RaiseLocalEvent(uid, ref ev);
|
|
|
|
args.Args.TemperatureDelta *= ev.Coefficient;
|
|
}
|
|
|
|
private void OnParentChange(EntityUid uid, TemperatureComponent component,
|
|
ref EntParentChangedMessage args)
|
|
{
|
|
var temperatureQuery = GetEntityQuery<TemperatureComponent>();
|
|
var transformQuery = GetEntityQuery<TransformComponent>();
|
|
var thresholdsQuery = GetEntityQuery<ContainerTemperatureDamageThresholdsComponent>();
|
|
// We only need to update thresholds if the thresholds changed for the entity's ancestors.
|
|
var oldThresholds = args.OldParent != null
|
|
? RecalculateParentThresholds(args.OldParent.Value, transformQuery, thresholdsQuery)
|
|
: (null, null);
|
|
var newThresholds = RecalculateParentThresholds(transformQuery.GetComponent(uid).ParentUid, transformQuery, thresholdsQuery);
|
|
|
|
if (oldThresholds != newThresholds)
|
|
{
|
|
RecursiveThresholdUpdate(uid, temperatureQuery, transformQuery, thresholdsQuery);
|
|
}
|
|
}
|
|
|
|
private void OnParentThresholdStartup(EntityUid uid, ContainerTemperatureDamageThresholdsComponent component,
|
|
ComponentStartup args)
|
|
{
|
|
RecursiveThresholdUpdate(uid, GetEntityQuery<TemperatureComponent>(), GetEntityQuery<TransformComponent>(),
|
|
GetEntityQuery<ContainerTemperatureDamageThresholdsComponent>());
|
|
}
|
|
|
|
private void OnParentThresholdShutdown(EntityUid uid, ContainerTemperatureDamageThresholdsComponent component,
|
|
ComponentShutdown args)
|
|
{
|
|
RecursiveThresholdUpdate(uid, GetEntityQuery<TemperatureComponent>(), GetEntityQuery<TransformComponent>(),
|
|
GetEntityQuery<ContainerTemperatureDamageThresholdsComponent>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalculate and apply parent thresholds for the root entity and all its descendant.
|
|
/// </summary>
|
|
/// <param name="root"></param>
|
|
/// <param name="temperatureQuery"></param>
|
|
/// <param name="transformQuery"></param>
|
|
/// <param name="tempThresholdsQuery"></param>
|
|
private void RecursiveThresholdUpdate(EntityUid root, EntityQuery<TemperatureComponent> temperatureQuery,
|
|
EntityQuery<TransformComponent> transformQuery,
|
|
EntityQuery<ContainerTemperatureDamageThresholdsComponent> tempThresholdsQuery)
|
|
{
|
|
RecalculateAndApplyParentThresholds(root, temperatureQuery, transformQuery, tempThresholdsQuery);
|
|
|
|
var enumerator = Transform(root).ChildEnumerator;
|
|
while (enumerator.MoveNext(out var child))
|
|
{
|
|
RecursiveThresholdUpdate(child, temperatureQuery, transformQuery, tempThresholdsQuery);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalculate parent thresholds and apply them on the uid temperature component.
|
|
/// </summary>
|
|
/// <param name="uid"></param>
|
|
/// <param name="temperatureQuery"></param>
|
|
/// <param name="transformQuery"></param>
|
|
/// <param name="tempThresholdsQuery"></param>
|
|
private void RecalculateAndApplyParentThresholds(EntityUid uid,
|
|
EntityQuery<TemperatureComponent> temperatureQuery, EntityQuery<TransformComponent> transformQuery,
|
|
EntityQuery<ContainerTemperatureDamageThresholdsComponent> tempThresholdsQuery)
|
|
{
|
|
if (!temperatureQuery.TryGetComponent(uid, out var temperature))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var newThresholds = RecalculateParentThresholds(transformQuery.GetComponent(uid).ParentUid, transformQuery, tempThresholdsQuery);
|
|
temperature.ParentHeatDamageThreshold = newThresholds.Item1;
|
|
temperature.ParentColdDamageThreshold = newThresholds.Item2;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalculate Parent Heat/Cold DamageThreshold by recursively checking each ancestor and fetching the
|
|
/// maximum HeatDamageThreshold and the minimum ColdDamageThreshold if any exists (aka the best value for each).
|
|
/// </summary>
|
|
/// <param name="initialParentUid"></param>
|
|
/// <param name="transformQuery"></param>
|
|
/// <param name="tempThresholdsQuery"></param>
|
|
private (float?, float?) RecalculateParentThresholds(
|
|
EntityUid initialParentUid,
|
|
EntityQuery<TransformComponent> transformQuery,
|
|
EntityQuery<ContainerTemperatureDamageThresholdsComponent> tempThresholdsQuery)
|
|
{
|
|
// Recursively check parents for the best threshold available
|
|
var parentUid = initialParentUid;
|
|
float? newHeatThreshold = null;
|
|
float? newColdThreshold = null;
|
|
while (parentUid.IsValid())
|
|
{
|
|
if (tempThresholdsQuery.TryGetComponent(parentUid, out var newThresholds))
|
|
{
|
|
if (newThresholds.HeatDamageThreshold != null)
|
|
{
|
|
newHeatThreshold = Math.Max(newThresholds.HeatDamageThreshold.Value,
|
|
newHeatThreshold ?? 0);
|
|
}
|
|
|
|
if (newThresholds.ColdDamageThreshold != null)
|
|
{
|
|
newColdThreshold = Math.Min(newThresholds.ColdDamageThreshold.Value,
|
|
newColdThreshold ?? float.MaxValue);
|
|
}
|
|
}
|
|
|
|
parentUid = transformQuery.GetComponent(parentUid).ParentUid;
|
|
}
|
|
|
|
return (newHeatThreshold, newColdThreshold);
|
|
}
|
|
}
|
|
|
|
public sealed class OnTemperatureChangeEvent : EntityEventArgs
|
|
{
|
|
public float CurrentTemperature { get; }
|
|
public float LastTemperature { get; }
|
|
public float TemperatureDelta { get; }
|
|
|
|
public OnTemperatureChangeEvent(float current, float last, float delta)
|
|
{
|
|
CurrentTemperature = current;
|
|
LastTemperature = last;
|
|
TemperatureDelta = delta;
|
|
}
|
|
}
|