using Content.Client.Atmos.Components;
using Content.Shared.Atmos;
using Robust.Client.GameObjects;
using Robust.Shared.Map;
namespace Content.Client.Atmos.EntitySystems;
///
/// This handles the display of fire effects on flammable entities.
///
public sealed class FireVisualizerSystem : VisualizerSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnComponentInit);
SubscribeLocalEvent(OnShutdown);
}
private void OnShutdown(EntityUid uid, FireVisualsComponent component, ComponentShutdown args)
{
if (component.LightEntity != null)
{
Del(component.LightEntity.Value);
component.LightEntity = null;
}
// Need LayerMapTryGet because Init fails if there's no existing sprite / appearancecomp
// which means in some setups (most frequently no AppearanceComp) the layer never exists.
if (TryComp(uid, out var sprite) &&
sprite.LayerMapTryGet(FireVisualLayers.Fire, out var layer))
{
sprite.RemoveLayer(layer);
}
}
private void OnComponentInit(EntityUid uid, FireVisualsComponent component, ComponentInit args)
{
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.Sprite != null)
UpdateAppearance(uid, component, args.Sprite, args.Component);
}
private void UpdateAppearance(EntityUid uid, FireVisualsComponent component, SpriteComponent sprite, AppearanceComponent appearance)
{
if (!sprite.LayerMapTryGet(FireVisualLayers.Fire, out var index))
return;
AppearanceSystem.TryGetData(uid, FireVisuals.OnFire, out var onFire, appearance);
AppearanceSystem.TryGetData(uid, FireVisuals.FireStacks, out var fireStacks, appearance);
sprite.LayerSetVisible(index, onFire);
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(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.
}
}
public enum FireVisualLayers : byte
{
Fire
}