diff --git a/Content.Client/Light/EntitySystems/LightBulbSystem.cs b/Content.Client/Light/EntitySystems/LightBulbSystem.cs index c028cc64c6..a3698fc199 100644 --- a/Content.Client/Light/EntitySystems/LightBulbSystem.cs +++ b/Content.Client/Light/EntitySystems/LightBulbSystem.cs @@ -1,36 +1,46 @@ using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; using Robust.Client.GameObjects; -namespace Content.Client.Light.Visualizers; +namespace Content.Client.Light.EntitySystems; -public sealed class LightBulbSystem : VisualizerSystem +public sealed class LightBulbSystem : SharedLightBulbSystem { - protected override void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args) + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly SpriteSystem _sprite = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAppearanceChange); + } + + private void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args) { if (args.Sprite == null) return; // update sprite state - if (AppearanceSystem.TryGetData(uid, LightBulbVisuals.State, out var state, args.Component)) + if (_appearance.TryGetData(uid, LightBulbVisuals.State, out var state, args.Component)) { switch (state) { case LightBulbState.Normal: - SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState); + _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState); break; case LightBulbState.Broken: - SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState); + _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState); break; case LightBulbState.Burned: - SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState); + _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState); break; } } // also update sprites color - if (AppearanceSystem.TryGetData(uid, LightBulbVisuals.Color, out var color, args.Component)) + if (_appearance.TryGetData(uid, LightBulbVisuals.Color, out var color, args.Component)) { - SpriteSystem.SetColor((uid, args.Sprite), color); + _sprite.SetColor((uid, args.Sprite), color); } } } diff --git a/Content.Client/Light/EntitySystems/PoweredLightSystem.cs b/Content.Client/Light/EntitySystems/PoweredLightSystem.cs new file mode 100644 index 0000000000..b8a6b16da4 --- /dev/null +++ b/Content.Client/Light/EntitySystems/PoweredLightSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Light.EntitySystems; + +namespace Content.Client.Light.EntitySystems; + +public sealed class PoweredLightSystem : SharedPoweredLightSystem; diff --git a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs index fbebcb7cf8..ead686189e 100644 --- a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs +++ b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs @@ -5,4 +5,5 @@ namespace Content.Client.Power.Components; [RegisterComponent] public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent { + public override float Load { get; set; } } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 05dfffa4a9..1f04421c0f 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Administration.Logs; -using Content.Server.Light.Components; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.NodeGroups; @@ -16,6 +15,7 @@ using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Jittering; +using Content.Shared.Light.Components; using Content.Shared.Maps; using Content.Shared.NodeContainer; using Content.Shared.NodeContainer.NodeGroups; diff --git a/Content.Server/Light/EntitySystems/LightBulbSystem.cs b/Content.Server/Light/EntitySystems/LightBulbSystem.cs index 5714bde3e5..ba5b795e2d 100644 --- a/Content.Server/Light/EntitySystems/LightBulbSystem.cs +++ b/Content.Server/Light/EntitySystems/LightBulbSystem.cs @@ -1,87 +1,5 @@ -using Content.Server.Light.Components; -using Content.Shared.Destructible; -using Content.Shared.Light.Components; -using Content.Shared.Throwing; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; +using Content.Shared.Light.EntitySystems; -namespace Content.Server.Light.EntitySystems -{ - public sealed class LightBulbSystem : EntitySystem - { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; +namespace Content.Server.Light.EntitySystems; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(HandleLand); - SubscribeLocalEvent(OnBreak); - } - - private void OnInit(EntityUid uid, LightBulbComponent bulb, ComponentInit args) - { - // update default state of bulbs - SetColor(uid, bulb.Color, bulb); - SetState(uid, bulb.State, bulb); - } - - private void HandleLand(EntityUid uid, LightBulbComponent bulb, ref LandEvent args) - { - PlayBreakSound(uid, bulb); - SetState(uid, LightBulbState.Broken, bulb); - } - - private void OnBreak(EntityUid uid, LightBulbComponent component, BreakageEventArgs args) - { - SetState(uid, LightBulbState.Broken, component); - } - - /// - /// 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; - - _audio.PlayPvs(bulb.BreakSound, 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(uid, LightBulbVisuals.State, bulb.State, appearance); - _appearance.SetData(uid, LightBulbVisuals.Color, bulb.Color, appearance); - } - } -} +public sealed class LightBulbSystem : SharedLightBulbSystem; diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index b5b9f20432..948c44cd75 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -1,440 +1,64 @@ -using Content.Server.DeviceLinking.Systems; -using Content.Server.DeviceNetwork; -using Content.Server.DeviceNetwork.Systems; using Content.Server.Emp; using Content.Server.Ghost; -using Content.Server.Light.Components; -using Content.Server.Power.Components; -using Content.Shared.Audio; -using Content.Shared.Damage; -using Content.Shared.DeviceLinking.Events; -using Content.Shared.DoAfter; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Interaction; -using Content.Shared.Light; using Content.Shared.Light.Components; -using Robust.Server.GameObjects; -using Robust.Shared.Containers; -using Robust.Shared.Timing; -using Robust.Shared.Audio.Systems; -using Content.Shared.Damage.Systems; -using Content.Shared.Damage.Components; -using Content.Shared.DeviceNetwork; -using Content.Shared.DeviceNetwork.Events; -using Content.Shared.Power; +using Content.Shared.Light.EntitySystems; -namespace Content.Server.Light.EntitySystems +namespace Content.Server.Light.EntitySystems; + +/// +/// System for the PoweredLightComponents +/// +public sealed class PoweredLightSystem : SharedPoweredLightSystem { - /// - /// System for the PoweredLightComponents - /// - public sealed class PoweredLightSystem : EntitySystem + public override void Initialize() { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!; - [Dependency] private readonly LightBulbSystem _bulbSystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly PointLightSystem _pointLight = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!; + base.Initialize(); + SubscribeLocalEvent(OnMapInit); - private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2); - public const string LightBulbContainer = "light_bulb"; + SubscribeLocalEvent(OnGhostBoo); - public override void Initialize() + SubscribeLocalEvent(OnEmpPulse); + } + + private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args) + { + if (light.IgnoreGhostsBoo) + return; + + // check cooldown first to prevent abuse + var time = GameTiming.CurTime; + if (light.LastGhostBlink != null) { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnInteractHand); - - SubscribeLocalEvent(OnGhostBoo); - SubscribeLocalEvent(HandleLightDamaged); - - SubscribeLocalEvent(OnSignalReceived); - SubscribeLocalEvent(OnPacketReceived); - - SubscribeLocalEvent(OnPowerChanged); - - SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnEmpPulse); - } - - private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) - { - light.LightBulbContainer = _containerSystem.EnsureContainer(uid, LightBulbContainer); - _signalSystem.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort); - } - - private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args) - { - // TODO: Use ContainerFill dog - if (light.HasLampOnSpawn != null) - { - var entity = Spawn(light.HasLampOnSpawn, Comp(uid).Coordinates); - _containerSystem.Insert(entity, light.LightBulbContainer); - } - // need this to update visualizers - UpdateLight(uid, light); - } - - private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args) - { - if (args.Handled) + if (time <= light.LastGhostBlink + light.GhostBlinkingCooldown) return; - - args.Handled = InsertBulb(uid, args.Used, component); } - private void OnInteractHand(EntityUid uid, PoweredLightComponent light, InteractHandEvent args) + light.LastGhostBlink = time; + + ToggleBlinkingLight(uid, light, true); + uid.SpawnTimer(light.GhostBlinkingTime, () => { - if (args.Handled) - return; + ToggleBlinkingLight(uid, light, false); + }); - // check if light has bulb to eject - var bulbUid = GetBulb(uid, light); - if (bulbUid == null) - return; + args.Handled = true; + } - var userUid = args.User; - //removing a broken/burned bulb, so allow instant removal - if(TryComp(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal) - { - args.Handled = EjectBulb(uid, userUid, light) != null; - return; - } - - // removing a working bulb, so require a delay - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid) - { - BreakOnMove = true, - BreakOnDamage = true, - }); - - args.Handled = true; - } - - #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) + private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args) + { + // TODO: Use ContainerFill dog + if (light.HasLampOnSpawn != 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 (!TryComp(bulbUid, out LightBulbComponent? lightBulb)) - return false; - if (lightBulb.Type != light.BulbType) - return false; - - // try to insert bulb in container - if (!_containerSystem.Insert(bulbUid, light.LightBulbContainer)) - return false; - - UpdateLight(uid, light); - return true; + var entity = EntityManager.SpawnEntity(light.HasLampOnSpawn, EntityManager.GetComponent(uid).Coordinates); + ContainerSystem.Insert(entity, light.LightBulbContainer); } + // need this to update visualizers + UpdateLight(uid, light); + } - /// - /// 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 - if (GetBulb(uid, light) is not { Valid: true } bulb) - return null; - - // try to remove bulb from container - if (!_containerSystem.Remove(bulb, light.LightBulbContainer)) - return null; - - // try to place bulb in hands - _handsSystem.PickupOrDrop(userUid, bulb); - - UpdateLight(uid, light); - return bulb; - } - - /// - /// Replaces the spawned prototype of a pre-mapinit powered light with a different variant. - /// - public bool ReplaceSpawnedPrototype(Entity light, string bulb) - { - if (light.Comp.LightBulbContainer.ContainedEntity != null) - return false; - - if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized) - return false; - - light.Comp.HasLampOnSpawn = bulb; - return true; - } - - /// - /// 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; - } - - /// - /// Try to break bulb inside light fixture - /// - public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light, false)) - return false; - - // if we aren't mapinited, - // just null the spawned bulb - if (LifeStage(uid) < EntityLifeStage.MapInitialized) - { - light.HasLampOnSpawn = null; - return true; - } - - // check bulb state - var bulbUid = GetBulb(uid, light); - if (bulbUid == null || !TryComp(bulbUid.Value, out LightBulbComponent? lightBulb)) - return false; - if (lightBulb.State == LightBulbState.Broken) - return false; - - // break it - _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb); - _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb); - UpdateLight(uid, light); - return true; - } - #endregion - - private void UpdateLight(EntityUid uid, - PoweredLightComponent? light = null, - ApcPowerReceiverComponent? powerReceiver = null, - AppearanceComponent? appearance = null) - { - if (!Resolve(uid, ref light, ref powerReceiver, false)) - return; - - // Optional component. - Resolve(uid, ref appearance, false); - - // check if light has bulb - var bulbUid = GetBulb(uid, light); - if (bulbUid == null || !TryComp(bulbUid.Value, out LightBulbComponent? lightBulb)) - { - SetLight(uid, false, light: light); - powerReceiver.Load = 0; - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Empty, appearance); - return; - } - - switch (lightBulb.State) - { - case LightBulbState.Normal: - if (powerReceiver.Powered && light.On) - { - SetLight(uid, true, lightBulb.Color, light, lightBulb.LightRadius, lightBulb.LightEnergy, lightBulb.LightSoftness); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.On, appearance); - var time = _gameTiming.CurTime; - if (time > light.LastThunk + ThunkDelay) - { - light.LastThunk = time; - _audio.PlayPvs(light.TurnOnSound, uid, light.TurnOnSound.Params.AddVolume(-10f)); - } - } - else - { - SetLight(uid, false, light: light); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Off, appearance); - } - break; - case LightBulbState.Broken: - SetLight(uid, false, light: light); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Broken, appearance); - break; - case LightBulbState.Burned: - SetLight(uid, false, light: light); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Burned, appearance); - break; - } - - powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0; - } - - /// - /// Destroy the light bulb if the light took any damage. - /// - public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args) - { - // Was it being repaired, or did it take damage? - if (args.DamageIncreased) - { - // Eventually, this logic should all be done by this (or some other) system, not a component. - TryDestroyBulb(uid, component); - } - } - - private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args) - { - if (light.IgnoreGhostsBoo) - return; - - // check cooldown first to prevent abuse - var time = _gameTiming.CurTime; - if (light.LastGhostBlink != null) - { - if (time <= light.LastGhostBlink + light.GhostBlinkingCooldown) - return; - } - - light.LastGhostBlink = time; - - ToggleBlinkingLight(uid, light, true); - uid.SpawnTimer(light.GhostBlinkingTime, () => - { - ToggleBlinkingLight(uid, light, false); - }); - - args.Handled = true; - } - - private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, ref PowerChangedEvent args) - { - // TODO: Power moment - var metadata = MetaData(uid); - - if (metadata.EntityPaused || TerminatingOrDeleted(uid, metadata)) - return; - - UpdateLight(uid, component); - } - - public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking) - { - if (light.IsBlinking == isNowBlinking) - return; - - light.IsBlinking = isNowBlinking; - - if (!TryComp(uid, out AppearanceComponent? appearance)) - return; - - _appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance); - } - - private void OnSignalReceived(EntityUid uid, PoweredLightComponent component, ref SignalReceivedEvent args) - { - if (args.Port == component.OffPort) - SetState(uid, false, component); - else if (args.Port == component.OnPort) - SetState(uid, true, component); - else if (args.Port == component.TogglePort) - ToggleLight(uid, component); - } - - /// - /// Turns the light on or of when receiving a command. - /// The light is turned on or of according to the value - /// - private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, DeviceNetworkPacketEvent args) - { - if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return; - if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return; - - SetState(uid, enabled, component); - } - - private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null) - { - if (!Resolve(uid, ref light)) - return; - - light.CurrentLit = value; - _ambientSystem.SetAmbience(uid, value); - - if (TryComp(uid, out PointLightComponent? pointLight)) - { - _pointLight.SetEnabled(uid, value, pointLight); - - if (color != null) - _pointLight.SetColor(uid, color.Value, pointLight); - if (radius != null) - _pointLight.SetRadius(uid, (float) radius, pointLight); - if (energy != null) - _pointLight.SetEnergy(uid, (float) energy, pointLight); - if (softness != null) - _pointLight.SetSoftness(uid, (float) softness, pointLight); - } - - // light bulbs burn your hands! - if (TryComp(uid, out var damageOnInteractComp)) - _damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value); - } - - public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null) - { - 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); - } - - private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args) - { - if (args.Handled || args.Cancelled || args.Args.Target == null) - return; - - EjectBulb(args.Args.Target.Value, args.Args.User, component); - - args.Handled = true; - } - - private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args) - { - if (TryDestroyBulb(uid, component)) - args.Affected = true; - } + private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args) + { + if (TryDestroyBulb(uid, component)) + args.Affected = true; } } diff --git a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs index aa8feff3e0..bfd096a253 100644 --- a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs +++ b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs @@ -14,9 +14,12 @@ namespace Content.Server.Power.Components /// /// Amount of charge this needs from an APC per second to function. /// - [ViewVariables(VVAccess.ReadWrite)] [DataField("powerLoad")] - public float Load { get => NetworkLoad.DesiredPower; set => NetworkLoad.DesiredPower = value; } + public override float Load + { + get => NetworkLoad.DesiredPower; + set => NetworkLoad.DesiredPower = value; + } public ApcPowerProviderComponent? Provider = null; diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 0ef7b8f533..3f91b0fa71 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -20,6 +20,7 @@ using Content.Shared.DoAfter; using Content.Shared.Emag.Systems; using Content.Shared.FixedPoint; using Content.Shared.Humanoid; +using Content.Shared.Light.Components; using Content.Shared.Maps; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; diff --git a/Content.Server/StationEvents/Events/SolarFlareRule.cs b/Content.Server/StationEvents/Events/SolarFlareRule.cs index 19f6e393d2..b6530d867f 100644 --- a/Content.Server/StationEvents/Events/SolarFlareRule.cs +++ b/Content.Server/StationEvents/Events/SolarFlareRule.cs @@ -8,6 +8,7 @@ using Content.Shared.Radio.Components; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; using Content.Shared.GameTicking.Components; +using Content.Shared.Light.Components; namespace Content.Server.StationEvents.Events; diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs index 4c4073b123..6e7c4fc4ad 100644 --- a/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs +++ b/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Ghost; -using Content.Server.Light.Components; using Content.Server.Xenoarchaeology.Artifact.XAE.Components; +using Content.Shared.Light.Components; using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact.XAE; using Robust.Shared.Random; diff --git a/Content.Shared/Light/Components/LightBulbComponent.cs b/Content.Shared/Light/Components/LightBulbComponent.cs index 35b04be897..fcd3840a0c 100644 --- a/Content.Shared/Light/Components/LightBulbComponent.cs +++ b/Content.Shared/Light/Components/LightBulbComponent.cs @@ -8,69 +8,61 @@ namespace Content.Shared.Light.Components; /// Component that represents a light bulb. Can be broken, or burned, which turns them mostly useless. /// TODO: Breaking and burning should probably be moved to another component eventually. /// -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class LightBulbComponent : Component { /// /// The color of the lightbulb and the light it produces. /// - [DataField("color")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public Color Color = Color.White; /// /// The type of lightbulb. Tube/bulb/etc... /// [DataField("bulb")] - [ViewVariables(VVAccess.ReadWrite)] public LightBulbType Type = LightBulbType.Tube; /// /// The initial state of the lightbulb. /// - [DataField("startingState")] + [DataField("startingState"), AutoNetworkedField] public LightBulbState State = LightBulbState.Normal; /// /// The temperature the air around the lightbulb is exposed to when the lightbulb burns out. /// [DataField("BurningTemperature")] - [ViewVariables(VVAccess.ReadWrite)] public int BurningTemperature = 1400; /// /// Relates to how bright the light produced by the lightbulb is. /// - [DataField("lightEnergy")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float LightEnergy = 0.8f; /// /// The maximum radius of the point light source this light produces. /// - [DataField("lightRadius")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float LightRadius = 10; /// /// Relates to the falloff constant of the light produced by the lightbulb. /// - [DataField("lightSoftness")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float LightSoftness = 1; /// /// The amount of power used by the lightbulb when it's active. /// [DataField("PowerUse")] - [ViewVariables(VVAccess.ReadWrite)] public int PowerUse = 60; /// /// The sound produced when the lightbulb breaks. /// - [DataField("breakSound")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public SoundSpecifier BreakSound = new SoundCollectionSpecifier("GlassBreak", AudioParams.Default.WithVolume(-6f)); #region Appearance @@ -78,22 +70,19 @@ public sealed partial class LightBulbComponent : Component /// /// The sprite state used when the lightbulb is intact. /// - [DataField("normalSpriteState")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string NormalSpriteState = "normal"; /// /// The sprite state used when the lightbulb is broken. /// - [DataField("brokenSpriteState")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string BrokenSpriteState = "broken"; /// /// The sprite state used when the lightbulb is burned. /// - [DataField("burnedSpriteState")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string BurnedSpriteState = "burned"; #endregion Appearance diff --git a/Content.Shared/Light/Components/LightReplacerComponent.cs b/Content.Shared/Light/Components/LightReplacerComponent.cs index 3ce9647c88..1276ff9edc 100644 --- a/Content.Shared/Light/Components/LightReplacerComponent.cs +++ b/Content.Shared/Light/Components/LightReplacerComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Light.Components; using Content.Shared.Light.EntitySystems; using Content.Shared.Storage; using Robust.Shared.Audio; diff --git a/Content.Server/Light/Components/PoweredLightComponent.cs b/Content.Shared/Light/Components/PoweredLightComponent.cs similarity index 54% rename from Content.Server/Light/Components/PoweredLightComponent.cs rename to Content.Shared/Light/Components/PoweredLightComponent.cs index 1a6f610516..ad9a9d401c 100644 --- a/Content.Server/Light/Components/PoweredLightComponent.cs +++ b/Content.Shared/Light/Components/PoweredLightComponent.cs @@ -1,80 +1,87 @@ -using Content.Server.Light.EntitySystems; -using Content.Shared.Damage; using Content.Shared.DeviceLinking; -using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; using Robust.Shared.Audio; using Robust.Shared.Containers; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Light.Components +namespace Content.Shared.Light.Components { /// /// Component that represents a wall light. It has a light bulb that can be replaced when broken. /// - [RegisterComponent, Access(typeof(PoweredLightSystem))] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause, Access(typeof(SharedPoweredLightSystem))] public sealed partial class PoweredLightComponent : Component { - [DataField("burnHandSound")] + /* + * Stop adding more fields, use components or I will shed you. + */ + + [DataField] public SoundSpecifier BurnHandSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); - [DataField("turnOnSound")] + [DataField] public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Machines/light_tube_on.ogg"); - [DataField("hasLampOnSpawn", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? HasLampOnSpawn = null; + // Should be using containerfill? + [DataField] + public EntProtoId? HasLampOnSpawn = null; [DataField("bulb")] public LightBulbType BulbType; - [DataField("on")] + [DataField, AutoNetworkedField] public bool On = true; - [DataField("ignoreGhostsBoo")] + [DataField] public bool IgnoreGhostsBoo; - [DataField("ghostBlinkingTime")] + [DataField] public TimeSpan GhostBlinkingTime = TimeSpan.FromSeconds(10); - [DataField("ghostBlinkingCooldown")] + [DataField] public TimeSpan GhostBlinkingCooldown = TimeSpan.FromSeconds(60); [ViewVariables] public ContainerSlot LightBulbContainer = default!; - [ViewVariables] + + [AutoNetworkedField] public bool CurrentLit; - [ViewVariables] + + [DataField, AutoNetworkedField] public bool IsBlinking; - [ViewVariables] + + [DataField, AutoNetworkedField, AutoPausedField] public TimeSpan LastThunk; - [ViewVariables] + + [DataField, AutoPausedField] public TimeSpan? LastGhostBlink; - [DataField("onPort", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string OnPort = "On"; + [DataField] + public ProtoId OnPort = "On"; - [DataField("offPort", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string OffPort = "Off"; + [DataField] + public ProtoId OffPort = "Off"; - [DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string TogglePort = "Toggle"; + [DataField] + public ProtoId TogglePort = "Toggle"; /// /// How long it takes to eject a bulb from this /// - [DataField("ejectBulbDelay")] + [DataField] public float EjectBulbDelay = 2; /// /// Shock damage done to a mob that hits the light with an unarmed attack /// - [DataField("unarmedHitShock")] + [DataField] public int UnarmedHitShock = 20; /// /// Stun duration applied to a mob that hits the light with an unarmed attack /// - [DataField("unarmedHitStun")] + [DataField] public TimeSpan UnarmedHitStun = TimeSpan.FromSeconds(5); } } diff --git a/Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs b/Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs new file mode 100644 index 0000000000..34c986b4de --- /dev/null +++ b/Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs @@ -0,0 +1,84 @@ +using Content.Shared.Destructible; +using Content.Shared.Light.Components; +using Content.Shared.Throwing; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared.Light.EntitySystems; + +public abstract class SharedLightBulbSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(HandleLand); + SubscribeLocalEvent(OnBreak); + } + + private void OnInit(EntityUid uid, LightBulbComponent bulb, ComponentInit args) + { + // update default state of bulbs + SetColor(uid, bulb.Color, bulb); + SetState(uid, bulb.State, bulb); + } + + private void HandleLand(EntityUid uid, LightBulbComponent bulb, ref LandEvent args) + { + PlayBreakSound(uid, bulb); + SetState(uid, LightBulbState.Broken, bulb); + } + + private void OnBreak(EntityUid uid, LightBulbComponent component, BreakageEventArgs args) + { + SetState(uid, LightBulbState.Broken, component); + } + + /// + /// 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) || bulb.Color.Equals(color)) + return; + + bulb.Color = color; + Dirty(uid, bulb); + 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) || bulb.State == state) + return; + + bulb.State = state; + Dirty(uid, bulb); + UpdateAppearance(uid, bulb); + } + + public void PlayBreakSound(EntityUid uid, LightBulbComponent? bulb = null, EntityUid? user = null) + { + if (!Resolve(uid, ref bulb)) + return; + + _audio.PlayPredicted(bulb.BreakSound, uid, user: user); + } + + 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(uid, LightBulbVisuals.State, bulb.State, appearance); + _appearance.SetData(uid, LightBulbVisuals.Color, bulb.Color, appearance); + } +} diff --git a/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs new file mode 100644 index 0000000000..e1dc4be5d0 --- /dev/null +++ b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs @@ -0,0 +1,417 @@ +using Content.Shared.Audio; +using Content.Shared.Damage; +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.DeviceLinking; +using Content.Shared.DeviceLinking.Events; +using Content.Shared.DeviceNetwork; +using Content.Shared.DeviceNetwork.Events; +using Content.Shared.DoAfter; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Light.Components; +using Content.Shared.Power; +using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; +using Content.Shared.Storage.EntitySystems; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Timing; + +namespace Content.Shared.Light.EntitySystems; + +public abstract class SharedPoweredLightSystem : EntitySystem +{ + [Dependency] protected readonly IGameTiming GameTiming = default!; + [Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedLightBulbSystem _bulbSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; + [Dependency] private readonly SharedPointLightSystem _pointLight = default!; + [Dependency] private readonly SharedStorageSystem _storage = default!; + [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!; + + private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2); + public const string LightBulbContainer = "light_bulb"; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemoved); + SubscribeLocalEvent(OnInserted); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnSignalReceived); + SubscribeLocalEvent(OnPacketReceived); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(HandleLightDamaged); + } + + private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) + { + light.LightBulbContainer = ContainerSystem.EnsureContainer(uid, LightBulbContainer); + _deviceLink.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort); + } + + private void OnRemoved(Entity light, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != LightBulbContainer) + return; + + UpdateLight(light, light); + } + + private void OnInserted(Entity light, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != LightBulbContainer) + return; + + UpdateLight(light, light); + } + + private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + + args.Handled = InsertBulb(uid, args.Used, component, user: args.User, playAnimation: true); + } + + 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; + + var userUid = args.User; + //removing a broken/burned bulb, so allow instant removal + if (TryComp(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal) + { + args.Handled = EjectBulb(uid, userUid, light) != null; + return; + } + + // removing a working bulb, so require a delay + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid) + { + BreakOnMove = true, + BreakOnDamage = true, + }); + + args.Handled = true; + } + + private void OnSignalReceived(Entity ent, ref SignalReceivedEvent args) + { + if (args.Port == ent.Comp.OffPort) + SetState(ent, false, ent.Comp); + else if (args.Port == ent.Comp.OnPort) + SetState(ent, true, ent.Comp); + else if (args.Port == ent.Comp.TogglePort) + ToggleLight(ent, ent.Comp); + } + + /// + /// Turns the light on or of when receiving a command. + /// The light is turned on or of according to the value + /// + private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, DeviceNetworkPacketEvent args) + { + if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return; + if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return; + + SetState(uid, enabled, component); + } + + /// + /// 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, EntityUid? user = null, bool playAnimation = false) + { + 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 (!TryComp(bulbUid, out var lightBulb)) + return false; + + if (lightBulb.Type != light.BulbType) + return false; + + // try to insert bulb in container + if (!ContainerSystem.Insert(bulbUid, light.LightBulbContainer)) + return false; + + if (playAnimation && TryComp(user, out TransformComponent? xform)) + { + var itemXform = Transform(uid); + _storage.PlayPickupAnimation(bulbUid, xform.Coordinates, itemXform.Coordinates, itemXform.LocalRotation, user: user); + } + + 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 + if (GetBulb(uid, light) is not { Valid: true } bulb) + return null; + + // try to remove bulb from container + if (!ContainerSystem.Remove(bulb, light.LightBulbContainer)) + return null; + + // try to place bulb in hands + _handsSystem.PickupOrDrop(userUid, bulb); + + return bulb; + } + + /// + /// Replaces the spawned prototype of a pre-mapinit powered light with a different variant. + /// + public bool ReplaceSpawnedPrototype(Entity light, string bulb) + { + if (light.Comp.LightBulbContainer.ContainedEntity != null) + return false; + + if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized) + return false; + + light.Comp.HasLampOnSpawn = bulb; + return true; + } + + /// + /// 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; + } + + /// + /// Try to break bulb inside light fixture + /// + public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light, false)) + return false; + + // if we aren't mapinited, + // just null the spawned bulb + if (LifeStage(uid) < EntityLifeStage.MapInitialized) + { + light.HasLampOnSpawn = null; + return true; + } + + // check bulb state + var bulbUid = GetBulb(uid, light); + if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb)) + return false; + if (lightBulb.State == LightBulbState.Broken) + return false; + + // break it + _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb); + _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb); + UpdateLight(uid, light); + return true; + } + + protected void UpdateLight(EntityUid uid, + PoweredLightComponent? light = null, + SharedApcPowerReceiverComponent? powerReceiver = null, + AppearanceComponent? appearance = null, + EntityUid? user = null) + { + if (!Resolve(uid, ref light, false)) + return; + + if (!_receiver.ResolveApc(uid, ref powerReceiver)) + return; + + // Optional component. + Resolve(uid, ref appearance, false); + + // check if light has bulb + var bulbUid = GetBulb(uid, light); + if (bulbUid == null || !TryComp(bulbUid.Value, out var lightBulb)) + { + SetLight(uid, false, light: light); + powerReceiver.Load = 0; + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Empty, appearance); + return; + } + + switch (lightBulb.State) + { + case LightBulbState.Normal: + if (powerReceiver.Powered && light.On) + { + SetLight(uid, true, lightBulb.Color, light, lightBulb.LightRadius, lightBulb.LightEnergy, lightBulb.LightSoftness); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.On, appearance); + var time = GameTiming.CurTime; + if (time > light.LastThunk + ThunkDelay) + { + light.LastThunk = time; + Dirty(uid, light); + _audio.PlayPredicted(light.TurnOnSound, uid, user: user, light.TurnOnSound.Params.AddVolume(-10f)); + } + } + else + { + SetLight(uid, false, light: light); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Off, appearance); + } + break; + case LightBulbState.Broken: + SetLight(uid, false, light: light); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Broken, appearance); + break; + case LightBulbState.Burned: + SetLight(uid, false, light: light); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Burned, appearance); + break; + } + + powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0; + } + + /// + /// Destroy the light bulb if the light took any damage. + /// + public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args) + { + // Was it being repaired, or did it take damage? + if (args.DamageIncreased) + { + // Eventually, this logic should all be done by this (or some other) system, not a component. + TryDestroyBulb(uid, component); + } + } + + private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, ref PowerChangedEvent args) + { + // TODO: Power moment + var metadata = MetaData(uid); + + if (metadata.EntityPaused || TerminatingOrDeleted(uid, metadata)) + return; + + UpdateLight(uid, component); + } + + public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking) + { + if (light.IsBlinking == isNowBlinking) + return; + + light.IsBlinking = isNowBlinking; + Dirty(uid, light); + + if (!TryComp(uid, out var appearance)) + return; + + _appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance); + } + + private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null) + { + if (!Resolve(uid, ref light)) + return; + + if (light.CurrentLit != value) + { + light.CurrentLit = value; + Dirty(uid, light); + } + + _ambientSystem.SetAmbience(uid, value); + + if (_pointLight.TryGetLight(uid, out var pointLight)) + { + _pointLight.SetEnabled(uid, value, pointLight); + + if (color != null) + _pointLight.SetColor(uid, color.Value, pointLight); + if (radius != null) + _pointLight.SetRadius(uid, (float)radius, pointLight); + if (energy != null) + _pointLight.SetEnergy(uid, (float)energy, pointLight); + if (softness != null) + _pointLight.SetSoftness(uid, (float)softness, pointLight); + } + + // light bulbs burn your hands! + if (TryComp(uid, out var damageOnInteractComp)) + _damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value); + } + + public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null) + { + 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; + Dirty(uid, light); + UpdateLight(uid, light); + } + + private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + EjectBulb(args.Args.Target.Value, args.Args.User, component); + + args.Handled = true; + } +} diff --git a/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs index 80bacd24bd..96189a92ae 100644 --- a/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs +++ b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs @@ -19,4 +19,7 @@ public abstract partial class SharedApcPowerReceiverComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] public virtual bool PowerDisabled { get; set; } + + // Doesn't actually do anything on the client just here for shared code. + public abstract float Load { get; set; } }