LightBehaviourComponent.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. using System.Linq;
  2. using Content.Shared.Light.Components;
  3. using JetBrains.Annotations;
  4. using Robust.Client.Animations;
  5. using Robust.Client.GameObjects;
  6. using Robust.Shared.Animations;
  7. using Robust.Shared.Random;
  8. using Robust.Shared.Serialization;
  9. namespace Content.Client.Light.Components
  10. {
  11. #region LIGHT_BEHAVIOURS
  12. /// <summary>
  13. /// Base class for all light behaviours to derive from.
  14. /// This AnimationTrack derivative does not rely on keyframes since it often needs to have a randomized duration.
  15. /// </summary>
  16. [Serializable]
  17. [ImplicitDataDefinitionForInheritors]
  18. public abstract partial class LightBehaviourAnimationTrack : AnimationTrackProperty
  19. {
  20. protected IEntityManager _entMan = default!;
  21. protected IRobustRandom _random = default!;
  22. [DataField("id")] public string ID { get; set; } = string.Empty;
  23. [DataField("property")]
  24. public virtual string Property { get; protected set; } = nameof(PointLightComponent.AnimatedRadius);
  25. [DataField("isLooped")] public bool IsLooped { get; set; }
  26. [DataField("enabled")] public bool Enabled { get; set; }
  27. [DataField("startValue")] public float StartValue { get; set; } = 0f;
  28. [DataField("endValue")] public float EndValue { get; set; } = 2f;
  29. [DataField("minDuration")] public float MinDuration { get; set; } = -1f;
  30. [DataField("maxDuration")] public float MaxDuration { get; set; } = 2f;
  31. [DataField("interpolate")] public AnimationInterpolationMode InterpolateMode { get; set; } = AnimationInterpolationMode.Linear;
  32. [ViewVariables] protected float MaxTime { get; set; }
  33. private float _maxTime = default;
  34. private EntityUid _parent = default!;
  35. public void Initialize(EntityUid parent, IRobustRandom random, IEntityManager entMan)
  36. {
  37. _random = random;
  38. _entMan = entMan;
  39. _parent = parent;
  40. if (Enabled && _entMan.TryGetComponent(_parent, out PointLightComponent? light))
  41. {
  42. _entMan.System<PointLightSystem>().SetEnabled(_parent, true, light);
  43. }
  44. OnInitialize();
  45. }
  46. public void UpdatePlaybackValues(Animation owner)
  47. {
  48. if (_entMan.TryGetComponent(_parent, out PointLightComponent? light))
  49. {
  50. _entMan.System<PointLightSystem>().SetEnabled(_parent, true, light);
  51. }
  52. if (MinDuration > 0)
  53. {
  54. MaxTime = (float)_random.NextDouble() * (MaxDuration - MinDuration) + MinDuration;
  55. }
  56. else
  57. {
  58. MaxTime = MaxDuration;
  59. }
  60. owner.Length = TimeSpan.FromSeconds(MaxTime);
  61. }
  62. public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
  63. {
  64. OnStart();
  65. return (-1, _maxTime);
  66. }
  67. protected void ApplyProperty(object value)
  68. {
  69. if (Property == null)
  70. {
  71. throw new InvalidOperationException("Property parameter is null! Check the prototype!");
  72. }
  73. if (_entMan.TryGetComponent(_parent, out PointLightComponent? light))
  74. {
  75. AnimationHelper.SetAnimatableProperty(light, Property, value);
  76. }
  77. }
  78. protected override void ApplyProperty(object context, object value)
  79. {
  80. ApplyProperty(value);
  81. }
  82. public virtual void OnInitialize() { }
  83. public virtual void OnStart() { }
  84. }
  85. /// <summary>
  86. /// A light behaviour that alternates between StartValue and EndValue
  87. /// </summary>
  88. [UsedImplicitly]
  89. public sealed partial class PulseBehaviour : LightBehaviourAnimationTrack
  90. {
  91. public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
  92. object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
  93. {
  94. var playingTime = prevPlayingTime + frameTime;
  95. var interpolateValue = playingTime / MaxTime;
  96. if (Property == nameof(PointLightComponent.AnimatedEnable)) // special case for boolean
  97. {
  98. ApplyProperty(interpolateValue < 0.5f);
  99. return (-1, playingTime);
  100. }
  101. if (interpolateValue < 0.5f)
  102. {
  103. switch (InterpolateMode)
  104. {
  105. case AnimationInterpolationMode.Linear:
  106. ApplyProperty(InterpolateLinear(StartValue, EndValue, interpolateValue * 2f));
  107. break;
  108. case AnimationInterpolationMode.Cubic:
  109. ApplyProperty(InterpolateCubic(EndValue, StartValue, EndValue, StartValue, interpolateValue * 2f));
  110. break;
  111. default:
  112. case AnimationInterpolationMode.Nearest:
  113. ApplyProperty(StartValue);
  114. break;
  115. }
  116. }
  117. else
  118. {
  119. switch (InterpolateMode)
  120. {
  121. case AnimationInterpolationMode.Linear:
  122. ApplyProperty(InterpolateLinear(EndValue, StartValue, (interpolateValue - 0.5f) * 2f));
  123. break;
  124. case AnimationInterpolationMode.Cubic:
  125. ApplyProperty(InterpolateCubic(StartValue, EndValue, StartValue, EndValue, (interpolateValue - 0.5f) * 2f));
  126. break;
  127. default:
  128. case AnimationInterpolationMode.Nearest:
  129. ApplyProperty(EndValue);
  130. break;
  131. }
  132. }
  133. return (-1, playingTime);
  134. }
  135. }
  136. /// <summary>
  137. /// A light behaviour that interpolates from StartValue to EndValue
  138. /// </summary>
  139. [UsedImplicitly]
  140. public sealed partial class FadeBehaviour : LightBehaviourAnimationTrack
  141. {
  142. /// <summary>
  143. /// Automatically reverse the animation when EndValue is reached. In this particular case, MaxTime specifies the
  144. /// time of the full animation, including the reverse interpolation.
  145. /// </summary>
  146. [DataField("reverseWhenFinished")]
  147. public bool ReverseWhenFinished { get; set; }
  148. public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
  149. object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
  150. {
  151. var playingTime = prevPlayingTime + frameTime;
  152. var interpolateValue = playingTime / MaxTime;
  153. if (Property == nameof(PointLightComponent.AnimatedEnable)) // special case for boolean
  154. {
  155. ApplyProperty(interpolateValue < EndValue);
  156. return (-1, playingTime);
  157. }
  158. // From 0 to MaxTime/2, we go from StartValue to EndValue. From MaxTime/2 to MaxTime, we reverse this interpolation.
  159. if (ReverseWhenFinished)
  160. {
  161. if (interpolateValue < 0.5f)
  162. {
  163. ApplyInterpolation(StartValue, EndValue, interpolateValue * 2);
  164. }
  165. else
  166. {
  167. ApplyInterpolation(EndValue, StartValue, (interpolateValue - 0.5f) * 2);
  168. }
  169. }
  170. else
  171. {
  172. ApplyInterpolation(StartValue, EndValue, interpolateValue);
  173. }
  174. return (-1, playingTime);
  175. }
  176. private void ApplyInterpolation(float start, float end, float interpolateValue)
  177. {
  178. switch (InterpolateMode)
  179. {
  180. case AnimationInterpolationMode.Linear:
  181. ApplyProperty(InterpolateLinear(start, end, interpolateValue));
  182. break;
  183. case AnimationInterpolationMode.Cubic:
  184. ApplyProperty(InterpolateCubic(end, start, end, start, interpolateValue));
  185. break;
  186. default:
  187. case AnimationInterpolationMode.Nearest:
  188. ApplyProperty(interpolateValue < 0.5f ? start : end);
  189. break;
  190. }
  191. }
  192. }
  193. /// <summary>
  194. /// A light behaviour that interpolates using random values chosen between StartValue and EndValue.
  195. /// </summary>
  196. [UsedImplicitly]
  197. public sealed partial class RandomizeBehaviour : LightBehaviourAnimationTrack
  198. {
  199. private float _randomValue1;
  200. private float _randomValue2;
  201. private float _randomValue3;
  202. private float _randomValue4;
  203. public override void OnInitialize()
  204. {
  205. _randomValue1 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
  206. _randomValue2 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
  207. _randomValue3 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
  208. }
  209. public override void OnStart()
  210. {
  211. if (Property == nameof(PointLightComponent.AnimatedEnable)) // special case for boolean, we randomize it
  212. {
  213. ApplyProperty(_random.NextDouble() < 0.5);
  214. return;
  215. }
  216. if (InterpolateMode == AnimationInterpolationMode.Cubic)
  217. {
  218. _randomValue1 = _randomValue2;
  219. _randomValue2 = _randomValue3;
  220. }
  221. _randomValue3 = _randomValue4;
  222. _randomValue4 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
  223. }
  224. public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
  225. object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
  226. {
  227. var playingTime = prevPlayingTime + frameTime;
  228. var interpolateValue = playingTime / MaxTime;
  229. if (Property == nameof(PointLightComponent.AnimatedEnable))
  230. {
  231. return (-1, playingTime);
  232. }
  233. switch (InterpolateMode)
  234. {
  235. case AnimationInterpolationMode.Linear:
  236. ApplyProperty(InterpolateLinear(_randomValue3, _randomValue4, interpolateValue));
  237. break;
  238. case AnimationInterpolationMode.Cubic:
  239. ApplyProperty(InterpolateCubic(_randomValue1, _randomValue2, _randomValue3, _randomValue4, interpolateValue));
  240. break;
  241. default:
  242. case AnimationInterpolationMode.Nearest:
  243. ApplyProperty(interpolateValue < 0.5f ? _randomValue3 : _randomValue4);
  244. break;
  245. }
  246. return (-1, playingTime);
  247. }
  248. }
  249. /// <summary>
  250. /// A light behaviour that cycles through a list of colors.
  251. /// </summary>
  252. [UsedImplicitly]
  253. [DataDefinition]
  254. public sealed partial class ColorCycleBehaviour : LightBehaviourAnimationTrack, ISerializationHooks
  255. {
  256. [DataField("property")]
  257. public override string Property { get; protected set; } = nameof(PointLightComponent.Color);
  258. [DataField("colors")] public List<Color> ColorsToCycle { get; set; } = new();
  259. private int _colorIndex;
  260. public override void OnStart()
  261. {
  262. _colorIndex++;
  263. if (_colorIndex > ColorsToCycle.Count - 1)
  264. {
  265. _colorIndex = 0;
  266. }
  267. }
  268. public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
  269. object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
  270. {
  271. var playingTime = prevPlayingTime + frameTime;
  272. var interpolateValue = playingTime / MaxTime;
  273. switch (InterpolateMode)
  274. {
  275. case AnimationInterpolationMode.Linear:
  276. ApplyProperty(InterpolateLinear(ColorsToCycle[(_colorIndex - 1) % ColorsToCycle.Count],
  277. ColorsToCycle[_colorIndex],
  278. interpolateValue));
  279. break;
  280. case AnimationInterpolationMode.Cubic:
  281. ApplyProperty(InterpolateCubic(ColorsToCycle[_colorIndex],
  282. ColorsToCycle[(_colorIndex + 1) % ColorsToCycle.Count],
  283. ColorsToCycle[(_colorIndex + 2) % ColorsToCycle.Count],
  284. ColorsToCycle[(_colorIndex + 3) % ColorsToCycle.Count],
  285. interpolateValue));
  286. break;
  287. default:
  288. case AnimationInterpolationMode.Nearest:
  289. ApplyProperty(ColorsToCycle[_colorIndex]);
  290. break;
  291. }
  292. return (-1, playingTime);
  293. }
  294. void ISerializationHooks.AfterDeserialization()
  295. {
  296. if (ColorsToCycle.Count < 2)
  297. {
  298. throw new InvalidOperationException($"{nameof(ColorCycleBehaviour)} has less than 2 colors to cycle");
  299. }
  300. }
  301. }
  302. #endregion
  303. /// <summary>
  304. /// A component which applies a specific behaviour to a PointLightComponent on its owner.
  305. /// </summary>
  306. [RegisterComponent]
  307. public sealed partial class LightBehaviourComponent : SharedLightBehaviourComponent, ISerializationHooks
  308. {
  309. public const string KeyPrefix = nameof(LightBehaviourComponent);
  310. public sealed class AnimationContainer
  311. {
  312. public AnimationContainer(int key, Animation animation, LightBehaviourAnimationTrack track)
  313. {
  314. Key = key;
  315. Animation = animation;
  316. LightBehaviour = track;
  317. }
  318. public string FullKey => KeyPrefix + Key;
  319. public int Key { get; set; }
  320. public Animation Animation { get; set; }
  321. public LightBehaviourAnimationTrack LightBehaviour { get; set; }
  322. }
  323. [ViewVariables(VVAccess.ReadOnly)]
  324. [DataField("behaviours")]
  325. public List<LightBehaviourAnimationTrack> Behaviours = new();
  326. [ViewVariables(VVAccess.ReadOnly)]
  327. public readonly List<AnimationContainer> Animations = new();
  328. [ViewVariables(VVAccess.ReadOnly)]
  329. public Dictionary<string, object> OriginalPropertyValues = new();
  330. void ISerializationHooks.AfterDeserialization()
  331. {
  332. var key = 0;
  333. foreach (var behaviour in Behaviours)
  334. {
  335. var animation = new Animation()
  336. {
  337. AnimationTracks = { behaviour }
  338. };
  339. Animations.Add(new AnimationContainer(key, animation, behaviour));
  340. key++;
  341. }
  342. }
  343. }
  344. }