Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
40f0a28
Add thread storm
BobbyTheCatfish Apr 13, 2026
57e0750
finalize logic and components for thread storm
BobbyTheCatfish Apr 13, 2026
c8ccfe5
Add silk spear
BobbyTheCatfish Apr 13, 2026
b8b1b64
sharp dart
BobbyTheCatfish Apr 14, 2026
9f59825
Add cross stitch
BobbyTheCatfish Apr 15, 2026
cffd612
Add other players to sonar
BobbyTheCatfish Apr 15, 2026
8305f36
Add rune rage
BobbyTheCatfish Apr 17, 2026
b2b60aa
Add trailing pale nails
BobbyTheCatfish Apr 17, 2026
6a0eba2
standardize component removal
BobbyTheCatfish Apr 17, 2026
39f780a
standardize object destruction
BobbyTheCatfish Apr 17, 2026
59996c4
Ensure fsm injections are applied to all copies of nails
BobbyTheCatfish Apr 17, 2026
238cd22
finish pale nails
BobbyTheCatfish Apr 18, 2026
84717d6
add double jump feathers
BobbyTheCatfish Apr 18, 2026
7ff68b9
fix post-spawn position
BobbyTheCatfish Apr 18, 2026
738bfc6
Add umbrella inflate effect
BobbyTheCatfish Apr 18, 2026
05a77b7
Fix thread storm scaling
BobbyTheCatfish Apr 19, 2026
02f3e71
fix pale nail animation interference and rapidfiring
BobbyTheCatfish Apr 19, 2026
d2148ad
Add sawtooth circlet
BobbyTheCatfish Apr 19, 2026
438bc1b
move movement effects to other branch
BobbyTheCatfish Apr 19, 2026
b116226
make silk spear stop short if needed
BobbyTheCatfish Apr 20, 2026
395076d
cleanup and docs
BobbyTheCatfish Apr 20, 2026
d7b5f78
more docs
BobbyTheCatfish Apr 20, 2026
e3401b9
linting!
BobbyTheCatfish Apr 20, 2026
43421a7
fix weird bug where audio wouldn't play
BobbyTheCatfish Apr 20, 2026
1580039
add damage customization
BobbyTheCatfish Apr 20, 2026
4a23848
Revert "move movement effects to other branch"
BobbyTheCatfish Apr 20, 2026
5cfae83
move circlet to its own effect
BobbyTheCatfish Apr 20, 2026
6ea97fb
Set up configurable damage
BobbyTheCatfish Apr 20, 2026
c2255fa
Add magnetite dice
BobbyTheCatfish Apr 21, 2026
077ec9d
Add flea brew
BobbyTheCatfish Apr 21, 2026
672f8fc
add fractured mask
BobbyTheCatfish Apr 21, 2026
98c3de5
add magma bell
BobbyTheCatfish Apr 22, 2026
f7faa3f
fix setting name
BobbyTheCatfish May 1, 2026
0f7c87b
Merge branch 'main' into tools-batch-1
BobbyTheCatfish May 1, 2026
a8e803a
fix removed comment
BobbyTheCatfish May 1, 2026
121ad3d
Undo accidental reverts
BobbyTheCatfish May 1, 2026
0d894d3
comments
BobbyTheCatfish May 1, 2026
1fc3cb6
fix style
BobbyTheCatfish May 1, 2026
ee216c8
Add component to identify damaging tools
BobbyTheCatfish May 1, 2026
fca8350
Fix formatting, comments, and naming
Extremelyd1 May 8, 2026
ecb50b3
Stop certain effects when benching
BobbyTheCatfish May 12, 2026
11e5679
split bell recharge
BobbyTheCatfish May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions SSMP/Animation/AnimationClip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -768,4 +768,9 @@ internal enum AnimationClip {
WitchTentacles,
ShamanCancel,
BindInterrupt,
MagnetiteDice,
FleaBrew,
FracturedMask,
MagmaBell,
Bench
}
53 changes: 52 additions & 1 deletion SSMP/Animation/AnimationEffect.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using SSMP.Game.Settings;
using SSMP.Internals;
using SSMP.Util;
using UnityEngine;

namespace SSMP.Animation;
Expand All @@ -25,7 +26,7 @@ public void SetServerSettings(ServerSettings serverSettings) {
}

