using Content.Client.Atmos.Components; using Content.Shared.Atmos; using Robust.Client.GameObjects; using Robust.Shared.Map; using Robust.Shared.Utility; namespace Content.Client.Atmos.EntitySystems; /// /// This handles the display of fire effects on flammable entities. /// public sealed class FireVisualizerSystem : VisualizerSystem { [Dependency] private readonly SpriteSystem _sprite = default!; [Dependency] private readonly PointLightSystem _lights = default!; 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((uid, sprite), FireVisualLayers.Fire, out var layer, false)) { _sprite.RemoveLayer((uid, sprite), layer); } } private void OnComponentInit(EntityUid uid, FireVisualsComponent component, ComponentInit args) { if (!TryComp(uid, out var sprite) || !TryComp(uid, out AppearanceComponent? appearance)) return; _sprite.LayerMapReserve((uid, sprite), FireVisualLayers.Fire); _sprite.LayerSetVisible((uid, sprite), FireVisualLayers.Fire, false); sprite.LayerSetShader(FireVisualLayers.Fire, "unshaded"); if (component.Sprite != null) _sprite.LayerSetRsi((uid, sprite), FireVisualLayers.Fire, new ResPath(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((uid, sprite), FireVisualLayers.Fire, out var index, false)) return; AppearanceSystem.TryGetData(uid, FireVisuals.OnFire, out var onFire, appearance); AppearanceSystem.TryGetData(uid, FireVisuals.FireStacks, out var fireStacks, appearance); _sprite.LayerSetVisible((uid, sprite), 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.LayerSetRsiState((uid, sprite), index, component.AlternateState); else _sprite.LayerSetRsiState((uid, sprite), index, component.NormalState); component.LightEntity ??= Spawn(null, new EntityCoordinates(uid, default)); var light = EnsureComp(component.LightEntity.Value); _lights.SetColor(component.LightEntity.Value, component.LightColor, light); // light needs a minimum radius to be visible at all, hence the + 1.5f _lights.SetRadius(component.LightEntity.Value, Math.Clamp(1.5f + component.LightRadiusPerStack * fireStacks, 0f, component.MaxLightRadius), light); _lights.SetEnergy(component.LightEntity.Value, Math.Clamp(1 + component.LightEnergyPerStack * fireStacks, 0f, component.MaxLightEnergy), light); // TODO flickering animation? Or just add a noise mask to the light? But that requires an engine PR. } } public enum FireVisualLayers : byte { Fire }