diff --git a/Abstracts/CustomMonsterModel.cs b/Abstracts/CustomMonsterModel.cs
new file mode 100644
index 0000000..58461f7
--- /dev/null
+++ b/Abstracts/CustomMonsterModel.cs
@@ -0,0 +1,153 @@
+using BaseLib.Utils;
+using Godot;
+using HarmonyLib;
+using MegaCrit.Sts2.Core.Animation;
+using MegaCrit.Sts2.Core.Assets;
+using MegaCrit.Sts2.Core.Bindings.MegaSpine;
+using MegaCrit.Sts2.Core.Entities.Creatures;
+using MegaCrit.Sts2.Core.Models;
+using MegaCrit.Sts2.Core.MonsterMoves.MonsterMoveStateMachine;
+using MegaCrit.Sts2.Core.Nodes.Combat;
+
+namespace BaseLib.Abstracts;
+
+public abstract class CustomMonsterModel : MonsterModel, ICustomModel
+{
+ ///
+ /// Override this or place your scene at res://scenes/creature_visuals/class_name.tscn
+ ///
+ public virtual string? CustomVisualPath => null;
+
+ public virtual string? CustomAttackSfx => null;
+ public virtual string? CustomCastSfx => null;
+ public virtual string? CustomDeathSfx => null;
+
+
+ ///
+ /// By default, will convert a scene containing the necessary nodes into a NCreatureVisuals even if it is not one.
+ ///
+ ///
+ public virtual NCreatureVisuals? CreateCustomVisuals() {
+ string? path = (CustomVisualPath ?? VisualsPath);
+ if (path == null) return null;
+ return GodotUtils.CreatureVisualsFromScene(path);
+ }
+
+
+ ///
+ /// Override and return a CreatureAnimator if you need to set up states that differ from the default for the monster.
+ /// Using is suggested.
+ ///
+ ///
+ public virtual CreatureAnimator? SetupCustomAnimationStates(MegaSprite controller)
+ {
+ return null;
+ }
+
+ ///
+ /// If you have a spine animation without all the required animations,
+ /// use this method to set up a controller that will use animations of your choice for each animation.
+ /// Any omitted animation parameters will default to the idle animation.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static CreatureAnimator SetupAnimationState(MegaSprite controller, string idleName,
+ string? deadName = null, bool deadLoop = false,
+ string? hitName = null, bool hitLoop = false,
+ string? attackName = null, bool attackLoop = false,
+ string? castName = null, bool castLoop = false)
+ {
+ var idleAnim = new AnimState(idleName, true);
+ var deadAnim = deadName == null ? idleAnim : new AnimState(deadName, deadLoop);
+ var hitAnim = hitName == null ? idleAnim :
+ new AnimState(hitName, hitLoop)
+ {
+ NextState = idleAnim
+ };
+ var attackAnim = attackName == null ? idleAnim :
+ new AnimState(attackName, attackLoop)
+ {
+ NextState = idleAnim
+ };
+ var castAnim = castName == null ? idleAnim :
+ new AnimState(castName, castLoop)
+ {
+ NextState = idleAnim
+ };
+
+ var animator = new CreatureAnimator(idleAnim, controller);
+
+ animator.AddAnyState("Idle", idleAnim);
+ animator.AddAnyState("Dead", deadAnim);
+ animator.AddAnyState("Hit", hitAnim);
+ animator.AddAnyState("Attack", attackAnim);
+ animator.AddAnyState("Cast", castAnim);
+
+ return animator;
+ }
+}
+
+[HarmonyPatch(typeof(MonsterModel), nameof(MonsterModel.GenerateAnimator))]
+class GenerateAnimatorPatchMonster
+{
+ [HarmonyPrefix]
+ static bool CustomAnimator(MonsterModel __instance, MegaSprite controller, ref CreatureAnimator? __result)
+ {
+ if (__instance is not CustomMonsterModel customMon)
+ return true;
+
+ __result = customMon.SetupCustomAnimationStates(controller);
+ return __result == null;
+ }
+}
+
+[HarmonyPatch(typeof(MonsterModel), "AttackSfx", MethodType.Getter)]
+class AttackSfxMonster
+{
+ [HarmonyPrefix]
+ static bool Custom(MonsterModel __instance, ref string? __result)
+ {
+ if (__instance is not CustomMonsterModel customMon)
+ return true;
+
+ __result = customMon.CustomAttackSfx;
+ return __result == null;
+ }
+}
+
+[HarmonyPatch(typeof(MonsterModel), "CastSfx", MethodType.Getter)]
+class CastSfxMonster
+{
+ [HarmonyPrefix]
+ static bool Custom(MonsterModel __instance, ref string? __result)
+ {
+ if (__instance is not CustomMonsterModel customMon)
+ return true;
+
+ __result = customMon.CustomCastSfx;
+ return __result == null;
+ }
+}
+
+[HarmonyPatch(typeof(MonsterModel), "DeathSfx", MethodType.Getter)]
+class DeathSfxMonster
+{
+ [HarmonyPrefix]
+ static bool Custom(MonsterModel __instance, ref string? __result)
+ {
+ if (__instance is not CustomMonsterModel customMon)
+ return true;
+
+ __result = customMon.CustomDeathSfx;
+ return __result == null;
+ }
+}
\ No newline at end of file
diff --git a/Abstracts/CustomPetModel.cs b/Abstracts/CustomPetModel.cs
new file mode 100644
index 0000000..6dbd7e5
--- /dev/null
+++ b/Abstracts/CustomPetModel.cs
@@ -0,0 +1,39 @@
+using BaseLib.Utils;
+using MegaCrit.Sts2.Core.Animation;
+using MegaCrit.Sts2.Core.Bindings.MegaSpine;
+using MegaCrit.Sts2.Core.Entities.Creatures;
+using MegaCrit.Sts2.Core.Models;
+using MegaCrit.Sts2.Core.MonsterMoves.MonsterMoveStateMachine;
+using MegaCrit.Sts2.Core.Nodes.Combat;
+
+namespace BaseLib.Abstracts;
+
+public class CustomPetModel: CustomMonsterModel ,ICustomModel{
+ ///
+ /// Override this or place your scene at res://scenes/creature_visuals/class_name.tscn
+ ///
+ public new virtual string? CustomVisualPath => null;
+
+ public override int MinInitialHp => 9999;
+
+ public override int MaxInitialHp => 9999;
+
+ public override bool IsHealthBarVisible => false;
+
+ ///
+ /// By default, will convert a scene containing the necessary nodes into a NCreatureVisuals even if it is not one.
+ ///
+ ///
+ public virtual NCreatureVisuals? CreateCustomVisuals()
+ {
+ if (CustomVisualPath == null) return null;
+ return GodotUtils.CreatureVisualsFromScene(CustomVisualPath);
+ }
+
+ protected override MonsterMoveStateMachine GenerateMoveStateMachine()
+ {
+ MoveState nothingState = new MoveState("NOTHING_MOVE", (IReadOnlyList _) => Task.CompletedTask);
+ nothingState.FollowUpState = nothingState;
+ return new MonsterMoveStateMachine([nothingState], nothingState);
+ }
+}
\ No newline at end of file
diff --git a/Patches/Content/CustomAnimationPatch.cs b/Patches/Content/CustomAnimationPatch.cs
index 1f0fa47..b97d09d 100644
--- a/Patches/Content/CustomAnimationPatch.cs
+++ b/Patches/Content/CustomAnimationPatch.cs
@@ -3,58 +3,80 @@
using MegaCrit.Sts2.Core.Animation;
using MegaCrit.Sts2.Core.Nodes.Combat;
-namespace BaseLib.Patches.Content
+namespace BaseLib.Patches.Content;
+
+[HarmonyPatch(typeof(NCreature), nameof(NCreature.SetAnimationTrigger))]
+static class CustomAnimationPatch
{
- [HarmonyPatch(typeof(NCreature), nameof(NCreature.SetAnimationTrigger))]
- class CustomAnimationPatch
+ [HarmonyPrefix]
+ public static bool Prefix(NCreature __instance, string trigger)
{
- [HarmonyPrefix]
- public static bool Prefix(NCreature __instance, string trigger)
- {
- if (__instance.HasSpineAnimation) return true;
-
- var animPlayer = FindAnimationPlayer(__instance.Visuals);
- if (animPlayer == null) return false;
-
- var animName = trigger switch
- {
- CreatureAnimator.idleTrigger => "idle",
- CreatureAnimator.attackTrigger => "attack",
- CreatureAnimator.castTrigger => "cast",
- CreatureAnimator.hitTrigger => "hurt",
- CreatureAnimator.deathTrigger => "die",
- _ => trigger.ToLowerInvariant()
- };
-
- if (animPlayer.CurrentAnimation.Equals(animName) || animPlayer.CurrentAnimation.Equals(trigger))
- animPlayer.Stop();
+ if (__instance.HasSpineAnimation) return true;
- if (animPlayer.HasAnimation(animName))
- animPlayer.Play(animName);
- else if (animPlayer.HasAnimation(trigger))
- animPlayer.Play(trigger);
+ var animName = trigger switch
+ {
+ CreatureAnimator.idleTrigger => "idle",
+ CreatureAnimator.attackTrigger => "attack",
+ CreatureAnimator.castTrigger => "cast",
+ CreatureAnimator.hitTrigger => "hurt",
+ CreatureAnimator.deathTrigger => "die",
+ _ => trigger.ToLowerInvariant()
+ };
+
+ var visualNodeRoot = __instance.Visuals;
+ if (FindNode(visualNodeRoot)?.UseAnimationPlayer(animName, trigger) != null)
+ return false;
+ if (FindNode(visualNodeRoot)?.UseAnimatedSprite2D(animName, trigger) != null)
return false;
- }
- private static AnimationPlayer? FindAnimationPlayer(Node root)
- {
- return root.GetNodeOrNull("AnimationPlayer")
- ?? root.GetNodeOrNull("Visuals/AnimationPlayer")
- ?? root.GetNodeOrNull("Body/AnimationPlayer")
- ?? SearchRecursive(root);
- }
+ if (SearchRecursive(visualNodeRoot)?.UseAnimationPlayer(animName, trigger) != null)
+ return false;
+ if (SearchRecursive(visualNodeRoot)?.UseAnimatedSprite2D(animName, trigger) != null)
+ return false;
+
+ return true;
+ }
- private static AnimationPlayer? SearchRecursive(Node node)
+ private static AnimatedSprite2D UseAnimatedSprite2D(this AnimatedSprite2D animSprite, string animName, string trigger)
+ {
+ if (animSprite.SpriteFrames.HasAnimation(animName))
+ animSprite.Play(animName);
+ else if (animSprite.SpriteFrames.HasAnimation(trigger))
+ animSprite.Play(trigger);
+ return animSprite;
+ }
+
+ private static AnimationPlayer UseAnimationPlayer(this AnimationPlayer animPlayer, string animName, string trigger)
+ {
+ if (animPlayer.CurrentAnimation.Equals(animName) || animPlayer.CurrentAnimation.Equals(trigger))
+ animPlayer.Stop();
+
+ if (animPlayer.HasAnimation(animName))
+ animPlayer.Play(animName);
+ else if (animPlayer.HasAnimation(trigger))
+ animPlayer.Play(trigger);
+
+ return animPlayer;
+ }
+
+ private static T? FindNode(Node root, string? name = null) where T : Node?
+ {
+ name ??= nameof(T);
+ var n = root.GetNodeOrNull(name)
+ ?? root.GetNodeOrNull("Visuals/" + name)
+ ?? root.GetNodeOrNull("Body/" + name);
+ return n as T;
+ }
+
+ private static T? SearchRecursive(Node parent) where T : Node?
+ {
+ foreach (var child in parent.GetChildren())
{
- foreach (var child in node.GetChildren())
- {
- if (child is AnimationPlayer player) return player;
- var found = SearchRecursive(child);
- if (found != null) return found;
- }
- return null;
+ if (child is T nodeToFind) return nodeToFind;
+ var found = SearchRecursive(child);
+ if (found != null) return found;
}
-
+ return null;
}
-}
+}
\ No newline at end of file