diff --git a/Content.Client/Atmos/Components/FireVisualsComponent.cs b/Content.Client/Atmos/Components/FireVisualsComponent.cs
index 3d61879132..0eebb2040e 100644
--- a/Content.Client/Atmos/Components/FireVisualsComponent.cs
+++ b/Content.Client/Atmos/Components/FireVisualsComponent.cs
@@ -18,4 +18,25 @@ public sealed class FireVisualsComponent : Component
[DataField("sprite")]
public string? Sprite;
+
+ [DataField("lightEnergyPerStack")]
+ public float LightEnergyPerStack = 0.5f;
+
+ [DataField("lightRadiusPerStack")]
+ public float LightRadiusPerStack = 0.3f;
+
+ [DataField("maxLightEnergy")]
+ public float MaxLightEnergy = 10f;
+
+ [DataField("maxLightRadius")]
+ public float MaxLightRadius = 4f;
+
+ [DataField("lightColor")]
+ public Color LightColor = Color.Orange;
+
+ ///
+ /// Client side point-light entity. We use this instead of directly adding a light to
+ /// the burning entity as entities don't support having multiple point-lights.
+ ///
+ public EntityUid? LightEntity;
}
diff --git a/Content.Client/Atmos/EntitySystems/FireVisualizerSystem.cs b/Content.Client/Atmos/EntitySystems/FireVisualizerSystem.cs
index 7f58cbe409..b5a96f16f4 100644
--- a/Content.Client/Atmos/EntitySystems/FireVisualizerSystem.cs
+++ b/Content.Client/Atmos/EntitySystems/FireVisualizerSystem.cs
@@ -1,6 +1,8 @@
using Content.Client.Atmos.Components;
using Content.Shared.Atmos;
+using OpenToolkit.Graphics.OpenGL;
using Robust.Client.GameObjects;
+using Robust.Shared.Map;
namespace Content.Client.Atmos.EntitySystems;
@@ -14,42 +16,76 @@ public sealed class FireVisualizerSystem : VisualizerSystem(OnComponentInit);
+ SubscribeLocalEvent(OnShutdown);
+ }
+
+ private void OnShutdown(EntityUid uid, FireVisualsComponent component, ComponentShutdown args)
+ {
+ if (component.LightEntity != null)
+ {
+ Del(component.LightEntity.Value);
+ component.LightEntity = null;
+ }
+
+ if (TryComp(uid, out var sprite))
+ sprite.RemoveLayer(FireVisualLayers.Fire);
}
private void OnComponentInit(EntityUid uid, FireVisualsComponent component, ComponentInit args)
{
- if (!TryComp(uid, out var sprite))
+ if (!TryComp(uid, out var sprite) || !TryComp(uid, out AppearanceComponent? appearance))
return;
sprite.LayerMapReserveBlank(FireVisualLayers.Fire);
sprite.LayerSetVisible(FireVisualLayers.Fire, false);
sprite.LayerSetShader(FireVisualLayers.Fire, "unshaded");
+ if (component.Sprite != null)
+ sprite.LayerSetRSI(FireVisualLayers.Fire, component.Sprite);
+
+ UpdateAppearance(uid, component, sprite, appearance);
}
protected override void OnAppearanceChange(EntityUid uid, FireVisualsComponent component, ref AppearanceChangeEvent args)
{
- if (!args.Component.TryGetData(FireVisuals.OnFire, out bool onFire) ||
- !TryComp(component.Owner, out var sprite))
- return;
-
- var fireStacks = 0f;
- if (args.Component.TryGetData(FireVisuals.FireStacks, out float stacks))
- fireStacks = stacks;
-
- SetOnFire(sprite, args.Component, component, onFire, fireStacks);
+ if (args.Sprite != null)
+ UpdateAppearance(uid, component, args.Sprite, args.Component);
}
- private void SetOnFire(SpriteComponent sprite, AppearanceComponent appearance, FireVisualsComponent component, bool onFire, float fireStacks)
+ private void UpdateAppearance(EntityUid uid, FireVisualsComponent component, SpriteComponent sprite, AppearanceComponent appearance)
{
- if (component.Sprite != null)
- sprite.LayerSetRSI(FireVisualLayers.Fire, component.Sprite);
+ if (!sprite.LayerMapTryGet(FireVisualLayers.Fire, out var index))
+ return;
- sprite.LayerSetVisible(FireVisualLayers.Fire, onFire);
+ appearance.TryGetData(FireVisuals.OnFire, out bool onFire);
+ appearance.TryGetData(FireVisuals.FireStacks, out float fireStacks);
+ sprite.LayerSetVisible(index, onFire);
- if(fireStacks > component.FireStackAlternateState && !string.IsNullOrEmpty(component.AlternateState))
- sprite.LayerSetState(FireVisualLayers.Fire, component.AlternateState);
+ if (!onFire)
+ {
+ if (component.LightEntity != null)
+ {
+ Del(component.LightEntity.Value);
+ component.LightEntity = null;
+ }
+
+ return;
+ }
+
+ if (fireStacks > component.FireStackAlternateState && !string.IsNullOrEmpty(component.AlternateState))
+ sprite.LayerSetState(index, component.AlternateState);
else
- sprite.LayerSetState(FireVisualLayers.Fire, component.NormalState);
+ sprite.LayerSetState(index, component.NormalState);
+
+ component.LightEntity ??= Spawn(null, new EntityCoordinates(uid, default));
+ var light = EnsureComp(component.LightEntity.Value);
+
+ light.Color = component.LightColor;
+
+ // light needs a minimum radius to be visible at all, hence the + 1.5f
+ light.Radius = Math.Clamp(1.5f + component.LightRadiusPerStack * fireStacks, 0f, component.MaxLightRadius);
+ light.Energy = Math.Clamp(1 + component.LightEnergyPerStack * fireStacks, 0f, component.MaxLightEnergy);
+
+ // TODO flickering animation? Or just add a noise mask to the light? But that requires an engine PR.
}
}
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
index 1dcda84dac..970764591f 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
@@ -147,7 +147,7 @@ public sealed partial class AdminVerbSystem
Act = () =>
{
// Fuck you. Burn Forever.
- flammable.FireStacks = 99999.9f;
+ flammable.FireStacks = FlammableSystem.MaximumFireStacks;
_flammableSystem.Ignite(args.Target);
var xform = Transform(args.Target);
_popupSystem.PopupEntity(Loc.GetString("admin-smite-set-alight-self"), args.Target,
diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs
index ec0087845b..321217222e 100644
--- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs
@@ -23,7 +23,7 @@ using Robust.Shared.Physics.Systems;
namespace Content.Server.Atmos.EntitySystems
{
- internal sealed class FlammableSystem : EntitySystem
+ public sealed class FlammableSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
@@ -35,11 +35,11 @@ namespace Content.Server.Atmos.EntitySystems
[Dependency] private readonly FixtureSystem _fixture = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
- private const float MinimumFireStacks = -10f;
- private const float MaximumFireStacks = 20f;
+ public const float MinimumFireStacks = -10f;
+ public const float MaximumFireStacks = 20f;
private const float UpdateTime = 1f;
- private const float MinIgnitionTemperature = 373.15f;
+ public const float MinIgnitionTemperature = 373.15f;
public const string FlammableFixtureID = "flammable";
private float _timer = 0f;