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 }