diff --git a/Robust.Client/Animus/Actions/AnimusActionAnimationBase.cs b/Robust.Client/Animus/Actions/AnimusActionAnimationBase.cs
new file mode 100644
index 00000000000..6d73b413f56
--- /dev/null
+++ b/Robust.Client/Animus/Actions/AnimusActionAnimationBase.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using JetBrains.Annotations;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
+using Robust.Shared.Animations;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Maths;
+using Robust.Shared.Serialization.Manager.Attributes;
+
+namespace Robust.Client.Animus.Actions;
+
+[ImplicitDataDefinitionForInheritors]
+[PublicAPI]
+public abstract partial class AnimusActionAnimationBase
+{
+ ///
+ /// Simple stopping animation for cases where only rotation and offset were affected.
+ ///
+ protected static readonly Animation StopAnimation = new()
+ {
+ Length = TimeSpan.FromSeconds(0),
+ AnimationTracks =
+ {
+ new AnimationTrackComponentProperty()
+ {
+ ComponentType = typeof(SpriteComponent),
+ Property = nameof(SpriteComponent.Rotation),
+ InterpolationMode = AnimationInterpolationMode.Linear,
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
+ },
+ },
+ new AnimationTrackComponentProperty()
+ {
+ ComponentType = typeof(SpriteComponent),
+ Property = nameof(SpriteComponent.Offset),
+ InterpolationMode = AnimationInterpolationMode.Linear,
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
+ },
+ },
+ },
+ };
+
+ public virtual void Initialize(EntityManager entityManager)
+ {
+
+ }
+
+ protected abstract Animation? GetNextAnimation(AppearanceSystem appearanceSystem, EntityUid entity, bool restarting);
+
+ protected abstract Animation? GetStopAnimation(AppearanceSystem appearanceSystem, EntityUid entity);
+
+ internal bool TryNextAnimation(AppearanceSystem appearanceSystem, EntityUid entity, [NotNullWhen(true)] out Animation? anim, bool restarting)
+ {
+ anim = GetNextAnimation(appearanceSystem, entity, restarting);
+ return anim != null;
+ }
+
+ internal bool TryStopAnimation(AppearanceSystem appearanceSystem, EntityUid entity, [NotNullWhen(true)] out Animation? anim)
+ {
+ anim = GetStopAnimation(appearanceSystem, entity);
+ return anim != null;
+ }
+}
+
+public sealed partial class AnimusActionAnimationNull : AnimusActionAnimationBase
+{
+ protected override Animation? GetNextAnimation(AppearanceSystem appearanceSystem, EntityUid entity, bool restarting)
+ {
+ return null;
+ }
+
+ protected override Animation? GetStopAnimation(AppearanceSystem appearanceSystem, EntityUid entity)
+ {
+ return null;
+ }
+}
diff --git a/Robust.Client/Animus/Actions/AnimusActionSpriteChangeBase.cs b/Robust.Client/Animus/Actions/AnimusActionSpriteChangeBase.cs
new file mode 100644
index 00000000000..36be38fa123
--- /dev/null
+++ b/Robust.Client/Animus/Actions/AnimusActionSpriteChangeBase.cs
@@ -0,0 +1,33 @@
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization.Manager.Attributes;
+
+namespace Robust.Client.Animus.Actions;
+
+[ImplicitDataDefinitionForInheritors]
+[PublicAPI]
+public abstract partial class AnimusActionSpriteChangeBase
+{
+ public abstract void Initialize(Entity entity, EntityManager entityManager);
+ public abstract void ExecuteSpriteChange(Entity entity);
+ public abstract void ResetSpriteChange(Entity entity);
+}
+
+public sealed partial class AnimusActionSpriteChangeNull : AnimusActionSpriteChangeBase
+{
+ public override void Initialize(Entity entity, EntityManager entityManager)
+ {
+
+ }
+
+ public override void ExecuteSpriteChange(Entity entity)
+ {
+
+ }
+
+ public override void ResetSpriteChange(Entity entity)
+ {
+
+ }
+}
diff --git a/Robust.Client/Animus/AnimusComponent.cs b/Robust.Client/Animus/AnimusComponent.cs
new file mode 100644
index 00000000000..2db725d69e3
--- /dev/null
+++ b/Robust.Client/Animus/AnimusComponent.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using Robust.Client.Animus.States;
+using Robust.Client.Animus.Timers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager.Attributes;
+
+namespace Robust.Client.Animus;
+
+[RegisterComponent]
+public sealed partial class AnimusComponent : Component
+{
+ [DataField]
+ internal List> StateMachines;
+
+ internal readonly List ActiveStateMachines = [];
+}
+
+///
+/// Prototypes are global instances but conditions and other objects may require per-instance data.
+///
+internal sealed class AnimusInstance
+{
+ internal ProtoId Prototype = default;
+ internal AnimusStateBase[] States = [];
+ internal AnimusTimerBase? Timer = null;
+ internal AnimusStateBase ActiveState = new AnimusStateNull();
+ internal AnimusStateBase DefaultState = new AnimusStateNull();
+ internal TimeSpan ActiveStateExitTime = TimeSpan.Zero;
+}
diff --git a/Robust.Client/Animus/AnimusPrototype.cs b/Robust.Client/Animus/AnimusPrototype.cs
new file mode 100644
index 00000000000..85eb375371f
--- /dev/null
+++ b/Robust.Client/Animus/AnimusPrototype.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using Robust.Client.Animus.States;
+using Robust.Client.Animus.Timers;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager.Attributes;
+using Robust.Shared.ViewVariables;
+
+namespace Robust.Client.Animus;
+
+[Prototype]
+internal sealed partial class AnimusPrototype : IPrototype
+{
+ [ViewVariables]
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ ///
+ /// A collection of possible states for this animus.
+ ///
+ [DataField]
+ internal List States = [];
+
+ ///
+ /// Optional timer for executing an update.
+ /// Setting this disables periodic condition checks.
+ ///
+ [DataField]
+ internal AnimusTimerBase? Timer;
+
+ ///
+ /// The default state to enter when no other state matches their conditions.
+ ///
+ [DataField]
+ internal AnimusStateBase DefaultState = new AnimusStateNull();
+
+}
diff --git a/Robust.Client/Animus/AnimusSystem.cs b/Robust.Client/Animus/AnimusSystem.cs
new file mode 100644
index 00000000000..9f6494d4632
--- /dev/null
+++ b/Robust.Client/Animus/AnimusSystem.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Robust.Client.Animus.States;
+using Robust.Client.GameObjects;
+using Robust.Client.Timing;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Log;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager;
+
+namespace Robust.Client.Animus;
+
+public sealed partial class AnimusSystem : EntitySystem
+{
+ [Dependency] private ILogManager _logger = default!;
+ [Dependency] private IClientGameTiming _timing = default!;
+ [Dependency] private IPrototypeManager _prototypeManager = default!;
+ [Dependency] private ISerializationManager _serializationManager = default!;
+
+ private ISawmill _sawmill = default!;
+
+ private readonly Dictionary> _actingComponentProperties = new();
+
+ // TODO: Subscribe to OnAnimationStarted event to register running legacy animations.
+ private void OnAnimationCompleted(Entity entity, ref AnimationCompletedEvent args)
+ {
+ // TODO: Deregister legacy animations.
+
+ if (!TryComp(entity, out var comp))
+ return;
+
+ AnimusInstance? animusInstance = null;
+ foreach (var machine in comp.ActiveStateMachines)
+ {
+ if (machine.ActiveState is AnimusStateAnimation animState && animState.RunningAnimationKey == args.Key)
+ {
+ animusInstance = machine;
+ }
+ }
+
+ if (animusInstance == null)
+ return;
+
+ if (animusInstance.ActiveState.OneShot)
+ {
+ SwitchState((entity, comp), animusInstance.DefaultState);
+ }
+ else if (EvaluateConditions((entity, comp), animusInstance.ActiveState))
+ {
+ animusInstance.ActiveState.Update(entity, args.Finished);
+ }
+ }
+
+ private void OnAnimationStateMachineComponentInit(Entity entity, ref ComponentInit args)
+ {
+ foreach (var animusInstance in entity.Comp.StateMachines)
+ {
+ InitializeStateMachine(entity, animusInstance);
+ }
+ }
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _sawmill = _logger.GetSawmill("asm");
+
+ SubscribeLocalEvent(OnAnimationStateMachineComponentInit);
+ SubscribeLocalEvent(OnAnimationCompleted);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var entity, out var comp))
+ {
+ foreach (var animusInstance in comp.ActiveStateMachines)
+ {
+ UpdateStateMachine((entity, comp), animusInstance);
+ }
+ }
+ }
+
+ internal void RegisterEntityAnimationProperty(EntityUid uid, Type type, string prop)
+ {
+ if (!_actingComponentProperties.ContainsKey(uid))
+ _actingComponentProperties.Add(uid, []);
+ if (_actingComponentProperties[uid]
+ .Any(x => x.Item1 == type && x.Item2 == prop))
+ {
+ _sawmill.Error($"An animation using the {prop} property on {type.Name} has already been registered for entity {uid}");
+ return;
+ }
+ _actingComponentProperties[uid].Add((type, prop));
+ }
+
+ internal void DeregisterEntityAnimationProperty(EntityUid uid, Type type, string prop)
+ {
+ if(!_actingComponentProperties.ContainsKey(uid))
+ _actingComponentProperties.Add(uid, []);
+ var tuple = _actingComponentProperties[uid].Single(x => x.Item1 == type && x.Item2 == prop);
+ _actingComponentProperties[uid].Remove(tuple);
+ }
+
+ ///
+ /// Initialize state machine from prototype and add to list of active instances.
+ ///
+ private void InitializeStateMachine(Entity entity, ProtoId protoId)
+ {
+ List states = [];
+
+ var proto = _prototypeManager.Index(protoId);
+
+ var animusInstance = new AnimusInstance()
+ {
+ Prototype = proto,
+ Timer = _serializationManager.CreateCopy(proto.Timer, null, false),
+ DefaultState = _serializationManager.CreateCopy(proto.DefaultState, null, false, false),
+ };
+
+ animusInstance.ActiveState.Initialize(entity, EntityManager, animusInstance);
+ animusInstance.DefaultState.Initialize(entity, EntityManager, animusInstance);
+
+ foreach (var state in proto.States)
+ {
+ // TODO: Add more info to error message.
+ if (state.Conditions.Length == 0)
+ {
+ _sawmill.Error("Every AnimusState must have at least one condition. (Except DefaultState)");
+ continue;
+ }
+
+ states.Add(CopyInitializeState(entity, state, animusInstance));
+ }
+
+ animusInstance.States = states.ToArray();
+ animusInstance.DefaultState = CopyInitializeState(entity, proto.DefaultState, animusInstance);
+
+ entity.Comp.ActiveStateMachines.Add(animusInstance);
+ }
+
+ private AnimusStateBase CopyInitializeState(Entity entity, AnimusStateBase state, AnimusInstance animusInstance)
+ {
+ var stateCopy = _serializationManager.CreateCopy(state, null, false, false);
+ stateCopy.Initialize(entity.Owner, EntityManager, animusInstance);
+
+ foreach (var cond in stateCopy.Conditions)
+ {
+ cond.Initialize(EntityManager);
+ }
+
+ return stateCopy;
+ }
+
+ private void UpdateStateMachine(Entity entity, AnimusInstance animusInstance)
+ {
+ ExitActiveStateIfExitTimerReached(entity, animusInstance);
+ CheckStateMachineConditionsAndUpdateState(entity, animusInstance);
+ }
+
+ private void ExitActiveStateIfExitTimerReached(Entity entity, AnimusInstance animusInstance)
+ {
+ if (animusInstance.ActiveStateExitTime == TimeSpan.Zero)
+ return;
+
+ if (animusInstance.ActiveStateExitTime > _timing.CurTime)
+ return;
+
+ SwitchState(entity, animusInstance.DefaultState);
+ animusInstance.ActiveStateExitTime = TimeSpan.Zero;
+ }
+
+ private void CheckStateMachineConditionsAndUpdateState(Entity entity, AnimusInstance animusInstance)
+ {
+ var currentState = animusInstance.ActiveState;
+ var nextState = animusInstance.DefaultState;
+
+ // Return if currentState has conditions that are still fulfilled.
+ if (currentState is { Conditions.Length: > 0, OneShot: false } &&
+ EvaluateConditions(entity, currentState) &&
+ animusInstance.ActiveStateExitTime == TimeSpan.Zero)
+ return;
+
+ foreach (var state in animusInstance.States)
+ {
+ if (!EvaluateConditions(entity, state))
+ continue;
+
+ nextState = state;
+ break;
+ }
+
+ SwitchState(entity, nextState);
+ if (nextState.ExitPeriod != TimeSpan.Zero)
+ animusInstance.ActiveStateExitTime = _timing.CurTime + nextState.ExitPeriod;
+ }
+
+ private bool EvaluateConditions(Entity entity, AnimusStateBase state)
+ {
+ foreach (var cond in state.Conditions)
+ {
+ if (!cond.EvaluateInternal(entity, _timing.CurTime, _timing.IsFirstTimePredicted))
+ return false;
+ }
+ return true;
+ }
+
+ private static void SwitchState(Entity entity, AnimusStateBase newState)
+ {
+ if (newState.Instance.ActiveState == newState)
+ return;
+
+ newState.Instance.ActiveState.Exit(entity.Owner);
+ newState.Enter(entity.Owner);
+ newState.Instance.ActiveState = newState;
+ }
+}
diff --git a/Robust.Client/Animus/Conditions/AnimusConditionBase.cs b/Robust.Client/Animus/Conditions/AnimusConditionBase.cs
new file mode 100644
index 00000000000..e373d9a0a28
--- /dev/null
+++ b/Robust.Client/Animus/Conditions/AnimusConditionBase.cs
@@ -0,0 +1,34 @@
+using System;
+using JetBrains.Annotations;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization.Manager.Attributes;
+
+namespace Robust.Client.Animus.Conditions;
+
+[ImplicitDataDefinitionForInheritors]
+[PublicAPI]
+public abstract partial class AnimusConditionBase
+{
+ private bool _lastResult;
+ private TimeSpan _lastUpdate;
+ protected virtual TimeSpan UpdateInterval { get; } = TimeSpan.FromSeconds(0.1);
+
+ ///
+ /// IoCManager.InjectDependencies doesn't work, override this method to initialize dependencies manually.
+ ///
+ ///
+ public virtual void Initialize(EntityManager entityManager)
+ {
+
+ }
+
+ protected abstract bool Evaluate(EntityUid ent);
+
+ internal bool EvaluateInternal(Entity ent, TimeSpan currentTime, bool isFirstTimePredicted)
+ {
+ if (!isFirstTimePredicted || currentTime - _lastUpdate < UpdateInterval)
+ return _lastResult;
+ _lastResult = Evaluate(ent);
+ return _lastResult;
+ }
+}
diff --git a/Robust.Client/Animus/States/AnimusStateAnimation.cs b/Robust.Client/Animus/States/AnimusStateAnimation.cs
new file mode 100644
index 00000000000..4bc3425ab37
--- /dev/null
+++ b/Robust.Client/Animus/States/AnimusStateAnimation.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using Robust.Client.Animations;
+using Robust.Client.Animus.Actions;
+using Robust.Client.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Log;
+using Robust.Shared.Serialization.Manager.Attributes;
+
+namespace Robust.Client.Animus.States;
+
+internal sealed partial class AnimusStateAnimation : AnimusStateBase
+{
+ private static readonly AnimusActionAnimationBase NullAction = new AnimusActionAnimationNull();
+
+ private AnimationPlayerSystem _animationPlayerSystem;
+ private AnimusSystem _animationStateMachineSystem;
+ private AppearanceSystem _appearanceSystem;
+
+ private readonly List<(Type, string)> _animationCompProps = [];
+
+ [DataField]
+ internal AnimusActionAnimationBase Action = NullAction;
+
+ internal string RunningAnimationKey => Action.GetType().Name + "_RUNNING";
+ internal string StopAnimationKey => Action.GetType().Name + "_STOP";
+
+ ///
+ /// IoCManager.InjectDependencies won't work, use this override to inject your dependencies manually.
+ ///
+ internal override void Initialize(EntityUid ent, EntityManager entityManager, AnimusInstance animusInstance)
+ {
+ base.Initialize(ent, entityManager, animusInstance);
+ _animationPlayerSystem = entityManager.System();
+ _animationStateMachineSystem = entityManager.System();
+ _appearanceSystem = entityManager.System();
+ Action.Initialize(entityManager);
+ }
+
+ internal override void Enter(EntityUid ent)
+ {
+ if (_animationPlayerSystem.HasRunningAnimation(ent, RunningAnimationKey) ||
+ !Action.TryNextAnimation(_appearanceSystem, ent, out var animation, false))
+ return;
+
+ UpdateAnimationCompProps(ent, animation);
+ _animationPlayerSystem.Play(ent, animation, RunningAnimationKey);
+ }
+
+ internal override void Update(EntityUid ent, bool finished)
+ {
+ if (_animationPlayerSystem.HasRunningAnimation(ent, RunningAnimationKey) ||
+ !Action.TryNextAnimation(_appearanceSystem, ent, out var animation, finished))
+ return;
+
+ UpdateAnimationCompProps(ent, animation);
+ _animationPlayerSystem.Play(ent, animation, RunningAnimationKey);
+ }
+
+ internal override void Exit(EntityUid ent)
+ {
+ // Stop the running animation of this state.
+ if (_animationPlayerSystem.HasRunningAnimation(ent, RunningAnimationKey))
+ _animationPlayerSystem.Stop(ent, RunningAnimationKey);
+
+ // TODO: The stopping animation can still clash with registered component properties but to fix this
+ // a state queue would be required first.
+ // For now, we'll test and see if any glitches would even occur due to this.
+ ClearAnimationCompProps(ent);
+
+ // Fetch stopping animation if it isn't running yet.
+ if (_animationPlayerSystem.HasRunningAnimation(ent, StopAnimationKey) ||
+ !Action.TryStopAnimation(_appearanceSystem, ent, out var animation))
+ return;
+
+ // Play the stopping animation.
+ _animationPlayerSystem.Play(ent, animation, StopAnimationKey);
+ }
+
+ private void ClearAnimationCompProps(EntityUid ent)
+ {
+ foreach (var compProp in _animationCompProps)
+ {
+ _animationStateMachineSystem.DeregisterEntityAnimationProperty(ent,
+ compProp.Item1,
+ compProp.Item2);
+ }
+ _animationCompProps.Clear();
+ }
+
+ private void UpdateAnimationCompProps(EntityUid ent, Animation anim)
+ {
+ ClearAnimationCompProps(ent);
+ foreach (var track in anim.AnimationTracks)
+ {
+ if (track is not AnimationTrackComponentProperty { ComponentType: not null, Property: not null } propTrack)
+ continue;
+
+ _animationCompProps.Add((propTrack.ComponentType, propTrack.Property));
+ _animationStateMachineSystem.RegisterEntityAnimationProperty(ent,
+ propTrack.ComponentType,
+ propTrack.Property);
+ }
+ }
+}
diff --git a/Robust.Client/Animus/States/AnimusStateBase.cs b/Robust.Client/Animus/States/AnimusStateBase.cs
new file mode 100644
index 00000000000..6427876b1d2
--- /dev/null
+++ b/Robust.Client/Animus/States/AnimusStateBase.cs
@@ -0,0 +1,56 @@
+using System;
+using Robust.Client.Animus.Conditions;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization.Manager.Attributes;
+
+namespace Robust.Client.Animus.States;
+
+[ImplicitDataDefinitionForInheritors]
+internal abstract partial class AnimusStateBase
+{
+ ///
+ /// A collection of conditions that must be true for this state to activate.
+ ///
+ [DataField]
+ internal AnimusConditionBase[] Conditions = [];
+
+ ///
+ /// If set to true, the state only executes its action one time instead of restarting it once the animation ends.
+ ///
+ [DataField]
+ internal bool OneShot;
+
+ ///
+ /// If set to non-zero, state exits automatically after the defined timespan.
+ ///
+ [DataField]
+ internal TimeSpan ExitPeriod = TimeSpan.Zero;
+
+ internal AnimusInstance Instance;
+
+ [MustCallBase]
+ internal virtual void Initialize(EntityUid ent, EntityManager entityManager, AnimusInstance animusInstance)
+ {
+ Instance = animusInstance;
+ }
+
+ internal virtual void Enter(EntityUid ent)
+ {
+
+ }
+
+ internal virtual void Update(EntityUid ent, bool finished)
+ {
+
+ }
+
+ internal virtual void Exit(EntityUid ent)
+ {
+
+ }
+}
+
+internal sealed partial class AnimusStateNull : AnimusStateBase
+{
+
+}
diff --git a/Robust.Client/Animus/States/AnimusStateSpriteChange.cs b/Robust.Client/Animus/States/AnimusStateSpriteChange.cs
new file mode 100644
index 00000000000..0b9258608ef
--- /dev/null
+++ b/Robust.Client/Animus/States/AnimusStateSpriteChange.cs
@@ -0,0 +1,40 @@
+using JetBrains.Annotations;
+using Robust.Client.Animus.Actions;
+using Robust.Client.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization.Manager.Attributes;
+
+namespace Robust.Client.Animus.States;
+
+[PublicAPI]
+internal sealed partial class AnimusStateSpriteChange : AnimusStateBase
+{
+ [DataField]
+ internal AnimusActionSpriteChangeBase Action = NullAction;
+
+ private static readonly AnimusActionSpriteChangeBase NullAction = new AnimusActionSpriteChangeNull();
+ private EntityManager _entityManager;
+
+ internal override void Initialize(EntityUid ent, EntityManager entityManager, AnimusInstance animusInstance)
+ {
+ base.Initialize(ent, entityManager, animusInstance);
+ _entityManager = entityManager;
+ if (!entityManager.TryGetComponent(ent, out var spriteComponent))
+ return;
+ Action.Initialize((ent, spriteComponent), entityManager);
+ }
+
+ internal override void Enter(EntityUid ent)
+ {
+ if (!_entityManager.TryGetComponent(ent, out var spriteComponent))
+ return;
+ Action.ExecuteSpriteChange((ent, spriteComponent));
+ }
+
+ internal override void Exit(EntityUid ent)
+ {
+ if (!_entityManager.TryGetComponent(ent, out var spriteComponent))
+ return;
+ Action.ResetSpriteChange((ent, spriteComponent));
+ }
+}
diff --git a/Robust.Client/Animus/Timers/AnimusTimerBase.cs b/Robust.Client/Animus/Timers/AnimusTimerBase.cs
new file mode 100644
index 00000000000..4d91a38582e
--- /dev/null
+++ b/Robust.Client/Animus/Timers/AnimusTimerBase.cs
@@ -0,0 +1,13 @@
+using System;
+using JetBrains.Annotations;
+using Robust.Shared.Random;
+using Robust.Shared.Serialization.Manager.Attributes;
+
+namespace Robust.Client.Animus.Timers;
+
+[ImplicitDataDefinitionForInheritors]
+[PublicAPI]
+public abstract partial class AnimusTimerBase
+{
+ public abstract TimeSpan GetNextPeriod(IRobustRandom random);
+}
diff --git a/Robust.Server/GameObjects/ServerComponentFactory.cs b/Robust.Server/GameObjects/ServerComponentFactory.cs
index 9e718fbb079..0257df45f50 100644
--- a/Robust.Server/GameObjects/ServerComponentFactory.cs
+++ b/Robust.Server/GameObjects/ServerComponentFactory.cs
@@ -14,5 +14,6 @@ public ServerComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflecti
RegisterIgnore("AnimationPlayer");
RegisterIgnore("GenericVisualizer");
RegisterIgnore("Sprite"); // Fucking finally
+ RegisterIgnore("Animus");
}
}
diff --git a/Robust.Server/Prototypes/ServerPrototypeManager.cs b/Robust.Server/Prototypes/ServerPrototypeManager.cs
index 02e44b91122..a0a3201b570 100644
--- a/Robust.Server/Prototypes/ServerPrototypeManager.cs
+++ b/Robust.Server/Prototypes/ServerPrototypeManager.cs
@@ -25,6 +25,7 @@ public ServerPrototypeManager()
RegisterIgnore("shader");
RegisterIgnore("uiTheme");
RegisterIgnore("font");
+ RegisterIgnore("animus");
}
public override void Initialize()