Predict PoweredLights (#36541)
* Move PoweredLight to shared * Predict the rest of the owl * reacher * compinit & anim * Fix names * Revert this? * Fix these * chicken drummies * deita * Fix * review * fix * fixes * fix PVS weirdness --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
@@ -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<LightBulbComponent>
|
||||
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<LightBulbComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
// update sprite state
|
||||
if (AppearanceSystem.TryGetData<LightBulbState>(uid, LightBulbVisuals.State, out var state, args.Component))
|
||||
if (_appearance.TryGetData<LightBulbState>(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<Color>(uid, LightBulbVisuals.Color, out var color, args.Component))
|
||||
if (_appearance.TryGetData<Color>(uid, LightBulbVisuals.Color, out var color, args.Component))
|
||||
{
|
||||
SpriteSystem.SetColor((uid, args.Sprite), color);
|
||||
_sprite.SetColor((uid, args.Sprite), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
Content.Client/Light/EntitySystems/PoweredLightSystem.cs
Normal file
5
Content.Client/Light/EntitySystems/PoweredLightSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
public sealed class PoweredLightSystem : SharedPoweredLightSystem;
|
||||
@@ -5,4 +5,5 @@ namespace Content.Client.Power.Components;
|
||||
[RegisterComponent]
|
||||
public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent
|
||||
{
|
||||
public override float Load { get; set; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<LightBulbComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<LightBulbComponent, LandEvent>(HandleLand);
|
||||
SubscribeLocalEvent<LightBulbComponent, BreakageEventArgs>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new color for a light bulb and raise event about change
|
||||
/// </summary>
|
||||
public void SetColor(EntityUid uid, Color color, LightBulbComponent? bulb = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bulb))
|
||||
return;
|
||||
|
||||
bulb.Color = color;
|
||||
UpdateAppearance(uid, bulb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new state for a light bulb (broken, burned) and raise event about change
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// System for the PoweredLightComponents
|
||||
/// </summary>
|
||||
public sealed class PoweredLightSystem : SharedPoweredLightSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// System for the PoweredLightComponents
|
||||
/// </summary>
|
||||
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<PoweredLightComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2);
|
||||
public const string LightBulbContainer = "light_bulb";
|
||||
SubscribeLocalEvent<PoweredLightComponent, GhostBooEvent>(OnGhostBoo);
|
||||
|
||||
public override void Initialize()
|
||||
SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(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<PoweredLightComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PoweredLightComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<PoweredLightComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<PoweredLightComponent, InteractHandEvent>(OnInteractHand);
|
||||
|
||||
SubscribeLocalEvent<PoweredLightComponent, GhostBooEvent>(OnGhostBoo);
|
||||
SubscribeLocalEvent<PoweredLightComponent, DamageChangedEvent>(HandleLightDamaged);
|
||||
|
||||
SubscribeLocalEvent<PoweredLightComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
SubscribeLocalEvent<PoweredLightComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
|
||||
SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
|
||||
SubscribeLocalEvent<PoweredLightComponent, PoweredLightDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
|
||||
{
|
||||
light.LightBulbContainer = _containerSystem.EnsureContainer<ContainerSlot>(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<TransformComponent>(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<LightBulbComponent>(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
|
||||
/// <summary>
|
||||
/// Inserts the bulb if possible.
|
||||
/// </summary>
|
||||
/// <returns>True if it could insert it, false if it couldn't.</returns>
|
||||
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<TransformComponent>(uid).Coordinates);
|
||||
ContainerSystem.Insert(entity, light.LightBulbContainer);
|
||||
}
|
||||
// need this to update visualizers
|
||||
UpdateLight(uid, light);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the bulb to a mob's hand if possible.
|
||||
/// </summary>
|
||||
/// <returns>Bulb uid if it was successfully ejected, null otherwise</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the spawned prototype of a pre-mapinit powered light with a different variant.
|
||||
/// </summary>
|
||||
public bool ReplaceSpawnedPrototype(Entity<PoweredLightComponent> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to replace current bulb with a new one
|
||||
/// If succeed old bulb just drops on floor
|
||||
/// </summary>
|
||||
public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null)
|
||||
{
|
||||
EjectBulb(uid, null, light);
|
||||
return InsertBulb(uid, bulb, light);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get light bulb inserted in powered light
|
||||
/// </summary>
|
||||
/// <returns>Bulb uid if it exist, null otherwise</returns>
|
||||
public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return null;
|
||||
|
||||
return light.LightBulbContainer.ContainedEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to break bulb inside light fixture
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy the light bulb if the light took any damage.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns the light on or of when receiving a <see cref="DeviceNetworkConstants.CmdSetState"/> command.
|
||||
/// The light is turned on or of according to the <see cref="DeviceNetworkConstants.StateEnabled"/> value
|
||||
/// </summary>
|
||||
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<DamageOnInteractComponent>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,12 @@ namespace Content.Server.Power.Components
|
||||
/// <summary>
|
||||
/// Amount of charge this needs from an APC per second to function.
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class LightBulbComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The color of the lightbulb and the light it produces.
|
||||
/// </summary>
|
||||
[DataField("color")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public Color Color = Color.White;
|
||||
|
||||
/// <summary>
|
||||
/// The type of lightbulb. Tube/bulb/etc...
|
||||
/// </summary>
|
||||
[DataField("bulb")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public LightBulbType Type = LightBulbType.Tube;
|
||||
|
||||
/// <summary>
|
||||
/// The initial state of the lightbulb.
|
||||
/// </summary>
|
||||
[DataField("startingState")]
|
||||
[DataField("startingState"), AutoNetworkedField]
|
||||
public LightBulbState State = LightBulbState.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// The temperature the air around the lightbulb is exposed to when the lightbulb burns out.
|
||||
/// </summary>
|
||||
[DataField("BurningTemperature")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int BurningTemperature = 1400;
|
||||
|
||||
/// <summary>
|
||||
/// Relates to how bright the light produced by the lightbulb is.
|
||||
/// </summary>
|
||||
[DataField("lightEnergy")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float LightEnergy = 0.8f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum radius of the point light source this light produces.
|
||||
/// </summary>
|
||||
[DataField("lightRadius")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float LightRadius = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Relates to the falloff constant of the light produced by the lightbulb.
|
||||
/// </summary>
|
||||
[DataField("lightSoftness")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float LightSoftness = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of power used by the lightbulb when it's active.
|
||||
/// </summary>
|
||||
[DataField("PowerUse")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PowerUse = 60;
|
||||
|
||||
/// <summary>
|
||||
/// The sound produced when the lightbulb breaks.
|
||||
/// </summary>
|
||||
[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
|
||||
/// <summary>
|
||||
/// The sprite state used when the lightbulb is intact.
|
||||
/// </summary>
|
||||
[DataField("normalSpriteState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string NormalSpriteState = "normal";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite state used when the lightbulb is broken.
|
||||
/// </summary>
|
||||
[DataField("brokenSpriteState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string BrokenSpriteState = "broken";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite state used when the lightbulb is burned.
|
||||
/// </summary>
|
||||
[DataField("burnedSpriteState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string BurnedSpriteState = "burned";
|
||||
|
||||
#endregion Appearance
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that represents a wall light. It has a light bulb that can be replaced when broken.
|
||||
/// </summary>
|
||||
[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<EntityPrototype>))]
|
||||
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<SinkPortPrototype>))]
|
||||
public string OnPort = "On";
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> OnPort = "On";
|
||||
|
||||
[DataField("offPort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
|
||||
public string OffPort = "Off";
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> OffPort = "Off";
|
||||
|
||||
[DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
|
||||
public string TogglePort = "Toggle";
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> TogglePort = "Toggle";
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to eject a bulb from this
|
||||
/// </summary>
|
||||
[DataField("ejectBulbDelay")]
|
||||
[DataField]
|
||||
public float EjectBulbDelay = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Shock damage done to a mob that hits the light with an unarmed attack
|
||||
/// </summary>
|
||||
[DataField("unarmedHitShock")]
|
||||
[DataField]
|
||||
public int UnarmedHitShock = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Stun duration applied to a mob that hits the light with an unarmed attack
|
||||
/// </summary>
|
||||
[DataField("unarmedHitStun")]
|
||||
[DataField]
|
||||
public TimeSpan UnarmedHitStun = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
}
|
||||
84
Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs
Normal file
84
Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs
Normal file
@@ -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<LightBulbComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<LightBulbComponent, LandEvent>(HandleLand);
|
||||
SubscribeLocalEvent<LightBulbComponent, BreakageEventArgs>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new color for a light bulb and raise event about change
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new state for a light bulb (broken, burned) and raise event about change
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
417
Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs
Normal file
417
Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs
Normal file
@@ -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<PoweredLightComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PoweredLightComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
||||
SubscribeLocalEvent<PoweredLightComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
||||
SubscribeLocalEvent<PoweredLightComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<PoweredLightComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<PoweredLightComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
SubscribeLocalEvent<PoweredLightComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<PoweredLightComponent, PoweredLightDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<PoweredLightComponent, DamageChangedEvent>(HandleLightDamaged);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
|
||||
{
|
||||
light.LightBulbContainer = ContainerSystem.EnsureContainer<ContainerSlot>(uid, LightBulbContainer);
|
||||
_deviceLink.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort);
|
||||
}
|
||||
|
||||
private void OnRemoved(Entity<PoweredLightComponent> light, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != LightBulbContainer)
|
||||
return;
|
||||
|
||||
UpdateLight(light, light);
|
||||
}
|
||||
|
||||
private void OnInserted(Entity<PoweredLightComponent> 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<LightBulbComponent>(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<PoweredLightComponent> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns the light on or of when receiving a <see cref="DeviceNetworkConstants.CmdSetState"/> command.
|
||||
/// The light is turned on or of according to the <see cref="DeviceNetworkConstants.StateEnabled"/> value
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the bulb if possible.
|
||||
/// </summary>
|
||||
/// <returns>True if it could insert it, false if it couldn't.</returns>
|
||||
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<LightBulbComponent>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the bulb to a mob's hand if possible.
|
||||
/// </summary>
|
||||
/// <returns>Bulb uid if it was successfully ejected, null otherwise</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the spawned prototype of a pre-mapinit powered light with a different variant.
|
||||
/// </summary>
|
||||
public bool ReplaceSpawnedPrototype(Entity<PoweredLightComponent> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to replace current bulb with a new one
|
||||
/// If succeed old bulb just drops on floor
|
||||
/// </summary>
|
||||
public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null)
|
||||
{
|
||||
EjectBulb(uid, null, light);
|
||||
return InsertBulb(uid, bulb, light);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get light bulb inserted in powered light
|
||||
/// </summary>
|
||||
/// <returns>Bulb uid if it exist, null otherwise</returns>
|
||||
public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return null;
|
||||
|
||||
return light.LightBulbContainer?.ContainedEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to break bulb inside light fixture
|
||||
/// </summary>
|
||||
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<LightBulbComponent>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy the light bulb if the light took any damage.
|
||||
/// </summary>
|
||||
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<AppearanceComponent>(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<DamageOnInteractComponent>(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;
|
||||
}
|
||||
}
|
||||
@@ -19,4 +19,7 @@ public abstract partial class SharedApcPowerReceiverComponent : Component
|
||||
/// </summary>
|
||||
[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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user