2023-07-23 16:11:13 +10:00
using System.Linq ;
using Content.Client.Light.Components ;
using Robust.Client.GameObjects ;
2024-08-10 18:45:37 -07:00
using Robust.Client.Animations ;
2023-07-23 16:11:13 +10:00
using Robust.Shared.Random ;
2024-08-10 18:45:37 -07:00
using Robust.Shared.Animations ;
2023-07-23 16:11:13 +10:00
namespace Content.Client.Light.EntitySystems ;
public sealed class LightBehaviorSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default ! ;
[Dependency] private readonly AnimationPlayerSystem _player = default ! ;
public override void Initialize ( )
{
base . Initialize ( ) ;
SubscribeLocalEvent < LightBehaviourComponent , ComponentStartup > ( OnLightStartup ) ;
SubscribeLocalEvent < LightBehaviourComponent , AnimationCompletedEvent > ( OnBehaviorAnimationCompleted ) ;
}
private void OnBehaviorAnimationCompleted ( EntityUid uid , LightBehaviourComponent component , AnimationCompletedEvent args )
{
2024-07-04 10:02:43 +02:00
if ( ! args . Finished )
return ;
2023-07-23 16:11:13 +10:00
var container = component . Animations . FirstOrDefault ( x = > x . FullKey = = args . Key ) ;
if ( container = = null )
{
return ;
}
if ( container . LightBehaviour . IsLooped )
{
container . LightBehaviour . UpdatePlaybackValues ( container . Animation ) ;
_player . Play ( uid , container . Animation , container . FullKey ) ;
}
}
2024-08-10 18:45:37 -07:00
private void OnLightStartup ( Entity < LightBehaviourComponent > entity , ref ComponentStartup args )
2023-07-23 16:11:13 +10:00
{
// TODO: Do NOT ensure component here. And use eventbus events instead...
2024-08-10 18:45:37 -07:00
EnsureComp < AnimationPlayerComponent > ( entity ) ;
2023-07-23 16:11:13 +10:00
2024-08-10 18:45:37 -07:00
foreach ( var container in entity . Comp . Animations )
2023-07-23 16:11:13 +10:00
{
2024-08-10 18:45:37 -07:00
container . LightBehaviour . Initialize ( entity , _random , EntityManager ) ;
2023-07-23 16:11:13 +10:00
}
// we need to initialize all behaviours before starting any
2024-08-10 18:45:37 -07:00
foreach ( var container in entity . Comp . Animations )
2023-07-23 16:11:13 +10:00
{
if ( container . LightBehaviour . Enabled )
{
2024-08-10 18:45:37 -07:00
StartLightBehaviour ( entity , container . LightBehaviour . ID ) ;
2023-07-23 16:11:13 +10:00
}
}
}
2024-08-10 18:45:37 -07:00
/// <summary>
/// If we disable all the light behaviours we want to be able to revert the light to its original state.
/// </summary>
private void CopyLightSettings ( Entity < LightBehaviourComponent > entity , string property )
{
2025-06-26 19:50:49 -04:00
if ( TryComp ( entity , out PointLightComponent ? light ) )
2024-08-10 18:45:37 -07:00
{
var propertyValue = AnimationHelper . GetAnimatableProperty ( light , property ) ;
if ( propertyValue ! = null )
{
entity . Comp . OriginalPropertyValues . Add ( property , propertyValue ) ;
}
}
else
{
2025-06-26 19:50:49 -04:00
Log . Warning ( $"{Comp<MetaDataComponent>(entity).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!" ) ;
2024-08-10 18:45:37 -07:00
}
}
/// <summary>
/// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries.
/// If specified light behaviours are already animating, calling this does nothing.
/// Multiple light behaviours can have the same ID.
/// </summary>
public void StartLightBehaviour ( Entity < LightBehaviourComponent > entity , string id = "" )
{
2025-06-26 19:50:49 -04:00
if ( ! TryComp ( entity , out AnimationPlayerComponent ? animation ) )
2024-08-10 18:45:37 -07:00
{
return ;
}
foreach ( var container in entity . Comp . Animations )
{
if ( container . LightBehaviour . ID = = id | | id = = string . Empty )
{
if ( ! _player . HasRunningAnimation ( entity , animation , LightBehaviourComponent . KeyPrefix + container . Key ) )
{
CopyLightSettings ( entity , container . LightBehaviour . Property ) ;
container . LightBehaviour . UpdatePlaybackValues ( container . Animation ) ;
_player . Play ( entity , container . Animation , LightBehaviourComponent . KeyPrefix + container . Key ) ;
}
}
}
}
/// <summary>
/// If any light behaviour with the specified ID is animating, then stop it.
/// If no ID is specified then all light behaviours will be stopped.
/// Multiple light behaviours can have the same ID.
/// </summary>
/// <param name="id"></param>
/// <param name="removeBehaviour">Should the behaviour(s) also be removed permanently?</param>
/// <param name="resetToOriginalSettings">Should the light have its original settings applied?</param>
public void StopLightBehaviour ( Entity < LightBehaviourComponent > entity , string id = "" , bool removeBehaviour = false , bool resetToOriginalSettings = false )
{
2025-06-26 19:50:49 -04:00
if ( ! TryComp ( entity , out AnimationPlayerComponent ? animation ) )
2024-08-10 18:45:37 -07:00
{
return ;
}
var comp = entity . Comp ;
var toRemove = new List < LightBehaviourComponent . AnimationContainer > ( ) ;
foreach ( var container in comp . Animations )
{
if ( container . LightBehaviour . ID = = id | | id = = string . Empty )
{
if ( _player . HasRunningAnimation ( entity , animation , LightBehaviourComponent . KeyPrefix + container . Key ) )
{
_player . Stop ( entity , animation , LightBehaviourComponent . KeyPrefix + container . Key ) ;
}
if ( removeBehaviour )
{
toRemove . Add ( container ) ;
}
}
}
foreach ( var container in toRemove )
{
comp . Animations . Remove ( container ) ;
}
2025-06-26 19:50:49 -04:00
if ( resetToOriginalSettings & & TryComp ( entity , out PointLightComponent ? light ) )
2024-08-10 18:45:37 -07:00
{
foreach ( var ( property , value ) in comp . OriginalPropertyValues )
{
AnimationHelper . SetAnimatableProperty ( light , property , value ) ;
}
}
comp . OriginalPropertyValues . Clear ( ) ;
}
/// <summary>
/// Checks if at least one behaviour is running.
/// </summary>
/// <returns>Whether at least one behaviour is running, false if none is.</returns>
public bool HasRunningBehaviours ( Entity < LightBehaviourComponent > entity )
{
//var uid = Owner;
2025-06-26 19:50:49 -04:00
if ( ! TryComp ( entity , out AnimationPlayerComponent ? animation ) )
2024-08-10 18:45:37 -07:00
{
return false ;
}
return entity . Comp . Animations . Any ( container = > _player . HasRunningAnimation ( entity , animation , LightBehaviourComponent . KeyPrefix + container . Key ) ) ;
}
/// <summary>
/// Add a new light behaviour to the component and start it immediately unless otherwise specified.
/// </summary>
public void AddNewLightBehaviour ( Entity < LightBehaviourComponent > entity , LightBehaviourAnimationTrack behaviour , bool playImmediately = true )
{
var key = 0 ;
var comp = entity . Comp ;
while ( comp . Animations . Any ( x = > x . Key = = key ) )
{
key + + ;
}
var animation = new Animation ( )
{
AnimationTracks = { behaviour }
} ;
behaviour . Initialize ( entity . Owner , _random , EntityManager ) ;
var container = new LightBehaviourComponent . AnimationContainer ( key , animation , behaviour ) ;
comp . Animations . Add ( container ) ;
if ( playImmediately )
{
StartLightBehaviour ( entity , behaviour . ID ) ;
}
}
2023-07-23 16:11:13 +10:00
}