2021-12-05 18:09:01 +01:00
using Content.Server.Administration.Logs ;
2021-10-24 01:23:19 +02:00
using Content.Server.DeviceNetwork ;
using Content.Server.DeviceNetwork.Systems ;
2021-08-31 11:33:55 +03:00
using Content.Server.Ghost ;
using Content.Server.Light.Components ;
2021-10-01 13:59:06 -07:00
using Content.Server.Power.Components ;
2021-12-05 18:09:01 +01:00
using Content.Server.Temperature.Components ;
using Content.Shared.Audio ;
2021-09-15 03:07:37 +10:00
using Content.Shared.Damage ;
2021-12-05 18:09:01 +01:00
using Content.Shared.Database ;
2022-03-17 20:13:31 +13:00
using Content.Shared.Hands.EntitySystems ;
2021-12-05 18:09:01 +01:00
using Content.Shared.Interaction ;
using Content.Shared.Light ;
2023-09-04 06:31:10 +01:00
using Content.Shared.Light.Components ;
2021-12-05 18:09:01 +01:00
using Content.Shared.Popups ;
2021-08-31 11:33:55 +03:00
using Robust.Server.GameObjects ;
2021-12-05 18:09:01 +01:00
using Robust.Shared.Audio ;
using Robust.Shared.Containers ;
using Robust.Shared.Player ;
using Robust.Shared.Timing ;
2023-02-24 19:01:25 -05:00
using Content.Shared.DoAfter ;
2023-03-06 22:05:12 +03:00
using Content.Server.Emp ;
2023-05-07 08:07:24 +02:00
using Content.Server.DeviceLinking.Events ;
using Content.Server.DeviceLinking.Systems ;
2021-08-27 17:46:02 +02:00
namespace Content.Server.Light.EntitySystems
{
2021-09-15 03:07:37 +10:00
/// <summary>
2022-05-12 20:46:20 +12:00
/// System for the PoweredLightComponents
2021-09-15 03:07:37 +10:00
/// </summary>
2022-02-16 00:23:23 -07:00
public sealed class PoweredLightSystem : EntitySystem
2021-08-27 17:46:02 +02:00
{
2021-08-31 11:33:55 +03:00
[Dependency] private readonly IGameTiming _gameTiming = default ! ;
2021-10-27 04:24:22 +03:00
[Dependency] private readonly DamageableSystem _damageableSystem = default ! ;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default ! ;
[Dependency] private readonly LightBulbSystem _bulbSystem = default ! ;
[Dependency] private readonly SharedPopupSystem _popupSystem = default ! ;
2022-05-28 23:41:17 -07:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2022-03-17 20:13:31 +13:00
[Dependency] private readonly SharedHandsSystem _handsSystem = default ! ;
2023-05-07 08:07:24 +02:00
[Dependency] private readonly DeviceLinkSystem _signalSystem = default ! ;
2022-05-12 20:46:20 +12:00
[Dependency] private readonly SharedContainerSystem _containerSystem = default ! ;
2023-04-03 13:13:48 +12:00
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default ! ;
2023-02-24 19:01:25 -05:00
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
2023-02-02 17:34:53 +01:00
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
2021-10-27 04:24:22 +03:00
private static readonly TimeSpan ThunkDelay = TimeSpan . FromSeconds ( 2 ) ;
2022-05-12 20:46:20 +12:00
public const string LightBulbContainer = "light_bulb" ;
2021-08-31 11:33:55 +03:00
2021-08-27 17:46:02 +02:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2021-10-27 04:24:22 +03:00
SubscribeLocalEvent < PoweredLightComponent , ComponentInit > ( OnInit ) ;
SubscribeLocalEvent < PoweredLightComponent , MapInitEvent > ( OnMapInit ) ;
SubscribeLocalEvent < PoweredLightComponent , InteractUsingEvent > ( OnInteractUsing ) ;
SubscribeLocalEvent < PoweredLightComponent , InteractHandEvent > ( OnInteractHand ) ;
2021-08-31 11:33:55 +03:00
SubscribeLocalEvent < PoweredLightComponent , GhostBooEvent > ( OnGhostBoo ) ;
2021-09-15 03:07:37 +10:00
SubscribeLocalEvent < PoweredLightComponent , DamageChangedEvent > ( HandleLightDamaged ) ;
2021-10-01 13:59:06 -07:00
2021-10-24 01:23:19 +02:00
SubscribeLocalEvent < PoweredLightComponent , SignalReceivedEvent > ( OnSignalReceived ) ;
2022-04-09 00:27:10 +12:00
SubscribeLocalEvent < PoweredLightComponent , DeviceNetworkPacketEvent > ( OnPacketReceived ) ;
2021-10-24 01:23:19 +02:00
2021-10-27 04:24:22 +03:00
SubscribeLocalEvent < PoweredLightComponent , PowerChangedEvent > ( OnPowerChanged ) ;
2022-06-21 20:55:06 -04:00
2023-04-03 13:13:48 +12:00
SubscribeLocalEvent < PoweredLightComponent , PoweredLightDoAfterEvent > ( OnDoAfter ) ;
2023-03-06 22:05:12 +03:00
SubscribeLocalEvent < PoweredLightComponent , EmpPulseEvent > ( OnEmpPulse ) ;
2021-10-27 04:24:22 +03:00
}
private void OnInit ( EntityUid uid , PoweredLightComponent light , ComponentInit args )
{
2022-05-12 20:46:20 +12:00
light . LightBulbContainer = _containerSystem . EnsureContainer < ContainerSlot > ( uid , LightBulbContainer ) ;
2023-05-07 08:07:24 +02:00
_signalSystem . EnsureSinkPorts ( uid , light . OnPort , light . OffPort , light . TogglePort ) ;
2021-10-27 04:24:22 +03:00
}
private void OnMapInit ( EntityUid uid , PoweredLightComponent light , MapInitEvent args )
{
2022-01-04 22:25:37 -07:00
if ( light . HasLampOnSpawn ! = null )
2021-10-27 04:24:22 +03:00
{
2022-01-04 22:25:37 -07:00
var entity = EntityManager . SpawnEntity ( light . HasLampOnSpawn , EntityManager . GetComponent < TransformComponent > ( light . Owner ) . Coordinates ) ;
2021-10-27 04:24:22 +03:00
light . LightBulbContainer . Insert ( entity ) ;
}
// need this to update visualizers
UpdateLight ( uid , light ) ;
}
private void OnInteractUsing ( EntityUid uid , PoweredLightComponent component , InteractUsingEvent args )
{
if ( args . Handled )
return ;
2021-12-03 15:53:09 +01:00
args . Handled = InsertBulb ( uid , args . Used , component ) ;
2021-10-27 04:24:22 +03:00
}
private void OnInteractHand ( EntityUid uid , PoweredLightComponent light , InteractHandEvent args )
{
if ( args . Handled )
return ;
// check if light has bulb to eject
var bulbUid = GetBulb ( uid , light ) ;
if ( bulbUid = = null )
return ;
// check if it's possible to apply burn damage to user
2021-12-05 18:09:01 +01:00
var userUid = args . User ;
2021-10-27 04:24:22 +03:00
if ( EntityManager . TryGetComponent ( userUid , out HeatResistanceComponent ? heatResist ) & &
EntityManager . TryGetComponent ( bulbUid . Value , out LightBulbComponent ? lightBulb ) )
{
// get users heat resistance
var res = heatResist . GetHeatResistance ( ) ;
// check heat resistance against user
var burnedHand = light . CurrentLit & & res < lightBulb . BurningTemperature ;
if ( burnedHand )
{
// apply damage to users hands and show message with sound
var burnMsg = Loc . GetString ( "powered-light-component-burn-hand" ) ;
2022-12-19 10:41:47 +13:00
_popupSystem . PopupEntity ( burnMsg , uid , userUid ) ;
2021-11-29 02:34:44 +13:00
2022-10-08 12:15:27 +02:00
var damage = _damageableSystem . TryChangeDamage ( userUid , light . Damage , origin : userUid ) ;
2021-11-29 02:34:44 +13:00
if ( damage ! = null )
2023-02-24 19:01:25 -05:00
_adminLogger . Add ( LogType . Damaged , $"{ToPrettyString(args.User):user} burned their hand on {ToPrettyString(args.Target):target} and received {damage.Total:damage} damage" ) ;
2021-11-29 02:34:44 +13:00
2023-02-24 19:01:25 -05:00
_audio . Play ( light . BurnHandSound , Filter . Pvs ( uid ) , uid , true ) ;
2021-10-27 04:24:22 +03:00
args . Handled = true ;
return ;
}
}
2022-06-21 20:55:06 -04:00
//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
2023-04-03 13:13:48 +12:00
_doAfterSystem . TryStartDoAfter ( new DoAfterArgs ( userUid , light . EjectBulbDelay , new PoweredLightDoAfterEvent ( ) , uid , target : uid )
2022-06-21 20:55:06 -04:00
{
BreakOnUserMove = true ,
BreakOnDamage = true ,
} ) ;
args . Handled = true ;
2021-10-27 04:24:22 +03:00
}
#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 )
{
if ( ! Resolve ( uid , ref light ) )
return false ;
// check if light already has bulb
if ( GetBulb ( uid , light ) ! = null )
return false ;
// check if bulb fits
if ( ! EntityManager . TryGetComponent ( bulbUid , out LightBulbComponent ? lightBulb ) )
return false ;
if ( lightBulb . Type ! = light . BulbType )
return false ;
// try to insert bulb in container
2021-12-05 21:02:04 +01:00
if ( ! light . LightBulbContainer . Insert ( bulbUid ) )
2021-10-27 04:24:22 +03:00
return false ;
UpdateLight ( uid , light ) ;
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
2022-04-04 01:13:03 -05:00
if ( GetBulb ( uid , light ) is not { Valid : true } bulb )
2021-10-27 04:24:22 +03:00
return null ;
// try to remove bulb from container
2021-12-05 21:02:04 +01:00
if ( ! light . LightBulbContainer . Remove ( bulb ) )
2021-10-27 04:24:22 +03:00
return null ;
// try to place bulb in hands
2022-03-17 20:13:31 +13:00
_handsSystem . PickupOrDrop ( userUid , bulb ) ;
2021-10-27 04:24:22 +03:00
UpdateLight ( uid , light ) ;
2021-12-05 21:02:04 +01:00
return bulb ;
2021-10-27 04:24:22 +03:00
}
/// <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 ;
2021-12-03 15:53:09 +01:00
return light . LightBulbContainer . ContainedEntity ;
2021-10-27 04:24:22 +03:00
}
/// <summary>
/// Try to break bulb inside light fixture
/// </summary>
2023-05-07 01:26:04 +10:00
public bool TryDestroyBulb ( EntityUid uid , PoweredLightComponent ? light = null )
2021-10-27 04:24:22 +03:00
{
// check bulb state
var bulbUid = GetBulb ( uid , light ) ;
if ( bulbUid = = null | | ! EntityManager . TryGetComponent ( bulbUid . Value , out LightBulbComponent ? lightBulb ) )
2023-05-07 01:26:04 +10:00
return false ;
2021-10-27 04:24:22 +03:00
if ( lightBulb . State = = LightBulbState . Broken )
2023-05-07 01:26:04 +10:00
return false ;
2021-10-27 04:24:22 +03:00
// break it
_bulbSystem . SetState ( bulbUid . Value , LightBulbState . Broken , lightBulb ) ;
_bulbSystem . PlayBreakSound ( bulbUid . Value , lightBulb ) ;
UpdateLight ( uid , light ) ;
2023-05-07 01:26:04 +10:00
return true ;
2021-10-27 04:24:22 +03:00
}
#endregion
private void UpdateLight ( EntityUid uid ,
PoweredLightComponent ? light = null ,
ApcPowerReceiverComponent ? powerReceiver = null ,
AppearanceComponent ? appearance = null )
{
2023-09-10 12:35:05 +12:00
if ( ! Resolve ( uid , ref light , ref powerReceiver , false ) )
2021-10-27 04:24:22 +03:00
return ;
2021-12-03 10:25:07 +01:00
// Optional component.
Resolve ( uid , ref appearance , false ) ;
2021-10-27 04:24:22 +03:00
// check if light has bulb
var bulbUid = GetBulb ( uid , light ) ;
if ( bulbUid = = null | | ! EntityManager . TryGetComponent ( bulbUid . Value , out LightBulbComponent ? lightBulb ) )
{
SetLight ( uid , false , light : light ) ;
powerReceiver . Load = 0 ;
2023-02-02 17:34:53 +01:00
_appearance . SetData ( uid , PoweredLightVisuals . BulbState , PoweredLightState . Empty , appearance ) ;
2021-10-27 04:24:22 +03:00
return ;
}
2023-01-19 03:56:45 +01:00
switch ( lightBulb . State )
{
case LightBulbState . Normal :
2023-09-01 09:47:30 +10:00
if ( powerReceiver . Powered & & light . On )
2023-01-19 03:56:45 +01:00
{
2023-09-01 09:47:30 +10:00
SetLight ( uid , true , lightBulb . Color , light , lightBulb . LightRadius , lightBulb . LightEnergy , lightBulb . LightSoftness ) ;
2023-02-02 17:34:53 +01:00
_appearance . SetData ( uid , PoweredLightVisuals . BulbState , PoweredLightState . On , appearance ) ;
2023-01-19 03:56:45 +01:00
var time = _gameTiming . CurTime ;
if ( time > light . LastThunk + ThunkDelay )
2021-10-27 04:24:22 +03:00
{
2023-01-19 03:56:45 +01:00
light . LastThunk = time ;
2023-02-24 19:01:25 -05:00
_audio . Play ( light . TurnOnSound , Filter . Pvs ( uid ) , uid , true , AudioParams . Default . WithVolume ( - 10f ) ) ;
2021-10-27 04:24:22 +03:00
}
2023-01-19 03:56:45 +01:00
}
else
{
2021-10-27 04:24:22 +03:00
SetLight ( uid , false , light : light ) ;
2023-02-02 17:34:53 +01:00
_appearance . SetData ( uid , PoweredLightVisuals . BulbState , PoweredLightState . Off , appearance ) ;
2023-01-19 03:56:45 +01:00
}
break ;
case LightBulbState . Broken :
SetLight ( uid , false , light : light ) ;
2023-02-02 17:34:53 +01:00
_appearance . SetData ( uid , PoweredLightVisuals . BulbState , PoweredLightState . Broken , appearance ) ;
2023-01-19 03:56:45 +01:00
break ;
case LightBulbState . Burned :
SetLight ( uid , false , light : light ) ;
2023-02-02 17:34:53 +01:00
_appearance . SetData ( uid , PoweredLightVisuals . BulbState , PoweredLightState . Burned , appearance ) ;
2023-01-19 03:56:45 +01:00
break ;
2021-10-27 04:24:22 +03:00
}
2023-01-19 03:56:45 +01:00
powerReceiver . Load = ( light . On & & lightBulb . State = = LightBulbState . Normal ) ? lightBulb . PowerUse : 0 ;
2021-09-15 03:07:37 +10:00
}
/// <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.
2021-10-27 04:24:22 +03:00
TryDestroyBulb ( uid , component ) ;
2021-09-15 03:07:37 +10:00
}
2021-08-27 17:46:02 +02:00
}
2021-08-31 11:33:55 +03:00
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 ;
2023-02-02 17:34:53 +01:00
ToggleBlinkingLight ( uid , light , true ) ;
2021-08-31 11:33:55 +03:00
light . Owner . SpawnTimer ( light . GhostBlinkingTime , ( ) = >
{
2023-02-02 17:34:53 +01:00
ToggleBlinkingLight ( uid , light , false ) ;
2021-08-31 11:33:55 +03:00
} ) ;
args . Handled = true ;
}
2022-10-15 15:08:15 +11:00
private void OnPowerChanged ( EntityUid uid , PoweredLightComponent component , ref PowerChangedEvent args )
2021-10-27 04:24:22 +03:00
{
2023-05-03 00:57:48 +10:00
// TODO: Power moment
if ( MetaData ( uid ) . EntityPaused )
return ;
2021-10-27 04:24:22 +03:00
UpdateLight ( uid , component ) ;
}
2023-02-02 17:34:53 +01:00
public void ToggleBlinkingLight ( EntityUid uid , PoweredLightComponent light , bool isNowBlinking )
2021-08-31 11:33:55 +03:00
{
if ( light . IsBlinking = = isNowBlinking )
return ;
light . IsBlinking = isNowBlinking ;
2023-02-24 19:01:25 -05:00
if ( ! EntityManager . TryGetComponent ( uid , out AppearanceComponent ? appearance ) )
2021-08-31 11:33:55 +03:00
return ;
2023-02-02 17:34:53 +01:00
_appearance . SetData ( uid , PoweredLightVisuals . Blinking , isNowBlinking , appearance ) ;
2021-08-31 11:33:55 +03:00
}
2023-05-07 08:07:24 +02:00
private void OnSignalReceived ( EntityUid uid , PoweredLightComponent component , ref SignalReceivedEvent args )
2021-08-27 17:46:02 +02:00
{
2022-05-12 20:46:20 +12:00
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 ) ;
2021-08-27 17:46:02 +02:00
}
2021-10-01 13:59:06 -07:00
2021-10-24 01:23:19 +02:00
/// <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
2022-04-04 01:13:03 -05:00
/// </summary>
2022-04-09 00:27:10 +12:00
private void OnPacketReceived ( EntityUid uid , PoweredLightComponent component , DeviceNetworkPacketEvent args )
2021-10-24 01:23:19 +02:00
{
if ( ! args . Data . TryGetValue ( DeviceNetworkConstants . Command , out string? command ) | | command ! = DeviceNetworkConstants . CmdSetState ) return ;
if ( ! args . Data . TryGetValue ( DeviceNetworkConstants . StateEnabled , out bool enabled ) ) return ;
2021-10-27 04:24:22 +03:00
SetState ( uid , enabled , component ) ;
2021-10-24 01:23:19 +02:00
}
2022-04-04 01:13:03 -05:00
private void SetLight ( EntityUid uid , bool value , Color ? color = null , PoweredLightComponent ? light = null , float? radius = null , float? energy = null , float? softness = null )
2021-10-01 13:59:06 -07:00
{
2021-10-27 04:24:22 +03:00
if ( ! Resolve ( uid , ref light ) )
return ;
light . CurrentLit = value ;
_ambientSystem . SetAmbience ( uid , value ) ;
if ( EntityManager . TryGetComponent ( uid , out PointLightComponent ? pointLight ) )
2021-10-01 13:59:06 -07:00
{
2021-10-27 04:24:22 +03:00
pointLight . Enabled = value ;
if ( color ! = null )
pointLight . Color = color . Value ;
2022-01-04 22:25:37 -07:00
if ( radius ! = null )
pointLight . Radius = ( float ) radius ;
if ( energy ! = null )
pointLight . Energy = ( float ) energy ;
if ( softness ! = null )
pointLight . Softness = ( float ) softness ;
2021-10-01 13:59:06 -07:00
}
}
2021-10-27 04:24:22 +03:00
public void ToggleLight ( EntityUid uid , PoweredLightComponent ? light = null )
2021-10-01 13:59:06 -07:00
{
2021-10-27 04:24:22 +03:00
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 ) ;
2021-10-01 13:59:06 -07:00
}
2022-06-21 20:55:06 -04:00
2023-02-24 19:01:25 -05:00
private void OnDoAfter ( EntityUid uid , PoweredLightComponent component , DoAfterEvent args )
2022-06-21 20:55:06 -04:00
{
2023-02-24 19:01:25 -05:00
if ( args . Handled | | args . Cancelled | | args . Args . Target = = null )
return ;
2022-06-21 20:55:06 -04:00
2023-02-24 19:01:25 -05:00
EjectBulb ( args . Args . Target . Value , args . Args . User , component ) ;
2022-06-21 20:55:06 -04:00
2023-02-24 19:01:25 -05:00
args . Handled = true ;
2022-06-21 20:55:06 -04:00
}
2023-03-06 22:05:12 +03:00
private void OnEmpPulse ( EntityUid uid , PoweredLightComponent component , ref EmpPulseEvent args )
{
2023-05-07 01:26:04 +10:00
if ( TryDestroyBulb ( uid , component ) )
args . Affected = true ;
2023-03-06 22:05:12 +03:00
}
2021-08-27 17:46:02 +02:00
}
}