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;