mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
* added AnimationStartedEvent * added AnimationStarted event to Control * reordered Control.AnimationStarted above Control.AnimationCompleted * fixed comment * switched to internal constructor and proxy method
268 lines
9.5 KiB
C#
268 lines
9.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Robust.Client.Animations;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Client.GameObjects
|
|
{
|
|
public sealed class AnimationPlayerSystem : EntitySystem
|
|
{
|
|
private readonly List<Entity<AnimationPlayerComponent>> _activeAnimations = new();
|
|
|
|
private EntityQuery<AnimationPlayerComponent> _playerQuery;
|
|
private EntityQuery<MetaDataComponent> _metaQuery;
|
|
|
|
#pragma warning disable CS0414
|
|
[Dependency] private readonly IComponentFactory _compFact = default!;
|
|
#pragma warning restore CS0414
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
_playerQuery = GetEntityQuery<AnimationPlayerComponent>();
|
|
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
|
}
|
|
|
|
public override void FrameUpdate(float frameTime)
|
|
{
|
|
// TODO: Active or something idk.
|
|
for (var i = 0; i < _activeAnimations.Count; i++)
|
|
{
|
|
var anim = _activeAnimations[i];
|
|
var uid = anim.Owner;
|
|
|
|
if (!_metaQuery.TryGetComponent(uid, out var metadata) ||
|
|
metadata.EntityPaused)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!Update(uid, anim.Comp, frameTime))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_activeAnimations.RemoveSwap(i);
|
|
i--;
|
|
anim.Comp.HasPlayingAnimation = false;
|
|
}
|
|
}
|
|
|
|
internal void AddComponent(Entity<AnimationPlayerComponent> ent)
|
|
{
|
|
if (ent.Comp.HasPlayingAnimation) return;
|
|
_activeAnimations.Add(ent);
|
|
ent.Comp.HasPlayingAnimation = true;
|
|
}
|
|
|
|
private bool Update(EntityUid uid, AnimationPlayerComponent component, float frameTime)
|
|
{
|
|
if (component.PlayingAnimationCount == 0 ||
|
|
component.Deleted)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var remie = new RemQueue<string>();
|
|
foreach (var (key, playback) in component.PlayingAnimations)
|
|
{
|
|
var keep = AnimationPlaybackShared.UpdatePlayback(uid, playback, frameTime);
|
|
if (!keep)
|
|
{
|
|
remie.Add(key);
|
|
}
|
|
}
|
|
|
|
foreach (var key in remie)
|
|
{
|
|
component.PlayingAnimations.Remove(key);
|
|
var completedEvent = new AnimationCompletedEvent(uid, component, key, true);
|
|
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start playing an animation.
|
|
/// </summary>
|
|
public void Play(EntityUid uid, Animation animation, string key)
|
|
{
|
|
var component = EnsureComp<AnimationPlayerComponent>(uid);
|
|
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
|
|
}
|
|
|
|
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
|
|
public void Play(EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key)
|
|
{
|
|
component ??= EnsureComp<AnimationPlayerComponent>(uid);
|
|
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
|
|
}
|
|
|
|
public void Play(Entity<AnimationPlayerComponent> ent, Animation animation, string key)
|
|
{
|
|
AddComponent(ent);
|
|
var playback = new AnimationPlaybackShared.AnimationPlayback(animation);
|
|
|
|
#if DEBUG
|
|
// Component networking checks
|
|
foreach (var track in animation.AnimationTracks)
|
|
{
|
|
if (track is not AnimationTrackComponentProperty compTrack)
|
|
continue;
|
|
|
|
if (compTrack.ComponentType == null)
|
|
{
|
|
Log.Error("Attempted to play a component animation without any component specified.");
|
|
return;
|
|
}
|
|
|
|
if (!EntityManager.TryGetComponent(ent, compTrack.ComponentType, out var animatedComp))
|
|
{
|
|
Log.Error(
|
|
$"Attempted to play a component animation, but the entity {ToPrettyString(ent)} does not have the component to be animated: {compTrack.ComponentType}.");
|
|
return;
|
|
}
|
|
|
|
if (IsClientSide(ent) || !animatedComp.NetSyncEnabled)
|
|
continue;
|
|
|
|
var reg = _compFact.GetRegistration(animatedComp);
|
|
|
|
// In principle there is nothing wrong with this, as long as the property of the component being
|
|
// animated is not part of the networked state and setting it does not dirty the component. Hence only a
|
|
// warning in debug mode.
|
|
if (reg.NetID != null && compTrack.Property != null)
|
|
{
|
|
if (animatedComp.GetType().GetProperty(compTrack.Property) is { } property &&
|
|
property.HasCustomAttribute<AutoNetworkedFieldAttribute>())
|
|
{
|
|
Log.Warning($"Playing a component animation on a networked component {reg.Name} belonging to {ToPrettyString(ent)}");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
foreach (var track in animation.AnimationTracks)
|
|
{
|
|
if (track is not AnimationTrackSpriteFlick)
|
|
continue;
|
|
|
|
track.AdvancePlayback(ent.Owner, 0, 0, 0f);
|
|
}
|
|
|
|
ent.Comp.PlayingAnimations.Add(key, playback);
|
|
|
|
var startedEvent = new AnimationStartedEvent(ent.Owner, ent.Comp, key);
|
|
RaiseLocalEvent(ent.Owner, startedEvent, true);
|
|
}
|
|
|
|
public bool HasRunningAnimation(EntityUid uid, string key)
|
|
{
|
|
return TryComp(uid, out AnimationPlayerComponent? component) &&
|
|
component.PlayingAnimations.ContainsKey(key);
|
|
}
|
|
|
|
public bool HasRunningAnimation(EntityUid uid, AnimationPlayerComponent? component, string key)
|
|
{
|
|
if (component == null)
|
|
TryComp(uid, out component);
|
|
|
|
return component != null && component.PlayingAnimations.ContainsKey(key);
|
|
}
|
|
|
|
public bool HasRunningAnimation(AnimationPlayerComponent component, string key)
|
|
{
|
|
return component.PlayingAnimations.ContainsKey(key);
|
|
}
|
|
|
|
[Obsolete]
|
|
public void Stop(AnimationPlayerComponent component, string key)
|
|
{
|
|
Stop((component.Owner, component), key);
|
|
}
|
|
|
|
public void Stop(Entity<AnimationPlayerComponent?> entity, string key)
|
|
{
|
|
if (!_playerQuery.Resolve(entity.Owner, ref entity.Comp, false) ||
|
|
!entity.Comp.PlayingAnimations.Remove(key))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var completedEvent = new AnimationCompletedEvent(entity.Owner, entity.Comp, key, false);
|
|
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
|
|
}
|
|
|
|
public void Stop(EntityUid uid, AnimationPlayerComponent? component, string key)
|
|
{
|
|
Stop((uid, component), key);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raised whenever an animation started playing.
|
|
/// </summary>
|
|
public sealed class AnimationStartedEvent : EntityEventArgs
|
|
{
|
|
/// <summary>
|
|
/// The entity associated with the event.
|
|
/// </summary>
|
|
public EntityUid Uid { get; init; }
|
|
|
|
/// <summary>
|
|
/// The animation player component associated with the entity this event was raised on.
|
|
/// </summary>
|
|
public AnimationPlayerComponent AnimationPlayer { get; init; }
|
|
|
|
/// <summary>
|
|
/// The key associated with the animation that was started.
|
|
/// </summary>
|
|
public string Key { get; init; } = string.Empty;
|
|
|
|
internal AnimationStartedEvent(EntityUid uid, AnimationPlayerComponent animationPlayer, string key)
|
|
{
|
|
Uid = uid;
|
|
AnimationPlayer = animationPlayer;
|
|
Key = key;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raised whenever an animation stops, either due to running its course or being stopped manually.
|
|
/// </summary>
|
|
public sealed class AnimationCompletedEvent : EntityEventArgs
|
|
{
|
|
/// <summary>
|
|
/// The entity associated with the event.
|
|
/// </summary>
|
|
public EntityUid Uid { get; init; }
|
|
|
|
/// <summary>
|
|
/// The animation player component associated with the entity this event was raised on.
|
|
/// </summary>
|
|
public AnimationPlayerComponent AnimationPlayer { get; init; }
|
|
|
|
/// <summary>
|
|
/// The key associated with the animation that was completed.
|
|
/// </summary>
|
|
public string Key { get; init; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// If true, the animation finished by getting to its natural end.
|
|
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(EntityUid,AnimationPlayerComponent,string)"/> or similar overloads.
|
|
/// </summary>
|
|
public bool Finished { get; init; }
|
|
|
|
public AnimationCompletedEvent(EntityUid uid, AnimationPlayerComponent animationPlayer, string key, bool finished = true)
|
|
{
|
|
Uid = uid;
|
|
AnimationPlayer = animationPlayer;
|
|
Key = key;
|
|
Finished = finished;
|
|
}
|
|
}
|
|
}
|