2020-09-06 17:16:08 +02:00
using System ;
2020-09-03 16:02:40 -04:00
using System.Collections.Generic ;
2020-09-13 14:23:52 +02:00
using System.Linq ;
2021-06-09 22:19:39 +02:00
using Content.Shared.Light.Component ;
2020-12-08 11:56:10 +01:00
using JetBrains.Annotations ;
2020-09-13 14:23:52 +02:00
using Robust.Client.Animations ;
2020-09-03 16:02:40 -04:00
using Robust.Client.GameObjects ;
using Robust.Shared.Animations ;
using Robust.Shared.GameObjects ;
using Robust.Shared.IoC ;
using Robust.Shared.Log ;
using Robust.Shared.Maths ;
2021-02-11 01:13:03 -08:00
using Robust.Shared.Random ;
2020-09-13 14:23:52 +02:00
using Robust.Shared.Serialization ;
2021-03-05 01:08:38 +01:00
using Robust.Shared.Serialization.Manager.Attributes ;
2020-09-13 14:23:52 +02:00
using Robust.Shared.ViewVariables ;
2020-09-03 16:02:40 -04:00
2021-06-09 22:19:39 +02:00
namespace Content.Client.Light.Components
2020-09-03 16:02:40 -04:00
{
#region LIGHT_BEHAVIOURS
/// <summary>
/// Base class for all light behaviours to derive from.
/// This AnimationTrack derivative does not rely on keyframes since it often needs to have a randomized duration.
/// </summary>
[Serializable]
2021-03-05 01:08:38 +01:00
[ImplicitDataDefinitionForInheritors]
public abstract class LightBehaviourAnimationTrack : AnimationTrackProperty
2020-09-03 16:02:40 -04:00
{
2021-09-01 13:23:52 +02:00
protected IRobustRandom _random = default ! ;
2021-03-10 14:48:29 +01:00
2021-03-05 01:08:38 +01:00
[DataField("id")] [ ViewVariables ] public string ID { get ; set ; } = string . Empty ;
[DataField("property")]
[ViewVariables]
public virtual string Property { get ; protected set ; } = "Radius" ;
[DataField("isLooped")] [ ViewVariables ] public bool IsLooped { get ; set ; }
[DataField("enabled")] [ ViewVariables ] public bool Enabled { get ; set ; }
2021-05-28 00:56:06 -07:00
[DataField("startValue")] [ ViewVariables ] public float StartValue { get ; set ; } = 0f ;
2021-03-05 01:08:38 +01:00
2021-05-28 00:56:06 -07:00
[DataField("endValue")] [ ViewVariables ] public float EndValue { get ; set ; } = 2f ;
2021-03-05 01:08:38 +01:00
2021-05-28 00:56:06 -07:00
[DataField("minDuration")] [ ViewVariables ] public float MinDuration { get ; set ; } = - 1f ;
2021-03-05 01:08:38 +01:00
2021-05-28 00:56:06 -07:00
[DataField("maxDuration")] [ ViewVariables ] public float MaxDuration { get ; set ; } = 2f ;
2021-03-05 01:08:38 +01:00
2021-05-28 00:56:06 -07:00
[DataField("interpolate")] [ ViewVariables ] public AnimationInterpolationMode InterpolateMode { get ; set ; } = AnimationInterpolationMode . Linear ;
2020-09-03 16:02:40 -04:00
[ViewVariables] protected float MaxTime { get ; set ; }
private float _maxTime = default ;
2021-03-10 14:48:29 +01:00
private IEntity _parent = default ! ;
2020-09-06 17:16:08 +02:00
2021-09-01 13:23:52 +02:00
public void Initialize ( IEntity parent , IRobustRandom random )
2020-09-03 16:02:40 -04:00
{
2021-09-01 13:23:52 +02:00
_random = random ;
2021-03-10 14:48:29 +01:00
_parent = parent ;
2020-09-03 16:02:40 -04:00
2021-12-03 15:53:09 +01:00
if ( Enabled & & IoCManager . Resolve < IEntityManager > ( ) . TryGetComponent ( _parent , out PointLightComponent ? light ) )
2020-09-03 16:02:40 -04:00
{
2021-03-10 14:48:29 +01:00
light . Enabled = true ;
2020-09-03 16:02:40 -04:00
}
OnInitialize ( ) ;
}
public void UpdatePlaybackValues ( Animation owner )
{
2021-12-03 15:53:09 +01:00
if ( IoCManager . Resolve < IEntityManager > ( ) . TryGetComponent ( _parent , out PointLightComponent ? light ) )
2021-03-10 14:48:29 +01:00
{
light . Enabled = true ;
}
2020-09-03 16:02:40 -04:00
if ( MinDuration > 0 )
{
2021-05-28 00:56:06 -07:00
MaxTime = ( float ) _random . NextDouble ( ) * ( MaxDuration - MinDuration ) + MinDuration ;
2020-09-03 16:02:40 -04:00
}
else
{
MaxTime = MaxDuration ;
}
2020-09-06 17:16:08 +02:00
owner . Length = TimeSpan . FromSeconds ( MaxTime ) ;
2020-09-03 16:02:40 -04:00
}
public override ( int KeyFrameIndex , float FramePlayingTime ) InitPlayback ( )
{
OnStart ( ) ;
return ( - 1 , _maxTime ) ;
}
protected void ApplyProperty ( object value )
{
if ( Property = = null )
{
throw new InvalidOperationException ( "Property parameter is null! Check the prototype!" ) ;
}
2021-12-03 15:53:09 +01:00
if ( IoCManager . Resolve < IEntityManager > ( ) . TryGetComponent ( _parent , out PointLightComponent ? light ) )
2020-09-03 16:02:40 -04:00
{
2021-03-10 14:48:29 +01:00
AnimationHelper . SetAnimatableProperty ( light , Property , value ) ;
2020-09-03 16:02:40 -04:00
}
}
protected override void ApplyProperty ( object context , object value )
{
ApplyProperty ( value ) ;
}
public virtual void OnInitialize ( ) { }
public virtual void OnStart ( ) { }
}
/// <summary>
/// A light behaviour that alternates between StartValue and EndValue
/// </summary>
2020-12-08 11:56:10 +01:00
[UsedImplicitly]
public class PulseBehaviour : LightBehaviourAnimationTrack
2020-09-03 16:02:40 -04:00
{
public override ( int KeyFrameIndex , float FramePlayingTime ) AdvancePlayback (
object context , int prevKeyFrameIndex , float prevPlayingTime , float frameTime )
{
var playingTime = prevPlayingTime + frameTime ;
var interpolateValue = playingTime / MaxTime ;
if ( Property = = "Enabled" ) // special case for boolean
{
ApplyProperty ( interpolateValue < 0.5f ? true : false ) ;
return ( - 1 , playingTime ) ;
}
if ( interpolateValue < 0.5f )
{
switch ( InterpolateMode )
{
case AnimationInterpolationMode . Linear :
ApplyProperty ( InterpolateLinear ( StartValue , EndValue , interpolateValue * 2f ) ) ;
break ;
case AnimationInterpolationMode . Cubic :
ApplyProperty ( InterpolateCubic ( EndValue , StartValue , EndValue , StartValue , interpolateValue * 2f ) ) ;
break ;
default :
case AnimationInterpolationMode . Nearest :
ApplyProperty ( StartValue ) ;
break ;
}
}
else
{
switch ( InterpolateMode )
{
case AnimationInterpolationMode . Linear :
ApplyProperty ( InterpolateLinear ( EndValue , StartValue , ( interpolateValue - 0.5f ) * 2f ) ) ;
break ;
case AnimationInterpolationMode . Cubic :
ApplyProperty ( InterpolateCubic ( StartValue , EndValue , StartValue , EndValue , ( interpolateValue - 0.5f ) * 2f ) ) ;
break ;
default :
case AnimationInterpolationMode . Nearest :
ApplyProperty ( EndValue ) ;
break ;
}
}
return ( - 1 , playingTime ) ;
}
}
/// <summary>
/// A light behaviour that interpolates from StartValue to EndValue
/// </summary>
2020-12-08 11:56:10 +01:00
[UsedImplicitly]
2020-09-03 16:02:40 -04:00
public class FadeBehaviour : LightBehaviourAnimationTrack
{
public override ( int KeyFrameIndex , float FramePlayingTime ) AdvancePlayback (
object context , int prevKeyFrameIndex , float prevPlayingTime , float frameTime )
{
var playingTime = prevPlayingTime + frameTime ;
var interpolateValue = playingTime / MaxTime ;
if ( Property = = "Enabled" ) // special case for boolean
{
ApplyProperty ( interpolateValue < EndValue ? true : false ) ;
return ( - 1 , playingTime ) ;
}
switch ( InterpolateMode )
{
case AnimationInterpolationMode . Linear :
ApplyProperty ( InterpolateLinear ( StartValue , EndValue , interpolateValue ) ) ;
break ;
case AnimationInterpolationMode . Cubic :
ApplyProperty ( InterpolateCubic ( EndValue , StartValue , EndValue , StartValue , interpolateValue ) ) ;
break ;
default :
case AnimationInterpolationMode . Nearest :
ApplyProperty ( interpolateValue < 0.5f ? StartValue : EndValue ) ;
break ;
}
return ( - 1 , playingTime ) ;
}
}
/// <summary>
/// A light behaviour that interpolates using random values chosen between StartValue and EndValue.
/// </summary>
2020-12-08 11:56:10 +01:00
[UsedImplicitly]
2020-09-03 16:02:40 -04:00
public class RandomizeBehaviour : LightBehaviourAnimationTrack
{
2021-05-28 00:56:06 -07:00
private float _randomValue1 ;
private float _randomValue2 ;
private float _randomValue3 ;
private float _randomValue4 ;
2020-09-03 16:02:40 -04:00
public override void OnInitialize ( )
{
2021-05-28 00:56:06 -07:00
_randomValue1 = ( float ) InterpolateLinear ( StartValue , EndValue , ( float ) _random . NextDouble ( ) ) ;
_randomValue2 = ( float ) InterpolateLinear ( StartValue , EndValue , ( float ) _random . NextDouble ( ) ) ;
_randomValue3 = ( float ) InterpolateLinear ( StartValue , EndValue , ( float ) _random . NextDouble ( ) ) ;
2020-09-03 16:02:40 -04:00
}
public override void OnStart ( )
{
if ( Property = = "Enabled" ) // special case for boolean, we randomize it
{
2021-05-28 00:56:06 -07:00
ApplyProperty ( _random . NextDouble ( ) < 0.5 ) ;
2020-09-03 16:02:40 -04:00
return ;
}
if ( InterpolateMode = = AnimationInterpolationMode . Cubic )
{
_randomValue1 = _randomValue2 ;
_randomValue2 = _randomValue3 ;
}
_randomValue3 = _randomValue4 ;
2021-05-28 00:56:06 -07:00
_randomValue4 = ( float ) InterpolateLinear ( StartValue , EndValue , ( float ) _random . NextDouble ( ) ) ;
2020-09-03 16:02:40 -04:00
}
public override ( int KeyFrameIndex , float FramePlayingTime ) AdvancePlayback (
object context , int prevKeyFrameIndex , float prevPlayingTime , float frameTime )
{
var playingTime = prevPlayingTime + frameTime ;
var interpolateValue = playingTime / MaxTime ;
if ( Property = = "Enabled" )
{
return ( - 1 , playingTime ) ;
}
switch ( InterpolateMode )
{
case AnimationInterpolationMode . Linear :
ApplyProperty ( InterpolateLinear ( _randomValue3 , _randomValue4 , interpolateValue ) ) ;
break ;
case AnimationInterpolationMode . Cubic :
2021-03-10 14:48:29 +01:00
ApplyProperty ( InterpolateCubic ( _randomValue1 ! , _randomValue2 , _randomValue3 , _randomValue4 , interpolateValue ) ) ;
2020-09-03 16:02:40 -04:00
break ;
default :
case AnimationInterpolationMode . Nearest :
ApplyProperty ( interpolateValue < 0.5f ? _randomValue3 : _randomValue4 ) ;
break ;
}
return ( - 1 , playingTime ) ;
}
}
/// <summary>
/// A light behaviour that cycles through a list of colors.
/// </summary>
2020-12-08 11:56:10 +01:00
[UsedImplicitly]
2021-03-05 01:08:38 +01:00
[DataDefinition]
public class ColorCycleBehaviour : LightBehaviourAnimationTrack , ISerializationHooks
2020-09-03 16:02:40 -04:00
{
2021-03-05 01:08:38 +01:00
[DataField("property")]
[ViewVariables]
public override string Property { get ; protected set ; } = "Color" ;
[DataField("colors")] public List < Color > ColorsToCycle { get ; set ; } = new ( ) ;
2020-09-03 16:02:40 -04:00
2021-03-05 01:08:38 +01:00
private int _colorIndex ;
2020-09-03 16:02:40 -04:00
public override void OnStart ( )
{
_colorIndex + + ;
if ( _colorIndex > ColorsToCycle . Count - 1 )
{
_colorIndex = 0 ;
}
}
public override ( int KeyFrameIndex , float FramePlayingTime ) AdvancePlayback (
object context , int prevKeyFrameIndex , float prevPlayingTime , float frameTime )
{
var playingTime = prevPlayingTime + frameTime ;
var interpolateValue = playingTime / MaxTime ;
switch ( InterpolateMode )
{
case AnimationInterpolationMode . Linear :
ApplyProperty ( InterpolateLinear ( ColorsToCycle [ ( _colorIndex - 1 ) % ColorsToCycle . Count ] ,
ColorsToCycle [ _colorIndex ] ,
interpolateValue ) ) ;
break ;
case AnimationInterpolationMode . Cubic :
ApplyProperty ( InterpolateCubic ( ColorsToCycle [ _colorIndex ] ,
ColorsToCycle [ ( _colorIndex + 1 ) % ColorsToCycle . Count ] ,
ColorsToCycle [ ( _colorIndex + 2 ) % ColorsToCycle . Count ] ,
ColorsToCycle [ ( _colorIndex + 3 ) % ColorsToCycle . Count ] ,
interpolateValue ) ) ;
break ;
default :
case AnimationInterpolationMode . Nearest :
ApplyProperty ( ColorsToCycle [ _colorIndex ] ) ;
break ;
}
return ( - 1 , playingTime ) ;
}
2021-03-05 01:08:38 +01:00
void ISerializationHooks . AfterDeserialization ( )
2020-09-03 16:02:40 -04:00
{
if ( ColorsToCycle . Count < 2 )
{
throw new InvalidOperationException ( $"{nameof(ColorCycleBehaviour)} has less than 2 colors to cycle" ) ;
}
}
}
#endregion
/// <summary>
/// A component which applies a specific behaviour to a PointLightComponent on its owner.
/// </summary>
[RegisterComponent]
2021-03-05 01:08:38 +01:00
public class LightBehaviourComponent : SharedLightBehaviourComponent , ISerializationHooks
2020-09-03 16:02:40 -04:00
{
private const string KeyPrefix = nameof ( LightBehaviourComponent ) ;
2021-03-05 01:08:38 +01:00
public class AnimationContainer
2020-09-03 16:02:40 -04:00
{
public AnimationContainer ( int key , Animation animation , LightBehaviourAnimationTrack track )
{
Key = key ;
Animation = animation ;
LightBehaviour = track ;
}
public string FullKey = > KeyPrefix + Key ;
public int Key { get ; set ; }
public Animation Animation { get ; set ; }
public LightBehaviourAnimationTrack LightBehaviour { get ; set ; }
}
2021-03-05 01:08:38 +01:00
[ViewVariables(VVAccess.ReadOnly)]
[DataField("behaviours")]
public readonly List < LightBehaviourAnimationTrack > Behaviours = new ( ) ;
2020-09-03 16:02:40 -04:00
[ViewVariables(VVAccess.ReadOnly)]
2020-11-27 11:00:49 +01:00
private readonly List < AnimationContainer > _animations = new ( ) ;
2020-09-03 16:02:40 -04:00
2021-03-05 01:08:38 +01:00
private float _originalRadius ;
private float _originalEnergy ;
private Angle _originalRotation ;
private Color _originalColor ;
private bool _originalEnabled ;
void ISerializationHooks . AfterDeserialization ( )
{
var key = 0 ;
foreach ( var behaviour in Behaviours )
{
var animation = new Animation ( )
{
2021-03-10 14:48:29 +01:00
AnimationTracks = { behaviour }
2021-03-05 01:08:38 +01:00
} ;
_animations . Add ( new AnimationContainer ( key , animation , behaviour ) ) ;
key + + ;
}
}
2020-09-03 16:02:40 -04:00
protected override void Startup ( )
{
base . Startup ( ) ;
CopyLightSettings ( ) ;
2021-11-08 11:59:14 +01:00
// TODO: Do NOT ensure component here. And use eventbus events instead...
Owner . EnsureComponent < AnimationPlayerComponent > ( ) ;
2021-03-10 14:48:29 +01:00
2021-12-03 15:53:09 +01:00
if ( IoCManager . Resolve < IEntityManager > ( ) . TryGetComponent ( Owner , out AnimationPlayerComponent ? animation ) )
2021-03-10 14:48:29 +01:00
{
2021-10-27 18:10:40 +02:00
#pragma warning disable 618
2021-03-10 14:48:29 +01:00
animation . AnimationCompleted + = OnAnimationCompleted ;
2021-10-27 18:10:40 +02:00
#pragma warning restore 618
2021-03-10 14:48:29 +01:00
}
2020-09-03 16:02:40 -04:00
foreach ( var container in _animations )
{
2021-09-01 13:23:52 +02:00
container . LightBehaviour . Initialize ( Owner , IoCManager . Resolve < IRobustRandom > ( ) ) ;
2020-09-03 16:02:40 -04:00
}
2020-09-06 17:16:08 +02:00
// we need to initialize all behaviours before starting any
2020-09-03 16:02:40 -04:00
foreach ( var container in _animations )
{
if ( container . LightBehaviour . Enabled )
{
StartLightBehaviour ( container . LightBehaviour . ID ) ;
}
}
}
private void OnAnimationCompleted ( string key )
{
var container = _animations . FirstOrDefault ( x = > x . FullKey = = key ) ;
2021-03-10 14:48:29 +01:00
if ( container = = null )
{
return ;
}
2020-09-03 16:02:40 -04:00
if ( container . LightBehaviour . IsLooped )
{
container . LightBehaviour . UpdatePlaybackValues ( container . Animation ) ;
2021-03-10 14:48:29 +01:00
2021-12-03 15:53:09 +01:00
if ( IoCManager . Resolve < IEntityManager > ( ) . TryGetComponent ( Owner , out AnimationPlayerComponent ? animation ) )
2021-03-10 14:48:29 +01:00
{
animation . Play ( container . Animation , container . FullKey ) ;
}
2020-09-03 16:02:40 -04: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 ( )
{
2021-12-03 15:53:09 +01:00
if ( IoCManager . Resolve < IEntityManager > ( ) . TryGetComponent ( Owner , out PointLightComponent ? light ) )
2020-09-03 16:02:40 -04:00
{
2021-03-10 14:48:29 +01:00
_originalColor = light . Color ;
_originalEnabled = light . Enabled ;
_originalEnergy = light . Energy ;
_originalRadius = light . Radius ;
_originalRotation = light . Rotation ;
2020-09-03 16:02:40 -04:00
}
else
{
2021-12-03 15:53:09 +01:00
Logger . Warning ( $"{IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(Owner).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!" ) ;
2020-09-03 16:02:40 -04: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 ( string id = "" )
{
2021-12-03 15:53:09 +01:00
if ( ! IoCManager . Resolve < IEntityManager > ( ) . TryGetComponent ( Owner , out AnimationPlayerComponent ? animation ) )
2021-03-10 14:48:29 +01:00
{
return ;
}
2020-09-03 16:02:40 -04:00
foreach ( var container in _animations )
{
if ( container . LightBehaviour . ID = = id | | id = = string . Empty )
{
2021-03-10 14:48:29 +01:00
if ( ! animation . HasRunningAnimation ( KeyPrefix + container . Key ) )
2020-09-03 16:02:40 -04:00
{
container . LightBehaviour . UpdatePlaybackValues ( container . Animation ) ;
2021-03-10 14:48:29 +01:00
animation . Play ( container . Animation , KeyPrefix + container . Key ) ;
2020-09-03 16:02:40 -04:00
}
}
}
}
/// <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 ( string id = "" , bool removeBehaviour = false , bool resetToOriginalSettings = false )
{
2021-12-03 15:53:09 +01:00
if ( ! IoCManager . Resolve < IEntityManager > ( ) . TryGetComponent ( Owner , out AnimationPlayerComponent ? animation ) )
2021-03-10 14:48:29 +01:00
{
return ;
}
2020-09-03 16:02:40 -04:00
var toRemove = new List < AnimationContainer > ( ) ;
foreach ( var container in _animations )
{
if ( container . LightBehaviour . ID = = id | | id = = string . Empty )
{
2021-03-10 14:48:29 +01:00
if ( animation . HasRunningAnimation ( KeyPrefix + container . Key ) )
2020-09-03 16:02:40 -04:00
{
2021-03-10 14:48:29 +01:00
animation . Stop ( KeyPrefix + container . Key ) ;
2020-09-03 16:02:40 -04:00
}
if ( removeBehaviour )
{
toRemove . Add ( container ) ;
}
}
}
foreach ( var container in toRemove )
{
_animations . Remove ( container ) ;
}
2021-12-03 15:53:09 +01:00
if ( resetToOriginalSettings & & IoCManager . Resolve < IEntityManager > ( ) . TryGetComponent ( Owner , out PointLightComponent ? light ) )
2020-09-03 16:02:40 -04:00
{
2021-03-10 14:48:29 +01:00
light . Color = _originalColor ;
light . Enabled = _originalEnabled ;
light . Energy = _originalEnergy ;
light . Radius = _originalRadius ;
light . Rotation = _originalRotation ;
2020-09-03 16:02:40 -04:00
}
}
/// <summary>
/// Add a new light behaviour to the component and start it immediately unless otherwise specified.
/// </summary>
public void AddNewLightBehaviour ( LightBehaviourAnimationTrack behaviour , bool playImmediately = true )
{
2021-03-10 14:48:29 +01:00
var key = 0 ;
2020-09-03 16:02:40 -04:00
while ( _animations . Any ( x = > x . Key = = key ) )
{
key + + ;
}
var animation = new Animation ( )
{
2021-03-10 14:48:29 +01:00
AnimationTracks = { behaviour }
2020-09-03 16:02:40 -04:00
} ;
2021-09-01 13:23:52 +02:00
behaviour . Initialize ( Owner , IoCManager . Resolve < IRobustRandom > ( ) ) ;
2021-03-10 14:48:29 +01:00
2020-09-03 16:02:40 -04:00
var container = new AnimationContainer ( key , animation , behaviour ) ;
_animations . Add ( container ) ;
if ( playImmediately )
{
StartLightBehaviour ( behaviour . ID ) ;
}
}
}
}