/// <summary>
/// Locate the damages_enemy FSM and change the attack direction to the given direciton. This will ensure that
/// Locate the damages_enemy FSM and change the attack direction to the given direction. This will ensure that
/// enemies are getting knocked back in the correct direction from remote player's attacks.
/// </summary>
/// <param name="targetObject">The target GameObject to change.</param>
Expand All @@ -52,4 +53,54 @@ protected static void HidePlayer(GameObject playerObject) {
playerObject.GetComponent<tk2dSpriteAnimator>().Stop();
playerObject.GetComponent<tk2dSprite>().SetSprite("wall_puff0004");
}

/// <summary>
/// Gets the Effects object for a given player object. If the player object does not have an Effects object, it
/// will be created.
/// </summary>
/// <param name="playerObject">The player object for the player using the effect.</param>
/// <returns>The player's effects object.</returns>
protected static GameObject GetPlayerEffects(GameObject playerObject) {
var effects = playerObject.FindGameObjectInChildren("Effects");
if (effects == null) {
effects = new GameObject("Effects");
effects.transform.SetParentReset(playerObject.transform);
}

return effects;
}

/// <summary>
/// Attempts to get or create an effect from the Effects sub-object.
/// </summary>
/// <param name="playerObject">The player object for the player using the effect.</param>
/// <param name="effectName">The name of the effect object.</param>
/// <param name="effect">The effect, if found or created.</param>
/// <returns>True if created, false otherwise.</returns>
protected static bool TryGetEffect(GameObject playerObject, string effectName, out GameObject? effect) {
// Find or create effects for player
var effects = GetPlayerEffects(playerObject);

// Find existing effect
effect = effects.FindGameObjectInChildren(effectName);
if (effect != null) {
return false;
}

// Create new effect
var localEffects = HeroController.instance.gameObject.FindGameObjectInChildren("Effects");
if (localEffects == null) {
return false;
}

var localEffect = localEffects.FindGameObjectInChildren(effectName);
if (localEffect == null) {
return false;
}

effect = Object.Instantiate(localEffect, effects.transform);
effect.name = effectName;

return true;
}
}
213 changes: 177 additions & 36 deletions SSMP/Animation/AnimationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.Linq;
using HutongGames.PlayMaker.Actions;
using SSMP.Animation.Effects;
using SSMP.Animation.Effects.Movement;
using SSMP.Animation.Effects.SilkSkills;
using SSMP.Animation.Effects.Tools;
using SSMP.Collection;
using SSMP.Fsm;
using SSMP.Game;
Expand Down Expand Up @@ -621,9 +623,15 @@ internal class AnimationManager {
{ "Wound Double Strike", AnimationClip.WoundDoubleStrike },
{ "Wound Zap", AnimationClip.WoundZap },

{ "Bench", AnimationClip.Bench },

{ "Witch Tentacles!", AnimationClip.WitchTentacles },
{ "Shaman Cancel", AnimationClip.ShamanCancel },
{ "Bind Fail Burst", AnimationClip.BindInterrupt }
{ "Bind Fail Burst", AnimationClip.BindInterrupt },
{ "Magnetite Dice", AnimationClip.MagnetiteDice },
{ "Flea Brew", AnimationClip.FleaBrew },
{ "Fractured Mask", AnimationClip.FracturedMask },
{ "Magma Bell", AnimationClip.MagmaBell }
};

