diff --git a/Content.Client/Light/Visualizers/LightBulbVisualizer.cs b/Content.Client/Light/Visualizers/LightBulbVisualizer.cs new file mode 100644 index 0000000000..cb4c5f5f17 --- /dev/null +++ b/Content.Client/Light/Visualizers/LightBulbVisualizer.cs @@ -0,0 +1,42 @@ +using Content.Shared.Light; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Shared.Maths; + +namespace Content.Client.Light.Visualizers +{ + [UsedImplicitly] + public class LightBulbVisualizer : AppearanceVisualizer + { + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (!component.Owner.TryGetComponent(out var sprite)) + return; + + // update sprite state + if (component.TryGetData(LightBulbVisuals.State, out LightBulbState state)) + { + switch (state) + { + case LightBulbState.Normal: + sprite.LayerSetState(0, "normal"); + break; + case LightBulbState.Broken: + sprite.LayerSetState(0, "broken"); + break; + case LightBulbState.Burned: + sprite.LayerSetState(0, "burned"); + break; + } + } + + // also update sprites color + if (component.TryGetData(LightBulbVisuals.Color, out Color color)) + { + sprite.Color = color; + } + } + } +} diff --git a/Content.Server/Light/Components/LightBulbComponent.cs b/Content.Server/Light/Components/LightBulbComponent.cs index 740faca969..a411303691 100644 --- a/Content.Server/Light/Components/LightBulbComponent.cs +++ b/Content.Server/Light/Components/LightBulbComponent.cs @@ -1,131 +1,45 @@ -using System; +using Content.Server.Light.EntitySystems; using Content.Shared.Acts; -using Content.Shared.Audio; +using Content.Shared.Light; using Content.Shared.Sound; -using Content.Shared.Throwing; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; +using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Maths; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; namespace Content.Server.Light.Components { - public enum LightBulbState - { - Normal, - Broken, - Burned, - } - - public enum LightBulbType - { - Bulb, - Tube, - } - /// /// Component that represents a light bulb. Can be broken, or burned, which turns them mostly useless. /// - [RegisterComponent] + [RegisterComponent, Friend(typeof(LightBulbSystem))] public class LightBulbComponent : Component, IBreakAct { - /// - /// Invoked whenever the state of the light bulb changes. - /// - public event EventHandler? OnLightBulbStateChange; - public event EventHandler? OnLightColorChange; + public override string Name => "LightBulb"; [DataField("color")] - private Color _color = Color.White; - - [ViewVariables(VVAccess.ReadWrite)] - public Color Color - { - get { return _color; } - set - { - _color = value; - OnLightColorChange?.Invoke(this, null); - UpdateColor(); - } - } - - public override string Name => "LightBulb"; + public Color Color = Color.White; [DataField("bulb")] public LightBulbType Type = LightBulbType.Tube; + [DataField("startingState")] + public LightBulbState State = LightBulbState.Normal; + [DataField("BurningTemperature")] - private int _burningTemperature = 1400; - public int BurningTemperature => _burningTemperature; + public int BurningTemperature = 1400; [DataField("PowerUse")] - private int _powerUse = 40; - public int PowerUse => _powerUse; + public int PowerUse = 40; [DataField("breakSound")] - private SoundSpecifier _breakSound = new SoundCollectionSpecifier("GlassBreak"); - - /// - /// The current state of the light bulb. Invokes the OnLightBulbStateChange event when set. - /// It also updates the bulb's sprite accordingly. - /// - [ViewVariables(VVAccess.ReadWrite)] - public LightBulbState State - { - get { return _state; } - set - { - var sprite = Owner.GetComponent(); - OnLightBulbStateChange?.Invoke(this, EventArgs.Empty); - _state = value; - switch (value) - { - case LightBulbState.Normal: - sprite.LayerSetState(0, "normal"); - break; - case LightBulbState.Broken: - sprite.LayerSetState(0, "broken"); - break; - case LightBulbState.Burned: - sprite.LayerSetState(0, "burned"); - break; - } - } - } - - private LightBulbState _state = LightBulbState.Normal; - - public void UpdateColor() - { - if (!Owner.TryGetComponent(out SpriteComponent? sprite)) - { - return; - } - - sprite.Color = Color; - } - - protected override void Initialize() - { - base.Initialize(); - UpdateColor(); - } + public SoundSpecifier BreakSound = new SoundCollectionSpecifier("GlassBreak"); + // TODO: move me to ECS public void OnBreak(BreakageEventArgs eventArgs) { - State = LightBulbState.Broken; - } - - public void PlayBreakSound() - { - SoundSystem.Play(Filter.Pvs(Owner), _breakSound.GetSound(), Owner); + EntitySystem.Get() + .SetState(Owner.Uid, LightBulbState.Broken, this); } } } diff --git a/Content.Server/Light/Components/LightReplacerComponent.cs b/Content.Server/Light/Components/LightReplacerComponent.cs index fcbe69c2f3..8873ecf490 100644 --- a/Content.Server/Light/Components/LightReplacerComponent.cs +++ b/Content.Server/Light/Components/LightReplacerComponent.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Content.Server.Light.EntitySystems; using Content.Server.Storage.Components; +using Content.Shared.Light; using Content.Shared.Popups; using Content.Shared.Sound; using Robust.Shared.Audio; @@ -41,7 +43,14 @@ namespace Content.Server.Light.Components public bool TryReplaceBulb(PoweredLightComponent fixture, IEntity? user = null) { // check if light bulb is broken or missing - if (fixture.LightBulb != null && fixture.LightBulb.State == LightBulbState.Normal) return false; + var lightSystem = EntitySystem.Get(); + var fixtureBulbUid = lightSystem.GetBulb(fixture.Owner.Uid, fixture); + if (fixtureBulbUid == null) + return false; + if (!Owner.EntityManager.TryGetComponent(fixtureBulbUid.Value, out LightBulbComponent? fixtureBulb)) + return false; + + if (fixtureBulb.State == LightBulbState.Normal) return false; // try get first inserted bulb of the same type as targeted light fixtutre var bulb = _insertedBulbs.ContainedEntities.FirstOrDefault( @@ -79,7 +88,7 @@ namespace Content.Server.Light.Components } // insert it into fixture - var wasReplaced = fixture.ReplaceBulb(bulb); + var wasReplaced = lightSystem.ReplaceBulb(fixture.Owner.Uid, bulb.Uid, fixture); if (wasReplaced) { SoundSystem.Play(Filter.Pvs(Owner), _sound.GetSound(), Owner, diff --git a/Content.Server/Light/Components/PoweredLightComponent.cs b/Content.Server/Light/Components/PoweredLightComponent.cs index 700a701f9b..1ecde1882c 100644 --- a/Content.Server/Light/Components/PoweredLightComponent.cs +++ b/Content.Server/Light/Components/PoweredLightComponent.cs @@ -1,25 +1,12 @@ using System; -using System.Threading.Tasks; -using Content.Server.Hands.Components; -using Content.Server.Items; -using Content.Server.Power.Components; -using Content.Server.Temperature.Components; -using Content.Shared.Audio; +using Content.Server.Light.EntitySystems; using Content.Shared.Damage; -using Content.Shared.Interaction; using Content.Shared.Light; -using Content.Shared.Popups; using Content.Shared.Sound; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; +using Robust.Shared.Analyzers; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Maths; -using Robust.Shared.Player; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Timing; using Robust.Shared.ViewVariables; namespace Content.Server.Light.Components @@ -27,39 +14,30 @@ namespace Content.Server.Light.Components /// /// Component that represents a wall light. It has a light bulb that can be replaced when broken. /// - [RegisterComponent] - public class PoweredLightComponent : Component, IInteractHand, IInteractUsing, IMapInit + [RegisterComponent, Friend(typeof(PoweredLightSystem))] + public class PoweredLightComponent : Component { - [Dependency] private readonly IGameTiming _gameTiming = default!; - public override string Name => "PoweredLight"; - private static readonly TimeSpan _thunkDelay = TimeSpan.FromSeconds(2); - - [ComponentDependency] - private readonly AppearanceComponent? _appearance; - - private TimeSpan _lastThunk; - public TimeSpan? LastGhostBlink; - [DataField("burnHandSound")] - private SoundSpecifier _burnHandSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); + public SoundSpecifier BurnHandSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); [DataField("turnOnSound")] - private SoundSpecifier _turnOnSound = new SoundPathSpecifier("/Audio/Machines/light_tube_on.ogg"); + public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Machines/light_tube_on.ogg"); [DataField("hasLampOnSpawn")] - private bool _hasLampOnSpawn = true; + public bool HasLampOnSpawn = true; + + [DataField("bulb")] + public LightBulbType BulbType; [ViewVariables] [DataField("on")] - private bool _on = true; + public bool On = true; - [ViewVariables] - private bool _currentLit; - - [ViewVariables] - public bool IsBlinking; + [DataField("damage", required: true)] + [ViewVariables(VVAccess.ReadWrite)] + public DamageSpecifier Damage = default!; [ViewVariables] [DataField("ignoreGhostsBoo")] @@ -73,254 +51,15 @@ namespace Content.Server.Light.Components [DataField("ghostBlinkingCooldown")] public TimeSpan GhostBlinkingCooldown = TimeSpan.FromSeconds(60); - - [DataField("bulb")] private LightBulbType _bulbType = LightBulbType.Tube; - public LightBulbType BulbType => _bulbType; - - [ViewVariables] private ContainerSlot _lightBulbContainer = default!; - - [DataField("damage", required: true)] - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier Damage = default!; - - protected override void Initialize() - { - base.Initialize(); - _lightBulbContainer = Owner.EnsureContainer("light_bulb"); - } - [ViewVariables] - public LightBulbComponent? LightBulb - { - get - { - if (_lightBulbContainer.ContainedEntity == null) return null; - - _lightBulbContainer.ContainedEntity.TryGetComponent(out LightBulbComponent? bulb); - - return bulb; - } - } - - // TODO CONSTRUCTION make this use a construction graph - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - return InsertBulb(eventArgs.Using); - } - - bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) - { - if (!eventArgs.User.HasComponent()) - { - Eject(); - return false; - } - if (eventArgs.User.TryGetComponent(out HeatResistanceComponent? heatResistanceComponent)) - { - if (CanBurn(heatResistanceComponent.GetHeatResistance())) - { - Burn(); - return true; - } - } - Eject(); - return true; - - bool CanBurn(int heatResistance) - { - if (LightBulb == null) - return false; - - return _currentLit && heatResistance < LightBulb.BurningTemperature; - } - - void Burn() - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("powered-light-component-burn-hand")); - EntitySystem.Get().TryChangeDamage(eventArgs.User.Uid, Damage); - SoundSystem.Play(Filter.Pvs(Owner), _burnHandSound.GetSound(), Owner); - } - - void Eject() - { - EjectBulb(eventArgs.User); - UpdateLight(); - } - } - - /// - /// Try to replace current bulb with a new one - /// - public bool ReplaceBulb(IEntity bulb) - { - EjectBulb(); - return InsertBulb(bulb); - } - - /// - /// Inserts the bulb if possible. - /// - /// True if it could insert it, false if it couldn't. - private bool InsertBulb(IEntity bulb) - { - if (LightBulb != null) return false; - if (!bulb.TryGetComponent(out LightBulbComponent? lightBulb)) return false; - if (lightBulb.Type != _bulbType) return false; - - var inserted = _lightBulbContainer.Insert(bulb); - - lightBulb.OnLightBulbStateChange += UpdateLight; - lightBulb.OnLightColorChange += UpdateLight; - - UpdateLight(); - - return inserted; - } - - /// - /// Ejects the bulb to a mob's hand if possible. - /// - private void EjectBulb(IEntity? user = null) - { - if (LightBulb == null) return; - - var bulb = LightBulb; - - bulb.OnLightBulbStateChange -= UpdateLight; - bulb.OnLightColorChange -= UpdateLight; - - if (!_lightBulbContainer.Remove(bulb.Owner)) return; - - if (user != null) - { - if (!user.TryGetComponent(out HandsComponent? hands) - || !hands.PutInHand(bulb.Owner.GetComponent())) - bulb.Owner.Transform.Coordinates = user.Transform.Coordinates; - } - else - { - bulb.Owner.Transform.Coordinates = Owner.Transform.Coordinates; - } - - } - - /// - /// For attaching UpdateLight() to events. - /// - public void UpdateLight(object? sender, EventArgs? e) - { - UpdateLight(); - } - - /// - /// Updates the light's power drain, sprite and actual light state. - /// - public void UpdateLight() - { - var powerReceiver = Owner.GetComponent(); - powerReceiver.Load = (LightBulb != null && _on && LightBulb.State == LightBulbState.Normal) ? LightBulb.PowerUse : 0; - - if (LightBulb == null) // No light bulb. - { - SetLight(false); - _appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Empty); - return; - } - - switch (LightBulb.State) - { - case LightBulbState.Normal: - if (powerReceiver.Powered && _on) - { - SetLight(true, LightBulb.Color); - _appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.On); - var time = _gameTiming.CurTime; - if (time > _lastThunk + _thunkDelay) - { - _lastThunk = time; - SoundSystem.Play(Filter.Pvs(Owner), _turnOnSound.GetSound(), Owner, AudioParams.Default.WithVolume(-10f)); - } - } - else - { - SetLight(false); - _appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Off); - } - break; - case LightBulbState.Broken: - SetLight(false); - _appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Broken); - break; - case LightBulbState.Burned: - SetLight(false); - _appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Burned); - break; - } - } - - private void SetLight(bool value, Color? color = null) - { - _currentLit = value; - EntitySystem.Get().SetAmbience(Owner.Uid, value); - - if (!Owner.TryGetComponent(out PointLightComponent? pointLight)) return; - pointLight.Enabled = value; - - if (color != null) - pointLight.Color = color.Value; - } - - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - switch (message) - { - case PowerChangedMessage: - UpdateLight(); - break; - } - } - - public void TryDestroyBulb() - { - if (LightBulb == null || LightBulb.State == LightBulbState.Broken) - return; - - LightBulb.State = LightBulbState.Broken; - LightBulb.PlayBreakSound(); - UpdateLight(); - } - - void IMapInit.MapInit() - { - if (_hasLampOnSpawn) - { - var prototype = _bulbType switch - { - LightBulbType.Bulb => "LightBulb", - LightBulbType.Tube => "LightTube", - _ => throw new ArgumentOutOfRangeException() - }; - - var entity = Owner.EntityManager.SpawnEntity(prototype, Owner.Transform.Coordinates); - _lightBulbContainer.Insert(entity); - } - - // need this to update visualizers - UpdateLight(); - } - - public void ToggleLight() - { - _on = !_on; - UpdateLight(); - } - - public void SetState(bool state) - { - _on = state; - UpdateLight(); - } + public ContainerSlot LightBulbContainer = default!; + [ViewVariables] + public bool CurrentLit; + [ViewVariables] + public bool IsBlinking; + [ViewVariables] + public TimeSpan LastThunk; + [ViewVariables] + public TimeSpan? LastGhostBlink; } } diff --git a/Content.Server/Light/EntitySystems/LightBulbSystem.cs b/Content.Server/Light/EntitySystems/LightBulbSystem.cs index 79c5c1285c..cb1105fd3b 100644 --- a/Content.Server/Light/EntitySystems/LightBulbSystem.cs +++ b/Content.Server/Light/EntitySystems/LightBulbSystem.cs @@ -1,6 +1,12 @@ using Content.Server.Light.Components; +using Content.Server.Light.Events; +using Content.Shared.Light; using Content.Shared.Throwing; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.Player; namespace Content.Server.Light.EntitySystems { @@ -9,13 +15,65 @@ namespace Content.Server.Light.EntitySystems public override void Initialize() { base.Initialize(); + + SubscribeLocalEvent(OnInit); SubscribeLocalEvent(HandleLand); } - private void HandleLand(EntityUid uid, LightBulbComponent component, LandEvent args) + private void OnInit(EntityUid uid, LightBulbComponent bulb, ComponentInit args) { - component.PlayBreakSound(); - component.State = LightBulbState.Broken; + // update default state of bulbs + SetColor(uid, bulb.Color, bulb); + SetState(uid, bulb.State, bulb); + } + + private void HandleLand(EntityUid uid, LightBulbComponent bulb, LandEvent args) + { + PlayBreakSound(uid, bulb); + SetState(uid, LightBulbState.Broken, bulb); + } + + /// + /// Set a new color for a light bulb and raise event about change + /// + public void SetColor(EntityUid uid, Color color, LightBulbComponent? bulb = null) + { + if (!Resolve(uid, ref bulb)) + return; + + bulb.Color = color; + UpdateAppearance(uid, bulb); + } + + /// + /// Set a new state for a light bulb (broken, burned) and raise event about change + /// + public void SetState(EntityUid uid, LightBulbState state, LightBulbComponent? bulb = null) + { + if (!Resolve(uid, ref bulb)) + return; + + bulb.State = state; + UpdateAppearance(uid, bulb); + } + + public void PlayBreakSound(EntityUid uid, LightBulbComponent? bulb = null) + { + if (!Resolve(uid, ref bulb)) + return; + + SoundSystem.Play(Filter.Pvs(uid), bulb.BreakSound.GetSound(), uid); + } + + private void UpdateAppearance(EntityUid uid, LightBulbComponent? bulb = null, + AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref bulb, ref appearance, logMissing: false)) + return; + + // try to update appearance and color + appearance.SetData(LightBulbVisuals.State, bulb.State); + appearance.SetData(LightBulbVisuals.Color, bulb.Color); } } } diff --git a/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs b/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs new file mode 100644 index 0000000000..fad5fa9e07 --- /dev/null +++ b/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs @@ -0,0 +1,34 @@ +using Content.Server.Light.Components; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; + +namespace Content.Server.Light.EntitySystems +{ + public class LitOnPoweredSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnPowerSupply); + } + + private void OnPowerChanged(EntityUid uid, LitOnPoweredComponent component, PowerChangedEvent args) + { + if (EntityManager.TryGetComponent(uid, out var light)) + { + light.Enabled = args.Powered; + } + } + + private void OnPowerSupply(EntityUid uid, LitOnPoweredComponent component, PowerNetBatterySupplyEvent args) + { + if (EntityManager.TryGetComponent(uid, out var light)) + { + light.Enabled = args.Supply; + } + } + } +} diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index e9ac10eae4..c51ae60002 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -12,27 +12,270 @@ using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Timing; +using Robust.Shared.Containers; +using Content.Shared.Interaction; +using Content.Shared.Hands.Components; +using Content.Server.Temperature.Components; +using Content.Shared.Popups; +using Robust.Shared.Localization; +using Robust.Shared.Audio; +using Robust.Shared.Player; +using Robust.Shared.Maths; +using Content.Shared.Audio; namespace Content.Server.Light.EntitySystems { /// - /// System for the PoweredLightComponent. Currently bare-bones, to handle events from the DamageableSystem + /// System for the PoweredLightComponens /// public class PoweredLightSystem : EntitySystem { [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!; + [Dependency] private readonly LightBulbSystem _bulbSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + + private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2); public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnGhostBoo); SubscribeLocalEvent(HandleLightDamaged); SubscribeLocalEvent(OnSignalReceived); SubscribeLocalEvent(OnPacketReceived); - SubscribeLocalEvent(OnPowerChanged); - SubscribeLocalEvent(OnPowerSupply); + SubscribeLocalEvent(OnPowerChanged); + } + + private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) + { + light.LightBulbContainer = light.Owner.EnsureContainer("light_bulb"); + } + + private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args) + { + if (light.HasLampOnSpawn) + { + var prototype = light.BulbType switch + { + LightBulbType.Bulb => "LightBulb", + LightBulbType.Tube => "LightTube", + _ => throw new ArgumentOutOfRangeException() + }; + + var entity = EntityManager.SpawnEntity(prototype, light.Owner.Transform.Coordinates); + light.LightBulbContainer.Insert(entity); + } + + // need this to update visualizers + UpdateLight(uid, light); + } + + private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + + args.Handled = InsertBulb(uid, args.Used.Uid, component); + } + + private void OnInteractHand(EntityUid uid, PoweredLightComponent light, InteractHandEvent args) + { + if (args.Handled) + return; + + // check if light has bulb to eject + var bulbUid = GetBulb(uid, light); + if (bulbUid == null) + return; + + // check if it's possible to apply burn damage to user + var userUid = args.User.Uid; + if (EntityManager.TryGetComponent(userUid, out HeatResistanceComponent? heatResist) && + EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb)) + { + // get users heat resistance + var res = heatResist.GetHeatResistance(); + + // check heat resistance against user + var burnedHand = light.CurrentLit && res < lightBulb.BurningTemperature; + if (burnedHand) + { + // apply damage to users hands and show message with sound + var burnMsg = Loc.GetString("powered-light-component-burn-hand"); + _popupSystem.PopupEntity(burnMsg, uid, Filter.Entities(userUid)); + _damageableSystem.TryChangeDamage(userUid, light.Damage); + SoundSystem.Play(Filter.Pvs(uid), light.BurnHandSound.GetSound(), uid); + + args.Handled = true; + return; + } + } + + // all checks passed + // just try to eject bulb + args.Handled = EjectBulb(uid, userUid, light) != null; + } + + #region Bulb Logic API + /// + /// Inserts the bulb if possible. + /// + /// True if it could insert it, false if it couldn't. + public bool InsertBulb(EntityUid uid, EntityUid bulbUid, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return false; + + // check if light already has bulb + if (GetBulb(uid, light) != null) + return false; + + // check if bulb fits + if (!EntityManager.TryGetComponent(bulbUid, out LightBulbComponent? lightBulb)) + return false; + if (lightBulb.Type != light.BulbType) + return false; + + // try to insert bulb in container + if (!light.LightBulbContainer.Insert(EntityManager.GetEntity(bulbUid))) + return false; + + UpdateLight(uid, light); + + return true; + } + + /// + /// Ejects the bulb to a mob's hand if possible. + /// + /// Bulb uid if it was successfully ejected, null otherwise + public EntityUid? EjectBulb(EntityUid uid, EntityUid? userUid = null, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return null; + + // check if light has bulb + var bulbUid = GetBulb(uid, light); + if (bulbUid == null) + return null; + + // try to remove bulb from container + var bulbEnt = EntityManager.GetEntity(bulbUid.Value); + if (!light.LightBulbContainer.Remove(bulbEnt)) + return null; + + // try to place bulb in hands + if (userUid != null) + { + if (EntityManager.TryGetComponent(userUid.Value, out SharedHandsComponent? hands)) + hands.TryPutInActiveHandOrAny(bulbEnt); + } + + UpdateLight(uid, light); + return bulbUid; + } + + /// + /// Try to replace current bulb with a new one + /// If succeed old bulb just drops on floor + /// + public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null) + { + EjectBulb(uid, null, light); + return InsertBulb(uid, bulb, light); + } + + /// + /// Try to get light bulb inserted in powered light + /// + /// Bulb uid if it exist, null otherwise + public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return null; + + return light.LightBulbContainer.ContainedEntity?.Uid; + } + + /// + /// Try to break bulb inside light fixture + /// + public void TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null) + { + // check bulb state + var bulbUid = GetBulb(uid, light); + if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb)) + return; + if (lightBulb.State == LightBulbState.Broken) + return; + + // break it + _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb); + _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb); + UpdateLight(uid, light); + } + #endregion + + private void UpdateLight(EntityUid uid, + PoweredLightComponent? light = null, + ApcPowerReceiverComponent? powerReceiver = null, + AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref light, ref powerReceiver, ref appearance)) + return; + + // check if light has bulb + var bulbUid = GetBulb(uid, light); + if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb)) + { + SetLight(uid, false, light: light); + powerReceiver.Load = 0; + appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Empty); + return; + } + else + { + powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0; + + switch (lightBulb.State) + { + case LightBulbState.Normal: + if (powerReceiver.Powered && light.On) + { + SetLight(uid, true, lightBulb.Color, light); + appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.On); + var time = _gameTiming.CurTime; + if (time > light.LastThunk + ThunkDelay) + { + light.LastThunk = time; + SoundSystem.Play(Filter.Pvs(uid), light.TurnOnSound.GetSound(), uid, AudioParams.Default.WithVolume(-10f)); + } + } + else + { + SetLight(uid, false, light: light); + appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Off); + } + break; + case LightBulbState.Broken: + SetLight(uid, false, light: light); + appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Broken); + break; + case LightBulbState.Burned: + SetLight(uid, false, light: light); + appearance?.SetData(PoweredLightVisuals.BulbState, PoweredLightState.Burned); + break; + } + } } /// @@ -44,7 +287,7 @@ namespace Content.Server.Light.EntitySystems if (args.DamageIncreased) { // Eventually, this logic should all be done by this (or some other) system, not a component. - component.TryDestroyBulb(); + TryDestroyBulb(uid, component); } } @@ -72,6 +315,11 @@ namespace Content.Server.Light.EntitySystems args.Handled = true; } + private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, PowerChangedEvent args) + { + UpdateLight(uid, component); + } + public void ToggleBlinkingLight(PoweredLightComponent light, bool isNowBlinking) { if (light.IsBlinking == isNowBlinking) @@ -89,7 +337,7 @@ namespace Content.Server.Light.EntitySystems switch (args.Port) { case "state": - component.ToggleLight(); + ToggleLight(uid, component); break; } } @@ -103,23 +351,42 @@ namespace Content.Server.Light.EntitySystems if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return; if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return; - component.SetState(enabled); + SetState(uid, enabled, component); } - private void OnPowerChanged(EntityUid uid, LitOnPoweredComponent component, PowerChangedEvent args) + private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null) { - if (EntityManager.TryGetComponent(uid, out var light)) + if (!Resolve(uid, ref light)) + return; + + light.CurrentLit = value; + _ambientSystem.SetAmbience(uid, value); + + if (EntityManager.TryGetComponent(uid, out PointLightComponent? pointLight)) { - light.Enabled = args.Powered; + pointLight.Enabled = value; + + if (color != null) + pointLight.Color = color.Value; } } - private void OnPowerSupply(EntityUid uid, LitOnPoweredComponent component, PowerNetBatterySupplyEvent args) + public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null) { - if (EntityManager.TryGetComponent(uid, out var light)) - { - light.Enabled = args.Supply; - } + if (!Resolve(uid, ref light)) + return; + + light.On = !light.On; + UpdateLight(uid, light); + } + + public void SetState(EntityUid uid, bool state, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return; + + light.On = state; + UpdateLight(uid, light); } } } diff --git a/Content.Shared/Light/SharedLightBulb.cs b/Content.Shared/Light/SharedLightBulb.cs new file mode 100644 index 0000000000..ce15e05625 --- /dev/null +++ b/Content.Shared/Light/SharedLightBulb.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Serialization; +using System; + +namespace Content.Shared.Light +{ + [Serializable, NetSerializable] + public enum LightBulbState : byte + { + Normal, + Broken, + Burned, + } + + [Serializable, NetSerializable] + public enum LightBulbVisuals : byte + { + State, + Color + } + + [Serializable, NetSerializable] + public enum LightBulbType : byte + { + Bulb, + Tube, + } +} diff --git a/Resources/Prototypes/Entities/Objects/Power/lights.yml b/Resources/Prototypes/Entities/Objects/Power/lights.yml index 6d0a77a0e7..0126e4d101 100644 --- a/Resources/Prototypes/Entities/Objects/Power/lights.yml +++ b/Resources/Prototypes/Entities/Objects/Power/lights.yml @@ -1,9 +1,13 @@ -# TODO: Add description (1) +# TODO: Add description (1) - type: entity parent: BaseItem id: BaseLightbulb abstract: true components: + - type: Sprite + netsync: false + sprite: Objects/Power/light_bulb.rsi + state: normal - type: LightBulb - type: Damageable damageContainer: Inorganic @@ -40,6 +44,9 @@ max: 1 - !type:DoActsBehavior acts: [ "Destruction" ] + - type: Appearance + visuals: + - type: LightBulbVisualizer # Lighting color values gathered from # https://andi-siess.de/rgb-to-color-temperature/