/// <summary>
Expand Down Expand Up @@ -662,6 +670,8 @@ internal class AnimationManager {
{ AnimationClip.BindBurstAir, BindBurst.Instance },
{ AnimationClip.RageBindBurst, BindBurst.Instance },
{ AnimationClip.Death, new Death() },
{ AnimationClip.DoubleJump, new FaydownCloak() },
{ AnimationClip.UmbrellaInflate, new DriftersCloak() },

// Silk Skills
{ AnimationClip.NeedleThrowThrowing, new SilkSpear() },
Expand All @@ -679,9 +689,18 @@ internal class AnimationManager {
{ AnimationClip.WitchTentacles, BindBurst.Instance },
{ AnimationClip.ShamanCancel, new Bind { BindState = Bind.State.ShamanCancel } },
{ AnimationClip.BindInterrupt, BindInterrupt.Instance },
{ AnimationClip.Bench, new Bench() },

// Silk Skills
{ AnimationClip.AirSphereRefresh, new ThreadStorm() },
{ AnimationClip.SilkBombLocations, new RuneRage() },
{ AnimationClip.SilkBossNeedleFire, new PaleNails() }
{ AnimationClip.SilkBossNeedleFire, new PaleNails() },

// Tools
{ AnimationClip.MagnetiteDice, new MagnetiteDice() },
{ AnimationClip.FleaBrew, FleaBrew.Instance },
{ AnimationClip.FracturedMask, new FracturedMask() },
{ AnimationClip.MagmaBell, new MagmaBell() }
};

/// <summary>
Expand Down Expand Up @@ -814,6 +833,7 @@ public void RegisterHooks() {
CreateHeroHooks(HeroController.instance);
}

EventHooks.UseLavaBell += OnMagmaBell;

// Register a callback so we know when the dash has finished
// On.HeroController.CancelDash += HeroControllerOnCancelDash;
Expand All @@ -838,6 +858,17 @@ public void DeregisterHooks() {

HeroController.OnHeroInstanceSet -= CreateHeroHooks;
FsmStateActionInjector.UninjectAll();

MagnetiteDice.Unhook();

EventHooks.HeroControllerDie -= OnDeath;
EventHooks.UseLavaBell -= OnMagmaBell;

// Remove listener for benching
var eventRegister = HeroController.SilentInstance?.gameObject.GetComponents<EventRegister>().FirstOrDefault(r => r.SubscribedEvent == "BENCHREST START");
if (eventRegister) {
eventRegister.ReceivedEvent -= OnBench;
}
// On.HeroAnimationController.Play -= HeroAnimationControllerOnPlay;
// On.HeroAnimationController.PlayFromFrame -= HeroAnimationControllerOnPlayFromFrame;

Expand Down Expand Up @@ -1099,6 +1130,9 @@ private void CreateHeroHooks(HeroController hc) {
bellFsm.Init();
}

CreateSkillHooks();
CreateToolHooks();

// Find bind FSM
var heroFsms = hc.GetComponents<PlayMakerFSM>();

Expand All @@ -1117,8 +1151,53 @@ private void CreateHeroHooks(HeroController hc) {
Logger.Warn("Unable to find Bind FSM to hook.");
}

// Add listener for benching
var eventRegister = hc.gameObject.GetComponents<EventRegister>().FirstOrDefault(r => r.SubscribedEvent == "BENCHREST START");
if (eventRegister) {
eventRegister.ReceivedEvent += OnBench;
}
}

/// <summary>
/// Animation subanimation hook for the Witch Tentacles FSM state.
/// </summary>
private void OnWitchTentacles(PlayMakerFSM fsm) {
var dummyClip = new tk2dSpriteAnimationClip {
name = "Witch Tentacles!",
wrapMode = tk2dSpriteAnimationClip.WrapMode.Once
};
OnAnimationEvent(dummyClip);
}

/// <summary>
/// Animation subanimation hook for the Shaman Air Cancel FSM state.
/// </summary>

private void OnShamanCancel(PlayMakerFSM fsm) {
var dummyClip = new tk2dSpriteAnimationClip {
name = "Shaman Cancel",
wrapMode = tk2dSpriteAnimationClip.WrapMode.Once
};
OnAnimationEvent(dummyClip);
}

/// <summary>
/// Animation subanimation hook for interrupted binds.
/// </summary>
private void OnBindInterrupt(PlayMakerFSM fsm) {
var dummyClip = new tk2dSpriteAnimationClip {
name = "Bind Fail Burst",
wrapMode = tk2dSpriteAnimationClip.WrapMode.Once
};
OnAnimationEvent(dummyClip);
}

/// <summary>
/// Creates hooks for silk skills.
/// </summary>
private void CreateSkillHooks() {
// Silk skill injections
var silkSkillFsm = hc.silkSpecialFSM;
var silkSkillFsm = HeroController.instance.silkSpecialFSM;
if (silkSkillFsm == null) {
Logger.Warn("Unable to find Silk Skill FSM to hook.");
return;
Expand Down Expand Up @@ -1191,39 +1270,6 @@ private void CreateHeroHooks(HeroController hc) {
}
}

/// <summary>
/// Animation subanimation hook for the Witch Tentacles FSM state.
/// </summary>
private void OnWitchTentacles(PlayMakerFSM fsm) {
var dummyClip = new tk2dSpriteAnimationClip {
name = "Witch Tentacles!",
wrapMode = tk2dSpriteAnimationClip.WrapMode.Once
};
OnAnimationEvent(dummyClip);
}

/// <summary>
/// Animation subanimation hook for the Shaman Air Cancel FSM state.
/// </summary>
private void OnShamanCancel(PlayMakerFSM fsm) {
var dummyClip = new tk2dSpriteAnimationClip {
name = "Shaman Cancel",
wrapMode = tk2dSpriteAnimationClip.WrapMode.Once
};
OnAnimationEvent(dummyClip);
}

/// <summary>
/// Animation subanimation hook for interrupted binds.
/// </summary>
private void OnBindInterrupt(PlayMakerFSM fsm) {
var dummyClip = new tk2dSpriteAnimationClip {
name = "Bind Fail Burst",
wrapMode = tk2dSpriteAnimationClip.WrapMode.Once
};
OnAnimationEvent(dummyClip);
}

/// <summary>
/// Animation subanimation hook for extending a thread storm.
/// </summary>
Expand Down Expand Up @@ -1409,6 +1455,101 @@ private void OnPaleNailFire(PlayMakerFSM fsm) {
_netClient.UpdateManager.UpdatePlayerAnimation(AnimationClip.SilkBossNeedleFire, 0, effectInfo);
}

/// <summary>
/// Creates hooks for tools.
/// </summary>
private void CreateToolHooks() {
MagnetiteDice.Hook(OnDiceEnable);
MagmaBell.HookRecharge(OnMagmaBellRecharge);

var toolFsm = HeroController.instance.toolsFSM;
var brewBurst = toolFsm.GetState("Flea Brew Burst");
FsmStateActionInjector.Inject(brewBurst, OnFleaBrew, 0, "Flea Brew");

var maskFsm = HeroController.instance.gameObject
.FindGameObjectInChildren("Charm Effects")?
.FindGameObjectInChildren("Fractured Mask Break")?
.LocateMyFSM("Spawn Effect");

if (maskFsm) {
var maskEffect = maskFsm.GetState("Instantiate Effect");
FsmStateActionInjector.Inject(maskEffect, OnFracturedMaskBreak, 0, "Fractured Mask Break");
}
}

/// <summary>
/// Hook for the magnetite dice being triggered.
/// </summary>
private void OnDiceEnable() {
// If we are not connected, there is nothing to send to
if (!_netClient.IsConnected) {
return;
}

_netClient.UpdateManager.UpdatePlayerAnimation(AnimationClip.MagnetiteDice);
}

/// <summary>
/// Hook for the flea brew being triggered.
/// </summary>
private void OnFleaBrew(PlayMakerFSM fsm) {
// If we are not connected, there is nothing to send to
if (!_netClient.IsConnected) {
return;
}

var effectInfo = FleaBrew.Instance.GetEffectInfo();
_netClient.UpdateManager.UpdatePlayerAnimation(AnimationClip.FleaBrew, 0, effectInfo);
}

/// <summary>
/// Hook for the fractured mask being triggered.
/// </summary>
private void OnFracturedMaskBreak(PlayMakerFSM fsm) {
// If we are not connected, there is nothing to send to
if (!_netClient.IsConnected) {
return;
}

_netClient.UpdateManager.UpdatePlayerAnimation(AnimationClip.FracturedMask);
}

/// <summary>
/// Hook for the magma bell being triggered.
/// </summary>
private void OnMagmaBell() {
// If we are not connected, there is nothing to send to
if (!_netClient.IsConnected) {
return;
}

_netClient.UpdateManager.UpdatePlayerAnimation(AnimationClip.MagmaBell, 0, [0]);
}

/// <summary>
/// Hook for the magma bell being triggered.
/// </summary>
private void OnMagmaBellRecharge() {
// If we are not connected, there is nothing to send to
if (!_netClient.IsConnected) {
return;
}

_netClient.UpdateManager.UpdatePlayerAnimation(AnimationClip.MagmaBell, 0, [1]);
}

/// <summary>
/// Hook for resting on a bench.
/// </summary>
private void OnBench() {
// If we are not connected, there is nothing to send to
if (!_netClient.IsConnected) {
return;
}

_netClient.UpdateManager.UpdatePlayerAnimation(AnimationClip.Bench);
}

// /// <summary>
// /// Callback method on the HeroAnimationController#Play method.
// /// </summary>
Expand Down
Loading
Loading