From 893b81d314e47ef35746a79fcf08b7f079758959 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:29:28 -0500 Subject: [PATCH 01/33] Add missing animations --- SSMP/Animation/AnimationClip.cs | 363 +++++++++++++++++++++++++++-- SSMP/Animation/AnimationManager.cs | 348 +++++++++++++++++++++++++-- 2 files changed, 684 insertions(+), 27 deletions(-) diff --git a/SSMP/Animation/AnimationClip.cs b/SSMP/Animation/AnimationClip.cs index 0b0c350..d53f2f1 100644 --- a/SSMP/Animation/AnimationClip.cs +++ b/SSMP/Animation/AnimationClip.cs @@ -12,21 +12,62 @@ internal enum AnimationClip { SitLook2, SitLook3, SitLook4, + SitLean, + SitLookLeft, + SittingAsleep, GetOff, + Land, + HardLand, + HardLandGreymoor, + HardLandQuick, + SuperHardLand, + LandToSprint, + + Hop, + HopLand, + HopToSomersault, + Run, RunQ, RunToIdle, + RunToIdleC, + Idle, + IdleBackward, + IdleBG, + IdleHurtListen, + IdleHurtListenBackward, + IdleHurtListenWindy, + IdleHurtNoNeedle, + IdleHurtTalk, + IdleHurtTalkBackward, + IdleHurtTalkTurnBackward, + IdleHurtTalkTurnForward, + IdleHurtTalkWindy, + IdleHurtWindy, + IdleSlap, + IdleToRunShort, + IdleToRunWeak, + IdleWindy, IdleToRun, + WalkToIdle, RunToWalk, + RunToWalkWeak, LandToWalk, Turn, Dash, DashDown, DashDownLand, + DashDownEnd, + //DashStabEffect, + //DashStabEffectGlow, SprintLv2, + SprintLv3, + SprintRecoil, + SprintSkidToRun, + SprintToRun, MantleCling, MantleLand, MantleLandToIdle, @@ -38,10 +79,13 @@ internal enum AnimationClip { SprintAir, SprintAirLoop, DoubleJump, + DoubleJumpWings2, LandToRun, DashAttackAntic, DashAttack, DashAttackRecover, + DashAttackAnticLong, + DashAttackAnticShort, SkidEnd1, SkidEnd2, DashToIdle, @@ -53,6 +97,7 @@ internal enum AnimationClip { SlashLandRunAlt, DownSpikeAntic, DownSpike, + DownSpikeBurst, DownSpikeRecovery, DownSpikeRecoveryLand, DownSpikeFollowup, @@ -65,20 +110,62 @@ internal enum AnimationClip { SlashChargedLoop, UmbrellaInflateAntic, WallJump, + WallJumpAntic, + WallJumpPuff, + WallJumpSomersault, + Wallrun, + WallrunAntic, WallSlide, + SlideBrake, + SlideBrakeEnd, + SlideFast, + SlideFastEnd, + SlideNormal, + SlideStart, + WallCling, SitFallAsleep, + UmbrellaInflate, UmbrellaFloat, UmbrellaDeflate, + UmbrellaTurn, + IdleRest, MantleLandToRun, Sit, Sprint, SprintBackflip, + + AirSphere, AirSphereAntic, + AirSphereDissipate, + AirSphereRepeatAntic, + SilkBossNeedleCast, + Taunt, + TauntBack, + TauntBackEnd, + TauntBackUp, + TauntEndtoIdle, + TauntIdle, + TauntRings, + TauntRingsFlash, + TauntStraightBack, + TauntStraightBackQ, + TauntThread, + ChallengeStrong, + ChallengeTalk, + ToChallengeTalk, + ChallengeTalkEnd, + ChallengeTalkEndToIdle, + ChallengeTalkEndToTalk, + ChallengeTalkIdle, + ChallengeTalkIdleStart, + ChallengeTalkStart, + ChallengeStrongToIdle, + NeedolinStart, NeedolinStartCancelable, NeedolinEnd, @@ -86,25 +173,63 @@ internal enum AnimationClip { NeedolinSitStart, NeedolinSitPlay, NeedolinSitEnd, + NeedolinPlayLowTransition, + NeedolinPlayLow, + NeedolinPlayHighTransition, + NeedolinPlayHigh, + NeedolinDeepEnd, + NeedolinPromptedIdle, + NeedolinPromptedPlayEnd, + NeedolinPromptedPlayStart, + NeedolinPromptedStart, + NeedolinThread, + NeedolinTurn, + NeedolinSitTurn, + HarpoonAntic, HarpoonThrow, HarpoonCatch, - SilkChargeEnd, + HarpoonCatchBack, + HarpoonDash, + HarpoonNeedle, + HarpoonNeedleReturn, + HarpoonNeedleWallHit, + HarpoonThread, + MapOpen, MapIdle, MapAway, + MapTurn, + MapUpdate, + MapWalk, SitMapOpen, SitMapClose, + BindChargeGround, BindChargeAir, BindBurstAir, BindBurstGround, + BindCursedEnd, + BindCursedMid, + BindCursedStart, + BindFirstEnd, + BindFirstStand, + BindFlash, + BindSilk, + BindSilkFirstWeaver, + BindSilkLoop, + BindSilkQuick, + BindCancelAir, + BindCancelGround, + BindChargeGrabNeedle, + ReserveBindBurstAir, + ReserveBindBurstGround, + ReserveBindChargeAir, + ReserveBindChargeGround, + LookDown, LookDownEnd, - NeedolinPlayLowTransition, - NeedolinPlayLow, - NeedolinPlayHighTransition, - NeedolinPlayHigh, + SuperJumpAntic, SuperJumpThrow, SuperJumpThrowWait, @@ -112,13 +237,28 @@ internal enum AnimationClip { SuperJumpLoop, SuperJumpHitRoof, SuperJumpFall, + SuperJumpAnticCancel, + SuperJumpAnticEffect, + SuperJumpAnticEffectEnd, + SuperJumpCatchCancel, + SuperJumpHitRoofQ, + SuperJumpLoopCancel, + SuperJumpThread, + SuperJumpThreadLoop, + + Fall, + FallToProstrate, + LookUp, LookUpEnd, + SurfaceIn, SurfaceInToIdle, SurfaceIdle, SurfaceIdleToSwim, SurfaceTurnToSwim, + SurfaceCurrentInRecover, + SurfaceCurrentInTumble, SwimDash, SwimDashTurn, SwimDashBonk, @@ -126,14 +266,21 @@ internal enum AnimationClip { MantleCancelToJumpBackwards, WallScrambleAntic, WallScramble, + WallScrambleMantle, + WallScrambleQuickened, + WallScrambleRepeat, WallScrambleEnd, WallSlash, - WallJumpSomersault, + SomersaultPinDrop, Airborne, /// /// Sprinting into a wall /// + Bonked, SprintBonk, + BonkedFast, + BonkedLand, + SlashLandRun, /// /// Down slash with Reaper crest @@ -321,6 +468,12 @@ internal enum AnimationClip { NeedleThrowAnticA, NeedleThrowThrowing, NeedleThrowCatch, + NeedleThrowBurst, + NeedleThrowOut, + NeedleThrowReturn, + NeedleThrowReturnShort, + NeedleThrowThread, + NeedleThrowThunk, /// /// Cross Stitch in air /// @@ -331,6 +484,18 @@ internal enum AnimationClip { ParryStanceGround, ParryRecover, ParryRecoverGround, + ParryClash, + ParryClashEffect, + ParryDash, + ParryDashBurst, + ParryReady, + ParryRecoverySkid, + ParryStanceFlash, + ParryStanceFlashQ, + ParryThread, + GetParryDash, + GetParryEnd, + GetParryPrepare, /// /// Thread Storm /// @@ -339,24 +504,32 @@ internal enum AnimationClip { /// /// Sharpdart /// - SilkChargeAntic, SilkCharge, + SilkChargeAntic, + SilkChargeAnticZap, SilkChargeRecover, + SilkChargeRecoverZap, + SilkChargeZap, + SilkChargeEnd, /// /// Rune Rage /// SilkBombAntic, + SilkBombAnticQ, SilkBombLoop, SilkBombRecover, SitCraft, + SitCraftSilk, ToolThrowUp, /// /// Tools thrown forwards such as Straight Pin /// ToolThrowQ, ToolThrowAltQ, - + ToolThrowM, + ToolThrowWall, + /// /// Moving when exiting Delver's Drill attack /// @@ -366,53 +539,215 @@ internal enum AnimationClip { /// DownSpikeBounce1, DownSpikeBounce2, - /// /// Using Flea Brew /// ChargeUp, - + ChargeUpAir, + ChargeUpBench, + ChargeUpBenchSilk, + ChargeUpBurst, + IdleHurt, TurnWalk, + TurnBackThreeQuarter, + TurnBackThreeQuarterEndToIdle, + TurnHeadBackward, + TurnHeadForward, + TurnQuick, + TurnFromBG, + TurnFromBGLoop, + TurnToBG, + TurnToBGLoop, + TurnToChallengeIdle, + TurnToChallengeStrong, + TurnToChallengeTalk, + TurnToFG, + TurnToIdle, + + Walk, + WalkQ, LookUpHalf, LookUpHalfEnd, LookDownSlight, LookDownSlightEnd, - + LookDownTalk, + LookUpHalfFlinch, + LookUpHalfTalk, + LookUpTalk, + LookDownUpdraft, + LookDownWindy, + LookDownEndUpdraft, + LookDownEndWindy, + LookDownToIdle, + LookingUp, + LookUpUpdraft, + LookUpWindy, + LookUpEndUpdraft, + LookUpEndWindy, + LookUpToIdle, + ScuttleStart, ScuttleLoop, + ScuttleTurnToLoop, ScuttleEnd, ScuttleFall, ScuttleVault, - + ScuttleJump, + ScuttleClimb, + ScuttleClimbEnd, + Stun, Recoil, GrabEscape, IdleUpdraft, + UpdraftAntic, UpdraftAnticDJ, UpdraftRise, UpdraftRiseTurn, + UpdraftEnd, + UpdraftIdle, + UpdraftShoot, ProstrateRiseNoNeedle, BindFirst, BindBurstFirst, ThwipToIdle, - + GetUpToIdle, + WeakenedStun, WeakRiseToIdle, WeakWalk, WeakTryJumpAntic, WeakTryAttack, WeakenedStunEnd, - + WeakFall, + WeakFlinchToIdle, + WeakIdleLookUp, + WeakWalkFaster, + WeakWalkToIdle, + TauntCollapse1, TauntCollapse2, TauntCollapseHit, + Prostrate, ProstrateRise, + ProstrateNoNeedle, + ProstrateRiseSlow, + ProstrateRiseToKneel, + ProstrateRiseToKneelNoLoop, + ProstrateRiseToWound, + TalkingStandard, + TalkingBackward, + + AbyssKneel, + AbyssKneelBackIdle, + AbyssKneelBackTalk, + AbyssKneelIdle, + AbyssKneeltoStand, + AbyssKneelTurnBack, + KneelToProstrate, + Kneeling, + + AcidDeath, + Death, + DeathFinal, + SpikeDeath, + SpikeDeathAntic, + + BeastlingCallFail, + BeastlingCallFailWindy, + BellwayCall, + FastTravelCall, + FastTravelChildArrive, + FastTravelFail, + FastTravelLeap, + + CollectHeartPiece, + CollectHeartPieceEnd, + CollectMemoryOrb, + CollectNormal1, + CollectNormal1Q, + CollectNormal2, + CollectNormal3, + CollectNormal3Q, + CollectSilkHeart, + CollectStand1, + CollectStand2, + CollectStand3, + CollectToWound, + + DropToWounded, + BlueHealthOverBurst, + CrestShrinePowerupLoop, + HazardRespawn, + RespawnWake, + PodBounce, + QuickCharged, + QuickCraftSilk, + SprintmasterLow, + SprintmasterStart, + + DressFlourish, + GiveDress, + GiveDressIdle, + + Enter, + Exit, + ExitDoorToIdle, + ExitToIdle, + Wake, + WakeUpGround, + + HurtListenDown, + HurtListenUp, + HurtLookDown, + HurtLookDownWindy, + HurtLookDownWindyEnd, + HurtLookUp, + HurtLookUpEnd, + HurtLookUpWindy, + HurtLookUpWindyEnd, + HurtTalkDown, + HurtTalkUp, + HurtTalkUpWindy, + HurtToIdle, + + NeedleFall, + NeedleLand, + + RingDropImpact, + RingEject, + RingGrabHornet, + RingGrabRail, + RingHarpoonConnect, + RingLookDown, + RingLookDownEnd, + RingLookUp, + RingLookUpEnd, + RingTurn, + + RoarLock, + RoarToLookUp, + + SpaSurfaceIdle, + SpaSurfaceIdleToSwim, + SpaSurfaceIn, + SpaSurfaceInToIdle, + SpaSurfaceTurnToSwim, + + WeaverPray, + WeaverPrayEnd, + WeaverPrayPrepare, + WeaverPrayPrepareFront, + + Wound, + WoundDoubleStrike, + WoundZap, // Custom clip names DashEnd, diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 919c667..1f00a6f 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -57,21 +57,58 @@ internal class AnimationManager { { "SitLook 2", AnimationClip.SitLook2 }, { "SitLook 3", AnimationClip.SitLook3 }, { "SitLook 4", AnimationClip.SitLook4 }, + { "Sit Lean", AnimationClip.SitLean }, + { "Sit Look Left", AnimationClip.SitLookLeft }, + { "Sitting Asleep", AnimationClip.SittingAsleep }, { "Get Off", AnimationClip.GetOff }, { "Land", AnimationClip.Land }, + { "HardLand", AnimationClip.HardLand }, + { "HardLand Greymoor", AnimationClip.HardLandGreymoor }, + { "HardLand Quick", AnimationClip.HardLandQuick }, + { "Super Hard Land", AnimationClip.SuperHardLand }, + { "Hop", AnimationClip.Hop }, + { "Hop Land", AnimationClip.HopLand }, + { "Hop To Somersault", AnimationClip.HopToSomersault }, { "Run", AnimationClip.Run }, { "RunQ", AnimationClip.RunQ }, { "Run To Idle", AnimationClip.RunToIdle }, + { "Run To Idle C", AnimationClip.RunToIdleC }, { "Idle", AnimationClip.Idle }, + { "Idle Backward", AnimationClip.IdleBackward}, + { "Idle BG", AnimationClip.IdleBG}, + { "Idle Hurt", AnimationClip.IdleHurt}, + { "Idle Hurt Listen", AnimationClip.IdleHurtListen}, + { "Idle Hurt Listen Backward", AnimationClip.IdleHurtListenBackward}, + { "Idle Hurt Listen Windy", AnimationClip.IdleHurtListenWindy}, + { "Idle Hurt NoNeedle", AnimationClip.IdleHurtNoNeedle}, + { "Idle Hurt Talk", AnimationClip.IdleHurtTalk}, + { "Idle Hurt Talk Backward", AnimationClip.IdleHurtTalkBackward}, + { "Idle Hurt Talk Turn Backward", AnimationClip.IdleHurtTalkTurnBackward}, + { "Idle Hurt Talk Turn Forward", AnimationClip.IdleHurtTalkTurnForward}, + { "Idle Hurt Talk Windy", AnimationClip.IdleHurtTalkWindy}, + { "Idle Hurt Windy", AnimationClip.IdleHurtWindy}, + { "Idle Slap", AnimationClip.IdleSlap}, + { "Idle To Run Short", AnimationClip.IdleToRunShort}, + { "Idle To Run Weak", AnimationClip.IdleToRunWeak}, { "Idle To Run", AnimationClip.IdleToRun }, + { "Idle Windy", AnimationClip.IdleWindy }, { "Walk To Idle", AnimationClip.WalkToIdle }, { "Run To Walk", AnimationClip.RunToWalk }, + { "Run To Walk Weak", AnimationClip.RunToWalkWeak }, { "Land To Walk", AnimationClip.LandToWalk }, + { "Land to Sprint", AnimationClip.LandToSprint }, { "Turn", AnimationClip.Turn }, { "Dash", AnimationClip.Dash }, { "Dash Down", AnimationClip.DashDown }, { "Dash Down Land", AnimationClip.DashDownLand }, + { "Dash Down End", AnimationClip.DashDownEnd }, + //{ "DashStabEffect", AnimationClip.DashStabEffect }, + //{ "DashStabEffect_Glow", AnimationClip.DashStabEffectGlow }, { "Sprint Lv2", AnimationClip.SprintLv2 }, + { "Sprint Lv3", AnimationClip.SprintLv3 }, + { "Sprint Recoil", AnimationClip.SprintRecoil }, + { "Sprint Skid To Run", AnimationClip.SprintSkidToRun }, + { "Sprint To Run", AnimationClip.SprintToRun }, { "Mantle Cling", AnimationClip.MantleCling }, { "Mantle Land", AnimationClip.MantleLand }, { "Mantle Land To Idle", AnimationClip.MantleLandToIdle }, @@ -83,10 +120,13 @@ internal class AnimationManager { { "Sprint Air", AnimationClip.SprintAir }, { "Sprint Air Loop", AnimationClip.SprintAirLoop }, { "Double Jump", AnimationClip.DoubleJump }, + { "Double Jump Wings 2", AnimationClip.DoubleJumpWings2 }, { "Land To Run", AnimationClip.LandToRun }, { "Dash Attack Antic", AnimationClip.DashAttackAntic }, { "Dash Attack", AnimationClip.DashAttack }, { "Dash Attack Recover", AnimationClip.DashAttackRecover }, + { "Dash Attack Antic Long", AnimationClip.DashAttackAnticLong }, + { "Dash Attack Antic Short", AnimationClip.DashAttackAnticShort }, { "Skid End 1", AnimationClip.SkidEnd1 }, { "Skid End 2", AnimationClip.SkidEnd2 }, { "Dash To Idle", AnimationClip.DashToIdle }, @@ -98,6 +138,7 @@ internal class AnimationManager { { "Slash Land Run Alt", AnimationClip.SlashLandRunAlt }, { "DownSpike Antic", AnimationClip.DownSpikeAntic }, { "DownSpike", AnimationClip.DownSpike }, + { "DownSpike Burst", AnimationClip.DownSpikeBurst }, { "Downspike Recovery", AnimationClip.DownSpikeRecovery }, { "Downspike Recovery Land", AnimationClip.DownSpikeRecoveryLand }, { "Downspike Followup", AnimationClip.DownSpikeFollowup }, @@ -107,46 +148,118 @@ internal class AnimationManager { { "Slash_Charged_Loop", AnimationClip.SlashChargedLoop }, { "Umbrella Inflate Antic", AnimationClip.UmbrellaInflateAntic }, { "Walljump", AnimationClip.WallJump }, + { "Walljump Antic", AnimationClip.WallJumpAntic }, + { "Walljump Puff", AnimationClip.WallJumpPuff }, + { "Walljump Somersault", AnimationClip.WallJumpSomersault }, + { "Wallrun", AnimationClip.Wallrun }, + { "Wallrun Antic", AnimationClip.WallrunAntic }, { "Wall Slide", AnimationClip.WallSlide }, { "Wall Cling", AnimationClip.WallCling }, + { "Slide Brake", AnimationClip.SlideBrake }, + { "Slide Brake End", AnimationClip.SlideBrakeEnd }, + { "Slide Fast", AnimationClip.SlideFast }, + { "Slide Fast End", AnimationClip.SlideFastEnd }, + { "Slide Normal", AnimationClip.SlideNormal }, + { "Slide Start", AnimationClip.SlideStart }, { "Sit Fall Asleep", AnimationClip.SitFallAsleep }, { "Umbrella Inflate", AnimationClip.UmbrellaInflate }, { "Umbrella Float", AnimationClip.UmbrellaFloat }, { "Umbrella Deflate", AnimationClip.UmbrellaDeflate }, + { "Umbrella Turn", AnimationClip.UmbrellaTurn }, { "Idle Rest", AnimationClip.IdleRest }, { "Mantle Land To Run", AnimationClip.MantleLandToRun }, { "Sit", AnimationClip.Sit }, { "Sprint", AnimationClip.Sprint }, { "Sprint Backflip", AnimationClip.SprintBackflip }, + { "AirSphere", AnimationClip.AirSphere }, { "AirSphere Antic", AnimationClip.AirSphereAntic }, + { "AirSphere Dissipate", AnimationClip.AirSphereDissipate }, + { "AirSphere RepeatAntic", AnimationClip.AirSphereRepeatAntic }, { "Silk Boss Needle Cast", AnimationClip.SilkBossNeedleCast }, { "Taunt", AnimationClip.Taunt }, + { "Taunt Back", AnimationClip.TauntBack }, + { "Taunt Back End", AnimationClip.TauntBackEnd }, + { "Taunt Back Up", AnimationClip.TauntBackUp }, + { "Taunt End to Idle", AnimationClip.TauntEndtoIdle }, + { "Taunt Idle", AnimationClip.TauntIdle }, + { "Taunt Rings", AnimationClip.TauntRings }, + { "Taunt Rings Flash", AnimationClip.TauntRingsFlash }, + { "Taunt Straight Back", AnimationClip.TauntStraightBack }, + { "Taunt Straight Back Q", AnimationClip.TauntStraightBackQ }, + { "Taunt Thread", AnimationClip.TauntThread }, + { "Challenge Strong", AnimationClip.ChallengeStrong }, + { "Challenge Talk", AnimationClip.ChallengeTalk }, + { "ToChallengeTalk", AnimationClip.ToChallengeTalk }, + { "Challenge Talk End", AnimationClip.ChallengeTalkEnd }, + { "Challenge Talk End ToIdle", AnimationClip.ChallengeTalkEndToIdle }, + { "Challenge Talk End ToTalk", AnimationClip.ChallengeTalkEndToTalk }, + { "Challenge Talk Idle", AnimationClip.ChallengeTalkIdle }, + { "Challenge Talk Idle Start", AnimationClip.ChallengeTalkIdleStart }, + { "Challenge Talk Start", AnimationClip.ChallengeTalkStart }, + { "ChallengeStrongToIdle", AnimationClip.ChallengeStrongToIdle }, + { "Needolin Start", AnimationClip.NeedolinStart }, { "Needolin StartCancelable", AnimationClip.NeedolinStartCancelable }, { "Needolin End", AnimationClip.NeedolinEnd }, { "Needolin Play", AnimationClip.NeedolinPlay }, { "NeedolinSit Start", AnimationClip.NeedolinSitStart }, { "NeedolinSit Play", AnimationClip.NeedolinSitPlay }, + { "Needolin Play Low", AnimationClip.NeedolinPlayLow }, + { "Needolin Play Low Transition", AnimationClip.NeedolinPlayLowTransition }, + { "Needolin Play High", AnimationClip.NeedolinPlayHigh }, + { "Needolin Play High Transition", AnimationClip.NeedolinPlayHighTransition }, { "NeedolinSit End", AnimationClip.NeedolinSitEnd }, + { "NeedolinSit Turn", AnimationClip.NeedolinSitTurn }, + { "Needolin Deep End", AnimationClip.NeedolinDeepEnd }, + { "Needolin Prompted Idle", AnimationClip.NeedolinPromptedIdle }, + { "Needolin Prompted Play End", AnimationClip.NeedolinPromptedPlayEnd }, + { "Needolin Prompted Play Start", AnimationClip.NeedolinPromptedPlayStart }, + { "Needolin Prompted Start", AnimationClip.NeedolinPromptedStart }, + { "Needolin Thread", AnimationClip.NeedolinThread }, + { "Needolin Turn", AnimationClip.NeedolinTurn }, { "Harpoon Antic", AnimationClip.HarpoonAntic }, { "Harpoon Throw", AnimationClip.HarpoonThrow }, { "Harpoon Catch", AnimationClip.HarpoonCatch }, + { "Harpoon Catch Back", AnimationClip.HarpoonCatchBack }, + { "Harpoon Dash", AnimationClip.HarpoonDash }, + { "Harpoon Needle", AnimationClip.HarpoonNeedle }, + { "Harpoon Needle Return", AnimationClip.HarpoonNeedleReturn }, + { "Harpoon Needle Wall Hit", AnimationClip.HarpoonNeedleWallHit }, + { "Harpoon Thread", AnimationClip.HarpoonThread }, + { "Silk Charge End", AnimationClip.SilkChargeEnd }, { "Map Open", AnimationClip.MapOpen }, { "Map Idle", AnimationClip.MapIdle }, { "Map Away", AnimationClip.MapAway }, + { "Map Turn", AnimationClip.MapTurn }, + { "Map Update", AnimationClip.MapUpdate }, + { "Map Walk", AnimationClip.MapWalk }, { "Sit Map Open", AnimationClip.SitMapOpen }, { "Sit Map Close", AnimationClip.SitMapClose }, { "BindCharge Ground", AnimationClip.BindChargeGround }, { "BindCharge Air", AnimationClip.BindChargeAir }, { "BindBurst Air", AnimationClip.BindBurstAir }, { "BindBurst Ground", AnimationClip.BindBurstGround }, + { "Reserve BindBurst Air", AnimationClip.ReserveBindBurstAir }, + { "Reserve BindBurst Ground", AnimationClip.ReserveBindBurstGround }, + { "Bind Cursed End", AnimationClip.BindCursedEnd }, + { "Bind Cursed Start", AnimationClip.BindCursedStart }, + { "Bind Cursed Mid", AnimationClip.BindCursedMid }, + { "Bind First End", AnimationClip.BindFirstEnd }, + { "Bind First Stand", AnimationClip.BindFirstStand }, + { "Bind Flash", AnimationClip.BindFlash }, + { "Bind Silk", AnimationClip.BindSilk }, + { "Bind Silk FirstWeaver", AnimationClip.BindSilkFirstWeaver }, + { "Bind Silk Loop", AnimationClip.BindSilkLoop }, + { "Bind Silk Quick", AnimationClip.BindSilkQuick }, + { "BindCancel Air", AnimationClip.BindCancelAir }, + { "BindCancel Ground", AnimationClip.BindCancelGround }, + { "BindCharge GrabNeedle", AnimationClip.BindChargeGrabNeedle }, + { "Reserve BindCharge Air", AnimationClip.ReserveBindChargeAir }, + { "Reserve BindCharge Ground", AnimationClip.ReserveBindChargeGround }, { "LookDown", AnimationClip.LookDown }, { "LookDownEnd", AnimationClip.LookDownEnd }, - { "Needolin Play Low Transition", AnimationClip.NeedolinPlayLowTransition }, - { "Needolin Play Low", AnimationClip.NeedolinPlayLow }, - { "Needolin Play High Transition", AnimationClip.NeedolinPlayHighTransition }, - { "Needolin Play High", AnimationClip.NeedolinPlayHigh }, + { "Super Jump Antic", AnimationClip.SuperJumpAntic }, { "Super Jump Throw", AnimationClip.SuperJumpThrow }, { "Super Jump Throw Wait", AnimationClip.SuperJumpThrowWait }, @@ -154,6 +267,18 @@ internal class AnimationManager { { "Super Jump Loop", AnimationClip.SuperJumpLoop }, { "Super Jump Hit Roof", AnimationClip.SuperJumpHitRoof }, { "Super Jump Fall", AnimationClip.SuperJumpFall }, + { "Super Jump Antic Cancel", AnimationClip.SuperJumpAnticCancel }, + { "Super Jump Antic Effect", AnimationClip.SuperJumpAnticEffect }, + { "Super Jump Antic Effect End", AnimationClip.SuperJumpAnticEffectEnd }, + { "Super Jump Catch Cancel", AnimationClip.SuperJumpCatchCancel }, + { "Super Jump Hit Roof Q", AnimationClip.SuperJumpHitRoofQ }, + { "Super Jump Loop Cancel", AnimationClip.SuperJumpLoopCancel }, + { "Super Jump Thread", AnimationClip.SuperJumpThread }, + { "Super Jump Thread Loop", AnimationClip.SuperJumpThreadLoop }, + + { "Fall", AnimationClip.Fall }, + { "FallToProstrate", AnimationClip.FallToProstrate }, + { "LookUp", AnimationClip.LookUp }, { "LookUpEnd", AnimationClip.LookUpEnd }, { "Surface In", AnimationClip.SurfaceIn }, @@ -161,6 +286,8 @@ internal class AnimationManager { { "Surface Idle", AnimationClip.SurfaceIdle }, { "Surface IdleToSwim", AnimationClip.SurfaceIdleToSwim }, { "Surface TurnToSwim", AnimationClip.SurfaceTurnToSwim }, + { "Surface Current In Recover", AnimationClip.SurfaceCurrentInRecover }, + { "Surface Current In Tumble", AnimationClip.SurfaceCurrentInTumble }, { "Swim Dash", AnimationClip.SwimDash }, { "Swim Dash Turn", AnimationClip.SwimDashTurn }, { "Swim Dash Bonk", AnimationClip.SwimDashBonk }, @@ -169,10 +296,17 @@ internal class AnimationManager { { "Wall Scramble Antic", AnimationClip.WallScrambleAntic }, { "Wall Scramble", AnimationClip.WallScramble }, { "Wall Scramble End", AnimationClip.WallScrambleEnd }, + { "Wall Scramble Mantle", AnimationClip.WallScrambleMantle }, + { "Wall Scramble Quickened", AnimationClip.WallScrambleQuickened }, + { "Wall Scramble Repeat", AnimationClip.WallScrambleRepeat }, { "Wall Slash", AnimationClip.WallSlash }, + { "Somersault Pin Drop", AnimationClip.SomersaultPinDrop }, { "Walljump Somersault", AnimationClip.WallJumpSomersault }, { "Airborne", AnimationClip.Airborne }, + { "Bonked", AnimationClip.Bonked }, { "Sprint Bonk", AnimationClip.SprintBonk }, + { "Bonked Fast", AnimationClip.BonkedFast }, + { "Bonked Land", AnimationClip.BonkedLand }, { "Slash Land Run", AnimationClip.SlashLandRun }, { "v3 Down Slash Antic", AnimationClip.V3DownSlashAntic }, { "v3 Down Slash", AnimationClip.V3DownSlash }, @@ -226,47 +360,112 @@ internal class AnimationManager { { "NeedleThrow AnticA", AnimationClip.NeedleThrowAnticA }, { "NeedleThrow Throwing", AnimationClip.NeedleThrowThrowing }, { "NeedleThrow Catch", AnimationClip.NeedleThrowCatch }, - + { "NeedleThrow Burst", AnimationClip.NeedleThrowBurst }, + { "NeedleThrow Out", AnimationClip.NeedleThrowOut }, + { "NeedleThrow Return", AnimationClip.NeedleThrowReturn }, + { "NeedleThrow Return Short", AnimationClip.NeedleThrowReturnShort }, + { "NeedleThrow Thread", AnimationClip.NeedleThrowThread }, + { "NeedleThrow Thunk", AnimationClip.NeedleThrowThunk }, + { "Parry Stance", AnimationClip.ParryStance }, { "Parry Stance Ground", AnimationClip.ParryStanceGround }, { "Parry Recover", AnimationClip.ParryRecover }, { "Parry Recover Ground", AnimationClip.ParryRecoverGround }, - + { "Parry Clash", AnimationClip.ParryClash }, + { "Parry Clash Effect", AnimationClip.ParryClashEffect }, + { "Parry Dash", AnimationClip.ParryDash }, + { "Parry DashBurst", AnimationClip.ParryDashBurst }, + { "Parry Ready", AnimationClip.ParryReady }, + { "Parry Recovery Skid", AnimationClip.ParryRecoverySkid }, + { "Parry Stance Flash", AnimationClip.ParryStanceFlash }, + { "Parry Stance Flash Q", AnimationClip.ParryStanceFlashQ }, + { "Parry Thread", AnimationClip.ParryThread }, + { "Get Parry Dash", AnimationClip.GetParryDash }, + { "Get Parry End", AnimationClip.GetParryEnd }, + { "Get Parry Prepare", AnimationClip.GetParryPrepare }, + { "AirSphere Attack", AnimationClip.AirSphereAttack }, { "AirSphere End", AnimationClip.AirSphereEnd }, { "Silk Charge Antic", AnimationClip.SilkChargeAntic }, { "Silk Charge", AnimationClip.SilkCharge }, { "Silk Charge Recover", AnimationClip.SilkChargeRecover }, - + { "Silk Charge Zap", AnimationClip.SilkChargeZap }, + { "Silk Charge Recover Zap", AnimationClip.SilkChargeRecoverZap }, + { "Silk Charge Antic Zap", AnimationClip.SilkChargeAnticZap }, + + { "Silk Bomb Antic", AnimationClip.SilkBombAntic }, + { "Silk Bomb Antic Q", AnimationClip.SilkBombAnticQ }, { "Silk Bomb Loop", AnimationClip.SilkBombLoop }, { "Silk Bomb Recover", AnimationClip.SilkBombRecover }, { "Sit Craft", AnimationClip.SitCraft }, + { "Sit Craft Silk", AnimationClip.SitCraftSilk }, { "ToolThrow Up", AnimationClip.ToolThrowUp }, { "ToolThrow Q", AnimationClip.ToolThrowQ }, { "ToolThrowAlt Q", AnimationClip.ToolThrowAltQ }, - + { "ToolThrow M", AnimationClip.ToolThrowM }, + { "ToolThrow Wall", AnimationClip.ToolThrowWall }, + { "Recoil Twirl", AnimationClip.RecoilTwirl }, { "DownSpikeBounce 1", AnimationClip.DownSpikeBounce1 }, { "DownSpikeBounce 2", AnimationClip.DownSpikeBounce2 }, { "Charge Up", AnimationClip.ChargeUp }, - + { "Charge Up Air", AnimationClip.ChargeUpAir }, + { "Charge Up Bench", AnimationClip.ChargeUpBench }, + { "Charge Up Bench Silk", AnimationClip.ChargeUpBenchSilk }, + { "Charge Up Burst", AnimationClip.ChargeUpBurst }, + { "Idle Hurt", AnimationClip.IdleHurt }, { "TurnWalk", AnimationClip.TurnWalk }, + { "Turn Back Three Quarter", AnimationClip.TurnBackThreeQuarter }, + { "Turn Back Three Quarter EndToIdle", AnimationClip.TurnBackThreeQuarterEndToIdle }, + { "Turn Head Backward", AnimationClip.TurnHeadBackward }, + { "Turn Head Forward", AnimationClip.TurnHeadForward }, + { "Turn Quick", AnimationClip.TurnQuick }, + { "TurnFromBG", AnimationClip.TurnFromBG }, + { "TurnFromBG Loop", AnimationClip.TurnFromBGLoop }, + { "TurnToBG", AnimationClip.TurnToBG }, + { "TurnToBG Loop", AnimationClip.TurnToBGLoop }, + { "TurnToChallengeIdle", AnimationClip.TurnToChallengeIdle }, + { "TurnToChallengeStrong", AnimationClip.TurnToChallengeStrong }, + { "TurnToChallengeTalk", AnimationClip.TurnToChallengeTalk }, + { "TurnToFG", AnimationClip.TurnToFG }, + { "TurnToIdle", AnimationClip.TurnToIdle }, + { "Walk", AnimationClip.Walk }, + { "Walk Q", AnimationClip.WalkQ }, { "Look Up Half", AnimationClip.LookUpHalf }, { "Look Up Half End", AnimationClip.LookUpHalfEnd }, { "LookDown Slight", AnimationClip.LookDownSlight }, { "LookDown Slight End", AnimationClip.LookDownSlightEnd }, - + { "Look Down Talk", AnimationClip.LookDownTalk }, + { "Look Up Half Flinch", AnimationClip.LookUpHalfFlinch }, + { "Look Up Half Talk", AnimationClip.LookUpHalfTalk }, + { "Look Up Talk", AnimationClip.LookUpTalk }, + { "LookDown Updraft", AnimationClip.LookDownUpdraft }, + { "LookDown Windy", AnimationClip.LookDownWindy }, + { "LookDownEnd Updraft", AnimationClip.LookDownEndUpdraft }, + { "LookDownEnd Windy", AnimationClip.LookDownEndWindy }, + { "LookDownToIdle", AnimationClip.LookDownToIdle }, + { "LookingUp", AnimationClip.LookingUp }, + { "LookUp Updraft", AnimationClip.LookUpUpdraft }, + { "LookUp Windy", AnimationClip.LookUpWindy }, + { "LookUpEnd Updraft", AnimationClip.LookUpEndUpdraft }, + { "LookUpEnd Windy", AnimationClip.LookUpEndWindy }, + { "LookUpToIdle", AnimationClip.LookUpToIdle }, + { "Scuttle Start", AnimationClip.ScuttleStart }, { "Scuttle Loop", AnimationClip.ScuttleLoop }, + { "Scuttle TurnToLoop", AnimationClip.ScuttleTurnToLoop }, { "Scuttle End", AnimationClip.ScuttleEnd }, { "Scuttle Fall", AnimationClip.ScuttleFall }, { "Scuttle Vault", AnimationClip.ScuttleVault }, + { "Scuttle Jump", AnimationClip.ScuttleJump }, + { "Scuttle Climb", AnimationClip.ScuttleClimb }, + { "Scuttle Climb End", AnimationClip.ScuttleClimbEnd }, { "Stun", AnimationClip.Stun }, { "Recoil", AnimationClip.Recoil }, @@ -274,27 +473,150 @@ internal class AnimationManager { { "Grab Escape", AnimationClip.GrabEscape }, { "Idle Updraft", AnimationClip.IdleUpdraft }, + { "Updraft Antic", AnimationClip.UpdraftAntic }, { "Updraft Antic DJ", AnimationClip.UpdraftAnticDJ }, { "Updraft Rise", AnimationClip.UpdraftRise }, { "Updraft Rise Turn", AnimationClip.UpdraftRiseTurn }, - + { "Updraft End", AnimationClip.UpdraftEnd }, + { "Updraft Idle", AnimationClip.UpdraftIdle }, + { "Updraft Shoot", AnimationClip.UpdraftShoot }, + { "Prostrate Rise NoNeedle", AnimationClip.ProstrateRiseNoNeedle }, { "Bind First", AnimationClip.BindFirst }, { "BindBurst First", AnimationClip.BindBurstFirst }, { "Thwip To Idle", AnimationClip.ThwipToIdle }, - + { "GetUpToIdle", AnimationClip.GetUpToIdle }, + { "Weakened Stun", AnimationClip.WeakenedStun }, { "Weak Rise To Idle", AnimationClip.WeakRiseToIdle }, { "Weak Walk", AnimationClip.WeakWalk }, { "Weak TryJumpAntic", AnimationClip.WeakTryJumpAntic }, { "Weak TryAttack", AnimationClip.WeakTryAttack }, { "Weakened StunEnd", AnimationClip.WeakenedStunEnd }, - + { "Weak Fall", AnimationClip.WeakFall }, + { "Weak Flinch To Idle", AnimationClip.WeakFlinchToIdle }, + { "Weak Idle Look Up", AnimationClip.WeakIdleLookUp }, + { "Weak Walk Faster", AnimationClip.WeakWalkFaster }, + { "Weak Walk To Idle", AnimationClip.WeakWalkToIdle }, + + { "Taunt Collapse1", AnimationClip.TauntCollapse1 }, { "Taunt Collapse2", AnimationClip.TauntCollapse2 }, { "Taunt CollapseHit", AnimationClip.TauntCollapseHit }, + { "Prostrate", AnimationClip.Prostrate }, { "Prostrate Rise", AnimationClip.ProstrateRise }, + { "Prostrate NoNeedle", AnimationClip.ProstrateNoNeedle }, + { "Prostrate Rise Slow", AnimationClip.ProstrateRiseSlow }, + { "ProstrateRiseToKneel", AnimationClip.ProstrateRiseToKneel }, + { "ProstrateRiseToKneel NoLoop", AnimationClip.ProstrateRiseToKneelNoLoop }, + { "ProstrateRiseToWound", AnimationClip.ProstrateRiseToWound }, + { "Talking Standard", AnimationClip.TalkingStandard }, + { "Talking Backward", AnimationClip.TalkingBackward }, + + { "Abyss Kneel", AnimationClip.AbyssKneel }, + { "Abyss Kneel Back Idle", AnimationClip.AbyssKneelBackIdle }, + { "Abyss Kneel Back Talk", AnimationClip.AbyssKneelBackTalk }, + { "Abyss Kneel Idle", AnimationClip.AbyssKneelIdle }, + { "Abyss Kneel to Stand", AnimationClip.AbyssKneeltoStand }, + { "Abyss Kneel Turn Back", AnimationClip.AbyssKneelTurnBack }, + { "Kneel To Prostrate", AnimationClip.KneelToProstrate }, + { "Kneeling", AnimationClip.Kneeling }, + + { "Acid Death", AnimationClip.AcidDeath }, + { "Death", AnimationClip.Death }, + { "Death Final", AnimationClip.DeathFinal }, + { "Spike Death", AnimationClip.SpikeDeath }, + { "Spike Death Antic", AnimationClip.SpikeDeathAntic }, + + { "Beastling Call Fail", AnimationClip.BeastlingCallFail }, + { "Beastling Call Fail Windy", AnimationClip.BeastlingCallFailWindy }, + { "Bellway Call", AnimationClip.BellwayCall }, + { "Fast Travel Call", AnimationClip.FastTravelCall }, + { "Fast Travel Child Arrive", AnimationClip.FastTravelChildArrive }, + { "Fast Travel Fail", AnimationClip.FastTravelFail }, + { "Fast Travel Leap", AnimationClip.FastTravelLeap }, + + { "Collect Heart Piece", AnimationClip.CollectHeartPiece }, + { "Collect Heart Piece End", AnimationClip.CollectHeartPieceEnd }, + { "Collect Memory Orb", AnimationClip.CollectMemoryOrb }, + { "Collect Normal 1", AnimationClip.CollectNormal1 }, + { "Collect Normal 1 Q", AnimationClip.CollectNormal1Q }, + { "Collect Normal 2", AnimationClip.CollectNormal2 }, + { "Collect Normal 3", AnimationClip.CollectNormal3 }, + { "Collect Normal 3 Q", AnimationClip.CollectNormal3Q }, + { "Collect Silk Heart", AnimationClip.CollectSilkHeart }, + { "Collect Stand 1", AnimationClip.CollectStand1 }, + { "Collect Stand 2", AnimationClip.CollectStand2 }, + { "Collect Stand 3", AnimationClip.CollectStand3 }, + { "CollectToWound", AnimationClip.CollectToWound }, + { "DropToWounded", AnimationClip.DropToWounded }, + { "Blue Health Over Burst", AnimationClip.BlueHealthOverBurst }, + { "Crest Shrine Powerup Loop", AnimationClip.CrestShrinePowerupLoop }, + { "Hazard Respawn", AnimationClip.HazardRespawn }, + { "Respawn Wake", AnimationClip.RespawnWake }, + { "Pod Bounce", AnimationClip.PodBounce }, + { "Quick Charged", AnimationClip.QuickCharged }, + { "Quick Craft Silk", AnimationClip.QuickCraftSilk }, + { "Sprintmaster Low", AnimationClip.SprintmasterLow }, + { "Sprintmaster Start", AnimationClip.SprintmasterStart }, + + { "Dress Flourish", AnimationClip.DressFlourish }, + { "Give Dress", AnimationClip.GiveDress }, + { "Give Dress Idle", AnimationClip.GiveDressIdle }, + { "Enter", AnimationClip.Enter }, + { "Exit", AnimationClip.Exit }, + { "Exit Door To Idle", AnimationClip.ExitDoorToIdle }, + { "Exit To Idle", AnimationClip.ExitToIdle }, + { "Wake", AnimationClip.Wake }, + { "Wake Up Ground", AnimationClip.WakeUpGround }, + + { "Hurt Listen Down", AnimationClip.HurtListenDown }, + { "Hurt Listen Up", AnimationClip.HurtListenUp }, + { "Hurt Look Down", AnimationClip.HurtLookDown }, + { "Hurt Look Down Windy", AnimationClip.HurtLookDownWindy }, + { "Hurt Look Down Windy End", AnimationClip.HurtLookDownWindyEnd }, + { "Hurt Look Up", AnimationClip.HurtLookUp }, + { "Hurt Look Up End", AnimationClip.HurtLookUpEnd }, + { "Hurt Look Up Windy", AnimationClip.HurtLookUpWindy }, + { "Hurt Look Up Windy End", AnimationClip.HurtLookUpWindyEnd }, + { "Hurt Talk Down", AnimationClip.HurtTalkDown }, + { "Hurt Talk Up", AnimationClip.HurtTalkUp }, + { "Hurt Talk Up Windy", AnimationClip.HurtTalkUpWindy }, + { "Hurt To Idle", AnimationClip.HurtToIdle }, + + { "Needle Fall", AnimationClip.NeedleFall }, + { "Needle Land", AnimationClip.NeedleLand }, + + { "Ring Drop Impact", AnimationClip.RingDropImpact }, + { "Ring Eject", AnimationClip.RingEject }, + { "Ring Grab Hornet", AnimationClip.RingGrabHornet }, + { "Ring Grab Rail", AnimationClip.RingGrabRail }, + { "Ring Harpoon Connect", AnimationClip.RingHarpoonConnect }, + { "Ring Look Down", AnimationClip.RingLookDown }, + { "Ring Look Down End", AnimationClip.RingLookDownEnd }, + { "Ring Look Up", AnimationClip.RingLookUp }, + { "Ring Look Up End", AnimationClip.RingLookUpEnd }, + { "Ring Turn", AnimationClip.RingTurn }, + + { "Roar Lock", AnimationClip.RoarLock }, + { "Roar To LookUp", AnimationClip.RoarToLookUp }, + + { "Spa Surface Idle", AnimationClip.SpaSurfaceIdle }, + { "Spa Surface IdleToSwim", AnimationClip.SpaSurfaceIdleToSwim }, + { "Spa Surface In", AnimationClip.SpaSurfaceIn }, + { "Spa Surface InToIdle", AnimationClip.SpaSurfaceInToIdle }, + { "Spa Surface TurnToSwim", AnimationClip.SpaSurfaceTurnToSwim }, + + { "Weaver Pray", AnimationClip.WeaverPray }, + { "Weaver Pray End", AnimationClip.WeaverPrayEnd }, + { "Weaver Pray Prepare", AnimationClip.WeaverPrayPrepare }, + { "Weaver Pray Prepare Front", AnimationClip.WeaverPrayPrepareFront }, + + { "Wound", AnimationClip.Wound }, + { "Wound Double Strike", AnimationClip.WoundDoubleStrike }, + { "Wound Zap", AnimationClip.WoundZap }, + }; /// From b962ff7f4bc432617461a6aed27a2103a7013193 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:19:24 -0500 Subject: [PATCH 02/33] remove duplicate animations --- SSMP/Animation/AnimationManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 1f00a6f..bff6591 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -301,7 +301,6 @@ internal class AnimationManager { { "Wall Scramble Repeat", AnimationClip.WallScrambleRepeat }, { "Wall Slash", AnimationClip.WallSlash }, { "Somersault Pin Drop", AnimationClip.SomersaultPinDrop }, - { "Walljump Somersault", AnimationClip.WallJumpSomersault }, { "Airborne", AnimationClip.Airborne }, { "Bonked", AnimationClip.Bonked }, { "Sprint Bonk", AnimationClip.SprintBonk }, @@ -418,7 +417,6 @@ internal class AnimationManager { { "Charge Up Bench Silk", AnimationClip.ChargeUpBenchSilk }, { "Charge Up Burst", AnimationClip.ChargeUpBurst }, - { "Idle Hurt", AnimationClip.IdleHurt }, { "TurnWalk", AnimationClip.TurnWalk }, { "Turn Back Three Quarter", AnimationClip.TurnBackThreeQuarter }, { "Turn Back Three Quarter EndToIdle", AnimationClip.TurnBackThreeQuarterEndToIdle }, From b21b1a24d5660cc6a4ce169e57a447941600017f Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:20:11 -0500 Subject: [PATCH 03/33] prevent duplicate damageHero components --- SSMP/Animation/DamageAnimationEffect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSMP/Animation/DamageAnimationEffect.cs b/SSMP/Animation/DamageAnimationEffect.cs index d12de77..527eac7 100644 --- a/SSMP/Animation/DamageAnimationEffect.cs +++ b/SSMP/Animation/DamageAnimationEffect.cs @@ -34,7 +34,7 @@ public void SetShouldDoDamage(bool shouldDoDamage) { /// The target game object to attach the component to. /// The number of mask of damage it should deal. protected static void AddDamageHeroComponent(GameObject target, int damage = 1) { - var damageHero = target.AddComponent(); + var damageHero = target.AddComponentIfNotPresent(); damageHero.damageDealt = damage; damageHero.OnDamagedHero = new UnityEvent(); } From 5b5d95da11ba17e8f3a48515a1cfe7565a5f8286 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:22:06 -0500 Subject: [PATCH 04/33] Finalize basic bind start and end animations --- SSMP/Animation/Effects/Bind.cs | 198 ++++++++++++++++++++++------ SSMP/Animation/Effects/BindBurst.cs | 158 ++++++++++++++++++++++ 2 files changed, 313 insertions(+), 43 deletions(-) create mode 100644 SSMP/Animation/Effects/BindBurst.cs diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index dcdde7a..40c5019 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Linq; using HutongGames.PlayMaker.Actions; using SSMP.Internals; using SSMP.Util; @@ -12,22 +13,45 @@ namespace SSMP.Animation.Effects; /// /// Class for the animation effect of bind (healing). /// -internal class Bind : AnimationEffect { +internal class Bind : DamageAnimationEffect { + + protected string BIND_BELL_NAME = "bind_bell_appear_instance"; + + protected class Flags { + public bool BindBell = false; + public bool BaseMirror = false; + public bool UpgradedMirror = false; + public bool QuickBind = false; + public bool ReserveBind = false; + public bool Maggoted = false; + + public Flags(byte[]? info) { + if (info == null) return; + BindBell = info[0] == 1; + BaseMirror = info[1] == 1; + UpgradedMirror = info[2] == 1; + QuickBind = info[3] == 1; + ReserveBind = info[4] == 1; + Maggoted = info[5] == 1; + } + } + /// /// Cached FSM for Hornet's bind ability. /// - private static PlayMakerFSM? _bindFsm; + protected static PlayMakerFSM? _bindFsm; + + protected static GameObject? _localBindEffects; - private static GameObject? _localBindEffects; - public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { - MonoBehaviourUtil.Instance.StartCoroutine(PlayBindEffect(playerObject, crestType)); + Flags flags = new Flags(effectInfo); + MonoBehaviourUtil.Instance.StartCoroutine(PlayBindEffect(playerObject, crestType, flags)); } - private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType) { + private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, Flags flags) { var randomClipAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); var playAudioAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); - + AudioUtil.PlayAudioEventWithRandomAudioClipFromTableAtPlayerObject( playAudioAction, randomClipAction, @@ -37,70 +61,125 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType) var oneShotSingleAction = GetOrFindBindFsm().GetFirstAction("Check Grounded"); AudioUtil.PlayAudioOneShotSingleAtPlayerObject(oneShotSingleAction, playerObject); - _localBindEffects ??= HeroController.instance.gameObject.FindGameObjectInChildren("Bind Effects"); - if (_localBindEffects == null) { - Logger.Warn("Could not find local Bind Effects object in hero object"); + var created = CreateObjects(playerObject, out var bindEffects); + if (!created) { yield break; } - var bindEffects = playerObject.FindGameObjectInChildren("Bind Effects"); - if (bindEffects == null) { - Logger.Warn("Player object does not have Bind Effects child, cannot play bind"); - yield break; + // TODO: if Beast is equipped, activate rage burst object (State: Rage Burst?) + + Logger.Info("Determining crest animation..."); + + if (crestType == CrestType.Beast) { + Logger.Info("Playing Beast Crest Animation"); + PlayBeastBindStart(bindEffects); + } else if (crestType == CrestType.Cursed) { + Logger.Info("Playing Cursed Crest Animation"); + } else if (crestType == CrestType.Witch) { + Logger.Info("Playing Witch Crest Animation"); + } else if (crestType == CrestType.Shaman) { + Logger.Info("Playing Shaman Crest Animation"); + } else { + Logger.Info("Playing Default Animation"); + PlayNormalStart(bindEffects, flags); } - var bindSilkObj = bindEffects.FindGameObjectInChildren("Bind Silk"); - if (bindSilkObj == null) { - var localBindSilkObj = _localBindEffects.FindGameObjectInChildren("Bind Silk"); - if (localBindSilkObj == null) { - Logger.Warn("Could not find local Bind Silk object, cannot play bind"); - yield break; + // If bind bell, do effects in state "Bind Bell?" and "Bind Bell Disappear?" + if (flags.BindBell) { + StartBindBell(bindEffects); + } + + // TODO: If using reserve bind, use reserve bind animation? + + // TODO: Use air animations if applicable + + // TODO: Quick Craft animations + + Logger.Info("Getting clip name for no reason..."); + var playerAnimator = playerObject.GetComponent(); + var currentClip = playerAnimator?.currentClip; + Logger.Info($"Player Animator current clip for Bind: {currentClip?.name}"); + // TODO: figure out when animation triggers happen + + // TODO: adjust bind time based on crest and tools + } + + private void StartBindBell(GameObject bindEffects) { + var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); + + if (bindBell == null) { + var allObjects = Resources.FindObjectsOfTypeAll(); + var localBell = allObjects.FirstOrDefault(o => o.name == "bind_bell_appear"); + + if (localBell == null) { + Logger.Warn("Couldn't find bind bell object"); + return; } - bindSilkObj = Object.Instantiate(localBindSilkObj, bindEffects.transform); + bindBell = GameObject.Instantiate(localBell, bindEffects.transform); + bindBell.name = BIND_BELL_NAME; + + var follower = bindBell.GetComponent(); + follower.target = bindEffects.transform; + follower.useHero = false; + + var delay = bindBell.AddComponentIfNotPresent(); + delay.time = 5f; + } + + bindBell.SetActive(false); + bindBell.SetActive(true); + } + + private void PlayNormalStart(GameObject bindEffects, Flags flags) { + var bindSilkObj = CreateEffectIfNotExists(bindEffects, "Bind Silk"); + if (bindSilkObj == null) { + return; } var bindSilkMeshRenderer = bindSilkObj.GetComponent(); bindSilkMeshRenderer.enabled = true; var bindSilkAnimator = bindSilkObj.GetComponent(); - bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk")); - - // TODO: if Beast is equipped, activate rage burst object (State: Rage Burst?) - // TODO: if claw mirrors are equipped, do effects in state "Dazzle?" - // TODO: if maggoted, do effects in state "Maggoted?" - var playerAnimator = playerObject.GetComponent(); - var currentClip = playerAnimator.currentClip; - Logger.Error($"Player Animator current clip for Bind: {currentClip.name}"); - // TODO: figure out when animation triggers happen + Logger.Info("Playing Bind Silk animation"); - // TODO: adjust bind time based on crest and tools - var bindTime = 1.2f; + if (flags.QuickBind) bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk Quick")); + else bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk")); + } - yield return new WaitForSeconds(bindTime); + private void PlayBeastBindStart(GameObject bindEffects) { + var beastAntic = CreateEffectIfNotExists(bindEffects, "Warrior_Bind_antic_silk"); + if (beastAntic == null) { + return; + } + + beastAntic.SetActive(true); - bindSilkMeshRenderer.enabled = false; - - // TODO: activate object in state "Heal", last action - - } /// public override byte[]? GetEffectInfo() { - // Warding bell - // Claw mirrors - return null; + byte[] effectInfo = { + (byte) (ToolItemManager.IsToolEquipped("Bell Bind") ? 1 : 0), + (byte) (ToolItemManager.IsToolEquipped("Dazzle Bind") ? 1 : 0), + (byte) (ToolItemManager.IsToolEquipped("Dazzle Bind Upgraded") ? 1 : 0), + (byte) (ToolItemManager.IsToolEquipped("Quickbind") ? 1 : 0), + (byte) (ToolItemManager.IsToolEquipped("Reserve Bind") ? 1 : 0), + (byte) (HeroController.instance.cState.isMaggoted ? 1 : 0), + //(byte) (HeroController.instance.onFlatGround ? 0 : 1) + }; + + return effectInfo; } - + /// /// Get or find the Bind FSM on the hero object. Will be cached to . /// /// The FSM for Bind. /// Thrown if the FSM cannot be found, which shouldn't happen. /// - private PlayMakerFSM GetOrFindBindFsm() { + protected PlayMakerFSM GetOrFindBindFsm() { if (_bindFsm != null) { return _bindFsm; } @@ -115,4 +194,37 @@ private PlayMakerFSM GetOrFindBindFsm() { throw new InvalidOperationException("Could not find Bind FSM on hero"); } + + protected bool CreateObjects(GameObject playerObject, out GameObject bindEffects) { + _localBindEffects ??= HeroController.instance.gameObject.FindGameObjectInChildren("Bind Effects"); + if (_localBindEffects == null) { + Logger.Warn("Could not find local Bind Effects object in hero object"); + bindEffects = null; + return false; + } + + bindEffects = playerObject.FindGameObjectInChildren("Bind Effects"); + if (bindEffects == null) { + Logger.Warn("Player object does not have Bind Effects child, cannot play bind"); + return false; + } + + return true; + } + + protected GameObject? CreateEffectIfNotExists(GameObject bindEffects, string objectName) { + var obj = bindEffects.FindGameObjectInChildren(objectName); + if (obj == null) { + var localObj = _localBindEffects.FindGameObjectInChildren(objectName); + if (localObj == null) { + Logger.Warn($"Could not find local {objectName} object, cannot play bind"); + return null; + } + + obj = Object.Instantiate(localObj, bindEffects.transform); + obj.name = objectName; + } + + return obj; + } } diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs new file mode 100644 index 0000000..ad8f9f5 --- /dev/null +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using HutongGames.PlayMaker.Actions; +using SSMP.Internals; +using SSMP.Util; +using UnityEngine; +using Logger = SSMP.Logging.Logger; +using Object = UnityEngine.Object; + +namespace SSMP.Animation.Effects; + +internal class BindBurst : Bind { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { + Flags flags = new Flags(effectInfo); + MonoBehaviourUtil.Instance.StartCoroutine(PlayBindBurstEffect(playerObject, crestType, flags)); + } + + private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crestType, Flags flags) { + if (!CreateObjects(playerObject, out var bindEffects)) { + yield break; + } + + if (flags.BaseMirror || flags.UpgradedMirror) PlayMirror(bindEffects, flags.UpgradedMirror); + if (flags.BindBell) StopBindBell(bindEffects); + if (flags.Maggoted) PlayMaggotCleanse(bindEffects, playerObject); + + switch (crestType) { + case CrestType.Beast: + PlayBeastRage(bindEffects); + break; + //yield break; + default: + break; + } + + PlayNormalEnd(bindEffects); + } + + private GameObject? PrepareMirror(GameObject bindEffects, SetGameObject mirrorSource, string name) { + var mirror = bindEffects.FindGameObjectInChildren(name); + if (mirror != null) { + mirror.SetActive(false); + return mirror; + } + + if (mirrorSource == null) { + Logger.Warn("Unable to find mirror source"); + return null; + } + + mirror = mirrorSource.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + if (mirror == null) { + Logger.Warn("Unable to spawn mirror"); + return null; + } + mirror.name = name; + + var shaker = mirror.GetComponentInChildren(); + if (shaker != null) { + Component.DestroyImmediate(shaker); + } + var delay = mirror.AddComponentIfNotPresent(); + delay.time = 2f; + EffectUtils.SafelyRemoveAutoRecycle(mirror); + var haze = mirror.FindGameObjectInChildren("haze2"); + if (haze != null) { + GameObject.Destroy(haze); + } + + mirror.SetActive(false); + return mirror; + } + + private void PlayMirror(GameObject bindEffects, bool upgraded) { + Logger.Info("Playing Claw Mirror Animation"); + var regularClaw = GetOrFindBindFsm().GetAction("Dazzle?", 3)!; + var upgradedClaw = GetOrFindBindFsm().GetAction("Dazzle?", 4)!; + + GameObject? claw; + if (upgraded) claw = PrepareMirror(bindEffects, upgradedClaw, "dazzle_upgraded"); + else claw = PrepareMirror(bindEffects, regularClaw, "dazzle_regular"); + + if (claw == null) { + Logger.Warn("Unable to create claw mirror object."); + return; + } + + claw.SetActive(true); + if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { + var damagerParent = claw.FindGameObjectInChildren("Trobbio_dazzle_flash"); + var damager = damagerParent?.FindGameObjectInChildren("hero_dazzle_flash_damager"); + if (damager != null) { + damager.layer = (int) GlobalEnums.PhysLayers.HERO_ATTACK; + AddDamageHeroComponent(damager); + } else { + Logger.Warn("Couldn't find claw mirror damager"); + } + } else { + var damager = claw.GetComponentInChildren(true); + if (damager != null) { + Component.Destroy(damager); + } + } + } + + private void StopBindBell(GameObject bindEffects) { + var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); + if (bindBell != null) { + bindBell.SetActive(false); + } + } + + private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) { + Logger.Info("Playing Maggot Animation"); + var maggotBurst = GetOrFindBindFsm().GetAction("Maggoted?", 2); + var maggotFlash = GetOrFindBindFsm().GetAction("Maggoted?", 4); + var maggotAudio = GetOrFindBindFsm().GetFirstAction("Maggoted?"); + + if (maggotBurst != null) { + maggotBurst.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + } + if (maggotFlash != null) { + maggotFlash.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + } + if (maggotAudio != null) { + AudioUtil.PlayAudioEventAtPlayerObject( + maggotAudio, + playerObject + ); + } + } + + private void PlayBeastRage(GameObject bindEffects) { + var beastRage = CreateEffectIfNotExists(bindEffects, "crest rage_burst_effect(Clone)"); + beastRage?.SetActive(true); + } + + private void PlayNormalEnd(GameObject bindEffects) { + Logger.Info("Playing Heal Particles and Anim"); + var healParticle = CreateEffectIfNotExists(bindEffects, "Pt Heal"); + if (healParticle != null) { + healParticle.GetComponent().Play(); + } + + var healAnim = CreateEffectIfNotExists(bindEffects, "Heal Anim"); + if (healAnim != null) { + healAnim.SetActive(true); + } + + var bindSilkObj = bindEffects.FindGameObjectInChildren("Bind Silk"); + if (bindSilkObj != null) { + var bindSilkMeshRenderer = bindSilkObj.GetComponent(); + bindSilkMeshRenderer.enabled = false; + } + } +} From 1f6ec4be97c074384b9fb96ebcac3b1a5e10915d Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:22:31 -0500 Subject: [PATCH 05/33] Util to prevent recycling --- SSMP/Animation/AnimationManager.cs | 3 ++- SSMP/Animation/Effects/EffectUtils.cs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 SSMP/Animation/Effects/EffectUtils.cs diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index bff6591..95e3561 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -645,7 +645,8 @@ internal class AnimationManager { { AnimationClip.SlashCharged, new NeedleStrike(false) }, { AnimationClip.SlashChargedLoop, new NeedleStrike(true) }, { AnimationClip.NeedleArtDash, new NeedleStrike(false) }, - { AnimationClip.BindChargeGround, new Bind() } + { AnimationClip.BindChargeGround, new Bind() }, + { AnimationClip.BindBurstGround, new BindBurst() } }; /// diff --git a/SSMP/Animation/Effects/EffectUtils.cs b/SSMP/Animation/Effects/EffectUtils.cs new file mode 100644 index 0000000..c4ba757 --- /dev/null +++ b/SSMP/Animation/Effects/EffectUtils.cs @@ -0,0 +1,18 @@ +using UnityEngine; +using Logger = SSMP.Logging.Logger; +using Object = UnityEngine.Object; + +namespace SSMP.Animation.Effects; + +internal static class EffectUtils { + public static void SafelyRemoveAutoRecycle(GameObject obj) { + var recycler = obj.GetComponent(); + if (recycler != null) { + // Stop listeners before destroying + recycler.recycleTimerRunning = false; + recycler.subbed = false; + + Component.Destroy(recycler); + } + } +} From fdce0320de658e5dad07ccfd5997583247c9ae36 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:23:01 -0500 Subject: [PATCH 06/33] Temporary testing component for bind animation --- SSMP/Game/Client/PlayerManager.cs | 2 + SSMP/Testing/PlayerAnimation.cs | 106 ++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 SSMP/Testing/PlayerAnimation.cs diff --git a/SSMP/Game/Client/PlayerManager.cs b/SSMP/Game/Client/PlayerManager.cs index a8cdc31..b3dca56 100644 --- a/SSMP/Game/Client/PlayerManager.cs +++ b/SSMP/Game/Client/PlayerManager.cs @@ -149,6 +149,8 @@ private void TryCreatePlayerPool() { // Create a player container prefab, used to spawn players _playerContainerPrefab = new GameObject(PlayerContainerPrefabName); + _playerContainerPrefab.AddComponent(); + _playerContainerPrefab.AddComponent(); var playerPrefab = new GameObject( diff --git a/SSMP/Testing/PlayerAnimation.cs b/SSMP/Testing/PlayerAnimation.cs new file mode 100644 index 0000000..73d593f --- /dev/null +++ b/SSMP/Testing/PlayerAnimation.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Text; +using GlobalSettings; +using HarmonyLib; +using SSMP.Animation; +using SSMP.Animation.Effects; +using SSMP.Game.Settings; +using SSMP.Internals; +using UnityEngine; +using static UnityEngine.Rendering.RayTracingAccelerationStructure; +using Logger = SSMP.Logging.Logger; +using Object = UnityEngine.Object; + +namespace SSMP.Testing; + +public class PlayerAnimation : MonoBehaviour { + + CrestType DetermineCrest() { + var playerCrest = HeroController.instance.playerData.CurrentCrestID! ?? ""; + //if (playerCrest == null) return CrestType.Hunter; + + if (playerCrest == Gameplay.HunterCrest.name) return CrestType.Hunter; + if (playerCrest == Gameplay.HunterCrest2.name) return CrestType.HunterV2; + if (playerCrest == Gameplay.HunterCrest3.name) return CrestType.HunterV3; + if (playerCrest == Gameplay.ReaperCrest.name) return CrestType.Reaper; + + if (playerCrest == Gameplay.WandererCrest.name) return CrestType.Wanderer; + if (playerCrest == Gameplay.CloaklessCrest.name) return CrestType.Cloakless; + if (playerCrest == Gameplay.WarriorCrest.name) return CrestType.Beast; + if (playerCrest == Gameplay.ToolmasterCrest.name) return CrestType.Architect; + if (playerCrest == Gameplay.CursedCrest.name) return CrestType.Cursed; + if (playerCrest == Gameplay.SpellCrest.name) return CrestType.Shaman; + if (playerCrest == Gameplay.WitchCrest.name) return CrestType.Witch; + + return CrestType.Hunter; + } + + public bool IsEquipped(string toolName) { + return ToolItemManager.IsToolEquipped(toolName); + } + + public void StartAnimation() { + gameObject.SetActive(true); + + + string clipName = "BindCharge Ground"; + CrestType crest = DetermineCrest(); + + var hornet = HeroController.instance.gameObject; + var position = hornet.transform.position; + var playerPosition = new Vector3(position.x + 10, position.y, position.z); + transform.position = playerPosition; + + var playerObject = transform.GetChild(0).gameObject; + var bind = new Bind(); + + var info = bind.GetEffectInfo(); + Logger.Info(string.Join(", ", info)); + + //bind.UsingClawMirrors = ToolItemManager.IsToolEquipped("Dazzle Blind"); + //bind.ClawMirrorsUpgraded = ToolItemManager.IsToolEquipped("Dazzle Blind Upgraded"); + //bind.UsingBindBell = ToolItemManager.IsToolEquipped("Bell Bind"); + //bind.UsingMultiBind = ToolItemManager.IsToolEquipped("Multibind"); + //bind.UsingQuickBind = ToolItemManager.IsToolEquipped("Quickbind"); + //bind.UsingReserveBind = ToolItemManager.IsToolEquipped("Reserve Bind"); + //bind.InAir = !HeroController.instance.onFlatGround; + //bind.Maggoted = HeroController.instance.cState.isMaggoted; + + SetSettings(bind); + bind.Play(playerObject, crest, info); + + var spriteAnimator = playerObject.GetComponent(); + + var clip = spriteAnimator.GetClipByName(clipName); + spriteAnimator.PlayFromFrame(clip, 0); + } + + void SetSettings(AnimationEffect bind) { + var plugin = GameObject.FindFirstObjectByType(); + var traverse = Traverse.Create(plugin).Field("_gameManager").Field("_clientManager").Field("_serverSettings"); + var settings = traverse.GetValue(); + + bind.SetServerSettings(settings); + } + + public void StopAnimation() { + var clipName = "BindBurst Ground"; + + var playerObject = transform.GetChild(0).gameObject; + + var bind = new BindBurst(); + SetSettings(bind); + bind.SetShouldDoDamage(true); + + var info = bind.GetEffectInfo(); + CrestType crest = DetermineCrest(); + + bind.Play(playerObject, crest, info); + + var spriteAnimator = playerObject.GetComponent(); + + var clip = spriteAnimator.GetClipByName(clipName); + spriteAnimator.PlayFromFrame(clip, 0); + } +} From 3a42c82b5e18e8aad9975c42623c6582e2298de5 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sun, 8 Mar 2026 01:22:54 -0500 Subject: [PATCH 07/33] add shaman bind --- SSMP/Animation/AnimationManager.cs | 6 ++- SSMP/Animation/Effects/Bind.cs | 35 +++++++++++++++++ SSMP/Animation/Effects/BindBurst.cs | 12 +++++- SSMP/Testing/PlayerAnimation.cs | 61 +++++++++++++++++------------ 4 files changed, 87 insertions(+), 27 deletions(-) diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 95e3561..3468349 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -646,7 +646,9 @@ internal class AnimationManager { { AnimationClip.SlashChargedLoop, new NeedleStrike(true) }, { AnimationClip.NeedleArtDash, new NeedleStrike(false) }, { AnimationClip.BindChargeGround, new Bind() }, - { AnimationClip.BindBurstGround, new BindBurst() } + { AnimationClip.BindChargeGroundLand, new Bind { ShamanDoneFalling = true } }, + { AnimationClip.BindBurstGround, new BindBurst() }, + { AnimationClip.BindChargeHealBurst, new BindBurst() } }; /// @@ -955,7 +957,7 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { return; } - if (_debugLogAnimations) Logger.Info($" conditions 2: {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Loop}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.LoopSection}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Once}"); + //if (_debugLogAnimations) Logger.Info($" conditions 2: {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Loop}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.LoopSection}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Once}"); // Skip clips that do not have the wrap mode loop, loop-section or once if (clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Loop && diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 40c5019..b943cbc 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -17,6 +17,8 @@ internal class Bind : DamageAnimationEffect { protected string BIND_BELL_NAME = "bind_bell_appear_instance"; + public bool ShamanDoneFalling = false; + protected class Flags { public bool BindBell = false; public bool BaseMirror = false; @@ -79,6 +81,14 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, Logger.Info("Playing Witch Crest Animation"); } else if (crestType == CrestType.Shaman) { Logger.Info("Playing Shaman Crest Animation"); + if (ShamanDoneFalling) { + PlayShamanFall(bindEffects); + yield break; + } + + PlayShamanFallEnd(bindEffects); + PlayNormalStart(bindEffects, flags); + //PlayShamanBindStart(bindEffects); } else { Logger.Info("Playing Default Animation"); PlayNormalStart(bindEffects, flags); @@ -154,8 +164,33 @@ private void PlayBeastBindStart(GameObject bindEffects) { return; } + beastAntic.SetActive(false); beastAntic.SetActive(true); + } + + private void PlayShamanFall(GameObject bindEffects) { + var shamanAntic = CreateEffectIfNotExists(bindEffects, "Shaman_Bind_antic_silk"); + if (shamanAntic == null) { + return; + } + var delay = shamanAntic.AddComponentIfNotPresent(); + delay.time = 5; + + shamanAntic.SetActive(false); + shamanAntic.SetActive(true); + } + + private void PlayShamanFallEnd(GameObject bindEffects) { + var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); + if (shamanAntic == null) { + return; + } + var animator = shamanAntic.GetComponent(); + animator.Play("End"); + //var clip = animator.runtimeAnimatorController.animationClips[2]; + + //shamanAntic.SetActive(false); } /// diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index ad8f9f5..05924e9 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -30,7 +30,9 @@ private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crest case CrestType.Beast: PlayBeastRage(bindEffects); break; - //yield break; + case CrestType.Shaman: + PlayShamanEnd(bindEffects); + break; default: break; } @@ -137,6 +139,14 @@ private void PlayBeastRage(GameObject bindEffects) { beastRage?.SetActive(true); } + private void PlayShamanEnd(GameObject bindEffects) { + var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); + if (shamanAntic == null) { + return; + } + shamanAntic.SetActive(false); + } + private void PlayNormalEnd(GameObject bindEffects) { Logger.Info("Playing Heal Particles and Anim"); var healParticle = CreateEffectIfNotExists(bindEffects, "Pt Heal"); diff --git a/SSMP/Testing/PlayerAnimation.cs b/SSMP/Testing/PlayerAnimation.cs index 73d593f..eec09c8 100644 --- a/SSMP/Testing/PlayerAnimation.cs +++ b/SSMP/Testing/PlayerAnimation.cs @@ -36,52 +36,65 @@ CrestType DetermineCrest() { return CrestType.Hunter; } - public bool IsEquipped(string toolName) { - return ToolItemManager.IsToolEquipped(toolName); + void SetSettings(AnimationEffect bind) { + var plugin = GameObject.FindFirstObjectByType(); + var traverse = Traverse.Create(plugin).Field("_gameManager").Field("_clientManager").Field("_serverSettings"); + var settings = traverse.GetValue(); + + bind.SetServerSettings(settings); } - public void StartAnimation() { + void Init() { gameObject.SetActive(true); - - string clipName = "BindCharge Ground"; - CrestType crest = DetermineCrest(); - var hornet = HeroController.instance.gameObject; var position = hornet.transform.position; var playerPosition = new Vector3(position.x + 10, position.y, position.z); transform.position = playerPosition; + } + + public void StartPreAnimation() { + CrestType crest = DetermineCrest(); + + if (crest != CrestType.Shaman) { + return; + } + Init(); + string clipName = "BindCharge Ground"; var playerObject = transform.GetChild(0).gameObject; - var bind = new Bind(); + var bind = new Bind { ShamanDoneFalling = false }; var info = bind.GetEffectInfo(); - Logger.Info(string.Join(", ", info)); - - //bind.UsingClawMirrors = ToolItemManager.IsToolEquipped("Dazzle Blind"); - //bind.ClawMirrorsUpgraded = ToolItemManager.IsToolEquipped("Dazzle Blind Upgraded"); - //bind.UsingBindBell = ToolItemManager.IsToolEquipped("Bell Bind"); - //bind.UsingMultiBind = ToolItemManager.IsToolEquipped("Multibind"); - //bind.UsingQuickBind = ToolItemManager.IsToolEquipped("Quickbind"); - //bind.UsingReserveBind = ToolItemManager.IsToolEquipped("Reserve Bind"); - //bind.InAir = !HeroController.instance.onFlatGround; - //bind.Maggoted = HeroController.instance.cState.isMaggoted; SetSettings(bind); bind.Play(playerObject, crest, info); - + var spriteAnimator = playerObject.GetComponent(); var clip = spriteAnimator.GetClipByName(clipName); spriteAnimator.PlayFromFrame(clip, 0); } - void SetSettings(AnimationEffect bind) { - var plugin = GameObject.FindFirstObjectByType(); - var traverse = Traverse.Create(plugin).Field("_gameManager").Field("_clientManager").Field("_serverSettings"); - var settings = traverse.GetValue(); + public void StartAnimation() { - bind.SetServerSettings(settings); + Init(); + + string clipName = "BindCharge Ground"; + CrestType crest = DetermineCrest(); + + var playerObject = transform.GetChild(0).gameObject; + var bind = new Bind { ShamanDoneFalling = true }; + + var info = bind.GetEffectInfo(); + + SetSettings(bind); + bind.Play(playerObject, crest, info); + + var spriteAnimator = playerObject.GetComponent(); + + var clip = spriteAnimator.GetClipByName(clipName); + spriteAnimator.PlayFromFrame(clip, 0); } public void StopAnimation() { From 1fd5ce7c8073e635c5ae0a1cdea5370485e72a5f Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sun, 8 Mar 2026 23:43:30 -0400 Subject: [PATCH 08/33] Finalize claw mirrors and shamans --- SSMP/Animation/AnimationManager.cs | 4 +- SSMP/Animation/Effects/Bind.cs | 44 ++++++++++++++------ SSMP/Animation/Effects/BindBurst.cs | 62 +++++++++++++++++++---------- 3 files changed, 77 insertions(+), 33 deletions(-) diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 3468349..91fb0c9 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -648,7 +648,9 @@ internal class AnimationManager { { AnimationClip.BindChargeGround, new Bind() }, { AnimationClip.BindChargeGroundLand, new Bind { ShamanDoneFalling = true } }, { AnimationClip.BindBurstGround, new BindBurst() }, - { AnimationClip.BindChargeHealBurst, new BindBurst() } + { AnimationClip.BindChargeHealBurst, new BindBurst() }, + { AnimationClip.BindBurstAir, new BindBurst() }, + { AnimationClip.RageBindBurst, new BindBurst() }, }; /// diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index b943cbc..08f568c 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -68,8 +68,6 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, yield break; } - // TODO: if Beast is equipped, activate rage burst object (State: Rage Burst?) - Logger.Info("Determining crest animation..."); if (crestType == CrestType.Beast) { @@ -81,14 +79,14 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, Logger.Info("Playing Witch Crest Animation"); } else if (crestType == CrestType.Shaman) { Logger.Info("Playing Shaman Crest Animation"); - if (ShamanDoneFalling) { + if (!ShamanDoneFalling) { PlayShamanFall(bindEffects); + } else { + PlayShamanFallEnd(bindEffects); + PlayNormalStart(bindEffects, flags); yield break; } - PlayShamanFallEnd(bindEffects); - PlayNormalStart(bindEffects, flags); - //PlayShamanBindStart(bindEffects); } else { Logger.Info("Playing Default Animation"); PlayNormalStart(bindEffects, flags); @@ -101,19 +99,17 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, // TODO: If using reserve bind, use reserve bind animation? - // TODO: Use air animations if applicable - // TODO: Quick Craft animations Logger.Info("Getting clip name for no reason..."); var playerAnimator = playerObject.GetComponent(); var currentClip = playerAnimator?.currentClip; Logger.Info($"Player Animator current clip for Bind: {currentClip?.name}"); - // TODO: figure out when animation triggers happen - - // TODO: adjust bind time based on crest and tools } + /// + /// Creates the bind bell + /// private void StartBindBell(GameObject bindEffects) { var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); @@ -140,7 +136,9 @@ private void StartBindBell(GameObject bindEffects) { bindBell.SetActive(false); bindBell.SetActive(true); } - + /// + /// Plays the normal silk animation + /// private void PlayNormalStart(GameObject bindEffects, Flags flags) { var bindSilkObj = CreateEffectIfNotExists(bindEffects, "Bind Silk"); if (bindSilkObj == null) { @@ -158,6 +156,9 @@ private void PlayNormalStart(GameObject bindEffects, Flags flags) { else bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk")); } + /// + /// Plays the Beast Crest specific silk animation + /// private void PlayBeastBindStart(GameObject bindEffects) { var beastAntic = CreateEffectIfNotExists(bindEffects, "Warrior_Bind_antic_silk"); if (beastAntic == null) { @@ -168,6 +169,10 @@ private void PlayBeastBindStart(GameObject bindEffects) { beastAntic.SetActive(true); } + /// + /// Plays the Shaman Crest falling silk animation + /// + /// private void PlayShamanFall(GameObject bindEffects) { var shamanAntic = CreateEffectIfNotExists(bindEffects, "Shaman_Bind_antic_silk"); if (shamanAntic == null) { @@ -181,6 +186,9 @@ private void PlayShamanFall(GameObject bindEffects) { shamanAntic.SetActive(true); } + /// + /// Transitions from the falling silk animation to the normal silk animation + /// private void PlayShamanFallEnd(GameObject bindEffects) { var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); if (shamanAntic == null) { @@ -230,6 +238,12 @@ protected PlayMakerFSM GetOrFindBindFsm() { throw new InvalidOperationException("Could not find Bind FSM on hero"); } + /// + /// Attempts to locate and bind the 'Bind Effects' GameObject to the specified player object. + /// + /// The player's object. + /// The player's 'Bind Effects' object, or null if not found. + /// true if the 'Bind Effects' GameObject is successfully found and bound; otherwise, false. protected bool CreateObjects(GameObject playerObject, out GameObject bindEffects) { _localBindEffects ??= HeroController.instance.gameObject.FindGameObjectInChildren("Bind Effects"); if (_localBindEffects == null) { @@ -247,6 +261,12 @@ protected bool CreateObjects(GameObject playerObject, out GameObject bindEffects return true; } + /// + /// Finds and returns a bind effect with the specified name, creating it if it doesn't already exist. + /// + /// The player's Bind Effects object. + /// The name of the effect to find or create. + /// The existing or new effect, or null if the effect cannot be found or created protected GameObject? CreateEffectIfNotExists(GameObject bindEffects, string objectName) { var obj = bindEffects.FindGameObjectInChildren(objectName); if (obj == null) { diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index 05924e9..6c1285e 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Text; using HutongGames.PlayMaker.Actions; using SSMP.Internals; @@ -22,9 +23,11 @@ private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crest yield break; } - if (flags.BaseMirror || flags.UpgradedMirror) PlayMirror(bindEffects, flags.UpgradedMirror); - if (flags.BindBell) StopBindBell(bindEffects); + if (flags.BaseMirror || flags.UpgradedMirror) PlayMirror(playerObject, flags.UpgradedMirror); if (flags.Maggoted) PlayMaggotCleanse(bindEffects, playerObject); + + // Stop regardless of if its on or not + StopBindBell(bindEffects); switch (crestType) { case CrestType.Beast: @@ -40,19 +43,23 @@ private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crest PlayNormalEnd(bindEffects); } - private GameObject? PrepareMirror(GameObject bindEffects, SetGameObject mirrorSource, string name) { - var mirror = bindEffects.FindGameObjectInChildren(name); - if (mirror != null) { - mirror.SetActive(false); - return mirror; - } + /// + /// Creates the appropriate Claw Mirror object. + /// The object will be destroyed after it finishes. + /// + private GameObject? PrepareMirror(GameObject playerObject, SetGameObject mirrorSource, string name) { if (mirrorSource == null) { Logger.Warn("Unable to find mirror source"); return null; } - mirror = mirrorSource.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + // This is ugly and i hate it, but it works + var mirror = GameObject.Instantiate(mirrorSource.gameObject.Value, playerObject.transform); + mirror.transform.SetParent(null); + mirror.transform.position = playerObject.transform.position; + mirror.SetActive(true); + if (mirror == null) { Logger.Warn("Unable to spawn mirror"); return null; @@ -63,26 +70,31 @@ private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crest if (shaker != null) { Component.DestroyImmediate(shaker); } - var delay = mirror.AddComponentIfNotPresent(); - delay.time = 2f; + EffectUtils.SafelyRemoveAutoRecycle(mirror); + mirror.DestroyAfterTime(2f); + var haze = mirror.FindGameObjectInChildren("haze2"); if (haze != null) { GameObject.Destroy(haze); } - mirror.SetActive(false); + //mirror.SetActive(false); return mirror; } - private void PlayMirror(GameObject bindEffects, bool upgraded) { + /// + /// Plays the appropriate Claw Mirror animation. + /// Adds a damage component if appropriate. + /// + private void PlayMirror(GameObject playerObject, bool upgraded) { Logger.Info("Playing Claw Mirror Animation"); var regularClaw = GetOrFindBindFsm().GetAction("Dazzle?", 3)!; var upgradedClaw = GetOrFindBindFsm().GetAction("Dazzle?", 4)!; GameObject? claw; - if (upgraded) claw = PrepareMirror(bindEffects, upgradedClaw, "dazzle_upgraded"); - else claw = PrepareMirror(bindEffects, regularClaw, "dazzle_regular"); + if (upgraded) claw = PrepareMirror(playerObject, upgradedClaw, "dazzle_upgraded"); + else claw = PrepareMirror(playerObject, regularClaw, "dazzle_regular"); if (claw == null) { Logger.Warn("Unable to create claw mirror object."); @@ -99,14 +111,12 @@ private void PlayMirror(GameObject bindEffects, bool upgraded) { } else { Logger.Warn("Couldn't find claw mirror damager"); } - } else { - var damager = claw.GetComponentInChildren(true); - if (damager != null) { - Component.Destroy(damager); - } } } + /// + /// Stops the bind bell animation + /// private void StopBindBell(GameObject bindEffects) { var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); if (bindBell != null) { @@ -114,6 +124,9 @@ private void StopBindBell(GameObject bindEffects) { } } + /// + /// Plays the maggot cleanse animation + /// private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) { Logger.Info("Playing Maggot Animation"); var maggotBurst = GetOrFindBindFsm().GetAction("Maggoted?", 2); @@ -134,11 +147,17 @@ private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) } } + /// + /// Plays the Beast Crest specific rage animation + /// private void PlayBeastRage(GameObject bindEffects) { var beastRage = CreateEffectIfNotExists(bindEffects, "crest rage_burst_effect(Clone)"); beastRage?.SetActive(true); } + /// + /// Stops the Shaman Crest specific silk animation + /// private void PlayShamanEnd(GameObject bindEffects) { var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); if (shamanAntic == null) { @@ -147,6 +166,9 @@ private void PlayShamanEnd(GameObject bindEffects) { shamanAntic.SetActive(false); } + /// + /// Plays particles and shows a flash + /// private void PlayNormalEnd(GameObject bindEffects) { Logger.Info("Playing Heal Particles and Anim"); var healParticle = CreateEffectIfNotExists(bindEffects, "Pt Heal"); From f20f9ce22045289349a7357553d25370cf77a146 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:30:57 -0400 Subject: [PATCH 09/33] Add cursed crest --- SSMP/Animation/Effects/Bind.cs | 41 +++++++++++++++++++++++++++++++++ SSMP/Testing/PlayerAnimation.cs | 3 ++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 08f568c..f94da68 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -75,6 +75,8 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, PlayBeastBindStart(bindEffects); } else if (crestType == CrestType.Cursed) { Logger.Info("Playing Cursed Crest Animation"); + PlayCursedFail(bindEffects); + yield break; } else if (crestType == CrestType.Witch) { Logger.Info("Playing Witch Crest Animation"); } else if (crestType == CrestType.Shaman) { @@ -168,6 +170,45 @@ private void PlayBeastBindStart(GameObject bindEffects) { beastAntic.SetActive(false); beastAntic.SetActive(true); } + + private void PlayCursedFail(GameObject bindEffects) { + var failAntic = bindEffects.FindGameObjectInChildren("cursed_bind_fail"); + + tk2dSpriteAnimator animator; + if (failAntic == null) { + var effects = HeroController.instance.gameObject.FindGameObjectInChildren("Effects"); + if (effects == null) { + Logger.Warn("Unable to find local effects object"); + return; + } + + var localFailAntic = effects.FindGameObjectInChildren("Cursed Bind Hornet"); + if (localFailAntic == null) { + Logger.Warn("Unable to find local cursed bind object"); + return; + } + + failAntic = GameObject.Instantiate(localFailAntic, bindEffects.transform); + failAntic.name = "cursed_bind_fail"; + animator = failAntic.GetComponent(); + animator.AnimationCompletedEvent += PlayNextCursedPart; + } else { + animator = failAntic.GetComponent(); + } + + failAntic.SetActive(false); + failAntic.SetActive(true); + + animator.Play("Bind Cursed Start"); + } + + private void PlayNextCursedPart(tk2dSpriteAnimator animator, tk2dSpriteAnimationClip clip) { + if (clip.name == "Bind Cursed Start") { + animator.Play("Bind Cursed Mid"); + } else if (clip.name == "Bind Cursed Mid") { + animator.Play("Bind Cursed End"); + } + } /// /// Plays the Shaman Crest falling silk animation diff --git a/SSMP/Testing/PlayerAnimation.cs b/SSMP/Testing/PlayerAnimation.cs index eec09c8..e9d1b2f 100644 --- a/SSMP/Testing/PlayerAnimation.cs +++ b/SSMP/Testing/PlayerAnimation.cs @@ -46,6 +46,7 @@ void SetSettings(AnimationEffect bind) { void Init() { gameObject.SetActive(true); + ToolItemManager.SetEquippedCrest(Gameplay.CursedCrest.name); var hornet = HeroController.instance.gameObject; var position = hornet.transform.position; @@ -54,12 +55,12 @@ void Init() { } public void StartPreAnimation() { + Init(); CrestType crest = DetermineCrest(); if (crest != CrestType.Shaman) { return; } - Init(); string clipName = "BindCharge Ground"; var playerObject = transform.GetChild(0).gameObject; From 5edff7f3b3f8d93e466bc07a25d37f32a5a50af8 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:14:36 -0400 Subject: [PATCH 10/33] Create method to intercept FSM states --- SSMP/Fsm/FsmStateActionInjector.cs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 SSMP/Fsm/FsmStateActionInjector.cs diff --git a/SSMP/Fsm/FsmStateActionInjector.cs b/SSMP/Fsm/FsmStateActionInjector.cs new file mode 100644 index 0000000..15db8ec --- /dev/null +++ b/SSMP/Fsm/FsmStateActionInjector.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using HutongGames.PlayMaker; + +namespace SSMP.Fsm; + +internal class FsmStateActionInjector : FsmStateAction { + + private Action? _onStateEnter; + public override void OnEnter() { + _onStateEnter?.Invoke(Fsm.FsmComponent); + Finish(); + } + + public static void Inject(FsmState state, Action onEnter) { + Inject(state, 0, onEnter); + } + public static void Inject(FsmState state, int actionIndex, Action onEnter) { + var action = new FsmStateActionInjector(); + action.Fsm = state.Fsm; + action._onStateEnter = onEnter; + + var stateActions = state.Actions.ToList(); + stateActions.Insert(actionIndex, action); + state.Actions = stateActions.ToArray(); + state.SaveActions(); + } +} From e2201b278cb525fa640b6a5940ce621478c8a9cb Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:15:26 -0400 Subject: [PATCH 11/33] Add system for sub-effects --- SSMP/Animation/AnimationClip.cs | 3 ++ SSMP/Animation/AnimationManager.cs | 65 ++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/SSMP/Animation/AnimationClip.cs b/SSMP/Animation/AnimationClip.cs index d53f2f1..4884316 100644 --- a/SSMP/Animation/AnimationClip.cs +++ b/SSMP/Animation/AnimationClip.cs @@ -756,4 +756,7 @@ internal enum AnimationClip { NailArtChargeEnd, WallSlideEnd, HazardDeath, + + // Sub-animation names + WitchTentacles, } diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 91fb0c9..6b26f99 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -3,6 +3,7 @@ using System.Linq; using SSMP.Animation.Effects; using SSMP.Collection; +using SSMP.Fsm; using SSMP.Game; using SSMP.Game.Client; using SSMP.Game.Settings; @@ -615,6 +616,8 @@ internal class AnimationManager { { "Wound Double Strike", AnimationClip.WoundDoubleStrike }, { "Wound Zap", AnimationClip.WoundZap }, + { "Witch Tentacles!", AnimationClip.WitchTentacles } + }; /// @@ -653,6 +656,10 @@ internal class AnimationManager { { AnimationClip.RageBindBurst, new BindBurst() }, }; + private static readonly Dictionary SubAnimationEffects = new() { + { AnimationClip.WitchTentacles, new BindBurst() } + }; + /// /// The net client for sending animation updates. /// @@ -732,6 +739,10 @@ public void Initialize(ServerSettings serverSettings) { foreach (var effect in AnimationEffects.Values) { effect.SetServerSettings(serverSettings); } + + foreach (var effect in SubAnimationEffects.Values) { + effect.SetServerSettings(serverSettings); + } } /// @@ -749,6 +760,14 @@ public void RegisterHooks() { EventHooks.SpriteAnimatorWarpClipToLocalTime += Tk2dSpriteAnimatorOnWarpClipToLocalTime; EventHooks.SpriteAnimatorProcessEvents += Tk2dSpriteAnimatorOnProcessEvents; + // Register FSM hooks for certain bind actions + if (HeroController.SilentInstance != null) { + CreateWitchTentaclesHook(HeroController.instance); + } else { + HeroController.OnHeroInstanceSet += CreateWitchTentaclesHook; + } + + // Register a callback so we know when the dash has finished // On.HeroController.CancelDash += HeroControllerOnCancelDash; @@ -806,7 +825,11 @@ public void OnPlayerAnimationUpdate(ushort id, int clipId, int frame, byte[]? ef var animationClip = (AnimationClip) clipId; - if (AnimationEffects.TryGetValue(animationClip, out var animationEffect)) { + if (!AnimationEffects.TryGetValue(animationClip, out var animationEffect)) { + SubAnimationEffects.TryGetValue(animationClip, out animationEffect); + } + + if (animationEffect != null) { var playerObject = _playerManager.GetPlayerObject(id); if (!playerObject) { // Logger.Get().Warn(this, $"Tried to play animation effect {clipName} with ID: {id}, but player object doesn't exist"); @@ -854,6 +877,11 @@ public void UpdatePlayerAnimation(ushort id, int clipId, int frame, CrestType cr if (_debugLogAnimations) Logger.Info($"Received PlayerAnimationUpdate: {animationClip}"); + if (SubAnimationEffects.ContainsKey(animationClip)) { + if (_debugLogAnimations) Logger.Info($"PlayerAnimationUpdate was sub-effect: {animationClip}"); + return; + } + if (!ClipEnumNames.ContainsSecond(animationClip)) { // This happens when we send custom clips, that can't be played by the sprite animator, so for now we // don't log it. This warning might be useful if we seem to be missing animations from the Knights @@ -950,7 +978,7 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { return; } - if (_debugLogAnimations) Logger.Info($" conditions 1: {clip.name.Equals(_lastAnimationClip)}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Once}, {!AllowedLoopAnimations.Contains(clip.name)}"); + //if (_debugLogAnimations) Logger.Info($" conditions 1: {clip.name.Equals(_lastAnimationClip)}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Once}, {!AllowedLoopAnimations.Contains(clip.name)}"); // Skip event handling when we already handled this clip, unless it is a clip with wrap mode once if (clip.name.Equals(_lastAnimationClip) @@ -997,12 +1025,15 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { if (AnimationEffects.TryGetValue(animationClip, out var effect)) { var effectInfo = effect.GetEffectInfo(); + } else if (SubAnimationEffects.TryGetValue(animationClip, out var subEffect)) { + var effectInfo = subEffect.GetEffectInfo(); + _netClient.UpdateManager.UpdatePlayerAnimation(animationClip, 0, effectInfo); } else { _netClient.UpdateManager.UpdatePlayerAnimation(animationClip); } - if (_debugLogAnimations) Logger.Info($" Sending animation: {animationClip}"); + //if (_debugLogAnimations) Logger.Info($" Sending animation: {animationClip}"); // Update the last clip name, since it changed _lastAnimationClip = clip.name; @@ -1011,6 +1042,31 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { // _animationControllerWasLastSent = false; } + var heroFsms = hc.GetComponents(); + PlayMakerFSM bindFsm = heroFsms.FirstOrDefault(fsm => fsm.FsmName == "Bind"); + if (bindFsm != null) { + // Find witch crest tenticles + var tenticles = bindFsm.GetState("Witch Tentancles!"); // no that's not a typo... at least on my end + if (tenticles != null) { + FsmStateActionInjector.Inject(tenticles, 4, OnWitchTentacles); + } else { + Logger.Warn("Unable to find Witch Tentacles! state"); + } + } else { + Logger.Warn("Unable to find Bind FSM"); + } + } + + /// + /// Animation subanimation hook for the Witch Tentacles FSM state + /// + /// + private void OnWitchTentacles(PlayMakerFSM fsm) { + var dummyClip = new tk2dSpriteAnimationClip(); + dummyClip.name = "Witch Tentacles!"; + dummyClip.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once; + OnAnimationEvent(dummyClip); + } // /// // /// Callback method on the HeroAnimationController#Play method. // /// @@ -1168,7 +1224,7 @@ float time var frame = clip.frames[index]; if (index == 0 || frame.triggerEvent || AllowedLoopAnimations.Contains(clip.name)) { - if (_debugLogAnimations) Logger.Info($"OnAnimationEvent from tk2dSpriteAnimatorOnWarpClipToLocalTime: {clip.name}, conditions: {index == 0}, {frame.triggerEvent}, {AllowedLoopAnimations.Contains(clip.name)}"); + //if (_debugLogAnimations) Logger.Info($"OnAnimationEvent from tk2dSpriteAnimatorOnWarpClipToLocalTime: {clip.name}, conditions: {index == 0}, {frame.triggerEvent}, {AllowedLoopAnimations.Contains(clip.name)}"); OnAnimationEvent(clip); } } @@ -1221,6 +1277,7 @@ int direction // } if (_debugLogAnimations) Logger.Info($"OnAnimationEvent from tk2dSpriteAnimatorOnProcessEvents: {self.CurrentClip.name}, conditions: {i}, {frames[i].triggerEvent}"); + //if (_debugLogAnimations) Logger.Info($"OnAnimationEvent from tk2dSpriteAnimatorOnProcessEvents: {self.CurrentClip.name}, conditions: {i}, {frames[i].triggerEvent}"); // OnAnimationEvent(self.CurrentClip); } } From ea6de8b745e51525bfba9fe4c70e084565c99bb4 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:15:34 -0400 Subject: [PATCH 12/33] Finalize witch crest --- SSMP/Animation/Effects/Bind.cs | 67 ++++++++++++++++++++++++++--- SSMP/Animation/Effects/BindBurst.cs | 62 ++++++++++++++++---------- 2 files changed, 100 insertions(+), 29 deletions(-) diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index f94da68..220cf2e 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -54,11 +54,13 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, var randomClipAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); var playAudioAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); - AudioUtil.PlayAudioEventWithRandomAudioClipFromTableAtPlayerObject( - playAudioAction, - randomClipAction, - playerObject - ); + if (!ShamanDoneFalling) { + AudioUtil.PlayAudioEventWithRandomAudioClipFromTableAtPlayerObject( + playAudioAction, + randomClipAction, + playerObject + ); + } var oneShotSingleAction = GetOrFindBindFsm().GetFirstAction("Check Grounded"); AudioUtil.PlayAudioOneShotSingleAtPlayerObject(oneShotSingleAction, playerObject); @@ -79,6 +81,15 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, yield break; } else if (crestType == CrestType.Witch) { Logger.Info("Playing Witch Crest Animation"); + if (flags.Maggoted) { + PlayWitchMaggoted(bindEffects); + PlayMaggotCleanse(bindEffects, playerObject); + yield break; + } else { + PlayWitchAnimationAntic(bindEffects); + //PlayWitchAnimation(bindEffects); + } + } else if (crestType == CrestType.Shaman) { Logger.Info("Playing Shaman Crest Animation"); if (!ShamanDoneFalling) { @@ -156,6 +167,9 @@ private void PlayNormalStart(GameObject bindEffects, Flags flags) { if (flags.QuickBind) bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk Quick")); else bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk")); + + bindSilkObj.SetActive(false); + bindSilkObj.SetActive(true); } /// @@ -209,7 +223,48 @@ private void PlayNextCursedPart(tk2dSpriteAnimator animator, tk2dSpriteAnimation animator.Play("Bind Cursed End"); } } - + + private void PlayWitchMaggoted(GameObject bindEffects) { + var maggotCleanse = CreateEffectIfNotExists(bindEffects, "Witch Bind Maggot Cleanse"); + if (maggotCleanse != null) { + maggotCleanse.SetActive(false); + maggotCleanse.SetActive(true); + } + } + + private void PlayWitchAnimationAntic(GameObject bindEffects) { + var silkAntic = CreateEffectIfNotExists(bindEffects, "Whip_Bind_silk_antic"); + if (silkAntic == null) { + return; + } + + silkAntic.SetActive(false); + silkAntic.SetActive(true); + } + + /// + /// Plays the maggot cleanse animation + /// + protected void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) { + Logger.Info("Playing Maggot Animation"); + var maggotBurst = GetOrFindBindFsm().GetAction("Maggoted?", 2); + var maggotFlash = GetOrFindBindFsm().GetAction("Maggoted?", 4); + var maggotAudio = GetOrFindBindFsm().GetFirstAction("Maggoted?"); + + if (maggotBurst != null) { + maggotBurst.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + } + if (maggotFlash != null) { + maggotFlash.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + } + if (maggotAudio != null) { + AudioUtil.PlayAudioEventAtPlayerObject( + maggotAudio, + playerObject + ); + } + } + /// /// Plays the Shaman Crest falling silk animation /// diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index 6c1285e..6d05ad1 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -33,6 +33,9 @@ private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crest case CrestType.Beast: PlayBeastRage(bindEffects); break; + case CrestType.Witch: + PlayWitchEnd(bindEffects); + break; case CrestType.Shaman: PlayShamanEnd(bindEffects); break; @@ -124,29 +127,6 @@ private void StopBindBell(GameObject bindEffects) { } } - /// - /// Plays the maggot cleanse animation - /// - private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) { - Logger.Info("Playing Maggot Animation"); - var maggotBurst = GetOrFindBindFsm().GetAction("Maggoted?", 2); - var maggotFlash = GetOrFindBindFsm().GetAction("Maggoted?", 4); - var maggotAudio = GetOrFindBindFsm().GetFirstAction("Maggoted?"); - - if (maggotBurst != null) { - maggotBurst.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); - } - if (maggotFlash != null) { - maggotFlash.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); - } - if (maggotAudio != null) { - AudioUtil.PlayAudioEventAtPlayerObject( - maggotAudio, - playerObject - ); - } - } - /// /// Plays the Beast Crest specific rage animation /// @@ -155,6 +135,42 @@ private void PlayBeastRage(GameObject bindEffects) { beastRage?.SetActive(true); } + private void PlayWitchEnd(GameObject bindEffects) { + var witchBind = bindEffects.FindGameObjectInChildren("Witch Bind"); + if (witchBind == null) { + var localWitchBind = _localBindEffects.FindGameObjectInChildren("Witch Bind"); + if (localWitchBind == null) { + Logger.Warn("Unable to find local Witch Bind object"); + return; + } + + witchBind = GameObject.Instantiate(localWitchBind, bindEffects.transform); + + var shaker = witchBind.GetComponent(); + if (shaker != null) { + Component.DestroyImmediate(shaker); + } + + if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { + SetWitchDamagers(witchBind); + } + } + + witchBind.SetActive(false); + witchBind.SetActive(true); + } + + private void SetWitchDamagers(GameObject witchBind) { + for (int i = 0; i < witchBind.transform.childCount; i++) { + var child = witchBind.transform.GetChild(i); + if (!child.name.StartsWith("Damager")) { + continue; + } + + AddDamageHeroComponent(child.gameObject); + } + } + /// /// Stops the Shaman Crest specific silk animation /// From 2567a379bc5bc1685fcfcb74dfd9d1f6b0eb7dc1 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:37:40 -0400 Subject: [PATCH 13/33] Accidentally committed log removals --- SSMP/Animation/AnimationManager.cs | 11 ++++++----- SSMP/Testing/PlayerAnimation.cs | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 6b26f99..38582da 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -978,7 +978,7 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { return; } - //if (_debugLogAnimations) Logger.Info($" conditions 1: {clip.name.Equals(_lastAnimationClip)}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Once}, {!AllowedLoopAnimations.Contains(clip.name)}"); + if (_debugLogAnimations) Logger.Info($" conditions 1: {clip.name.Equals(_lastAnimationClip)}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Once}, {!AllowedLoopAnimations.Contains(clip.name)}"); // Skip event handling when we already handled this clip, unless it is a clip with wrap mode once if (clip.name.Equals(_lastAnimationClip) @@ -987,7 +987,7 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { return; } - //if (_debugLogAnimations) Logger.Info($" conditions 2: {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Loop}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.LoopSection}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Once}"); + if (_debugLogAnimations) Logger.Info($" conditions 2: {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Loop}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.LoopSection}, {clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Once}"); // Skip clips that do not have the wrap mode loop, loop-section or once if (clip.wrapMode != tk2dSpriteAnimationClip.WrapMode.Loop && @@ -1025,6 +1025,7 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { if (AnimationEffects.TryGetValue(animationClip, out var effect)) { var effectInfo = effect.GetEffectInfo(); + _netClient.UpdateManager.UpdatePlayerAnimation(animationClip, 0, effectInfo); } else if (SubAnimationEffects.TryGetValue(animationClip, out var subEffect)) { var effectInfo = subEffect.GetEffectInfo(); @@ -1033,7 +1034,7 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { _netClient.UpdateManager.UpdatePlayerAnimation(animationClip); } - //if (_debugLogAnimations) Logger.Info($" Sending animation: {animationClip}"); + if (_debugLogAnimations) Logger.Info($" Sending animation: {animationClip}"); // Update the last clip name, since it changed _lastAnimationClip = clip.name; @@ -1042,6 +1043,7 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { // _animationControllerWasLastSent = false; } + private void CreateWitchTentaclesHook(HeroController hc) { var heroFsms = hc.GetComponents(); PlayMakerFSM bindFsm = heroFsms.FirstOrDefault(fsm => fsm.FsmName == "Bind"); if (bindFsm != null) { @@ -1224,7 +1226,7 @@ float time var frame = clip.frames[index]; if (index == 0 || frame.triggerEvent || AllowedLoopAnimations.Contains(clip.name)) { - //if (_debugLogAnimations) Logger.Info($"OnAnimationEvent from tk2dSpriteAnimatorOnWarpClipToLocalTime: {clip.name}, conditions: {index == 0}, {frame.triggerEvent}, {AllowedLoopAnimations.Contains(clip.name)}"); + if (_debugLogAnimations) Logger.Info($"OnAnimationEvent from tk2dSpriteAnimatorOnWarpClipToLocalTime: {clip.name}, conditions: {index == 0}, {frame.triggerEvent}, {AllowedLoopAnimations.Contains(clip.name)}"); OnAnimationEvent(clip); } } @@ -1277,7 +1279,6 @@ int direction // } if (_debugLogAnimations) Logger.Info($"OnAnimationEvent from tk2dSpriteAnimatorOnProcessEvents: {self.CurrentClip.name}, conditions: {i}, {frames[i].triggerEvent}"); - //if (_debugLogAnimations) Logger.Info($"OnAnimationEvent from tk2dSpriteAnimatorOnProcessEvents: {self.CurrentClip.name}, conditions: {i}, {frames[i].triggerEvent}"); // OnAnimationEvent(self.CurrentClip); } } diff --git a/SSMP/Testing/PlayerAnimation.cs b/SSMP/Testing/PlayerAnimation.cs index e9d1b2f..dd738f9 100644 --- a/SSMP/Testing/PlayerAnimation.cs +++ b/SSMP/Testing/PlayerAnimation.cs @@ -46,7 +46,7 @@ void SetSettings(AnimationEffect bind) { void Init() { gameObject.SetActive(true); - ToolItemManager.SetEquippedCrest(Gameplay.CursedCrest.name); + //ToolItemManager.SetEquippedCrest(Gameplay.CursedCrest.name); var hornet = HeroController.instance.gameObject; var position = hornet.transform.position; From b335f556e60648e7274d9b7531c8c4aab3e23ec0 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:08:36 -0400 Subject: [PATCH 14/33] Fix architect charged downspike --- SSMP/Animation/AnimationClip.cs | 4 ++++ SSMP/Animation/AnimationManager.cs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/SSMP/Animation/AnimationClip.cs b/SSMP/Animation/AnimationClip.cs index 4884316..a9c8677 100644 --- a/SSMP/Animation/AnimationClip.cs +++ b/SSMP/Animation/AnimationClip.cs @@ -415,6 +415,10 @@ internal enum AnimationClip { /// DownSpikeCharge, /// + /// Fully charged down slash with Architect crest + /// + DownSpikeCharged, + /// /// Slash while sprinting/dashing with Architect crest /// DashAttackCharge, diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 38582da..8bff850 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -344,6 +344,7 @@ internal class AnimationManager { { "BindCharge Witch Long", AnimationClip.BindChargeWitchLong }, { "DownSpike Charge", AnimationClip.DownSpikeCharge }, + { "DownSpike Charged", AnimationClip.DownSpikeCharged }, { "Dash Attack Charge", AnimationClip.DashAttackCharge }, { "Quick Craft Ground", AnimationClip.QuickCraftGround }, { "Quick Craft Air", AnimationClip.QuickCraftAir }, @@ -629,6 +630,7 @@ internal class AnimationManager { { AnimationClip.UpSlash, new Slash(SlashBase.SlashType.Up) }, { AnimationClip.WallSlash, new Slash(SlashBase.SlashType.Wall) }, { AnimationClip.DownSpike, new DownSpike() }, // Hunter Crest down slash + { AnimationClip.DownSpikeCharged, new DownSpike() }, // Architect Crest charged down slash { AnimationClip.V3DownSlash, new Slash(SlashBase.SlashType.Down) }, { AnimationClip.DownSlash, new Slash(SlashBase.SlashType.Down) }, { AnimationClip.DownSlashAlt, new Slash(SlashBase.SlashType.DownAlt) }, From 0bb6f5c92710c9b6960667f974fe04b6a2a42b98 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:13:12 -0400 Subject: [PATCH 15/33] use static instance of bindburst --- SSMP/Animation/AnimationManager.cs | 10 +++++----- SSMP/Animation/Effects/Bind.cs | 2 +- SSMP/Animation/Effects/BindBurst.cs | 6 ++++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 8bff850..af39e0e 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -652,14 +652,14 @@ internal class AnimationManager { { AnimationClip.NeedleArtDash, new NeedleStrike(false) }, { AnimationClip.BindChargeGround, new Bind() }, { AnimationClip.BindChargeGroundLand, new Bind { ShamanDoneFalling = true } }, - { AnimationClip.BindBurstGround, new BindBurst() }, - { AnimationClip.BindChargeHealBurst, new BindBurst() }, - { AnimationClip.BindBurstAir, new BindBurst() }, - { AnimationClip.RageBindBurst, new BindBurst() }, + { AnimationClip.BindBurstGround, BindBurst.Instance }, + { AnimationClip.BindChargeHealBurst, BindBurst.Instance }, + { AnimationClip.BindBurstAir, BindBurst.Instance }, + { AnimationClip.RageBindBurst, BindBurst.Instance }, }; private static readonly Dictionary SubAnimationEffects = new() { - { AnimationClip.WitchTentacles, new BindBurst() } + { AnimationClip.WitchTentacles, BindBurst.Instance } }; /// diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 220cf2e..88f4c7a 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -15,7 +15,7 @@ namespace SSMP.Animation.Effects; /// internal class Bind : DamageAnimationEffect { - protected string BIND_BELL_NAME = "bind_bell_appear_instance"; + protected const string BIND_BELL_NAME = "bind_bell_appear_instance"; public bool ShamanDoneFalling = false; diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index 6d05ad1..e7401cc 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -13,6 +13,12 @@ namespace SSMP.Animation.Effects; internal class BindBurst : Bind { + /// + /// Static instance for access by multiple animation clips in . + /// + private static BindBurst? _instance; + /// + public new static BindBurst Instance => _instance ??= new BindBurst(); public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { Flags flags = new Flags(effectInfo); MonoBehaviourUtil.Instance.StartCoroutine(PlayBindBurstEffect(playerObject, crestType, flags)); From 3c8e537ec412909c75a1558128d2449a9d0fdbe0 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:59:54 -0400 Subject: [PATCH 16/33] finish shaman, fix maggot effect --- SSMP/Animation/AnimationClip.cs | 1 + SSMP/Animation/AnimationEffect.cs | 2 +- SSMP/Animation/AnimationManager.cs | 59 ++++++++++----- SSMP/Animation/DamageAnimationEffect.cs | 2 +- SSMP/Animation/Effects/Bind.cs | 91 +++++++++++------------ SSMP/Animation/Effects/BindBurst.cs | 52 +++++++++++-- SSMP/Animation/Effects/DashSlash.cs | 4 +- SSMP/Animation/Effects/DashSlashAntic.cs | 4 +- SSMP/Animation/Effects/DashSlashReaper.cs | 4 +- SSMP/Animation/Effects/DownSpike.cs | 2 +- SSMP/Animation/Effects/EffectUtils.cs | 13 ++++ SSMP/Animation/Effects/NeedleStrike.cs | 4 +- SSMP/Animation/Effects/Slash.cs | 4 +- SSMP/Animation/Effects/SlashBase.cs | 2 +- SSMP/Animation/IAnimationEffect.cs | 5 +- SSMP/Testing/PlayerAnimation.cs | 39 +++++++--- 16 files changed, 194 insertions(+), 94 deletions(-) diff --git a/SSMP/Animation/AnimationClip.cs b/SSMP/Animation/AnimationClip.cs index a9c8677..cb1bd97 100644 --- a/SSMP/Animation/AnimationClip.cs +++ b/SSMP/Animation/AnimationClip.cs @@ -763,4 +763,5 @@ internal enum AnimationClip { // Sub-animation names WitchTentacles, + ShamanCancel } diff --git a/SSMP/Animation/AnimationEffect.cs b/SSMP/Animation/AnimationEffect.cs index 56e6960..ed09520 100644 --- a/SSMP/Animation/AnimationEffect.cs +++ b/SSMP/Animation/AnimationEffect.cs @@ -14,7 +14,7 @@ internal abstract class AnimationEffect : IAnimationEffect { protected ServerSettings ServerSettings = null!; /// - public abstract void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo); + public abstract void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo); /// public abstract byte[]? GetEffectInfo(); diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index af39e0e..30ceb4e 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -617,7 +617,8 @@ internal class AnimationManager { { "Wound Double Strike", AnimationClip.WoundDoubleStrike }, { "Wound Zap", AnimationClip.WoundZap }, - { "Witch Tentacles!", AnimationClip.WitchTentacles } + { "Witch Tentacles!", AnimationClip.WitchTentacles }, + { "Shaman Cancel", AnimationClip.ShamanCancel }, }; @@ -651,15 +652,16 @@ internal class AnimationManager { { AnimationClip.SlashChargedLoop, new NeedleStrike(true) }, { AnimationClip.NeedleArtDash, new NeedleStrike(false) }, { AnimationClip.BindChargeGround, new Bind() }, - { AnimationClip.BindChargeGroundLand, new Bind { ShamanDoneFalling = true } }, + { AnimationClip.BindChargeGroundLand, new Bind { BindState = Bind.State.ShamanDoneFalling } }, { AnimationClip.BindBurstGround, BindBurst.Instance }, { AnimationClip.BindChargeHealBurst, BindBurst.Instance }, { AnimationClip.BindBurstAir, BindBurst.Instance }, - { AnimationClip.RageBindBurst, BindBurst.Instance }, + { AnimationClip.RageBindBurst, BindBurst.Instance } }; private static readonly Dictionary SubAnimationEffects = new() { - { AnimationClip.WitchTentacles, BindBurst.Instance } + { AnimationClip.WitchTentacles, BindBurst.Instance }, + { AnimationClip.ShamanCancel, new Bind { BindState = Bind.State.ShamanCancel } } }; /// @@ -764,9 +766,9 @@ public void RegisterHooks() { // Register FSM hooks for certain bind actions if (HeroController.SilentInstance != null) { - CreateWitchTentaclesHook(HeroController.instance); + CreateBindHooks(HeroController.instance); } else { - HeroController.OnHeroInstanceSet += CreateWitchTentaclesHook; + HeroController.OnHeroInstanceSet += CreateBindHooks; } @@ -856,6 +858,7 @@ public void OnPlayerAnimationUpdate(ushort id, int clipId, int frame, byte[]? ef animationEffect.Play( playerObject, crestType, + id, effectInfo ); } @@ -1045,32 +1048,54 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { // _animationControllerWasLastSent = false; } - private void CreateWitchTentaclesHook(HeroController hc) { + /// + /// Creates hooks for the Witch Tentacles and Shaman Cancel states in + /// the Bind fsm once the HeroController is ready. + /// + private void CreateBindHooks(HeroController hc) { var heroFsms = hc.GetComponents(); PlayMakerFSM bindFsm = heroFsms.FirstOrDefault(fsm => fsm.FsmName == "Bind"); - if (bindFsm != null) { - // Find witch crest tenticles - var tenticles = bindFsm.GetState("Witch Tentancles!"); // no that's not a typo... at least on my end - if (tenticles != null) { - FsmStateActionInjector.Inject(tenticles, 4, OnWitchTentacles); - } else { - Logger.Warn("Unable to find Witch Tentacles! state"); - } + if (bindFsm == null) { + Logger.Warn("Unable to find Bind FSM to hook."); + return; + } + + // Find witch crest tenticles + var tenticles = bindFsm.GetState("Witch Tentancles!"); // no that's not a typo... at least on my end + if (tenticles != null) { + FsmStateActionInjector.Inject(tenticles, 4, OnWitchTentacles); + } else { + Logger.Warn("Unable to find Witch Tentacles! state"); + } + + var shamanCancel = bindFsm.GetState("Shaman Air Cancel"); + if (shamanCancel != null) { + FsmStateActionInjector.Inject(shamanCancel, OnShamanCancel); } else { - Logger.Warn("Unable to find Bind FSM"); + Logger.Warn("Unable to find Shaman Air Cancel state"); } } /// /// Animation subanimation hook for the Witch Tentacles FSM state /// - /// private void OnWitchTentacles(PlayMakerFSM fsm) { var dummyClip = new tk2dSpriteAnimationClip(); dummyClip.name = "Witch Tentacles!"; dummyClip.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once; OnAnimationEvent(dummyClip); } + + /// + /// Animation subanimation hook for the Shaman Air Cancel FSM state + /// + private void OnShamanCancel(PlayMakerFSM fsm) { + var dummyClip = new tk2dSpriteAnimationClip(); + dummyClip.name = "Shaman Cancel"; + dummyClip.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once; + OnAnimationEvent(dummyClip); + } + // /// // /// Callback method on the HeroAnimationController#Play method. // /// diff --git a/SSMP/Animation/DamageAnimationEffect.cs b/SSMP/Animation/DamageAnimationEffect.cs index 527eac7..f22cd04 100644 --- a/SSMP/Animation/DamageAnimationEffect.cs +++ b/SSMP/Animation/DamageAnimationEffect.cs @@ -14,7 +14,7 @@ internal abstract class DamageAnimationEffect : AnimationEffect { protected bool ShouldDoDamage; /// - public abstract override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo); + public abstract override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo); /// public abstract override byte[]? GetEffectInfo(); diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 88f4c7a..cf46e68 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -15,9 +15,15 @@ namespace SSMP.Animation.Effects; /// internal class Bind : DamageAnimationEffect { + public enum State { + Normal, + ShamanCancel, + ShamanDoneFalling + } + protected const string BIND_BELL_NAME = "bind_bell_appear_instance"; - public bool ShamanDoneFalling = false; + public State BindState = State.Normal; protected class Flags { public bool BindBell = false; @@ -45,8 +51,17 @@ public Flags(byte[]? info) { protected static GameObject? _localBindEffects; - public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { Flags flags = new Flags(effectInfo); + + // The maggot state is cleared by the time the bind burst is sent. + // This keeps track of it, although at a slight possible loss of consistancy + if (flags.Maggoted && !(crestType == CrestType.Shaman && BindState == State.ShamanCancel)) { + BindBurst.MaggotedPlayers.Add(playerId); + } else { + BindBurst.MaggotedPlayers.Remove(playerId); + } + MonoBehaviourUtil.Instance.StartCoroutine(PlayBindEffect(playerObject, crestType, flags)); } @@ -54,7 +69,7 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, var randomClipAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); var playAudioAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); - if (!ShamanDoneFalling) { + if (BindState == State.Normal) { AudioUtil.PlayAudioEventWithRandomAudioClipFromTableAtPlayerObject( playAudioAction, randomClipAction, @@ -81,19 +96,14 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, yield break; } else if (crestType == CrestType.Witch) { Logger.Info("Playing Witch Crest Animation"); - if (flags.Maggoted) { - PlayWitchMaggoted(bindEffects); - PlayMaggotCleanse(bindEffects, playerObject); - yield break; - } else { - PlayWitchAnimationAntic(bindEffects); - //PlayWitchAnimation(bindEffects); - } - + PlayWitchAnimationAntic(bindEffects); } else if (crestType == CrestType.Shaman) { Logger.Info("Playing Shaman Crest Animation"); - if (!ShamanDoneFalling) { + if (BindState == State.Normal) { PlayShamanFall(bindEffects); + } else if (BindState == State.ShamanCancel) { + PlayShamanCancel(playerObject, bindEffects); + yield break; } else { PlayShamanFallEnd(bindEffects); PlayNormalStart(bindEffects, flags); @@ -149,6 +159,7 @@ private void StartBindBell(GameObject bindEffects) { bindBell.SetActive(false); bindBell.SetActive(true); } + /// /// Plays the normal silk animation /// @@ -224,14 +235,6 @@ private void PlayNextCursedPart(tk2dSpriteAnimator animator, tk2dSpriteAnimation } } - private void PlayWitchMaggoted(GameObject bindEffects) { - var maggotCleanse = CreateEffectIfNotExists(bindEffects, "Witch Bind Maggot Cleanse"); - if (maggotCleanse != null) { - maggotCleanse.SetActive(false); - maggotCleanse.SetActive(true); - } - } - private void PlayWitchAnimationAntic(GameObject bindEffects) { var silkAntic = CreateEffectIfNotExists(bindEffects, "Whip_Bind_silk_antic"); if (silkAntic == null) { @@ -242,29 +245,6 @@ private void PlayWitchAnimationAntic(GameObject bindEffects) { silkAntic.SetActive(true); } - /// - /// Plays the maggot cleanse animation - /// - protected void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) { - Logger.Info("Playing Maggot Animation"); - var maggotBurst = GetOrFindBindFsm().GetAction("Maggoted?", 2); - var maggotFlash = GetOrFindBindFsm().GetAction("Maggoted?", 4); - var maggotAudio = GetOrFindBindFsm().GetFirstAction("Maggoted?"); - - if (maggotBurst != null) { - maggotBurst.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); - } - if (maggotFlash != null) { - maggotFlash.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); - } - if (maggotAudio != null) { - AudioUtil.PlayAudioEventAtPlayerObject( - maggotAudio, - playerObject - ); - } - } - /// /// Plays the Shaman Crest falling silk animation /// @@ -282,6 +262,24 @@ private void PlayShamanFall(GameObject bindEffects) { shamanAntic.SetActive(true); } + private void PlayShamanCancel(GameObject playerObject, GameObject bindEffects) { + var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); + if (shamanAntic != null) { + shamanAntic.SetActive(false); + } + + var silkPuffSpawner = GetOrFindBindFsm().GetFirstAction("Shaman Air Cancel"); + if (silkPuffSpawner == null) { + Logger.Warn("Unable to find FSM action for Shaman Air Cancel"); + return; + } + + var globalSilkPuff = silkPuffSpawner.gameObject.Value; + EffectUtils.SpawnGlobalPoolObject(globalSilkPuff, playerObject.transform, false); + + BindBurst.Instance.StopBindBell(bindEffects); + } + /// /// Transitions from the falling silk animation to the normal silk animation /// @@ -305,10 +303,9 @@ private void PlayShamanFallEnd(GameObject bindEffects) { (byte) (ToolItemManager.IsToolEquipped("Dazzle Bind Upgraded") ? 1 : 0), (byte) (ToolItemManager.IsToolEquipped("Quickbind") ? 1 : 0), (byte) (ToolItemManager.IsToolEquipped("Reserve Bind") ? 1 : 0), - (byte) (HeroController.instance.cState.isMaggoted ? 1 : 0), - //(byte) (HeroController.instance.onFlatGround ? 0 : 1) + (byte) (HeroController.instance.cState.isMaggoted ? 1 : 0) }; - + Logger.Info(HeroController.instance.cState.isMaggoted ? "MAGGOTS" : "NO MAGGOTS"); return effectInfo; } diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index e7401cc..41c09e2 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -18,9 +18,14 @@ internal class BindBurst : Bind { /// private static BindBurst? _instance; /// - public new static BindBurst Instance => _instance ??= new BindBurst(); - public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { + public static BindBurst Instance => _instance ??= new BindBurst(); + public static HashSet MaggotedPlayers = new HashSet(); + public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { Flags flags = new Flags(effectInfo); + if (MaggotedPlayers.Contains(playerId)) { + flags.Maggoted = true; + MaggotedPlayers.Remove(playerId); + } MonoBehaviourUtil.Instance.StartCoroutine(PlayBindBurstEffect(playerObject, crestType, flags)); } @@ -37,10 +42,16 @@ private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crest switch (crestType) { case CrestType.Beast: - PlayBeastRage(bindEffects); + if (!flags.Maggoted) { + PlayBeastRage(bindEffects); + } break; case CrestType.Witch: - PlayWitchEnd(bindEffects); + if (!flags.Maggoted) { + PlayWitchEnd(bindEffects); + } else { + PlayWitchMaggoted(bindEffects); + } break; case CrestType.Shaman: PlayShamanEnd(bindEffects); @@ -52,6 +63,29 @@ private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crest PlayNormalEnd(bindEffects); } + /// + /// Plays the maggot cleanse animation + /// + protected void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) { + Logger.Info("Playing Maggot Animation"); + var maggotBurst = GetOrFindBindFsm().GetAction("Maggoted?", 2); + var maggotFlash = GetOrFindBindFsm().GetAction("Maggoted?", 4); + var maggotAudio = GetOrFindBindFsm().GetFirstAction("Maggoted?"); + + if (maggotBurst != null) { + maggotBurst.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + } + if (maggotFlash != null) { + maggotFlash.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + } + if (maggotAudio != null) { + AudioUtil.PlayAudioEventAtPlayerObject( + maggotAudio, + playerObject + ); + } + } + /// /// Creates the appropriate Claw Mirror object. /// The object will be destroyed after it finishes. @@ -126,7 +160,7 @@ private void PlayMirror(GameObject playerObject, bool upgraded) { /// /// Stops the bind bell animation /// - private void StopBindBell(GameObject bindEffects) { + public void StopBindBell(GameObject bindEffects) { var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); if (bindBell != null) { bindBell.SetActive(false); @@ -141,6 +175,14 @@ private void PlayBeastRage(GameObject bindEffects) { beastRage?.SetActive(true); } + private void PlayWitchMaggoted(GameObject bindEffects) { + var maggotCleanse = CreateEffectIfNotExists(bindEffects, "Witch Bind Maggot Cleanse"); + if (maggotCleanse != null) { + maggotCleanse.SetActive(false); + maggotCleanse.SetActive(true); + } + } + private void PlayWitchEnd(GameObject bindEffects) { var witchBind = bindEffects.FindGameObjectInChildren("Witch Bind"); if (witchBind == null) { diff --git a/SSMP/Animation/Effects/DashSlash.cs b/SSMP/Animation/Effects/DashSlash.cs index 43b3f08..14edeb0 100644 --- a/SSMP/Animation/Effects/DashSlash.cs +++ b/SSMP/Animation/Effects/DashSlash.cs @@ -1,4 +1,4 @@ -using SSMP.Internals; +using SSMP.Internals; using SSMP.Networking.Packet; using SSMP.Util; using UnityEngine; @@ -25,7 +25,7 @@ public DashSlash(DashSlashType dashSlashType) { } /// - public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { if (effectInfo == null || effectInfo.Length < 1) { Logger.Error("Could not get null or empty effect info for DashAttack"); return; diff --git a/SSMP/Animation/Effects/DashSlashAntic.cs b/SSMP/Animation/Effects/DashSlashAntic.cs index 0e23af9..78c4845 100644 --- a/SSMP/Animation/Effects/DashSlashAntic.cs +++ b/SSMP/Animation/Effects/DashSlashAntic.cs @@ -1,4 +1,4 @@ -using HutongGames.PlayMaker.Actions; +using HutongGames.PlayMaker.Actions; using SSMP.Internals; using SSMP.Util; using UnityEngine; @@ -23,7 +23,7 @@ private DashSlashAntic() { } /// - public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { var sprintFsm = HeroController.instance.sprintFSM; PlayAudioEvent playAudioAction; switch (crestType) { diff --git a/SSMP/Animation/Effects/DashSlashReaper.cs b/SSMP/Animation/Effects/DashSlashReaper.cs index 1444fae..e095316 100644 --- a/SSMP/Animation/Effects/DashSlashReaper.cs +++ b/SSMP/Animation/Effects/DashSlashReaper.cs @@ -1,4 +1,4 @@ -using SSMP.Internals; +using SSMP.Internals; using SSMP.Util; using UnityEngine; @@ -9,7 +9,7 @@ namespace SSMP.Animation.Effects; /// internal class DashSlashReaper : SlashBase { /// - public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { // Call the base function with the correct parameters Play(playerObject, crestType, effectInfo, SlashType.Dash); diff --git a/SSMP/Animation/Effects/DownSpike.cs b/SSMP/Animation/Effects/DownSpike.cs index 88e7110..4b174a3 100644 --- a/SSMP/Animation/Effects/DownSpike.cs +++ b/SSMP/Animation/Effects/DownSpike.cs @@ -11,7 +11,7 @@ namespace SSMP.Animation.Effects; /// internal class DownSpike : SlashBase { /// - public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { if (effectInfo == null || effectInfo.Length < 1) { Logger.Error("Could not get null or empty effect info for SlashBase"); return; diff --git a/SSMP/Animation/Effects/EffectUtils.cs b/SSMP/Animation/Effects/EffectUtils.cs index c4ba757..6a029ff 100644 --- a/SSMP/Animation/Effects/EffectUtils.cs +++ b/SSMP/Animation/Effects/EffectUtils.cs @@ -15,4 +15,17 @@ public static void SafelyRemoveAutoRecycle(GameObject obj) { Component.Destroy(recycler); } } + + public static GameObject SpawnGlobalPoolObject(GameObject globalObj, Transform spawnLocation, bool keepParent = false) { + var newObj = GameObject.Instantiate(globalObj, spawnLocation); + + if (!keepParent) { + newObj.transform.SetParent(null); + newObj.transform.position = spawnLocation.position; + } + newObj.SetActive(true); + + SafelyRemoveAutoRecycle(newObj); + return newObj; + } } diff --git a/SSMP/Animation/Effects/NeedleStrike.cs b/SSMP/Animation/Effects/NeedleStrike.cs index 72b580f..a50d76f 100644 --- a/SSMP/Animation/Effects/NeedleStrike.cs +++ b/SSMP/Animation/Effects/NeedleStrike.cs @@ -1,4 +1,4 @@ -using System; +using System; using SSMP.Internals; using SSMP.Networking.Packet; using SSMP.Util; @@ -102,7 +102,7 @@ public NeedleStrike(bool witchLoop) { } /// - public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { if (effectInfo == null || effectInfo.Length < 1) { Logger.Error("Could not get null or empty effect info for SlashBase"); return; diff --git a/SSMP/Animation/Effects/Slash.cs b/SSMP/Animation/Effects/Slash.cs index 93ea975..c4350c5 100644 --- a/SSMP/Animation/Effects/Slash.cs +++ b/SSMP/Animation/Effects/Slash.cs @@ -1,4 +1,4 @@ -using SSMP.Internals; +using SSMP.Internals; using UnityEngine; namespace SSMP.Animation.Effects; @@ -21,7 +21,7 @@ public Slash(SlashType slashType) { } /// - public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { // Call the base function with the correct parameters Play(playerObject, crestType, effectInfo, _slashType); } diff --git a/SSMP/Animation/Effects/SlashBase.cs b/SSMP/Animation/Effects/SlashBase.cs index cff731b..e789410 100644 --- a/SSMP/Animation/Effects/SlashBase.cs +++ b/SSMP/Animation/Effects/SlashBase.cs @@ -16,7 +16,7 @@ namespace SSMP.Animation.Effects; /// internal abstract class SlashBase : ParryableEffect { /// - public abstract override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo); + public abstract override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo); /// public override byte[] GetEffectInfo() { diff --git a/SSMP/Animation/IAnimationEffect.cs b/SSMP/Animation/IAnimationEffect.cs index 1999f4e..f23ed45 100644 --- a/SSMP/Animation/IAnimationEffect.cs +++ b/SSMP/Animation/IAnimationEffect.cs @@ -1,4 +1,4 @@ -using SSMP.Game.Settings; +using SSMP.Game.Settings; using SSMP.Internals; using UnityEngine; @@ -13,8 +13,9 @@ internal interface IAnimationEffect { /// /// The GameObject representing the player. /// The type of crest the player is using. + /// The ID of the player sending the animation /// A byte array containing effect info. - void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo); + void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo); /// /// Get the effect info corresponding to this effect. diff --git a/SSMP/Testing/PlayerAnimation.cs b/SSMP/Testing/PlayerAnimation.cs index dd738f9..446b15f 100644 --- a/SSMP/Testing/PlayerAnimation.cs +++ b/SSMP/Testing/PlayerAnimation.cs @@ -54,7 +54,7 @@ void Init() { transform.position = playerPosition; } - public void StartPreAnimation() { + public void StartAnimation1() { Init(); CrestType crest = DetermineCrest(); @@ -64,12 +64,12 @@ public void StartPreAnimation() { string clipName = "BindCharge Ground"; var playerObject = transform.GetChild(0).gameObject; - var bind = new Bind { ShamanDoneFalling = false }; + var bind = new Bind { BindState = Bind.State.Normal }; var info = bind.GetEffectInfo(); SetSettings(bind); - bind.Play(playerObject, crest, info); + bind.Play(playerObject, crest, 1, info); var spriteAnimator = playerObject.GetComponent(); @@ -77,7 +77,28 @@ public void StartPreAnimation() { spriteAnimator.PlayFromFrame(clip, 0); } - public void StartAnimation() { + public void StartAnimation2() { + var clipName = "BindBurst Ground"; + + var playerObject = transform.GetChild(0).gameObject; + + var bind = new BindBurst(); + SetSettings(bind); + bind.SetShouldDoDamage(true); + + var info = bind.GetEffectInfo(); + CrestType crest = DetermineCrest(); + + bind.Play(playerObject, crest, 1, info); + + var spriteAnimator = playerObject.GetComponent(); + + var clip = spriteAnimator.GetClipByName(clipName); + spriteAnimator.PlayFromFrame(clip, 0); + } + + + public void StartAnimation3() { Init(); @@ -85,12 +106,12 @@ public void StartAnimation() { CrestType crest = DetermineCrest(); var playerObject = transform.GetChild(0).gameObject; - var bind = new Bind { ShamanDoneFalling = true }; + var bind = new Bind { BindState = Bind.State.ShamanDoneFalling }; var info = bind.GetEffectInfo(); SetSettings(bind); - bind.Play(playerObject, crest, info); + bind.Play(playerObject, crest, 1, info); var spriteAnimator = playerObject.GetComponent(); @@ -98,19 +119,19 @@ public void StartAnimation() { spriteAnimator.PlayFromFrame(clip, 0); } - public void StopAnimation() { + public void StartAnimation4() { var clipName = "BindBurst Ground"; var playerObject = transform.GetChild(0).gameObject; - var bind = new BindBurst(); + var bind = new Bind { BindState = Bind.State.ShamanCancel }; SetSettings(bind); bind.SetShouldDoDamage(true); var info = bind.GetEffectInfo(); CrestType crest = DetermineCrest(); - bind.Play(playerObject, crest, info); + bind.Play(playerObject, crest, 1, info); var spriteAnimator = playerObject.GetComponent(); From 902bc1dd0d48afc044274ea4e64d5f8e4e94d848 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Tue, 10 Mar 2026 00:46:01 -0400 Subject: [PATCH 17/33] clean up, add docs --- SSMP/Animation/Effects/Bind.cs | 114 ++++++++++++++++++++-------- SSMP/Animation/Effects/BindBurst.cs | 10 +++ 2 files changed, 92 insertions(+), 32 deletions(-) diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index cf46e68..2268695 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -55,7 +55,7 @@ public override void Play(GameObject playerObject, CrestType crestType, ushort p Flags flags = new Flags(effectInfo); // The maggot state is cleared by the time the bind burst is sent. - // This keeps track of it, although at a slight possible loss of consistancy + // This method keeps track of it, although at a slight possible loss of consistancy if (flags.Maggoted && !(crestType == CrestType.Shaman && BindState == State.ShamanCancel)) { BindBurst.MaggotedPlayers.Add(playerId); } else { @@ -87,32 +87,25 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, Logger.Info("Determining crest animation..."); - if (crestType == CrestType.Beast) { - Logger.Info("Playing Beast Crest Animation"); - PlayBeastBindStart(bindEffects); - } else if (crestType == CrestType.Cursed) { - Logger.Info("Playing Cursed Crest Animation"); - PlayCursedFail(bindEffects); - yield break; - } else if (crestType == CrestType.Witch) { - Logger.Info("Playing Witch Crest Animation"); - PlayWitchAnimationAntic(bindEffects); - } else if (crestType == CrestType.Shaman) { - Logger.Info("Playing Shaman Crest Animation"); - if (BindState == State.Normal) { - PlayShamanFall(bindEffects); - } else if (BindState == State.ShamanCancel) { - PlayShamanCancel(playerObject, bindEffects); + switch(crestType) { + case CrestType.Beast: + PlayBeastBindStart(bindEffects); + break; + case CrestType.Cursed: + PlayCursedFail(bindEffects); yield break; - } else { - PlayShamanFallEnd(bindEffects); + case CrestType.Witch: + PlayWitchAnimationAntic(bindEffects); + break; + case CrestType.Shaman: + var shouldContinue = PickShamanAnimation(playerObject, bindEffects, flags); + if (!shouldContinue) { + yield break; + } + break; + default: PlayNormalStart(bindEffects, flags); - yield break; - } - - } else { - Logger.Info("Playing Default Animation"); - PlayNormalStart(bindEffects, flags); + break; } // If bind bell, do effects in state "Bind Bell?" and "Bind Bell Disappear?" @@ -124,7 +117,6 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, // TODO: Quick Craft animations - Logger.Info("Getting clip name for no reason..."); var playerAnimator = playerObject.GetComponent(); var currentClip = playerAnimator?.currentClip; Logger.Info($"Player Animator current clip for Bind: {currentClip?.name}"); @@ -134,6 +126,7 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, /// Creates the bind bell /// private void StartBindBell(GameObject bindEffects) { + Logger.Debug("Starting bind bell"); var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); if (bindBell == null) { @@ -164,6 +157,7 @@ private void StartBindBell(GameObject bindEffects) { /// Plays the normal silk animation /// private void PlayNormalStart(GameObject bindEffects, Flags flags) { + Logger.Debug("Playing normal bind start animation"); var bindSilkObj = CreateEffectIfNotExists(bindEffects, "Bind Silk"); if (bindSilkObj == null) { return; @@ -187,6 +181,7 @@ private void PlayNormalStart(GameObject bindEffects, Flags flags) { /// Plays the Beast Crest specific silk animation /// private void PlayBeastBindStart(GameObject bindEffects) { + Logger.Debug("Playing Beast Crest start antic"); var beastAntic = CreateEffectIfNotExists(bindEffects, "Warrior_Bind_antic_silk"); if (beastAntic == null) { return; @@ -196,7 +191,11 @@ private void PlayBeastBindStart(GameObject bindEffects) { beastAntic.SetActive(true); } + /// + /// Starts the Cursed Crest bind animation + /// private void PlayCursedFail(GameObject bindEffects) { + Logger.Debug("Playing Cursed Crest bind fail animation"); var failAntic = bindEffects.FindGameObjectInChildren("cursed_bind_fail"); tk2dSpriteAnimator animator; @@ -227,6 +226,9 @@ private void PlayCursedFail(GameObject bindEffects) { animator.Play("Bind Cursed Start"); } + /// + /// Starts the next part of the Cursed Crest animation + /// private void PlayNextCursedPart(tk2dSpriteAnimator animator, tk2dSpriteAnimationClip clip) { if (clip.name == "Bind Cursed Start") { animator.Play("Bind Cursed Mid"); @@ -235,7 +237,11 @@ private void PlayNextCursedPart(tk2dSpriteAnimator animator, tk2dSpriteAnimation } } + /// + /// Plays the Witch Crest silk bind animation + /// private void PlayWitchAnimationAntic(GameObject bindEffects) { + Logger.Debug("Playing Witch Crest bind antic"); var silkAntic = CreateEffectIfNotExists(bindEffects, "Whip_Bind_silk_antic"); if (silkAntic == null) { return; @@ -245,11 +251,27 @@ private void PlayWitchAnimationAntic(GameObject bindEffects) { silkAntic.SetActive(true); } + + private bool PickShamanAnimation(GameObject playerObject, GameObject bindEffects, Flags flags) { + if (BindState == State.Normal) { + PlayShamanFall(bindEffects); + return true; + } else if (BindState == State.ShamanCancel) { + PlayShamanCancel(playerObject, bindEffects); + return false; + } else { + PlayShamanFallEnd(bindEffects); + PlayNormalStart(bindEffects, flags); + return false; + } + + } + /// /// Plays the Shaman Crest falling silk animation /// - /// private void PlayShamanFall(GameObject bindEffects) { + Logger.Debug("Playing Shaman Crest bind fall animation"); var shamanAntic = CreateEffectIfNotExists(bindEffects, "Shaman_Bind_antic_silk"); if (shamanAntic == null) { return; @@ -263,6 +285,7 @@ private void PlayShamanFall(GameObject bindEffects) { } private void PlayShamanCancel(GameObject playerObject, GameObject bindEffects) { + Logger.Debug("Playing Shaman Crest bind cancel/fail animation"); var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); if (shamanAntic != null) { shamanAntic.SetActive(false); @@ -284,25 +307,23 @@ private void PlayShamanCancel(GameObject playerObject, GameObject bindEffects) { /// Transitions from the falling silk animation to the normal silk animation /// private void PlayShamanFallEnd(GameObject bindEffects) { + Logger.Debug("Playing Shaman Crest bind fall finished transition animation"); var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); if (shamanAntic == null) { return; } var animator = shamanAntic.GetComponent(); animator.Play("End"); - //var clip = animator.runtimeAnimatorController.animationClips[2]; - - //shamanAntic.SetActive(false); } /// public override byte[]? GetEffectInfo() { byte[] effectInfo = { - (byte) (ToolItemManager.IsToolEquipped("Bell Bind") ? 1 : 0), + (byte) ((ToolItemManager.IsToolEquipped("Bell Bind") && !ToolItemManager.GetToolByName("Bell Bind").IsEmpty) ? 1 : 0), (byte) (ToolItemManager.IsToolEquipped("Dazzle Bind") ? 1 : 0), (byte) (ToolItemManager.IsToolEquipped("Dazzle Bind Upgraded") ? 1 : 0), (byte) (ToolItemManager.IsToolEquipped("Quickbind") ? 1 : 0), - (byte) (ToolItemManager.IsToolEquipped("Reserve Bind") ? 1 : 0), + (byte) ((ToolItemManager.IsToolEquipped("Reserve Bind") && !ToolItemManager.GetToolByName("Reserve Bind").IsEmpty) ? 1 : 0), (byte) (HeroController.instance.cState.isMaggoted ? 1 : 0) }; Logger.Info(HeroController.instance.cState.isMaggoted ? "MAGGOTS" : "NO MAGGOTS"); @@ -375,4 +396,33 @@ protected bool CreateObjects(GameObject playerObject, out GameObject bindEffects return obj; } + + public static void ForceStopAllEffects(GameObject bindEffects) { + BindBurst.Instance.StopBindBell(bindEffects); + + var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); + if (shamanAntic != null) { + shamanAntic.SetActive(false); + } + + var silkAntic = bindEffects.FindGameObjectInChildren("Whip_Bind_silk_antic"); + if (silkAntic != null) { + silkAntic.SetActive(false); + } + + var cursedFailAntic = bindEffects.FindGameObjectInChildren("cursed_bind_fail"); + if (cursedFailAntic != null) { + cursedFailAntic.SetActive(false); + } + + var beastAntic = bindEffects.FindGameObjectInChildren("Warrior_Bind_antic_silk"); + if (beastAntic != null) { + beastAntic.SetActive(false); + } + + var bindSilkObj = bindEffects.FindGameObjectInChildren("Bind Silk"); + if (bindSilkObj != null) { + bindSilkObj.SetActive(false); + } + } } diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index 41c09e2..a9adf56 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -175,6 +175,9 @@ private void PlayBeastRage(GameObject bindEffects) { beastRage?.SetActive(true); } + /// + /// Plays the witch maggot cleanse animation + /// private void PlayWitchMaggoted(GameObject bindEffects) { var maggotCleanse = CreateEffectIfNotExists(bindEffects, "Witch Bind Maggot Cleanse"); if (maggotCleanse != null) { @@ -183,6 +186,10 @@ private void PlayWitchMaggoted(GameObject bindEffects) { } } + /// + /// Plays the Witch Crest tentancles animation + /// Yes it's called Tentancles internally. Thanks TC. + /// private void PlayWitchEnd(GameObject bindEffects) { var witchBind = bindEffects.FindGameObjectInChildren("Witch Bind"); if (witchBind == null) { @@ -208,6 +215,9 @@ private void PlayWitchEnd(GameObject bindEffects) { witchBind.SetActive(true); } + /// + /// Adds hero damage components to Witch Crest bind if PVP is on + /// private void SetWitchDamagers(GameObject witchBind) { for (int i = 0; i < witchBind.transform.childCount; i++) { var child = witchBind.transform.GetChild(i); From d80d43b86931e6766ab79b7dad53d5f43fa70ddd Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Tue, 10 Mar 2026 00:46:26 -0400 Subject: [PATCH 18/33] add bind fail effect plus exploding warding bell --- SSMP/Animation/AnimationClip.cs | 3 +- SSMP/Animation/AnimationManager.cs | 19 ++++- SSMP/Animation/Effects/BindFail.cs | 110 +++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 SSMP/Animation/Effects/BindFail.cs diff --git a/SSMP/Animation/AnimationClip.cs b/SSMP/Animation/AnimationClip.cs index cb1bd97..4b0764b 100644 --- a/SSMP/Animation/AnimationClip.cs +++ b/SSMP/Animation/AnimationClip.cs @@ -763,5 +763,6 @@ internal enum AnimationClip { // Sub-animation names WitchTentacles, - ShamanCancel + ShamanCancel, + BindFailBurst, } diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 30ceb4e..efc69cd 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -619,7 +619,7 @@ internal class AnimationManager { { "Witch Tentacles!", AnimationClip.WitchTentacles }, { "Shaman Cancel", AnimationClip.ShamanCancel }, - + { "Bind Fail Burst", AnimationClip.BindFailBurst } }; /// @@ -661,7 +661,8 @@ internal class AnimationManager { private static readonly Dictionary SubAnimationEffects = new() { { AnimationClip.WitchTentacles, BindBurst.Instance }, - { AnimationClip.ShamanCancel, new Bind { BindState = Bind.State.ShamanCancel } } + { AnimationClip.ShamanCancel, new Bind { BindState = Bind.State.ShamanCancel } }, + { AnimationClip.BindFailBurst, BindFail.Instance } }; /// @@ -1053,6 +1054,7 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { /// the Bind fsm once the HeroController is ready. /// private void CreateBindHooks(HeroController hc) { + HeroController.instance.bellBindFSM.Init(); var heroFsms = hc.GetComponents(); PlayMakerFSM bindFsm = heroFsms.FirstOrDefault(fsm => fsm.FsmName == "Bind"); if (bindFsm == null) { @@ -1074,6 +1076,11 @@ private void CreateBindHooks(HeroController hc) { } else { Logger.Warn("Unable to find Shaman Air Cancel state"); } + + var bindFail = bindFsm.GetState("Remove Silk?"); + if (bindFsm != null) { + FsmStateActionInjector.Inject(bindFail, 2, OnBindFail); + } } /// @@ -1096,6 +1103,14 @@ private void OnShamanCancel(PlayMakerFSM fsm) { OnAnimationEvent(dummyClip); } + private void OnBindFail(PlayMakerFSM fsm) { + Logger.Warn("PLAYING BIND FAIL"); + var dummyClip = new tk2dSpriteAnimationClip(); + dummyClip.name = "Bind Fail Burst"; + dummyClip.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once; + OnAnimationEvent(dummyClip); + } + // /// // /// Callback method on the HeroAnimationController#Play method. // /// diff --git a/SSMP/Animation/Effects/BindFail.cs b/SSMP/Animation/Effects/BindFail.cs new file mode 100644 index 0000000..19ac915 --- /dev/null +++ b/SSMP/Animation/Effects/BindFail.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using HutongGames.PlayMaker.Actions; +using SSMP.Internals; +using SSMP.Util; +using UnityEngine; +using static UnityEngine.ParticleSystem; +using Logger = SSMP.Logging.Logger; + +namespace SSMP.Animation.Effects; + +internal class BindFail : Bind { + /// + /// Static instance for access by multiple animation clips in . + /// + private static BindFail? _instance; + /// + public static BindFail Instance => _instance ??= new BindFail(); + public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + Flags flags = new Flags(effectInfo); + + if (!CreateObjects(playerObject, out var bindEffects)) { + return; + } + + ForceStopAllEffects(bindEffects); + + if (flags.BindBell) { + PlayBellBurst(bindEffects); + } else { + PlayBindBurst(bindEffects); + } + } + + private void PlayBindBurst(GameObject bindEffects) { + var bellBurstSpawner = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); + if (bellBurstSpawner == null) { + Logger.Warn("Unable to find Bell Burst spawner"); + return; + } + + var globalBellBurst = bellBurstSpawner.gameObject.Value; + var burst = EffectUtils.SpawnGlobalPoolObject(globalBellBurst, bindEffects.transform); + + if (burst == null) { + Logger.Warn("Unable to create Bell Burst"); + return; + } + + burst.DestroyAfterTime(5f); + if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { + AddDamageHeroComponent(burst); + } + + var haze = burst.FindGameObjectInChildren("haze2"); + if (haze != null) { + GameObject.Destroy(haze); + } + + var shaker = burst.GetComponentInChildren(); + if (shaker != null) { + Component.DestroyImmediate(shaker); + } + } + + private void PlayBellBurst(GameObject bindEffects) { + Logger.Info("Playing Bell Burst"); + + var bellFsm = HeroController.instance.bellBindFSM; + if (!bellFsm.fsm.initialized) { + HeroController.instance.bellBindFSM.Init(); + } + + if (bellFsm == null) { + Logger.Warn("Unable to find bind bell fsm"); + return; + } + + var audio = bellFsm.GetFirstAction("Burst"); + var spawner = bellFsm.GetFirstAction("Burst"); + + if (spawner == null) { + Logger.Warn("Unable to find bind bell spawner"); + return; + } + + var bindBell = EffectUtils.SpawnGlobalPoolObject(spawner.gameObject.Value, bindEffects.transform); + var shaker = bindBell.GetComponentInChildren(); + if (shaker != null) { + Component.DestroyImmediate(shaker); + } + + var haze = bindBell.FindGameObjectInChildren("haze2 (1)"); + if (haze != null) { + GameObject.Destroy(haze); + } + + if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { + var damager = bindBell.FindGameObjectInChildren("damager"); + if (damager != null) { + AddDamageHeroComponent(damager); + } else { + Logger.Warn("Unable to add damager to bind bell burst"); + } + } + } +} From ce83546e421a1f9d5f699f159ea2f23ed07d6702 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:23:28 -0400 Subject: [PATCH 19/33] add more sounds --- SSMP/Animation/AnimationEffect.cs | 18 ++++++ SSMP/Animation/DamageAnimationEffect.cs | 23 ++++++- SSMP/Animation/Effects/Bind.cs | 20 +++--- SSMP/Animation/Effects/BindBurst.cs | 85 ++++++++++++------------- SSMP/Animation/Effects/BindFail.cs | 16 +++-- SSMP/Animation/Effects/EffectUtils.cs | 31 ++++++++- SSMP/Testing/PlayerAnimation.cs | 4 ++ 7 files changed, 135 insertions(+), 62 deletions(-) diff --git a/SSMP/Animation/AnimationEffect.cs b/SSMP/Animation/AnimationEffect.cs index ed09520..b05d89e 100644 --- a/SSMP/Animation/AnimationEffect.cs +++ b/SSMP/Animation/AnimationEffect.cs @@ -1,5 +1,7 @@ +using HutongGames.PlayMaker.Actions; using SSMP.Game.Settings; using SSMP.Internals; +using SSMP.Util; using UnityEngine; namespace SSMP.Animation; @@ -40,4 +42,20 @@ protected static void ChangeAttackDirection(GameObject targetObject, float direc var directionVar = damageFsm.FsmVariables.GetFsmFloat("direction"); directionVar.Value = direction; } + + protected static void PlaySound(GameObject source, AudioPlayerOneShotSingle audio) { + AudioUtil.PlayAudioOneShotSingleAtPlayerObject(audio, source); + } + + protected static void PlaySound(GameObject source, PlayAudioEvent audio) { + AudioUtil.PlayAudioEventAtPlayerObject(audio, source); + } + + protected static void PlaySound(GameObject source, GetRandomAudioClipFromTable getAction, PlayAudioEvent playAction) { + AudioUtil.PlayAudioEventWithRandomAudioClipFromTableAtPlayerObject( + playAction, + getAction, + source + ); + } } diff --git a/SSMP/Animation/DamageAnimationEffect.cs b/SSMP/Animation/DamageAnimationEffect.cs index f22cd04..d93f4ca 100644 --- a/SSMP/Animation/DamageAnimationEffect.cs +++ b/SSMP/Animation/DamageAnimationEffect.cs @@ -1,6 +1,7 @@ using SSMP.Internals; using UnityEngine; using UnityEngine.Events; +using Logger = SSMP.Logging.Logger; namespace SSMP.Animation; @@ -33,9 +34,29 @@ public void SetShouldDoDamage(bool shouldDoDamage) { /// /// The target game object to attach the component to. /// The number of mask of damage it should deal. - protected static void AddDamageHeroComponent(GameObject target, int damage = 1) { + protected static DamageHero AddDamageHeroComponent(GameObject target, int damage = 1) { var damageHero = target.AddComponentIfNotPresent(); damageHero.damageDealt = damage; damageHero.OnDamagedHero = new UnityEvent(); + + return damageHero; + } + + protected static void RemoveDamageHeroComponent(GameObject target) { + var damageHero = target.GetComponent(); + if (damageHero == null) { + return; + } + + Component.DestroyImmediate(damageHero); + } + + protected DamageHero? SetDamageHeroState(GameObject target, int damage = 1) { + if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { + return AddDamageHeroComponent(target, damage); + } else { + RemoveDamageHeroComponent(target); + return null; + } } } diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 2268695..f509884 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -70,15 +70,11 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, var playAudioAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); if (BindState == State.Normal) { - AudioUtil.PlayAudioEventWithRandomAudioClipFromTableAtPlayerObject( - playAudioAction, - randomClipAction, - playerObject - ); + PlaySound(playerObject, randomClipAction, playAudioAction); } var oneShotSingleAction = GetOrFindBindFsm().GetFirstAction("Check Grounded"); - AudioUtil.PlayAudioOneShotSingleAtPlayerObject(oneShotSingleAction, playerObject); + PlaySound(playerObject, oneShotSingleAction); var created = CreateObjects(playerObject, out var bindEffects); if (!created) { @@ -214,6 +210,8 @@ private void PlayCursedFail(GameObject bindEffects) { failAntic = GameObject.Instantiate(localFailAntic, bindEffects.transform); failAntic.name = "cursed_bind_fail"; + failAntic.transform.SetLocalPositionZ(failAntic.transform.localPosition.z - 0.25f); + animator = failAntic.GetComponent(); animator.AnimationCompletedEvent += PlayNextCursedPart; } else { @@ -297,8 +295,12 @@ private void PlayShamanCancel(GameObject playerObject, GameObject bindEffects) { return; } - var globalSilkPuff = silkPuffSpawner.gameObject.Value; - EffectUtils.SpawnGlobalPoolObject(globalSilkPuff, playerObject.transform, false); + var audio = GetOrFindBindFsm().GetFirstAction("Shaman Air Cancel"); + if (audio != null) { + PlaySound(playerObject, audio); + } + + EffectUtils.SpawnGlobalPoolObject(silkPuffSpawner, playerObject.transform, 5f); BindBurst.Instance.StopBindBell(bindEffects); } @@ -316,6 +318,8 @@ private void PlayShamanFallEnd(GameObject bindEffects) { animator.Play("End"); } + + /// public override byte[]? GetEffectInfo() { byte[] effectInfo = { diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index a9adf56..dd45347 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -21,22 +21,20 @@ internal class BindBurst : Bind { public static BindBurst Instance => _instance ??= new BindBurst(); public static HashSet MaggotedPlayers = new HashSet(); public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + // Set maggot info Flags flags = new Flags(effectInfo); if (MaggotedPlayers.Contains(playerId)) { flags.Maggoted = true; MaggotedPlayers.Remove(playerId); } - MonoBehaviourUtil.Instance.StartCoroutine(PlayBindBurstEffect(playerObject, crestType, flags)); - } - private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crestType, Flags flags) { if (!CreateObjects(playerObject, out var bindEffects)) { - yield break; + return; } if (flags.BaseMirror || flags.UpgradedMirror) PlayMirror(playerObject, flags.UpgradedMirror); if (flags.Maggoted) PlayMaggotCleanse(bindEffects, playerObject); - + // Stop regardless of if its on or not StopBindBell(bindEffects); @@ -52,7 +50,7 @@ private IEnumerator PlayBindBurstEffect(GameObject playerObject, CrestType crest } else { PlayWitchMaggoted(bindEffects); } - break; + return; case CrestType.Shaman: PlayShamanEnd(bindEffects); break; @@ -72,17 +70,16 @@ protected void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject var maggotFlash = GetOrFindBindFsm().GetAction("Maggoted?", 4); var maggotAudio = GetOrFindBindFsm().GetFirstAction("Maggoted?"); - if (maggotBurst != null) { - maggotBurst.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); - } - if (maggotFlash != null) { - maggotFlash.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); - } + EffectUtils.SpawnGlobalPoolObject(maggotBurst, bindEffects.transform, 5f); + //if (maggotBurst != null) { + //maggotBurst.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + //} + EffectUtils.SpawnGlobalPoolObject(maggotFlash, bindEffects.transform, 5f); + //if (maggotFlash != null) { + //maggotFlash.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); + //} if (maggotAudio != null) { - AudioUtil.PlayAudioEventAtPlayerObject( - maggotAudio, - playerObject - ); + PlaySound(playerObject, maggotAudio); } } @@ -90,39 +87,24 @@ protected void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject /// Creates the appropriate Claw Mirror object. /// The object will be destroyed after it finishes. /// - private GameObject? PrepareMirror(GameObject playerObject, SetGameObject mirrorSource, string name) { - - if (mirrorSource == null) { - Logger.Warn("Unable to find mirror source"); - return null; - } + private GameObject? PrepareMirror(GameObject playerObject, SetGameObject mirrorSource) { - // This is ugly and i hate it, but it works - var mirror = GameObject.Instantiate(mirrorSource.gameObject.Value, playerObject.transform); - mirror.transform.SetParent(null); - mirror.transform.position = playerObject.transform.position; - mirror.SetActive(true); + var mirror = EffectUtils.SpawnGlobalPoolObject(mirrorSource.gameObject.Value, playerObject.transform, 3f); if (mirror == null) { - Logger.Warn("Unable to spawn mirror"); return null; } - mirror.name = name; var shaker = mirror.GetComponentInChildren(); if (shaker != null) { Component.DestroyImmediate(shaker); } - EffectUtils.SafelyRemoveAutoRecycle(mirror); - mirror.DestroyAfterTime(2f); - var haze = mirror.FindGameObjectInChildren("haze2"); if (haze != null) { GameObject.Destroy(haze); } - //mirror.SetActive(false); return mirror; } @@ -136,24 +118,27 @@ private void PlayMirror(GameObject playerObject, bool upgraded) { var upgradedClaw = GetOrFindBindFsm().GetAction("Dazzle?", 4)!; GameObject? claw; - if (upgraded) claw = PrepareMirror(playerObject, upgradedClaw, "dazzle_upgraded"); - else claw = PrepareMirror(playerObject, regularClaw, "dazzle_regular"); + if (upgraded) claw = PrepareMirror(playerObject, upgradedClaw); + else claw = PrepareMirror(playerObject, regularClaw); if (claw == null) { - Logger.Warn("Unable to create claw mirror object."); return; } claw.SetActive(true); + if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { var damagerParent = claw.FindGameObjectInChildren("Trobbio_dazzle_flash"); var damager = damagerParent?.FindGameObjectInChildren("hero_dazzle_flash_damager"); - if (damager != null) { - damager.layer = (int) GlobalEnums.PhysLayers.HERO_ATTACK; - AddDamageHeroComponent(damager); - } else { + if (damager == null) { Logger.Warn("Couldn't find claw mirror damager"); + return; } + + damager.layer = (int) GlobalEnums.PhysLayers.HERO_ATTACK; + + var damageComponent = AddDamageHeroComponent(damager); + damageComponent.hazardType = GlobalEnums.HazardType.EXPLOSION; } } @@ -206,9 +191,12 @@ private void PlayWitchEnd(GameObject bindEffects) { Component.DestroyImmediate(shaker); } - if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { - SetWitchDamagers(witchBind); - } + SetWitchDamagers(witchBind); + } + + var audio = GetOrFindBindFsm().GetFirstAction("Witch Tentancles!"); + if (audio != null) { + PlaySound(bindEffects.transform.parent.gameObject, audio); } witchBind.SetActive(false); @@ -219,13 +207,13 @@ private void PlayWitchEnd(GameObject bindEffects) { /// Adds hero damage components to Witch Crest bind if PVP is on /// private void SetWitchDamagers(GameObject witchBind) { - for (int i = 0; i < witchBind.transform.childCount; i++) { + for (var i = 0; i < witchBind.transform.childCount; i++) { var child = witchBind.transform.GetChild(i); if (!child.name.StartsWith("Damager")) { continue; } - AddDamageHeroComponent(child.gameObject); + SetDamageHeroState(child.gameObject); } } @@ -260,5 +248,12 @@ private void PlayNormalEnd(GameObject bindEffects) { var bindSilkMeshRenderer = bindSilkObj.GetComponent(); bindSilkMeshRenderer.enabled = false; } + + var audio = GetOrFindBindFsm().GetFirstAction("Bind Burst"); + if (audio != null) { + PlaySound(bindEffects.transform.parent.gameObject, audio); + } + + } } diff --git a/SSMP/Animation/Effects/BindFail.cs b/SSMP/Animation/Effects/BindFail.cs index 19ac915..27b9af9 100644 --- a/SSMP/Animation/Effects/BindFail.cs +++ b/SSMP/Animation/Effects/BindFail.cs @@ -38,19 +38,21 @@ public override void Play(GameObject playerObject, CrestType crestType, ushort p private void PlayBindBurst(GameObject bindEffects) { var bellBurstSpawner = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); if (bellBurstSpawner == null) { - Logger.Warn("Unable to find Bell Burst spawner"); + Logger.Warn("Unable to find bind burst effect spawner"); return; } - var globalBellBurst = bellBurstSpawner.gameObject.Value; - var burst = EffectUtils.SpawnGlobalPoolObject(globalBellBurst, bindEffects.transform); + var audio = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); + + var burst = EffectUtils.SpawnGlobalPoolObject(bellBurstSpawner, bindEffects.transform, 5f); if (burst == null) { - Logger.Warn("Unable to create Bell Burst"); + Logger.Warn("Unable to create bind burst effect"); return; } - burst.DestroyAfterTime(5f); + PlaySound(bindEffects.transform.parent.gameObject, audio); + if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { AddDamageHeroComponent(burst); } @@ -80,6 +82,8 @@ private void PlayBellBurst(GameObject bindEffects) { } var audio = bellFsm.GetFirstAction("Burst"); + PlaySound(bindEffects.transform.parent.gameObject, audio); + var spawner = bellFsm.GetFirstAction("Burst"); if (spawner == null) { @@ -87,7 +91,7 @@ private void PlayBellBurst(GameObject bindEffects) { return; } - var bindBell = EffectUtils.SpawnGlobalPoolObject(spawner.gameObject.Value, bindEffects.transform); + var bindBell = EffectUtils.SpawnGlobalPoolObject(spawner, bindEffects.transform, 5f); var shaker = bindBell.GetComponentInChildren(); if (shaker != null) { Component.DestroyImmediate(shaker); diff --git a/SSMP/Animation/Effects/EffectUtils.cs b/SSMP/Animation/Effects/EffectUtils.cs index 6a029ff..1c17ed6 100644 --- a/SSMP/Animation/Effects/EffectUtils.cs +++ b/SSMP/Animation/Effects/EffectUtils.cs @@ -1,3 +1,5 @@ +using HutongGames.PlayMaker.Actions; +using SSMP.Util; using UnityEngine; using Logger = SSMP.Logging.Logger; using Object = UnityEngine.Object; @@ -16,16 +18,41 @@ public static void SafelyRemoveAutoRecycle(GameObject obj) { } } - public static GameObject SpawnGlobalPoolObject(GameObject globalObj, Transform spawnLocation, bool keepParent = false) { + public static GameObject? SpawnGlobalPoolObject(SpawnObjectFromGlobalPool? spawner, Transform spawnLocation, float destroyAfterDelay, bool keepParent = false) { + if (spawner == null) { + Logger.Warn("Unable to find global pool object"); + return null; + } + + return SpawnGlobalPoolObject(spawner.gameObject.Value, spawnLocation, destroyAfterDelay, keepParent); + } + + public static GameObject? SpawnGlobalPoolObject(GameObject? globalObj, Transform spawnLocation, float destroyAfterDelay, bool keepParent = false) { + if (globalObj == null) { + Logger.Warn("Unable to find global pool object"); + return null; + } + var newObj = GameObject.Instantiate(globalObj, spawnLocation); - + if (newObj == null) { + Logger.Warn($"Unable to spawn global pool object {globalObj.name}"); + return null; + } + + // This is ugly and i hate it, but it works if (!keepParent) { newObj.transform.SetParent(null); newObj.transform.position = spawnLocation.position; } + newObj.SetActive(true); SafelyRemoveAutoRecycle(newObj); + + if (destroyAfterDelay > 0) { + newObj.DestroyAfterTime(destroyAfterDelay); + } + return newObj; } } diff --git a/SSMP/Testing/PlayerAnimation.cs b/SSMP/Testing/PlayerAnimation.cs index 446b15f..ca798ad 100644 --- a/SSMP/Testing/PlayerAnimation.cs +++ b/SSMP/Testing/PlayerAnimation.cs @@ -44,6 +44,10 @@ void SetSettings(AnimationEffect bind) { bind.SetServerSettings(settings); } + void SetCrest(string crest) { + ToolItemManager.SetEquippedCrest(crest); + } + void Init() { gameObject.SetActive(true); //ToolItemManager.SetEquippedCrest(Gameplay.CursedCrest.name); From 10f6a13d004a109e4b0734b8764b465157944e07 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:42:05 -0400 Subject: [PATCH 20/33] fix shaman crest double silk sounds --- SSMP/Animation/Effects/Bind.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index f509884..d3172f5 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -71,10 +71,11 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, if (BindState == State.Normal) { PlaySound(playerObject, randomClipAction, playAudioAction); + + var oneShotSingleAction = GetOrFindBindFsm().GetFirstAction("Check Grounded"); + PlaySound(playerObject, oneShotSingleAction); } - var oneShotSingleAction = GetOrFindBindFsm().GetFirstAction("Check Grounded"); - PlaySound(playerObject, oneShotSingleAction); var created = CreateObjects(playerObject, out var bindEffects); if (!created) { From e2882077dfcf4055cf382d88b8b98933b76f9df5 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sat, 14 Mar 2026 19:28:58 -0400 Subject: [PATCH 21/33] rename bind fail, prevent fsm injection errors from interupting flow --- SSMP/Animation/AnimationClip.cs | 2 +- SSMP/Animation/AnimationManager.cs | 24 +++++----- .../Effects/{BindFail.cs => BindInterupt.cs} | 10 ++-- SSMP/Fsm/FsmStateActionInjector.cs | 46 +++++++++++++------ 4 files changed, 54 insertions(+), 28 deletions(-) rename SSMP/Animation/Effects/{BindFail.cs => BindInterupt.cs} (94%) diff --git a/SSMP/Animation/AnimationClip.cs b/SSMP/Animation/AnimationClip.cs index 4b0764b..9f2d568 100644 --- a/SSMP/Animation/AnimationClip.cs +++ b/SSMP/Animation/AnimationClip.cs @@ -764,5 +764,5 @@ internal enum AnimationClip { // Sub-animation names WitchTentacles, ShamanCancel, - BindFailBurst, + BindInterupt, } diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index efc69cd..fb1c011 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -619,7 +619,7 @@ internal class AnimationManager { { "Witch Tentacles!", AnimationClip.WitchTentacles }, { "Shaman Cancel", AnimationClip.ShamanCancel }, - { "Bind Fail Burst", AnimationClip.BindFailBurst } + { "Bind Fail Burst", AnimationClip.BindInterupt } }; /// @@ -662,7 +662,7 @@ internal class AnimationManager { private static readonly Dictionary SubAnimationEffects = new() { { AnimationClip.WitchTentacles, BindBurst.Instance }, { AnimationClip.ShamanCancel, new Bind { BindState = Bind.State.ShamanCancel } }, - { AnimationClip.BindFailBurst, BindFail.Instance } + { AnimationClip.BindInterupt, BindInterupt.Instance } }; /// @@ -766,10 +766,9 @@ public void RegisterHooks() { EventHooks.SpriteAnimatorProcessEvents += Tk2dSpriteAnimatorOnProcessEvents; // Register FSM hooks for certain bind actions + HeroController.OnHeroInstanceSet += CreateBindHooks; if (HeroController.SilentInstance != null) { CreateBindHooks(HeroController.instance); - } else { - HeroController.OnHeroInstanceSet += CreateBindHooks; } @@ -797,6 +796,8 @@ public void RegisterHooks() { public void DeregisterHooks() { SceneManager.activeSceneChanged -= OnSceneChange; + HeroController.OnHeroInstanceSet -= CreateBindHooks; + FsmStateActionInjector.UninjectAll(); // On.HeroAnimationController.Play -= HeroAnimationControllerOnPlay; // On.HeroAnimationController.PlayFromFrame -= HeroAnimationControllerOnPlayFromFrame; @@ -1065,7 +1066,7 @@ private void CreateBindHooks(HeroController hc) { // Find witch crest tenticles var tenticles = bindFsm.GetState("Witch Tentancles!"); // no that's not a typo... at least on my end if (tenticles != null) { - FsmStateActionInjector.Inject(tenticles, 4, OnWitchTentacles); + FsmStateActionInjector.Inject(tenticles, OnWitchTentacles, 4); } else { Logger.Warn("Unable to find Witch Tentacles! state"); } @@ -1077,9 +1078,9 @@ private void CreateBindHooks(HeroController hc) { Logger.Warn("Unable to find Shaman Air Cancel state"); } - var bindFail = bindFsm.GetState("Remove Silk?"); - if (bindFsm != null) { - FsmStateActionInjector.Inject(bindFail, 2, OnBindFail); + var bindInterupt = bindFsm.GetState("Remove Silk?"); + if (bindInterupt != null) { + FsmStateActionInjector.Inject(bindInterupt, OnBindInterupt, 2); } } @@ -1102,9 +1103,10 @@ private void OnShamanCancel(PlayMakerFSM fsm) { dummyClip.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once; OnAnimationEvent(dummyClip); } - - private void OnBindFail(PlayMakerFSM fsm) { - Logger.Warn("PLAYING BIND FAIL"); + /// + /// Animation subanimation hook for interupted binds + /// + private void OnBindInterupt(PlayMakerFSM fsm) { var dummyClip = new tk2dSpriteAnimationClip(); dummyClip.name = "Bind Fail Burst"; dummyClip.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once; diff --git a/SSMP/Animation/Effects/BindFail.cs b/SSMP/Animation/Effects/BindInterupt.cs similarity index 94% rename from SSMP/Animation/Effects/BindFail.cs rename to SSMP/Animation/Effects/BindInterupt.cs index 27b9af9..5887196 100644 --- a/SSMP/Animation/Effects/BindFail.cs +++ b/SSMP/Animation/Effects/BindInterupt.cs @@ -12,13 +12,13 @@ namespace SSMP.Animation.Effects; -internal class BindFail : Bind { +internal class BindInterupt : Bind { /// /// Static instance for access by multiple animation clips in . /// - private static BindFail? _instance; + private static BindInterupt? _instance; /// - public static BindFail Instance => _instance ??= new BindFail(); + public static BindInterupt Instance => _instance ??= new BindInterupt(); public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { Flags flags = new Flags(effectInfo); @@ -92,6 +92,10 @@ private void PlayBellBurst(GameObject bindEffects) { } var bindBell = EffectUtils.SpawnGlobalPoolObject(spawner, bindEffects.transform, 5f); + if (bindBell == null) { + return; + } + var shaker = bindBell.GetComponentInChildren(); if (shaker != null) { Component.DestroyImmediate(shaker); diff --git a/SSMP/Fsm/FsmStateActionInjector.cs b/SSMP/Fsm/FsmStateActionInjector.cs index 15db8ec..3c50145 100644 --- a/SSMP/Fsm/FsmStateActionInjector.cs +++ b/SSMP/Fsm/FsmStateActionInjector.cs @@ -3,28 +3,48 @@ using System.Linq; using System.Text; using HutongGames.PlayMaker; +using Steamworks; namespace SSMP.Fsm; internal class FsmStateActionInjector : FsmStateAction { - + private static Action? _onUninject; private Action? _onStateEnter; + private FsmStateActionInjector(FsmState state, Action onEnter) { + Fsm = state.Fsm; + State = state; + _onStateEnter = onEnter; + _onUninject += Uninject; + } + + private void DoInjection(int index) { + var stateActions = State.Actions.ToList(); + stateActions.Insert(index, this); + State.Actions = stateActions.ToArray(); + State.SaveActions(); + } + + public void Uninject() { + var actions = State.Actions.ToList(); + actions.Remove(this); + State.Actions = actions.ToArray(); + State.SaveActions(); + } + public override void OnEnter() { - _onStateEnter?.Invoke(Fsm.FsmComponent); Finish(); + _onStateEnter?.Invoke(Fsm.FsmComponent); } - public static void Inject(FsmState state, Action onEnter) { - Inject(state, 0, onEnter); + + public static FsmStateActionInjector Inject(FsmState state, Action onEnter, int actionIndex = 0) { + var action = new FsmStateActionInjector(state, onEnter); + action.DoInjection(actionIndex); + + return action; } - public static void Inject(FsmState state, int actionIndex, Action onEnter) { - var action = new FsmStateActionInjector(); - action.Fsm = state.Fsm; - action._onStateEnter = onEnter; - - var stateActions = state.Actions.ToList(); - stateActions.Insert(actionIndex, action); - state.Actions = stateActions.ToArray(); - state.SaveActions(); + + public static void UninjectAll() { + _onUninject?.Invoke(); } } From b21f4c16d202cb70d2d0cb33549add8eda5c038d Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:12:03 -0400 Subject: [PATCH 22/33] Catch errors to fully prevent fsm breaks --- SSMP/Fsm/FsmStateActionInjector.cs | 31 +++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/SSMP/Fsm/FsmStateActionInjector.cs b/SSMP/Fsm/FsmStateActionInjector.cs index 3c50145..ff9742f 100644 --- a/SSMP/Fsm/FsmStateActionInjector.cs +++ b/SSMP/Fsm/FsmStateActionInjector.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; using HutongGames.PlayMaker; -using Steamworks; +using SSMP.Logging; namespace SSMP.Fsm; @@ -11,12 +11,14 @@ internal class FsmStateActionInjector : FsmStateAction { private static Action? _onUninject; private Action? _onStateEnter; private FsmStateActionInjector(FsmState state, Action onEnter) { - Fsm = state.Fsm; - State = state; + Init(state); _onStateEnter = onEnter; _onUninject += Uninject; } + /// + /// Injects a delegate action into an FSM state + /// private void DoInjection(int index) { var stateActions = State.Actions.ToList(); stateActions.Insert(index, this); @@ -24,6 +26,9 @@ private void DoInjection(int index) { State.SaveActions(); } + /// + /// Removes the delegate action from the FSM state + /// public void Uninject() { var actions = State.Actions.ToList(); actions.Remove(this); @@ -31,12 +36,25 @@ public void Uninject() { State.SaveActions(); } + /// public override void OnEnter() { + if (_onStateEnter != null) { + try { + _onStateEnter?.Invoke(Fsm.FsmComponent); + } catch (Exception e) { + Logger.Error(e.ToString()); + } + } Finish(); - _onStateEnter?.Invoke(Fsm.FsmComponent); } - + /// + /// Injects a custom action into the specified FSM state to execute when the state is entered. + /// + /// The FSM state into which the action will be injected. + /// An action to execute when the state is entered. + /// The index at which to inject the action within the state's action list. Defaults to 0. + /// The injected action. public static FsmStateActionInjector Inject(FsmState state, Action onEnter, int actionIndex = 0) { var action = new FsmStateActionInjector(state, onEnter); action.DoInjection(actionIndex); @@ -44,6 +62,9 @@ public static FsmStateActionInjector Inject(FsmState state, Action return action; } + /// + /// Removes all injected FSM actions + /// public static void UninjectAll() { _onUninject?.Invoke(); } From c56568e1b9218e290bb198bcbd5da70951e7274a Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:12:18 -0400 Subject: [PATCH 23/33] Add documentation --- SSMP/Animation/AnimationManager.cs | 2 +- SSMP/Animation/DamageAnimationEffect.cs | 11 ++++- SSMP/Animation/Effects/Bind.cs | 23 +++++++-- SSMP/Animation/Effects/BindBurst.cs | 33 ++++++++----- SSMP/Animation/Effects/BindInterupt.cs | 65 +++++++++++++++---------- SSMP/Animation/Effects/EffectUtils.cs | 18 +++++++ 6 files changed, 106 insertions(+), 46 deletions(-) diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index fb1c011..922f9aa 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -25,7 +25,7 @@ internal class AnimationManager { /// Whether to log debug messages about animations. For debugging purposes this can be enabled so that all /// animation events are logged. /// - private static bool _debugLogAnimations = false; + private static bool _debugLogAnimations = true; /// /// The distance threshold for playing certain effects. diff --git a/SSMP/Animation/DamageAnimationEffect.cs b/SSMP/Animation/DamageAnimationEffect.cs index d93f4ca..876806c 100644 --- a/SSMP/Animation/DamageAnimationEffect.cs +++ b/SSMP/Animation/DamageAnimationEffect.cs @@ -1,7 +1,6 @@ using SSMP.Internals; using UnityEngine; using UnityEngine.Events; -using Logger = SSMP.Logging.Logger; namespace SSMP.Animation; @@ -42,6 +41,10 @@ protected static DamageHero AddDamageHeroComponent(GameObject target, int damage return damageHero; } + /// + /// Removes a component from the given game object. + /// + /// The target game object to detatch the component to. protected static void RemoveDamageHeroComponent(GameObject target) { var damageHero = target.GetComponent(); if (damageHero == null) { @@ -51,6 +54,12 @@ protected static void RemoveDamageHeroComponent(GameObject target) { Component.DestroyImmediate(damageHero); } + /// + /// Adds or removes a component from the given game object, + /// depending on the PVP and team settings. + /// + /// The target game object to detatch the component to. + /// The number of mask of damage it should deal. protected DamageHero? SetDamageHeroState(GameObject target, int damage = 1) { if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { return AddDamageHeroComponent(target, damage); diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index d3172f5..b898955 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -25,6 +25,9 @@ public enum State { public State BindState = State.Normal; + /// + /// Effect flags sent by the other player. Mostly items they have equipped. + /// protected class Flags { public bool BindBell = false; public bool BaseMirror = false; @@ -49,8 +52,12 @@ public Flags(byte[]? info) { /// protected static PlayMakerFSM? _bindFsm; + /// + /// Cached effects object for Hornet's bind ability. + /// protected static GameObject? _localBindEffects; + /// public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { Flags flags = new Flags(effectInfo); @@ -123,7 +130,7 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, /// Creates the bind bell /// private void StartBindBell(GameObject bindEffects) { - Logger.Debug("Starting bind bell"); + Logger.Debug("Starting warding bell"); var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); if (bindBell == null) { @@ -131,7 +138,7 @@ private void StartBindBell(GameObject bindEffects) { var localBell = allObjects.FirstOrDefault(o => o.name == "bind_bell_appear"); if (localBell == null) { - Logger.Warn("Couldn't find bind bell object"); + Logger.Warn("Couldn't find warding bell object"); return; } @@ -250,7 +257,9 @@ private void PlayWitchAnimationAntic(GameObject bindEffects) { silkAntic.SetActive(true); } - + /// + /// Picks and plays a Shaman bind animation + /// private bool PickShamanAnimation(GameObject playerObject, GameObject bindEffects, Flags flags) { if (BindState == State.Normal) { PlayShamanFall(bindEffects); @@ -283,6 +292,9 @@ private void PlayShamanFall(GameObject bindEffects) { shamanAntic.SetActive(true); } + /// + /// Plays the Shaman Crest 'failed to bind' animation + /// private void PlayShamanCancel(GameObject playerObject, GameObject bindEffects) { Logger.Debug("Playing Shaman Crest bind cancel/fail animation"); var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); @@ -401,7 +413,10 @@ protected bool CreateObjects(GameObject playerObject, out GameObject bindEffects return obj; } - + + /// + /// Turns off all bind effects + /// public static void ForceStopAllEffects(GameObject bindEffects) { BindBurst.Instance.StopBindBell(bindEffects); diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index dd45347..5fe134b 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -12,14 +12,24 @@ namespace SSMP.Animation.Effects; +/// +/// Class for the animation effect of a bind (healing) finishing. +/// internal class BindBurst : Bind { /// /// Static instance for access by multiple animation clips in . /// private static BindBurst? _instance; + /// public static BindBurst Instance => _instance ??= new BindBurst(); + + /// + /// A set of players who recently binded while maggoted + /// public static HashSet MaggotedPlayers = new HashSet(); + + /// public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { // Set maggot info Flags flags = new Flags(effectInfo); @@ -38,6 +48,7 @@ public override void Play(GameObject playerObject, CrestType crestType, ushort p // Stop regardless of if its on or not StopBindBell(bindEffects); + // Play crest-specific animations switch (crestType) { case CrestType.Beast: if (!flags.Maggoted) { @@ -71,13 +82,8 @@ protected void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject var maggotAudio = GetOrFindBindFsm().GetFirstAction("Maggoted?"); EffectUtils.SpawnGlobalPoolObject(maggotBurst, bindEffects.transform, 5f); - //if (maggotBurst != null) { - //maggotBurst.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); - //} EffectUtils.SpawnGlobalPoolObject(maggotFlash, bindEffects.transform, 5f); - //if (maggotFlash != null) { - //maggotFlash.gameObject.Value.Spawn(bindEffects.transform, Vector3.zero); - //} + if (maggotAudio != null) { PlaySound(playerObject, maggotAudio); } @@ -113,10 +119,12 @@ protected void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject /// Adds a damage component if appropriate. /// private void PlayMirror(GameObject playerObject, bool upgraded) { - Logger.Info("Playing Claw Mirror Animation"); + // Get claw prefabs + Logger.Debug("Playing Claw Mirror Animation"); var regularClaw = GetOrFindBindFsm().GetAction("Dazzle?", 3)!; var upgradedClaw = GetOrFindBindFsm().GetAction("Dazzle?", 4)!; + // Create the claw GameObject? claw; if (upgraded) claw = PrepareMirror(playerObject, upgradedClaw); else claw = PrepareMirror(playerObject, regularClaw); @@ -127,6 +135,7 @@ private void PlayMirror(GameObject playerObject, bool upgraded) { claw.SetActive(true); + // Add hitbox if appropriate if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { var damagerParent = claw.FindGameObjectInChildren("Trobbio_dazzle_flash"); var damager = damagerParent?.FindGameObjectInChildren("hero_dazzle_flash_damager"); @@ -147,9 +156,7 @@ private void PlayMirror(GameObject playerObject, bool upgraded) { /// public void StopBindBell(GameObject bindEffects) { var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); - if (bindBell != null) { - bindBell.SetActive(false); - } + bindBell?.SetActive(false); } /// @@ -172,13 +179,13 @@ private void PlayWitchMaggoted(GameObject bindEffects) { } /// - /// Plays the Witch Crest tentancles animation + /// Plays the Witch Crest tentancles animation. /// Yes it's called Tentancles internally. Thanks TC. /// private void PlayWitchEnd(GameObject bindEffects) { var witchBind = bindEffects.FindGameObjectInChildren("Witch Bind"); if (witchBind == null) { - var localWitchBind = _localBindEffects.FindGameObjectInChildren("Witch Bind"); + var localWitchBind = _localBindEffects?.FindGameObjectInChildren("Witch Bind"); if (localWitchBind == null) { Logger.Warn("Unable to find local Witch Bind object"); return; @@ -204,7 +211,7 @@ private void PlayWitchEnd(GameObject bindEffects) { } /// - /// Adds hero damage components to Witch Crest bind if PVP is on + /// Adds or removes hero damage components from Witch Crest bind /// private void SetWitchDamagers(GameObject witchBind) { for (var i = 0; i < witchBind.transform.childCount; i++) { diff --git a/SSMP/Animation/Effects/BindInterupt.cs b/SSMP/Animation/Effects/BindInterupt.cs index 5887196..843cd0d 100644 --- a/SSMP/Animation/Effects/BindInterupt.cs +++ b/SSMP/Animation/Effects/BindInterupt.cs @@ -1,24 +1,24 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; using HutongGames.PlayMaker.Actions; using SSMP.Internals; using SSMP.Util; using UnityEngine; -using static UnityEngine.ParticleSystem; using Logger = SSMP.Logging.Logger; namespace SSMP.Animation.Effects; +/// +/// Class for the animation effect of a bind (healing) being interupted by an attack. +/// internal class BindInterupt : Bind { /// /// Static instance for access by multiple animation clips in . /// private static BindInterupt? _instance; + /// public static BindInterupt Instance => _instance ??= new BindInterupt(); + + /// public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { Flags flags = new Flags(effectInfo); @@ -29,34 +29,35 @@ public override void Play(GameObject playerObject, CrestType crestType, ushort p ForceStopAllEffects(bindEffects); if (flags.BindBell) { - PlayBellBurst(bindEffects); + PlayBellExplode(bindEffects); } else { - PlayBindBurst(bindEffects); + PlayBindInterupt(bindEffects); } } - private void PlayBindBurst(GameObject bindEffects) { - var bellBurstSpawner = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); - if (bellBurstSpawner == null) { + /// + /// Plays the normal bind interput animation + /// + private void PlayBindInterupt(GameObject bindEffects) { + // Find prefab + var bindBurstSpawner = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); + if (bindBurstSpawner == null) { Logger.Warn("Unable to find bind burst effect spawner"); return; } - var audio = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); - - var burst = EffectUtils.SpawnGlobalPoolObject(bellBurstSpawner, bindEffects.transform, 5f); - + // Spawn in effect + var burst = EffectUtils.SpawnGlobalPoolObject(bindBurstSpawner, bindEffects.transform, 5f); if (burst == null) { Logger.Warn("Unable to create bind burst effect"); return; } + // Play audio + var audio = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); PlaySound(bindEffects.transform.parent.gameObject, audio); - if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { - AddDamageHeroComponent(burst); - } - + // Remove haze and camera controls var haze = burst.FindGameObjectInChildren("haze2"); if (haze != null) { GameObject.Destroy(haze); @@ -68,34 +69,43 @@ private void PlayBindBurst(GameObject bindEffects) { } } - private void PlayBellBurst(GameObject bindEffects) { - Logger.Info("Playing Bell Burst"); + /// + /// Creates a Warding Bell explosion + /// + private void PlayBellExplode(GameObject bindEffects) { + Logger.Debug("Playing Bell Burst"); + // Initialize warding bell FSM if it isn't already. + // This fills it in with the template var bellFsm = HeroController.instance.bellBindFSM; if (!bellFsm.fsm.initialized) { HeroController.instance.bellBindFSM.Init(); } if (bellFsm == null) { - Logger.Warn("Unable to find bind bell fsm"); + Logger.Warn("Unable to find warding bell fsm"); return; } - var audio = bellFsm.GetFirstAction("Burst"); - PlaySound(bindEffects.transform.parent.gameObject, audio); - var spawner = bellFsm.GetFirstAction("Burst"); if (spawner == null) { - Logger.Warn("Unable to find bind bell spawner"); + Logger.Warn("Unable to find warding bell spawner"); return; } + // Spawn warding bell var bindBell = EffectUtils.SpawnGlobalPoolObject(spawner, bindEffects.transform, 5f); if (bindBell == null) { return; } + // Play sound + var audio = bellFsm.GetFirstAction("Burst"); + PlaySound(bindBell, audio); + + + // Remove camera control and haze var shaker = bindBell.GetComponentInChildren(); if (shaker != null) { Component.DestroyImmediate(shaker); @@ -106,12 +116,13 @@ private void PlayBellBurst(GameObject bindEffects) { GameObject.Destroy(haze); } + // Add hitbox if appropriate if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { var damager = bindBell.FindGameObjectInChildren("damager"); if (damager != null) { AddDamageHeroComponent(damager); } else { - Logger.Warn("Unable to add damager to bind bell burst"); + Logger.Warn("Unable to add damager to warding bell burst"); } } } diff --git a/SSMP/Animation/Effects/EffectUtils.cs b/SSMP/Animation/Effects/EffectUtils.cs index 1c17ed6..f38dcff 100644 --- a/SSMP/Animation/Effects/EffectUtils.cs +++ b/SSMP/Animation/Effects/EffectUtils.cs @@ -7,6 +7,11 @@ namespace SSMP.Animation.Effects; internal static class EffectUtils { + /// + /// Removes the AutoRecycleSelf component from a specified GameObject, ensuring that any active timers and + /// subscriptions are stopped before removal. + /// + /// The GameObject from which to remove the AutoRecycleSelf from. public static void SafelyRemoveAutoRecycle(GameObject obj) { var recycler = obj.GetComponent(); if (recycler != null) { @@ -18,6 +23,14 @@ public static void SafelyRemoveAutoRecycle(GameObject obj) { } } + /// + /// Instantiates an object from the global pool at the specified location + /// + /// The spawner responsible for providing the global pool object. + /// The location where the object will be spawned. + /// The duration, in seconds, after which the spawned object will be destroyed. + /// Whether to keep the parent or unparent the new object + /// A newly spawned GameObject from the global pool. public static GameObject? SpawnGlobalPoolObject(SpawnObjectFromGlobalPool? spawner, Transform spawnLocation, float destroyAfterDelay, bool keepParent = false) { if (spawner == null) { Logger.Warn("Unable to find global pool object"); @@ -27,6 +40,11 @@ public static void SafelyRemoveAutoRecycle(GameObject obj) { return SpawnGlobalPoolObject(spawner.gameObject.Value, spawnLocation, destroyAfterDelay, keepParent); } + /// + /// The GameObject to spawn + /// + /// + /// public static GameObject? SpawnGlobalPoolObject(GameObject? globalObj, Transform spawnLocation, float destroyAfterDelay, bool keepParent = false) { if (globalObj == null) { Logger.Warn("Unable to find global pool object"); From 32a885f2fd64038fcac4b2d8f2b374625b976051 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:14:00 -0400 Subject: [PATCH 24/33] remove testing components --- SSMP/Game/Client/PlayerManager.cs | 2 - SSMP/Testing/PlayerAnimation.cs | 145 ------------------------------ 2 files changed, 147 deletions(-) delete mode 100644 SSMP/Testing/PlayerAnimation.cs diff --git a/SSMP/Game/Client/PlayerManager.cs b/SSMP/Game/Client/PlayerManager.cs index b3dca56..a8cdc31 100644 --- a/SSMP/Game/Client/PlayerManager.cs +++ b/SSMP/Game/Client/PlayerManager.cs @@ -149,8 +149,6 @@ private void TryCreatePlayerPool() { // Create a player container prefab, used to spawn players _playerContainerPrefab = new GameObject(PlayerContainerPrefabName); - _playerContainerPrefab.AddComponent(); - _playerContainerPrefab.AddComponent(); var playerPrefab = new GameObject( diff --git a/SSMP/Testing/PlayerAnimation.cs b/SSMP/Testing/PlayerAnimation.cs deleted file mode 100644 index ca798ad..0000000 --- a/SSMP/Testing/PlayerAnimation.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using GlobalSettings; -using HarmonyLib; -using SSMP.Animation; -using SSMP.Animation.Effects; -using SSMP.Game.Settings; -using SSMP.Internals; -using UnityEngine; -using static UnityEngine.Rendering.RayTracingAccelerationStructure; -using Logger = SSMP.Logging.Logger; -using Object = UnityEngine.Object; - -namespace SSMP.Testing; - -public class PlayerAnimation : MonoBehaviour { - - CrestType DetermineCrest() { - var playerCrest = HeroController.instance.playerData.CurrentCrestID! ?? ""; - //if (playerCrest == null) return CrestType.Hunter; - - if (playerCrest == Gameplay.HunterCrest.name) return CrestType.Hunter; - if (playerCrest == Gameplay.HunterCrest2.name) return CrestType.HunterV2; - if (playerCrest == Gameplay.HunterCrest3.name) return CrestType.HunterV3; - if (playerCrest == Gameplay.ReaperCrest.name) return CrestType.Reaper; - - if (playerCrest == Gameplay.WandererCrest.name) return CrestType.Wanderer; - if (playerCrest == Gameplay.CloaklessCrest.name) return CrestType.Cloakless; - if (playerCrest == Gameplay.WarriorCrest.name) return CrestType.Beast; - if (playerCrest == Gameplay.ToolmasterCrest.name) return CrestType.Architect; - if (playerCrest == Gameplay.CursedCrest.name) return CrestType.Cursed; - if (playerCrest == Gameplay.SpellCrest.name) return CrestType.Shaman; - if (playerCrest == Gameplay.WitchCrest.name) return CrestType.Witch; - - return CrestType.Hunter; - } - - void SetSettings(AnimationEffect bind) { - var plugin = GameObject.FindFirstObjectByType(); - var traverse = Traverse.Create(plugin).Field("_gameManager").Field("_clientManager").Field("_serverSettings"); - var settings = traverse.GetValue(); - - bind.SetServerSettings(settings); - } - - void SetCrest(string crest) { - ToolItemManager.SetEquippedCrest(crest); - } - - void Init() { - gameObject.SetActive(true); - //ToolItemManager.SetEquippedCrest(Gameplay.CursedCrest.name); - - var hornet = HeroController.instance.gameObject; - var position = hornet.transform.position; - var playerPosition = new Vector3(position.x + 10, position.y, position.z); - transform.position = playerPosition; - } - - public void StartAnimation1() { - Init(); - CrestType crest = DetermineCrest(); - - if (crest != CrestType.Shaman) { - return; - } - string clipName = "BindCharge Ground"; - - var playerObject = transform.GetChild(0).gameObject; - var bind = new Bind { BindState = Bind.State.Normal }; - - var info = bind.GetEffectInfo(); - - SetSettings(bind); - bind.Play(playerObject, crest, 1, info); - - var spriteAnimator = playerObject.GetComponent(); - - var clip = spriteAnimator.GetClipByName(clipName); - spriteAnimator.PlayFromFrame(clip, 0); - } - - public void StartAnimation2() { - var clipName = "BindBurst Ground"; - - var playerObject = transform.GetChild(0).gameObject; - - var bind = new BindBurst(); - SetSettings(bind); - bind.SetShouldDoDamage(true); - - var info = bind.GetEffectInfo(); - CrestType crest = DetermineCrest(); - - bind.Play(playerObject, crest, 1, info); - - var spriteAnimator = playerObject.GetComponent(); - - var clip = spriteAnimator.GetClipByName(clipName); - spriteAnimator.PlayFromFrame(clip, 0); - } - - - public void StartAnimation3() { - - Init(); - - string clipName = "BindCharge Ground"; - CrestType crest = DetermineCrest(); - - var playerObject = transform.GetChild(0).gameObject; - var bind = new Bind { BindState = Bind.State.ShamanDoneFalling }; - - var info = bind.GetEffectInfo(); - - SetSettings(bind); - bind.Play(playerObject, crest, 1, info); - - var spriteAnimator = playerObject.GetComponent(); - - var clip = spriteAnimator.GetClipByName(clipName); - spriteAnimator.PlayFromFrame(clip, 0); - } - - public void StartAnimation4() { - var clipName = "BindBurst Ground"; - - var playerObject = transform.GetChild(0).gameObject; - - var bind = new Bind { BindState = Bind.State.ShamanCancel }; - SetSettings(bind); - bind.SetShouldDoDamage(true); - - var info = bind.GetEffectInfo(); - CrestType crest = DetermineCrest(); - - bind.Play(playerObject, crest, 1, info); - - var spriteAnimator = playerObject.GetComponent(); - - var clip = spriteAnimator.GetClipByName(clipName); - spriteAnimator.PlayFromFrame(clip, 0); - } -} From 9d9f434d1b8c2c9dc0794cf6276644e858799826 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 18 Mar 2026 17:10:36 -0400 Subject: [PATCH 25/33] documentation, remove accidental debugs --- SSMP/Animation/AnimationEffect.cs | 17 +++++++++++++++++ SSMP/Animation/AnimationManager.cs | 2 +- SSMP/Animation/Effects/Bind.cs | 1 - 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/SSMP/Animation/AnimationEffect.cs b/SSMP/Animation/AnimationEffect.cs index b05d89e..b1ffd94 100644 --- a/SSMP/Animation/AnimationEffect.cs +++ b/SSMP/Animation/AnimationEffect.cs @@ -43,14 +43,31 @@ protected static void ChangeAttackDirection(GameObject targetObject, float direc directionVar.Value = direction; } + + /// + /// Plays a one-shot audio clip at the specified GameObject source. + /// + /// The object to play the sound at. + /// The audio clip to be played. protected static void PlaySound(GameObject source, AudioPlayerOneShotSingle audio) { AudioUtil.PlayAudioOneShotSingleAtPlayerObject(audio, source); } + /// + /// Plays a specified audio event at the specified GameObject source. + /// + /// The object to play the sound at. + /// The FSM action with the audio clip to be played. protected static void PlaySound(GameObject source, PlayAudioEvent audio) { AudioUtil.PlayAudioEventAtPlayerObject(audio, source); } + /// + /// Plays a random sound effect at the specified GameObject source + /// + /// The object to play the sound at. + /// The FSM action with the audio table. + /// The FSM action with the audio playing function. protected static void PlaySound(GameObject source, GetRandomAudioClipFromTable getAction, PlayAudioEvent playAction) { AudioUtil.PlayAudioEventWithRandomAudioClipFromTableAtPlayerObject( playAction, diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 922f9aa..fb1c011 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -25,7 +25,7 @@ internal class AnimationManager { /// Whether to log debug messages about animations. For debugging purposes this can be enabled so that all /// animation events are logged. /// - private static bool _debugLogAnimations = true; + private static bool _debugLogAnimations = false; /// /// The distance threshold for playing certain effects. diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index b898955..13b6930 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -343,7 +343,6 @@ private void PlayShamanFallEnd(GameObject bindEffects) { (byte) ((ToolItemManager.IsToolEquipped("Reserve Bind") && !ToolItemManager.GetToolByName("Reserve Bind").IsEmpty) ? 1 : 0), (byte) (HeroController.instance.cState.isMaggoted ? 1 : 0) }; - Logger.Info(HeroController.instance.cState.isMaggoted ? "MAGGOTS" : "NO MAGGOTS"); return effectInfo; } From 37b4038e588963a9d7bf5fdc20751a399ae8ed81 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 18 Mar 2026 17:30:32 -0400 Subject: [PATCH 26/33] requested style changes --- SSMP/Animation/DamageAnimationEffect.cs | 6 +- SSMP/Animation/Effects/Bind.cs | 133 ++++++++++++------------ SSMP/Animation/Effects/BindBurst.cs | 9 +- SSMP/Fsm/FsmStateActionInjector.cs | 5 +- 4 files changed, 72 insertions(+), 81 deletions(-) diff --git a/SSMP/Animation/DamageAnimationEffect.cs b/SSMP/Animation/DamageAnimationEffect.cs index 876806c..c8fde05 100644 --- a/SSMP/Animation/DamageAnimationEffect.cs +++ b/SSMP/Animation/DamageAnimationEffect.cs @@ -63,9 +63,9 @@ protected static void RemoveDamageHeroComponent(GameObject target) { protected DamageHero? SetDamageHeroState(GameObject target, int damage = 1) { if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { return AddDamageHeroComponent(target, damage); - } else { - RemoveDamageHeroComponent(target); - return null; } + + RemoveDamageHeroComponent(target); + return null; } } diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 13b6930..5eac9ea 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Linq; using HutongGames.PlayMaker.Actions; using SSMP.Internals; @@ -15,51 +16,23 @@ namespace SSMP.Animation.Effects; /// internal class Bind : DamageAnimationEffect { - public enum State { - Normal, - ShamanCancel, - ShamanDoneFalling - } - - protected const string BIND_BELL_NAME = "bind_bell_appear_instance"; + protected const string BindBellName = "bind_bell_appear_instance"; public State BindState = State.Normal; - /// - /// Effect flags sent by the other player. Mostly items they have equipped. - /// - protected class Flags { - public bool BindBell = false; - public bool BaseMirror = false; - public bool UpgradedMirror = false; - public bool QuickBind = false; - public bool ReserveBind = false; - public bool Maggoted = false; - - public Flags(byte[]? info) { - if (info == null) return; - BindBell = info[0] == 1; - BaseMirror = info[1] == 1; - UpgradedMirror = info[2] == 1; - QuickBind = info[3] == 1; - ReserveBind = info[4] == 1; - Maggoted = info[5] == 1; - } - } - /// /// Cached FSM for Hornet's bind ability. /// - protected static PlayMakerFSM? _bindFsm; + protected static PlayMakerFSM? BindFsm; /// /// Cached effects object for Hornet's bind ability. /// - protected static GameObject? _localBindEffects; + protected static GameObject? LocalBindEffects; /// public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { - Flags flags = new Flags(effectInfo); + var flags = new Flags(effectInfo); // The maggot state is cleared by the time the bind burst is sent. // This method keeps track of it, although at a slight possible loss of consistancy @@ -69,42 +42,35 @@ public override void Play(GameObject playerObject, CrestType crestType, ushort p BindBurst.MaggotedPlayers.Remove(playerId); } - MonoBehaviourUtil.Instance.StartCoroutine(PlayBindEffect(playerObject, crestType, flags)); - } - - private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, Flags flags) { var randomClipAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); var playAudioAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); if (BindState == State.Normal) { PlaySound(playerObject, randomClipAction, playAudioAction); - + var oneShotSingleAction = GetOrFindBindFsm().GetFirstAction("Check Grounded"); PlaySound(playerObject, oneShotSingleAction); } - var created = CreateObjects(playerObject, out var bindEffects); - if (!created) { - yield break; + if (!CreateObjects(playerObject, out var bindEffects)) { + return; } - Logger.Info("Determining crest animation..."); - - switch(crestType) { + switch (crestType) { case CrestType.Beast: PlayBeastBindStart(bindEffects); break; case CrestType.Cursed: PlayCursedFail(bindEffects); - yield break; + return; case CrestType.Witch: PlayWitchAnimationAntic(bindEffects); break; case CrestType.Shaman: var shouldContinue = PickShamanAnimation(playerObject, bindEffects, flags); if (!shouldContinue) { - yield break; + return; } break; default: @@ -120,10 +86,6 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, // TODO: If using reserve bind, use reserve bind animation? // TODO: Quick Craft animations - - var playerAnimator = playerObject.GetComponent(); - var currentClip = playerAnimator?.currentClip; - Logger.Info($"Player Animator current clip for Bind: {currentClip?.name}"); } /// @@ -131,7 +93,7 @@ private IEnumerator PlayBindEffect(GameObject playerObject, CrestType crestType, /// private void StartBindBell(GameObject bindEffects) { Logger.Debug("Starting warding bell"); - var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); + var bindBell = bindEffects.FindGameObjectInChildren(BindBellName); if (bindBell == null) { var allObjects = Resources.FindObjectsOfTypeAll(); @@ -142,8 +104,8 @@ private void StartBindBell(GameObject bindEffects) { return; } - bindBell = GameObject.Instantiate(localBell, bindEffects.transform); - bindBell.name = BIND_BELL_NAME; + bindBell = Object.Instantiate(localBell, bindEffects.transform); + bindBell.name = BindBellName; var follower = bindBell.GetComponent(); follower.target = bindEffects.transform; @@ -174,9 +136,11 @@ private void PlayNormalStart(GameObject bindEffects, Flags flags) { Logger.Info("Playing Bind Silk animation"); - if (flags.QuickBind) bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk Quick")); - else bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk")); - + if (flags.QuickBind) { + bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk Quick")); + } else { + bindSilkAnimator.Play(bindSilkAnimator.GetClipByName("Bind Silk")); + } bindSilkObj.SetActive(false); bindSilkObj.SetActive(true); } @@ -216,7 +180,7 @@ private void PlayCursedFail(GameObject bindEffects) { return; } - failAntic = GameObject.Instantiate(localFailAntic, bindEffects.transform); + failAntic = Object.Instantiate(localFailAntic, bindEffects.transform); failAntic.name = "cursed_bind_fail"; failAntic.transform.SetLocalPositionZ(failAntic.transform.localPosition.z - 0.25f); @@ -267,12 +231,11 @@ private bool PickShamanAnimation(GameObject playerObject, GameObject bindEffects } else if (BindState == State.ShamanCancel) { PlayShamanCancel(playerObject, bindEffects); return false; - } else { - PlayShamanFallEnd(bindEffects); - PlayNormalStart(bindEffects, flags); - return false; } + PlayShamanFallEnd(bindEffects); + PlayNormalStart(bindEffects, flags); + return false; } /// @@ -347,21 +310,21 @@ private void PlayShamanFallEnd(GameObject bindEffects) { } /// - /// Get or find the Bind FSM on the hero object. Will be cached to . + /// Get or find the Bind FSM on the hero object. Will be cached to . /// /// The FSM for Bind. /// Thrown if the FSM cannot be found, which shouldn't happen. /// protected PlayMakerFSM GetOrFindBindFsm() { - if (_bindFsm != null) { - return _bindFsm; + if (BindFsm != null) { + return BindFsm; } var heroFsms = HeroController.instance.GetComponents(); foreach (var heroFsm in heroFsms) { if (heroFsm.FsmName == "Bind") { - _bindFsm = heroFsm; - return _bindFsm; + BindFsm = heroFsm; + return BindFsm; } } @@ -374,9 +337,9 @@ protected PlayMakerFSM GetOrFindBindFsm() { /// The player's object. /// The player's 'Bind Effects' object, or null if not found. /// true if the 'Bind Effects' GameObject is successfully found and bound; otherwise, false. - protected bool CreateObjects(GameObject playerObject, out GameObject bindEffects) { - _localBindEffects ??= HeroController.instance.gameObject.FindGameObjectInChildren("Bind Effects"); - if (_localBindEffects == null) { + protected bool CreateObjects(GameObject playerObject, [MaybeNullWhen(false)] out GameObject bindEffects) { + LocalBindEffects ??= HeroController.instance.gameObject.FindGameObjectInChildren("Bind Effects"); + if (LocalBindEffects == null) { Logger.Warn("Could not find local Bind Effects object in hero object"); bindEffects = null; return false; @@ -400,7 +363,7 @@ protected bool CreateObjects(GameObject playerObject, out GameObject bindEffects protected GameObject? CreateEffectIfNotExists(GameObject bindEffects, string objectName) { var obj = bindEffects.FindGameObjectInChildren(objectName); if (obj == null) { - var localObj = _localBindEffects.FindGameObjectInChildren(objectName); + var localObj = LocalBindEffects!.FindGameObjectInChildren(objectName); if (localObj == null) { Logger.Warn($"Could not find local {objectName} object, cannot play bind"); return null; @@ -444,4 +407,36 @@ public static void ForceStopAllEffects(GameObject bindEffects) { bindSilkObj.SetActive(false); } } + + /// + /// Shaman Crest has special animations depending on which animation is + /// sent, but they're all related to the bind starting + /// + public enum State { + Normal, + ShamanCancel, + ShamanDoneFalling + } + + /// + /// Effect flags sent by the other player. Mostly items they have equipped. + /// + protected class Flags { + public bool BindBell = false; + public bool BaseMirror = false; + public bool UpgradedMirror = false; + public bool QuickBind = false; + public bool ReserveBind = false; + public bool Maggoted = false; + + public Flags(byte[]? info) { + if (info == null) return; + BindBell = info[0] == 1; + BaseMirror = info[1] == 1; + UpgradedMirror = info[2] == 1; + QuickBind = info[3] == 1; + ReserveBind = info[4] == 1; + Maggoted = info[5] == 1; + } + } } diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index 5fe134b..49cc46c 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; using HutongGames.PlayMaker.Actions; using SSMP.Internals; using SSMP.Util; using UnityEngine; using Logger = SSMP.Logging.Logger; -using Object = UnityEngine.Object; namespace SSMP.Animation.Effects; @@ -155,7 +150,7 @@ private void PlayMirror(GameObject playerObject, bool upgraded) { /// Stops the bind bell animation /// public void StopBindBell(GameObject bindEffects) { - var bindBell = bindEffects.FindGameObjectInChildren(BIND_BELL_NAME); + var bindBell = bindEffects.FindGameObjectInChildren(BindBellName); bindBell?.SetActive(false); } @@ -185,7 +180,7 @@ private void PlayWitchMaggoted(GameObject bindEffects) { private void PlayWitchEnd(GameObject bindEffects) { var witchBind = bindEffects.FindGameObjectInChildren("Witch Bind"); if (witchBind == null) { - var localWitchBind = _localBindEffects?.FindGameObjectInChildren("Witch Bind"); + var localWitchBind = LocalBindEffects?.FindGameObjectInChildren("Witch Bind"); if (localWitchBind == null) { Logger.Warn("Unable to find local Witch Bind object"); return; diff --git a/SSMP/Fsm/FsmStateActionInjector.cs b/SSMP/Fsm/FsmStateActionInjector.cs index ff9742f..2270597 100644 --- a/SSMP/Fsm/FsmStateActionInjector.cs +++ b/SSMP/Fsm/FsmStateActionInjector.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using HutongGames.PlayMaker; using SSMP.Logging; @@ -34,6 +32,9 @@ public void Uninject() { actions.Remove(this); State.Actions = actions.ToArray(); State.SaveActions(); + + _onStateEnter = null; + _onUninject -= Uninject; } /// From ec01d3cd7683d993ad69208af9cda6633db8d6bd Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 18 Mar 2026 17:36:00 -0400 Subject: [PATCH 27/33] remove player id from animations, use object instance id instead --- SSMP/Animation/AnimationEffect.cs | 2 +- SSMP/Animation/AnimationManager.cs | 1 - SSMP/Animation/DamageAnimationEffect.cs | 2 +- SSMP/Animation/Effects/Bind.cs | 7 ++++--- SSMP/Animation/Effects/BindBurst.cs | 18 +++++++++--------- SSMP/Animation/Effects/BindInterupt.cs | 2 +- SSMP/Animation/Effects/DashSlash.cs | 2 +- SSMP/Animation/Effects/DashSlashAntic.cs | 2 +- SSMP/Animation/Effects/DashSlashReaper.cs | 2 +- SSMP/Animation/Effects/DownSpike.cs | 2 +- SSMP/Animation/Effects/NeedleStrike.cs | 2 +- SSMP/Animation/Effects/Slash.cs | 2 +- SSMP/Animation/Effects/SlashBase.cs | 2 +- SSMP/Animation/IAnimationEffect.cs | 3 +-- SSMP/Fsm/FsmStateActionInjector.cs | 2 +- 15 files changed, 25 insertions(+), 26 deletions(-) diff --git a/SSMP/Animation/AnimationEffect.cs b/SSMP/Animation/AnimationEffect.cs index b1ffd94..fdce536 100644 --- a/SSMP/Animation/AnimationEffect.cs +++ b/SSMP/Animation/AnimationEffect.cs @@ -16,7 +16,7 @@ internal abstract class AnimationEffect : IAnimationEffect { protected ServerSettings ServerSettings = null!; /// - public abstract void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo); + public abstract void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo); /// public abstract byte[]? GetEffectInfo(); diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index fb1c011..69e49b6 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -860,7 +860,6 @@ public void OnPlayerAnimationUpdate(ushort id, int clipId, int frame, byte[]? ef animationEffect.Play( playerObject, crestType, - id, effectInfo ); } diff --git a/SSMP/Animation/DamageAnimationEffect.cs b/SSMP/Animation/DamageAnimationEffect.cs index c8fde05..be6fab6 100644 --- a/SSMP/Animation/DamageAnimationEffect.cs +++ b/SSMP/Animation/DamageAnimationEffect.cs @@ -14,7 +14,7 @@ internal abstract class DamageAnimationEffect : AnimationEffect { protected bool ShouldDoDamage; /// - public abstract override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo); + public abstract override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo); /// public abstract override byte[]? GetEffectInfo(); diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 5eac9ea..1ffa21a 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -31,15 +31,16 @@ internal class Bind : DamageAnimationEffect { protected static GameObject? LocalBindEffects; /// - public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { var flags = new Flags(effectInfo); // The maggot state is cleared by the time the bind burst is sent. // This method keeps track of it, although at a slight possible loss of consistancy + var playerObjectIdentifier = playerObject.GetInstanceID(); if (flags.Maggoted && !(crestType == CrestType.Shaman && BindState == State.ShamanCancel)) { - BindBurst.MaggotedPlayers.Add(playerId); + BindBurst.MaggotedPlayers.Add(playerObjectIdentifier); } else { - BindBurst.MaggotedPlayers.Remove(playerId); + BindBurst.MaggotedPlayers.Remove(playerObjectIdentifier); } var randomClipAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index 49cc46c..11d3a11 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -22,15 +22,17 @@ internal class BindBurst : Bind { /// /// A set of players who recently binded while maggoted /// - public static HashSet MaggotedPlayers = new HashSet(); + public static readonly HashSet MaggotedPlayers = new(); /// - public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { // Set maggot info - Flags flags = new Flags(effectInfo); - if (MaggotedPlayers.Contains(playerId)) { + var flags = new Flags(effectInfo); + + var playerObjectIdentifier = playerObject.GetInstanceID(); + if (MaggotedPlayers.Contains(playerObjectIdentifier)) { flags.Maggoted = true; - MaggotedPlayers.Remove(playerId); + MaggotedPlayers.Remove(playerObjectIdentifier); } if (!CreateObjects(playerObject, out var bindEffects)) { @@ -40,7 +42,7 @@ public override void Play(GameObject playerObject, CrestType crestType, ushort p if (flags.BaseMirror || flags.UpgradedMirror) PlayMirror(playerObject, flags.UpgradedMirror); if (flags.Maggoted) PlayMaggotCleanse(bindEffects, playerObject); - // Stop regardless of if its on or not + // Stop regardless of if it's on or not StopBindBell(bindEffects); // Play crest-specific animations @@ -60,8 +62,6 @@ public override void Play(GameObject playerObject, CrestType crestType, ushort p case CrestType.Shaman: PlayShamanEnd(bindEffects); break; - default: - break; } PlayNormalEnd(bindEffects); @@ -70,7 +70,7 @@ public override void Play(GameObject playerObject, CrestType crestType, ushort p /// /// Plays the maggot cleanse animation /// - protected void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) { + private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) { Logger.Info("Playing Maggot Animation"); var maggotBurst = GetOrFindBindFsm().GetAction("Maggoted?", 2); var maggotFlash = GetOrFindBindFsm().GetAction("Maggoted?", 4); diff --git a/SSMP/Animation/Effects/BindInterupt.cs b/SSMP/Animation/Effects/BindInterupt.cs index 843cd0d..eff3a7b 100644 --- a/SSMP/Animation/Effects/BindInterupt.cs +++ b/SSMP/Animation/Effects/BindInterupt.cs @@ -19,7 +19,7 @@ internal class BindInterupt : Bind { public static BindInterupt Instance => _instance ??= new BindInterupt(); /// - public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { Flags flags = new Flags(effectInfo); if (!CreateObjects(playerObject, out var bindEffects)) { diff --git a/SSMP/Animation/Effects/DashSlash.cs b/SSMP/Animation/Effects/DashSlash.cs index 14edeb0..8eac3f5 100644 --- a/SSMP/Animation/Effects/DashSlash.cs +++ b/SSMP/Animation/Effects/DashSlash.cs @@ -25,7 +25,7 @@ public DashSlash(DashSlashType dashSlashType) { } /// - public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { if (effectInfo == null || effectInfo.Length < 1) { Logger.Error("Could not get null or empty effect info for DashAttack"); return; diff --git a/SSMP/Animation/Effects/DashSlashAntic.cs b/SSMP/Animation/Effects/DashSlashAntic.cs index 78c4845..57e0add 100644 --- a/SSMP/Animation/Effects/DashSlashAntic.cs +++ b/SSMP/Animation/Effects/DashSlashAntic.cs @@ -23,7 +23,7 @@ private DashSlashAntic() { } /// - public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { var sprintFsm = HeroController.instance.sprintFSM; PlayAudioEvent playAudioAction; switch (crestType) { diff --git a/SSMP/Animation/Effects/DashSlashReaper.cs b/SSMP/Animation/Effects/DashSlashReaper.cs index e095316..72c4190 100644 --- a/SSMP/Animation/Effects/DashSlashReaper.cs +++ b/SSMP/Animation/Effects/DashSlashReaper.cs @@ -9,7 +9,7 @@ namespace SSMP.Animation.Effects; /// internal class DashSlashReaper : SlashBase { /// - public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { // Call the base function with the correct parameters Play(playerObject, crestType, effectInfo, SlashType.Dash); diff --git a/SSMP/Animation/Effects/DownSpike.cs b/SSMP/Animation/Effects/DownSpike.cs index 4b174a3..88e7110 100644 --- a/SSMP/Animation/Effects/DownSpike.cs +++ b/SSMP/Animation/Effects/DownSpike.cs @@ -11,7 +11,7 @@ namespace SSMP.Animation.Effects; /// internal class DownSpike : SlashBase { /// - public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { if (effectInfo == null || effectInfo.Length < 1) { Logger.Error("Could not get null or empty effect info for SlashBase"); return; diff --git a/SSMP/Animation/Effects/NeedleStrike.cs b/SSMP/Animation/Effects/NeedleStrike.cs index a50d76f..e6e03f1 100644 --- a/SSMP/Animation/Effects/NeedleStrike.cs +++ b/SSMP/Animation/Effects/NeedleStrike.cs @@ -102,7 +102,7 @@ public NeedleStrike(bool witchLoop) { } /// - public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { if (effectInfo == null || effectInfo.Length < 1) { Logger.Error("Could not get null or empty effect info for SlashBase"); return; diff --git a/SSMP/Animation/Effects/Slash.cs b/SSMP/Animation/Effects/Slash.cs index c4350c5..18636cf 100644 --- a/SSMP/Animation/Effects/Slash.cs +++ b/SSMP/Animation/Effects/Slash.cs @@ -21,7 +21,7 @@ public Slash(SlashType slashType) { } /// - public override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo) { + public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { // Call the base function with the correct parameters Play(playerObject, crestType, effectInfo, _slashType); } diff --git a/SSMP/Animation/Effects/SlashBase.cs b/SSMP/Animation/Effects/SlashBase.cs index e789410..cff731b 100644 --- a/SSMP/Animation/Effects/SlashBase.cs +++ b/SSMP/Animation/Effects/SlashBase.cs @@ -16,7 +16,7 @@ namespace SSMP.Animation.Effects; /// internal abstract class SlashBase : ParryableEffect { /// - public abstract override void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo); + public abstract override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo); /// public override byte[] GetEffectInfo() { diff --git a/SSMP/Animation/IAnimationEffect.cs b/SSMP/Animation/IAnimationEffect.cs index f23ed45..d8a9709 100644 --- a/SSMP/Animation/IAnimationEffect.cs +++ b/SSMP/Animation/IAnimationEffect.cs @@ -13,9 +13,8 @@ internal interface IAnimationEffect { /// /// The GameObject representing the player. /// The type of crest the player is using. - /// The ID of the player sending the animation /// A byte array containing effect info. - void Play(GameObject playerObject, CrestType crestType, ushort playerId, byte[]? effectInfo); + void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo); /// /// Get the effect info corresponding to this effect. diff --git a/SSMP/Fsm/FsmStateActionInjector.cs b/SSMP/Fsm/FsmStateActionInjector.cs index 2270597..cca9c56 100644 --- a/SSMP/Fsm/FsmStateActionInjector.cs +++ b/SSMP/Fsm/FsmStateActionInjector.cs @@ -5,7 +5,7 @@ namespace SSMP.Fsm; -internal class FsmStateActionInjector : FsmStateAction { +internal sealed class FsmStateActionInjector : FsmStateAction { private static Action? _onUninject; private Action? _onStateEnter; private FsmStateActionInjector(FsmState state, Action onEnter) { From a9418d93bcfea36dea90102e0d3eafa032172ab9 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 18 Mar 2026 17:59:54 -0400 Subject: [PATCH 28/33] fix typos and more style changes --- SSMP/Animation/AnimationManager.cs | 21 +++++++++--------- SSMP/Animation/DamageAnimationEffect.cs | 4 +++- SSMP/Animation/Effects/Bind.cs | 18 +++++++-------- SSMP/Animation/Effects/BindBurst.cs | 22 ++++++++++--------- .../{BindInterupt.cs => BindInterrupt.cs} | 16 +++++++------- SSMP/Animation/Effects/EffectUtils.cs | 15 ++++++++----- 6 files changed, 52 insertions(+), 44 deletions(-) rename SSMP/Animation/Effects/{BindInterupt.cs => BindInterrupt.cs} (91%) diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 69e49b6..d557977 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -662,7 +662,7 @@ internal class AnimationManager { private static readonly Dictionary SubAnimationEffects = new() { { AnimationClip.WitchTentacles, BindBurst.Instance }, { AnimationClip.ShamanCancel, new Bind { BindState = Bind.State.ShamanCancel } }, - { AnimationClip.BindInterupt, BindInterupt.Instance } + { AnimationClip.BindInterupt, BindInterrupt.Instance } }; /// @@ -1056,16 +1056,17 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { private void CreateBindHooks(HeroController hc) { HeroController.instance.bellBindFSM.Init(); var heroFsms = hc.GetComponents(); - PlayMakerFSM bindFsm = heroFsms.FirstOrDefault(fsm => fsm.FsmName == "Bind"); + + var bindFsm = heroFsms.FirstOrDefault(fsm => fsm.FsmName == "Bind"); if (bindFsm == null) { Logger.Warn("Unable to find Bind FSM to hook."); return; } - // Find witch crest tenticles - var tenticles = bindFsm.GetState("Witch Tentancles!"); // no that's not a typo... at least on my end - if (tenticles != null) { - FsmStateActionInjector.Inject(tenticles, OnWitchTentacles, 4); + // Find witch crest tentacles + var tentacles = bindFsm.GetState("Witch Tentancles!"); // no that's not a typo... at least on my end + if (tentacles != null) { + FsmStateActionInjector.Inject(tentacles, OnWitchTentacles, 4); } else { Logger.Warn("Unable to find Witch Tentacles! state"); } @@ -1077,9 +1078,9 @@ private void CreateBindHooks(HeroController hc) { Logger.Warn("Unable to find Shaman Air Cancel state"); } - var bindInterupt = bindFsm.GetState("Remove Silk?"); - if (bindInterupt != null) { - FsmStateActionInjector.Inject(bindInterupt, OnBindInterupt, 2); + var bindInterrupt = bindFsm.GetState("Remove Silk?"); + if (bindInterrupt != null) { + FsmStateActionInjector.Inject(bindInterrupt, OnBindInterrupt, 2); } } @@ -1105,7 +1106,7 @@ private void OnShamanCancel(PlayMakerFSM fsm) { /// /// Animation subanimation hook for interupted binds /// - private void OnBindInterupt(PlayMakerFSM fsm) { + private void OnBindInterrupt(PlayMakerFSM fsm) { var dummyClip = new tk2dSpriteAnimationClip(); dummyClip.name = "Bind Fail Burst"; dummyClip.wrapMode = tk2dSpriteAnimationClip.WrapMode.Once; diff --git a/SSMP/Animation/DamageAnimationEffect.cs b/SSMP/Animation/DamageAnimationEffect.cs index be6fab6..e7707cc 100644 --- a/SSMP/Animation/DamageAnimationEffect.cs +++ b/SSMP/Animation/DamageAnimationEffect.cs @@ -33,6 +33,7 @@ public void SetShouldDoDamage(bool shouldDoDamage) { /// /// The target game object to attach the component to. /// The number of mask of damage it should deal. + /// The component that was added to the game object protected static DamageHero AddDamageHeroComponent(GameObject target, int damage = 1) { var damageHero = target.AddComponentIfNotPresent(); damageHero.damageDealt = damage; @@ -51,7 +52,7 @@ protected static void RemoveDamageHeroComponent(GameObject target) { return; } - Component.DestroyImmediate(damageHero); + Object.DestroyImmediate(damageHero); } /// @@ -60,6 +61,7 @@ protected static void RemoveDamageHeroComponent(GameObject target) { /// /// The target game object to detatch the component to. /// The number of mask of damage it should deal. + /// The component that was added if PVP was turned on protected DamageHero? SetDamageHeroState(GameObject target, int damage = 1) { if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { return AddDamageHeroComponent(target, damage); diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 1ffa21a..4c54f3f 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -35,7 +35,7 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? var flags = new Flags(effectInfo); // The maggot state is cleared by the time the bind burst is sent. - // This method keeps track of it, although at a slight possible loss of consistancy + // This method keeps track of it, although at a slight possible loss of consistency var playerObjectIdentifier = playerObject.GetInstanceID(); if (flags.Maggoted && !(crestType == CrestType.Shaman && BindState == State.ShamanCancel)) { BindBurst.MaggotedPlayers.Add(playerObjectIdentifier); @@ -279,7 +279,7 @@ private void PlayShamanCancel(GameObject playerObject, GameObject bindEffects) { EffectUtils.SpawnGlobalPoolObject(silkPuffSpawner, playerObject.transform, 5f); - BindBurst.Instance.StopBindBell(bindEffects); + BindBurst.StopBindBell(bindEffects); } /// @@ -381,7 +381,7 @@ protected bool CreateObjects(GameObject playerObject, [MaybeNullWhen(false)] out /// Turns off all bind effects /// public static void ForceStopAllEffects(GameObject bindEffects) { - BindBurst.Instance.StopBindBell(bindEffects); + BindBurst.StopBindBell(bindEffects); var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); if (shamanAntic != null) { @@ -423,12 +423,12 @@ public enum State { /// Effect flags sent by the other player. Mostly items they have equipped. /// protected class Flags { - public bool BindBell = false; - public bool BaseMirror = false; - public bool UpgradedMirror = false; - public bool QuickBind = false; - public bool ReserveBind = false; - public bool Maggoted = false; + public readonly bool BindBell = false; + public readonly bool BaseMirror = false; + public readonly bool UpgradedMirror = false; + public readonly bool QuickBind = false; + public readonly bool ReserveBind = false; + public bool Maggoted = false; // Needs to be writable by BindBurst public Flags(byte[]? info) { if (info == null) return; diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index 11d3a11..ae7472b 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -88,8 +88,7 @@ private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) /// Creates the appropriate Claw Mirror object. /// The object will be destroyed after it finishes. /// - private GameObject? PrepareMirror(GameObject playerObject, SetGameObject mirrorSource) { - + private static GameObject? PrepareMirror(GameObject playerObject, SetGameObject mirrorSource) { var mirror = EffectUtils.SpawnGlobalPoolObject(mirrorSource.gameObject.Value, playerObject.transform, 3f); if (mirror == null) { @@ -98,12 +97,12 @@ private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) var shaker = mirror.GetComponentInChildren(); if (shaker != null) { - Component.DestroyImmediate(shaker); + Object.DestroyImmediate(shaker); } var haze = mirror.FindGameObjectInChildren("haze2"); if (haze != null) { - GameObject.Destroy(haze); + Object.Destroy(haze); } return mirror; @@ -121,8 +120,11 @@ private void PlayMirror(GameObject playerObject, bool upgraded) { // Create the claw GameObject? claw; - if (upgraded) claw = PrepareMirror(playerObject, upgradedClaw); - else claw = PrepareMirror(playerObject, regularClaw); + if (upgraded) { + claw = PrepareMirror(playerObject, upgradedClaw); + } else { + claw = PrepareMirror(playerObject, regularClaw); + } if (claw == null) { return; @@ -149,7 +151,7 @@ private void PlayMirror(GameObject playerObject, bool upgraded) { /// /// Stops the bind bell animation /// - public void StopBindBell(GameObject bindEffects) { + public static void StopBindBell(GameObject bindEffects) { var bindBell = bindEffects.FindGameObjectInChildren(BindBellName); bindBell?.SetActive(false); } @@ -186,11 +188,11 @@ private void PlayWitchEnd(GameObject bindEffects) { return; } - witchBind = GameObject.Instantiate(localWitchBind, bindEffects.transform); + witchBind = Object.Instantiate(localWitchBind, bindEffects.transform); var shaker = witchBind.GetComponent(); if (shaker != null) { - Component.DestroyImmediate(shaker); + Object.DestroyImmediate(shaker); } SetWitchDamagers(witchBind); @@ -222,7 +224,7 @@ private void SetWitchDamagers(GameObject witchBind) { /// /// Stops the Shaman Crest specific silk animation /// - private void PlayShamanEnd(GameObject bindEffects) { + private static void PlayShamanEnd(GameObject bindEffects) { var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); if (shamanAntic == null) { return; diff --git a/SSMP/Animation/Effects/BindInterupt.cs b/SSMP/Animation/Effects/BindInterrupt.cs similarity index 91% rename from SSMP/Animation/Effects/BindInterupt.cs rename to SSMP/Animation/Effects/BindInterrupt.cs index eff3a7b..dc36b04 100644 --- a/SSMP/Animation/Effects/BindInterupt.cs +++ b/SSMP/Animation/Effects/BindInterrupt.cs @@ -7,20 +7,20 @@ namespace SSMP.Animation.Effects; /// -/// Class for the animation effect of a bind (healing) being interupted by an attack. +/// Class for the animation effect of a bind (healing) being interrupted by an attack. /// -internal class BindInterupt : Bind { +internal class BindInterrupt : Bind { /// /// Static instance for access by multiple animation clips in . /// - private static BindInterupt? _instance; + private static BindInterrupt? _instance; /// - public static BindInterupt Instance => _instance ??= new BindInterupt(); + public static BindInterrupt Instance => _instance ??= new BindInterrupt(); /// public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { - Flags flags = new Flags(effectInfo); + var flags = new Flags(effectInfo); if (!CreateObjects(playerObject, out var bindEffects)) { return; @@ -60,12 +60,12 @@ private void PlayBindInterupt(GameObject bindEffects) { // Remove haze and camera controls var haze = burst.FindGameObjectInChildren("haze2"); if (haze != null) { - GameObject.Destroy(haze); + Object.Destroy(haze); } var shaker = burst.GetComponentInChildren(); if (shaker != null) { - Component.DestroyImmediate(shaker); + Object.DestroyImmediate(shaker); } } @@ -113,7 +113,7 @@ private void PlayBellExplode(GameObject bindEffects) { var haze = bindBell.FindGameObjectInChildren("haze2 (1)"); if (haze != null) { - GameObject.Destroy(haze); + Object.Destroy(haze); } // Add hitbox if appropriate diff --git a/SSMP/Animation/Effects/EffectUtils.cs b/SSMP/Animation/Effects/EffectUtils.cs index f38dcff..940899a 100644 --- a/SSMP/Animation/Effects/EffectUtils.cs +++ b/SSMP/Animation/Effects/EffectUtils.cs @@ -19,7 +19,7 @@ public static void SafelyRemoveAutoRecycle(GameObject obj) { recycler.recycleTimerRunning = false; recycler.subbed = false; - Component.Destroy(recycler); + Object.Destroy(recycler); } } @@ -42,22 +42,25 @@ public static void SafelyRemoveAutoRecycle(GameObject obj) { /// /// The GameObject to spawn - /// - /// - /// + /// The location where the object will be spawned. + /// The duration, in seconds, after which the spawned object will be destroyed. + /// Whether to keep the parent or unparent the new object + /// A newly spawned GameObject from the global pool. public static GameObject? SpawnGlobalPoolObject(GameObject? globalObj, Transform spawnLocation, float destroyAfterDelay, bool keepParent = false) { if (globalObj == null) { Logger.Warn("Unable to find global pool object"); return null; } - var newObj = GameObject.Instantiate(globalObj, spawnLocation); + // Regardless of if the parent is kept, the object needs to be moved outside + // of the HideAndDontSave flag, which can be done by moving to the other object's scene. + var newObj = Object.Instantiate(globalObj, spawnLocation); if (newObj == null) { Logger.Warn($"Unable to spawn global pool object {globalObj.name}"); return null; } - // This is ugly and i hate it, but it works + // Now it can be orphaned if needed. if (!keepParent) { newObj.transform.SetParent(null); newObj.transform.position = spawnLocation.position; From 3c1c65509f4590956d159410ff527e9b94d5186a Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:01:22 -0400 Subject: [PATCH 29/33] missed a component destroy --- SSMP/Animation/Effects/BindInterrupt.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSMP/Animation/Effects/BindInterrupt.cs b/SSMP/Animation/Effects/BindInterrupt.cs index dc36b04..e697106 100644 --- a/SSMP/Animation/Effects/BindInterrupt.cs +++ b/SSMP/Animation/Effects/BindInterrupt.cs @@ -108,7 +108,7 @@ private void PlayBellExplode(GameObject bindEffects) { // Remove camera control and haze var shaker = bindBell.GetComponentInChildren(); if (shaker != null) { - Component.DestroyImmediate(shaker); + Object.DestroyImmediate(shaker); } var haze = bindBell.FindGameObjectInChildren("haze2 (1)"); From acd47b3988339fa7c5da7aa70fcc1b32a8d74afe Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:06:46 -0400 Subject: [PATCH 30/33] i'm bad at splelling apparently --- SSMP/Animation/AnimationClip.cs | 2 +- SSMP/Animation/AnimationManager.cs | 6 +++--- SSMP/Animation/Effects/BindInterrupt.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SSMP/Animation/AnimationClip.cs b/SSMP/Animation/AnimationClip.cs index 9f2d568..c7e54e4 100644 --- a/SSMP/Animation/AnimationClip.cs +++ b/SSMP/Animation/AnimationClip.cs @@ -764,5 +764,5 @@ internal enum AnimationClip { // Sub-animation names WitchTentacles, ShamanCancel, - BindInterupt, + BindInterrupt, } diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index d557977..850f55c 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -619,7 +619,7 @@ internal class AnimationManager { { "Witch Tentacles!", AnimationClip.WitchTentacles }, { "Shaman Cancel", AnimationClip.ShamanCancel }, - { "Bind Fail Burst", AnimationClip.BindInterupt } + { "Bind Fail Burst", AnimationClip.BindInterrupt } }; /// @@ -662,7 +662,7 @@ internal class AnimationManager { private static readonly Dictionary SubAnimationEffects = new() { { AnimationClip.WitchTentacles, BindBurst.Instance }, { AnimationClip.ShamanCancel, new Bind { BindState = Bind.State.ShamanCancel } }, - { AnimationClip.BindInterupt, BindInterrupt.Instance } + { AnimationClip.BindInterrupt, BindInterrupt.Instance } }; /// @@ -1104,7 +1104,7 @@ private void OnShamanCancel(PlayMakerFSM fsm) { OnAnimationEvent(dummyClip); } /// - /// Animation subanimation hook for interupted binds + /// Animation subanimation hook for interrupted binds /// private void OnBindInterrupt(PlayMakerFSM fsm) { var dummyClip = new tk2dSpriteAnimationClip(); diff --git a/SSMP/Animation/Effects/BindInterrupt.cs b/SSMP/Animation/Effects/BindInterrupt.cs index e697106..7c4e538 100644 --- a/SSMP/Animation/Effects/BindInterrupt.cs +++ b/SSMP/Animation/Effects/BindInterrupt.cs @@ -31,14 +31,14 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? if (flags.BindBell) { PlayBellExplode(bindEffects); } else { - PlayBindInterupt(bindEffects); + PlayBindInterrupt(bindEffects); } } /// /// Plays the normal bind interput animation /// - private void PlayBindInterupt(GameObject bindEffects) { + private void PlayBindInterrupt(GameObject bindEffects) { // Find prefab var bindBurstSpawner = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); if (bindBurstSpawner == null) { From 868cdeba1211985c27151cb709f6650bbb3073b8 Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:01:30 -0400 Subject: [PATCH 31/33] remove redundant checks, use vars for more strings --- SSMP/Animation/AnimationManager.cs | 29 ++++---- SSMP/Animation/Effects/Bind.cs | 98 ++++++++++++++----------- SSMP/Animation/Effects/BindBurst.cs | 83 +++++++++++++-------- SSMP/Animation/Effects/BindInterrupt.cs | 37 ++++------ SSMP/Fsm/FsmStateActionInjector.cs | 6 +- 5 files changed, 140 insertions(+), 113 deletions(-) diff --git a/SSMP/Animation/AnimationManager.cs b/SSMP/Animation/AnimationManager.cs index 850f55c..6d300ee 100644 --- a/SSMP/Animation/AnimationManager.cs +++ b/SSMP/Animation/AnimationManager.cs @@ -1054,7 +1054,14 @@ private void OnAnimationEvent(tk2dSpriteAnimationClip clip) { /// the Bind fsm once the HeroController is ready. /// private void CreateBindHooks(HeroController hc) { - HeroController.instance.bellBindFSM.Init(); + // Initialize warding bell FSM if it isn't already. + // This fills it in with the template + var bellFsm = HeroController.instance.bellBindFSM; + if (!bellFsm.fsm.initialized) { + bellFsm.Init(); + } + + // Find bind FSM var heroFsms = hc.GetComponents(); var bindFsm = heroFsms.FirstOrDefault(fsm => fsm.FsmName == "Bind"); @@ -1063,25 +1070,15 @@ private void CreateBindHooks(HeroController hc) { return; } - // Find witch crest tentacles + // Find FSM states to inject var tentacles = bindFsm.GetState("Witch Tentancles!"); // no that's not a typo... at least on my end - if (tentacles != null) { - FsmStateActionInjector.Inject(tentacles, OnWitchTentacles, 4); - } else { - Logger.Warn("Unable to find Witch Tentacles! state"); - } - + FsmStateActionInjector.Inject(tentacles, OnWitchTentacles, 4); + var shamanCancel = bindFsm.GetState("Shaman Air Cancel"); - if (shamanCancel != null) { - FsmStateActionInjector.Inject(shamanCancel, OnShamanCancel); - } else { - Logger.Warn("Unable to find Shaman Air Cancel state"); - } + FsmStateActionInjector.Inject(shamanCancel, OnShamanCancel); var bindInterrupt = bindFsm.GetState("Remove Silk?"); - if (bindInterrupt != null) { - FsmStateActionInjector.Inject(bindInterrupt, OnBindInterrupt, 2); - } + FsmStateActionInjector.Inject(bindInterrupt, OnBindInterrupt, 2); } /// diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 4c54f3f..70e0421 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -15,9 +15,17 @@ namespace SSMP.Animation.Effects; /// Class for the animation effect of bind (healing). /// internal class Bind : DamageAnimationEffect { + // Effect object names + protected const string BindBellObjectName = "bind_bell_appear_instance"; + protected const string BindSilkObjectName = "Bind Silk"; + protected const string ShamanFallAnticObjectName = "Shaman_Bind_antic_silk"; + private const string BeastCrestAnticObjectName = "Warrior_Bind_antic_silk"; + private const string CursedBindFailObjectName = "Cursed Bind Hornet"; + private const string WitchBindObjectName = "Whip_Bind_silk_antic"; - protected const string BindBellName = "bind_bell_appear_instance"; - + /// + /// Keeps track of special bind scenarios, mostly to do with Shaman Crest. + /// public State BindState = State.Normal; /// @@ -94,7 +102,7 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? /// private void StartBindBell(GameObject bindEffects) { Logger.Debug("Starting warding bell"); - var bindBell = bindEffects.FindGameObjectInChildren(BindBellName); + var bindBell = bindEffects.FindGameObjectInChildren(BindBellObjectName); if (bindBell == null) { var allObjects = Resources.FindObjectsOfTypeAll(); @@ -106,7 +114,7 @@ private void StartBindBell(GameObject bindEffects) { } bindBell = Object.Instantiate(localBell, bindEffects.transform); - bindBell.name = BindBellName; + bindBell.name = BindBellObjectName; var follower = bindBell.GetComponent(); follower.target = bindEffects.transform; @@ -125,7 +133,7 @@ private void StartBindBell(GameObject bindEffects) { /// private void PlayNormalStart(GameObject bindEffects, Flags flags) { Logger.Debug("Playing normal bind start animation"); - var bindSilkObj = CreateEffectIfNotExists(bindEffects, "Bind Silk"); + var bindSilkObj = CreateEffectIfNotExists(bindEffects, BindSilkObjectName); if (bindSilkObj == null) { return; } @@ -151,7 +159,7 @@ private void PlayNormalStart(GameObject bindEffects, Flags flags) { /// private void PlayBeastBindStart(GameObject bindEffects) { Logger.Debug("Playing Beast Crest start antic"); - var beastAntic = CreateEffectIfNotExists(bindEffects, "Warrior_Bind_antic_silk"); + var beastAntic = CreateEffectIfNotExists(bindEffects, BeastCrestAnticObjectName); if (beastAntic == null) { return; } @@ -165,7 +173,7 @@ private void PlayBeastBindStart(GameObject bindEffects) { /// private void PlayCursedFail(GameObject bindEffects) { Logger.Debug("Playing Cursed Crest bind fail animation"); - var failAntic = bindEffects.FindGameObjectInChildren("cursed_bind_fail"); + var failAntic = bindEffects.FindGameObjectInChildren(CursedBindFailObjectName); tk2dSpriteAnimator animator; if (failAntic == null) { @@ -175,14 +183,14 @@ private void PlayCursedFail(GameObject bindEffects) { return; } - var localFailAntic = effects.FindGameObjectInChildren("Cursed Bind Hornet"); + var localFailAntic = effects.FindGameObjectInChildren(CursedBindFailObjectName); if (localFailAntic == null) { Logger.Warn("Unable to find local cursed bind object"); return; } failAntic = Object.Instantiate(localFailAntic, bindEffects.transform); - failAntic.name = "cursed_bind_fail"; + failAntic.name = CursedBindFailObjectName; failAntic.transform.SetLocalPositionZ(failAntic.transform.localPosition.z - 0.25f); animator = failAntic.GetComponent(); @@ -213,7 +221,7 @@ private void PlayNextCursedPart(tk2dSpriteAnimator animator, tk2dSpriteAnimation /// private void PlayWitchAnimationAntic(GameObject bindEffects) { Logger.Debug("Playing Witch Crest bind antic"); - var silkAntic = CreateEffectIfNotExists(bindEffects, "Whip_Bind_silk_antic"); + var silkAntic = CreateEffectIfNotExists(bindEffects, WitchBindObjectName); if (silkAntic == null) { return; } @@ -244,7 +252,7 @@ private bool PickShamanAnimation(GameObject playerObject, GameObject bindEffects /// private void PlayShamanFall(GameObject bindEffects) { Logger.Debug("Playing Shaman Crest bind fall animation"); - var shamanAntic = CreateEffectIfNotExists(bindEffects, "Shaman_Bind_antic_silk"); + var shamanAntic = CreateEffectIfNotExists(bindEffects, ShamanFallAnticObjectName); if (shamanAntic == null) { return; } @@ -261,22 +269,17 @@ private void PlayShamanFall(GameObject bindEffects) { /// private void PlayShamanCancel(GameObject playerObject, GameObject bindEffects) { Logger.Debug("Playing Shaman Crest bind cancel/fail animation"); - var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); + var shamanAntic = bindEffects.FindGameObjectInChildren(ShamanFallAnticObjectName); if (shamanAntic != null) { shamanAntic.SetActive(false); } - var silkPuffSpawner = GetOrFindBindFsm().GetFirstAction("Shaman Air Cancel"); - if (silkPuffSpawner == null) { - Logger.Warn("Unable to find FSM action for Shaman Air Cancel"); - return; - } - - var audio = GetOrFindBindFsm().GetFirstAction("Shaman Air Cancel"); - if (audio != null) { - PlaySound(playerObject, audio); - } + var cancelStateName = "Shaman Air Cancel"; + var silkPuffSpawner = GetOrFindBindFsm().GetFirstAction(cancelStateName); + var audio = GetOrFindBindFsm().GetFirstAction(cancelStateName); + PlaySound(playerObject, audio); + EffectUtils.SpawnGlobalPoolObject(silkPuffSpawner, playerObject.transform, 5f); BindBurst.StopBindBell(bindEffects); @@ -287,7 +290,7 @@ private void PlayShamanCancel(GameObject playerObject, GameObject bindEffects) { /// private void PlayShamanFallEnd(GameObject bindEffects) { Logger.Debug("Playing Shaman Crest bind fall finished transition animation"); - var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); + var shamanAntic = bindEffects.FindGameObjectInChildren(ShamanFallAnticObjectName); if (shamanAntic == null) { return; } @@ -360,50 +363,63 @@ protected bool CreateObjects(GameObject playerObject, [MaybeNullWhen(false)] out /// /// The player's Bind Effects object. /// The name of the effect to find or create. - /// The existing or new effect, or null if the effect cannot be found or created - protected GameObject? CreateEffectIfNotExists(GameObject bindEffects, string objectName) { - var obj = bindEffects.FindGameObjectInChildren(objectName); - if (obj == null) { - var localObj = LocalBindEffects!.FindGameObjectInChildren(objectName); - if (localObj == null) { - Logger.Warn($"Could not find local {objectName} object, cannot play bind"); - return null; - } + /// The effect that was found or created + /// Returns true if the effect was created, false if it was found. + protected bool CreateEffectIfNotExists(GameObject bindEffects, string objectName, out GameObject? effect) { + effect = bindEffects.FindGameObjectInChildren(objectName); + if (effect != null) { + return false; + } - obj = Object.Instantiate(localObj, bindEffects.transform); - obj.name = objectName; + var localObj = LocalBindEffects!.FindGameObjectInChildren(objectName); + if (localObj == null) { + Logger.Warn($"Could not find local {objectName} object, cannot play bind effect"); + return false; } - return obj; + effect = Object.Instantiate(localObj, bindEffects.transform); + effect.name = objectName; + return true; } - + + /// + /// Finds and returns a bind effect with the specified name, creating it if it doesn't already exist. + /// + /// The player's Bind Effects object. + /// The name of the effect to find or create. + /// The effect that was found or created + protected GameObject? CreateEffectIfNotExists(GameObject bindEffects, string objectName) { + CreateEffectIfNotExists(bindEffects, objectName, out var effect); + return effect; + } + /// /// Turns off all bind effects /// public static void ForceStopAllEffects(GameObject bindEffects) { BindBurst.StopBindBell(bindEffects); - var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); + var shamanAntic = bindEffects.FindGameObjectInChildren(ShamanFallAnticObjectName); if (shamanAntic != null) { shamanAntic.SetActive(false); } - var silkAntic = bindEffects.FindGameObjectInChildren("Whip_Bind_silk_antic"); + var silkAntic = bindEffects.FindGameObjectInChildren(WitchBindObjectName); if (silkAntic != null) { silkAntic.SetActive(false); } - var cursedFailAntic = bindEffects.FindGameObjectInChildren("cursed_bind_fail"); + var cursedFailAntic = bindEffects.FindGameObjectInChildren(CursedBindFailObjectName); if (cursedFailAntic != null) { cursedFailAntic.SetActive(false); } - var beastAntic = bindEffects.FindGameObjectInChildren("Warrior_Bind_antic_silk"); + var beastAntic = bindEffects.FindGameObjectInChildren(BeastCrestAnticObjectName); if (beastAntic != null) { beastAntic.SetActive(false); } - var bindSilkObj = bindEffects.FindGameObjectInChildren("Bind Silk"); + var bindSilkObj = bindEffects.FindGameObjectInChildren(BindSilkObjectName); if (bindSilkObj != null) { bindSilkObj.SetActive(false); } diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index ae7472b..b13bab3 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -11,16 +11,20 @@ namespace SSMP.Animation.Effects; /// Class for the animation effect of a bind (healing) finishing. /// internal class BindBurst : Bind { + // Effect object names + private const string BeastCrestRageObjectName = "crest rage_burst_effect(Clone)"; + private const string WitchBindCleanseObjectName = "Witch Bind Maggot Cleanse"; + private const string WitchBindObjectName = "Witch Bind"; + private const string HealAnimObjectName = "Heal Anim"; + private const string HealParticleObjectName = "Pt Heal"; + /// /// Static instance for access by multiple animation clips in . /// - private static BindBurst? _instance; - - /// - public static BindBurst Instance => _instance ??= new BindBurst(); + public static readonly BindBurst Instance = new(); /// - /// A set of players who recently binded while maggoted + /// A set of players who recently bound while maggoted /// public static readonly HashSet MaggotedPlayers = new(); @@ -39,6 +43,7 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? return; } + // Play flag-specific effects if (flags.BaseMirror || flags.UpgradedMirror) PlayMirror(playerObject, flags.UpgradedMirror); if (flags.Maggoted) PlayMaggotCleanse(bindEffects, playerObject); @@ -71,14 +76,17 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? /// Plays the maggot cleanse animation /// private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) { - Logger.Info("Playing Maggot Animation"); - var maggotBurst = GetOrFindBindFsm().GetAction("Maggoted?", 2); - var maggotFlash = GetOrFindBindFsm().GetAction("Maggoted?", 4); - var maggotAudio = GetOrFindBindFsm().GetFirstAction("Maggoted?"); + // Grab assets from Maggoted state + var stateName = "Maggoted?"; + var maggotBurst = GetOrFindBindFsm().GetAction(stateName, 2); + var maggotFlash = GetOrFindBindFsm().GetAction(stateName, 4); + var maggotAudio = GetOrFindBindFsm().GetFirstAction(stateName); + // Spawn copies of those assets EffectUtils.SpawnGlobalPoolObject(maggotBurst, bindEffects.transform, 5f); EffectUtils.SpawnGlobalPoolObject(maggotFlash, bindEffects.transform, 5f); + // Play audio if (maggotAudio != null) { PlaySound(playerObject, maggotAudio); } @@ -89,12 +97,14 @@ private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) /// The object will be destroyed after it finishes. /// private static GameObject? PrepareMirror(GameObject playerObject, SetGameObject mirrorSource) { + // Spawn mirror var mirror = EffectUtils.SpawnGlobalPoolObject(mirrorSource.gameObject.Value, playerObject.transform, 3f); if (mirror == null) { return null; } + // Remove camera and haze components var shaker = mirror.GetComponentInChildren(); if (shaker != null) { Object.DestroyImmediate(shaker); @@ -152,7 +162,8 @@ private void PlayMirror(GameObject playerObject, bool upgraded) { /// Stops the bind bell animation /// public static void StopBindBell(GameObject bindEffects) { - var bindBell = bindEffects.FindGameObjectInChildren(BindBellName); + // Only bother turning it off if it's on + var bindBell = bindEffects.FindGameObjectInChildren(BindBellObjectName); bindBell?.SetActive(false); } @@ -160,15 +171,22 @@ public static void StopBindBell(GameObject bindEffects) { /// Plays the Beast Crest specific rage animation /// private void PlayBeastRage(GameObject bindEffects) { - var beastRage = CreateEffectIfNotExists(bindEffects, "crest rage_burst_effect(Clone)"); - beastRage?.SetActive(true); + // Create and reactivate the rage effect + var beastRage = CreateEffectIfNotExists(bindEffects, BeastCrestRageObjectName); + + if (beastRage != null) { + beastRage.SetActive(false); + beastRage.SetActive(true); + } } /// /// Plays the witch maggot cleanse animation /// private void PlayWitchMaggoted(GameObject bindEffects) { - var maggotCleanse = CreateEffectIfNotExists(bindEffects, "Witch Bind Maggot Cleanse"); + // Spawn in the special tentacles + var maggotCleanse = CreateEffectIfNotExists(bindEffects, WitchBindCleanseObjectName); + if (maggotCleanse != null) { maggotCleanse.SetActive(false); maggotCleanse.SetActive(true); @@ -180,24 +198,24 @@ private void PlayWitchMaggoted(GameObject bindEffects) { /// Yes it's called Tentancles internally. Thanks TC. /// private void PlayWitchEnd(GameObject bindEffects) { - var witchBind = bindEffects.FindGameObjectInChildren("Witch Bind"); + // Get bind effect + var effectWasCreated = CreateEffectIfNotExists(bindEffects, WitchBindObjectName, out var witchBind); if (witchBind == null) { - var localWitchBind = LocalBindEffects?.FindGameObjectInChildren("Witch Bind"); - if (localWitchBind == null) { - Logger.Warn("Unable to find local Witch Bind object"); - return; - } - - witchBind = Object.Instantiate(localWitchBind, bindEffects.transform); + return; + } + // Remove camera controls if object was created + if (effectWasCreated) { var shaker = witchBind.GetComponent(); if (shaker != null) { Object.DestroyImmediate(shaker); } - - SetWitchDamagers(witchBind); } + // Toggle damage depending on if PVP is on or not + SetWitchDamagers(witchBind); + + // Play tentacles audio var audio = GetOrFindBindFsm().GetFirstAction("Witch Tentancles!"); if (audio != null) { PlaySound(bindEffects.transform.parent.gameObject, audio); @@ -211,12 +229,14 @@ private void PlayWitchEnd(GameObject bindEffects) { /// Adds or removes hero damage components from Witch Crest bind /// private void SetWitchDamagers(GameObject witchBind) { + // Loop through all children, looking for all the ones called "Damager" for (var i = 0; i < witchBind.transform.childCount; i++) { var child = witchBind.transform.GetChild(i); if (!child.name.StartsWith("Damager")) { continue; } + // Add or remove damage component from Damager object SetDamageHeroState(child.gameObject); } } @@ -225,7 +245,7 @@ private void SetWitchDamagers(GameObject witchBind) { /// Stops the Shaman Crest specific silk animation /// private static void PlayShamanEnd(GameObject bindEffects) { - var shamanAntic = bindEffects.FindGameObjectInChildren("Shaman_Bind_antic_silk"); + var shamanAntic = bindEffects.FindGameObjectInChildren(ShamanFallAnticObjectName); if (shamanAntic == null) { return; } @@ -233,31 +253,32 @@ private static void PlayShamanEnd(GameObject bindEffects) { } /// - /// Plays particles and shows a flash + /// Plays particles and shows a flash. Happens at the end of a normal bind. /// private void PlayNormalEnd(GameObject bindEffects) { - Logger.Info("Playing Heal Particles and Anim"); - var healParticle = CreateEffectIfNotExists(bindEffects, "Pt Heal"); + // Play particle effect + CreateEffectIfNotExists(bindEffects, HealParticleObjectName, out var healParticle); if (healParticle != null) { healParticle.GetComponent().Play(); } - var healAnim = CreateEffectIfNotExists(bindEffects, "Heal Anim"); + // Play silk animation + var healAnim = CreateEffectIfNotExists(bindEffects, HealAnimObjectName); if (healAnim != null) { healAnim.SetActive(true); } - var bindSilkObj = bindEffects.FindGameObjectInChildren("Bind Silk"); + // Disable mesh renderer because the game does that. Is this needed? + var bindSilkObj = bindEffects.FindGameObjectInChildren(BindSilkObjectName); if (bindSilkObj != null) { var bindSilkMeshRenderer = bindSilkObj.GetComponent(); bindSilkMeshRenderer.enabled = false; } + // Play audio var audio = GetOrFindBindFsm().GetFirstAction("Bind Burst"); if (audio != null) { PlaySound(bindEffects.transform.parent.gameObject, audio); } - - } } diff --git a/SSMP/Animation/Effects/BindInterrupt.cs b/SSMP/Animation/Effects/BindInterrupt.cs index 7c4e538..5dc9c6e 100644 --- a/SSMP/Animation/Effects/BindInterrupt.cs +++ b/SSMP/Animation/Effects/BindInterrupt.cs @@ -13,21 +13,21 @@ internal class BindInterrupt : Bind { /// /// Static instance for access by multiple animation clips in . /// - private static BindInterrupt? _instance; - - /// - public static BindInterrupt Instance => _instance ??= new BindInterrupt(); + public static readonly BindInterrupt Instance = new(); /// public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { var flags = new Flags(effectInfo); + // Create bind effects object if (!CreateObjects(playerObject, out var bindEffects)) { return; } + // Stop any effects currently playing, this effect "interrupts" everything else ForceStopAllEffects(bindEffects); + // Play a warding bell explosion if they had it equipped, otherwise do the normal one if (flags.BindBell) { PlayBellExplode(bindEffects); } else { @@ -36,25 +36,22 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? } /// - /// Plays the normal bind interput animation + /// Plays the normal bind interrupt animation /// private void PlayBindInterrupt(GameObject bindEffects) { // Find prefab - var bindBurstSpawner = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); - if (bindBurstSpawner == null) { - Logger.Warn("Unable to find bind burst effect spawner"); - return; - } + var stateName = "Remove Silk?"; + var bindBurstSpawner = GetOrFindBindFsm().GetFirstAction(stateName); // Spawn in effect var burst = EffectUtils.SpawnGlobalPoolObject(bindBurstSpawner, bindEffects.transform, 5f); if (burst == null) { - Logger.Warn("Unable to create bind burst effect"); + Logger.Warn("Unable to create bind interrupt effect"); return; } // Play audio - var audio = GetOrFindBindFsm().GetFirstAction("Remove Silk?"); + var audio = GetOrFindBindFsm().GetFirstAction(stateName); PlaySound(bindEffects.transform.parent.gameObject, audio); // Remove haze and camera controls @@ -75,24 +72,16 @@ private void PlayBindInterrupt(GameObject bindEffects) { private void PlayBellExplode(GameObject bindEffects) { Logger.Debug("Playing Bell Burst"); - // Initialize warding bell FSM if it isn't already. - // This fills it in with the template + // Locate warding bell FSM var bellFsm = HeroController.instance.bellBindFSM; - if (!bellFsm.fsm.initialized) { - HeroController.instance.bellBindFSM.Init(); - } if (bellFsm == null) { Logger.Warn("Unable to find warding bell fsm"); return; } - var spawner = bellFsm.GetFirstAction("Burst"); - - if (spawner == null) { - Logger.Warn("Unable to find warding bell spawner"); - return; - } + var stateName = "Burst"; + var spawner = bellFsm.GetFirstAction(stateName); // Spawn warding bell var bindBell = EffectUtils.SpawnGlobalPoolObject(spawner, bindEffects.transform, 5f); @@ -101,7 +90,7 @@ private void PlayBellExplode(GameObject bindEffects) { } // Play sound - var audio = bellFsm.GetFirstAction("Burst"); + var audio = bellFsm.GetFirstAction(stateName); PlaySound(bindBell, audio); diff --git a/SSMP/Fsm/FsmStateActionInjector.cs b/SSMP/Fsm/FsmStateActionInjector.cs index cca9c56..034ffb9 100644 --- a/SSMP/Fsm/FsmStateActionInjector.cs +++ b/SSMP/Fsm/FsmStateActionInjector.cs @@ -41,7 +41,7 @@ public void Uninject() { public override void OnEnter() { if (_onStateEnter != null) { try { - _onStateEnter?.Invoke(Fsm.FsmComponent); + _onStateEnter.Invoke(Fsm.FsmComponent); } catch (Exception e) { Logger.Error(e.ToString()); } @@ -57,6 +57,10 @@ public override void OnEnter() { /// The index at which to inject the action within the state's action list. Defaults to 0. /// The injected action. public static FsmStateActionInjector Inject(FsmState state, Action onEnter, int actionIndex = 0) { + if (state == null) { + throw new NullReferenceException("Received null state when injecting FSM"); + } + var action = new FsmStateActionInjector(state, onEnter); action.DoInjection(actionIndex); From 97dddc71bd0cf7a6070ee0874fc02a74b515738a Mon Sep 17 00:00:00 2001 From: BobbyTheCatfish <46359040+BobbyTheCatfish@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:07:39 -0400 Subject: [PATCH 32/33] add util to remove child gameobjects --- SSMP/Animation/Effects/BindBurst.cs | 5 +---- SSMP/Animation/Effects/BindInterrupt.cs | 10 ++-------- SSMP/Util/GameObjectUtil.cs | 20 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index b13bab3..367c3de 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -110,10 +110,7 @@ private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) Object.DestroyImmediate(shaker); } - var haze = mirror.FindGameObjectInChildren("haze2"); - if (haze != null) { - Object.Destroy(haze); - } + mirror.DestroyGameObjectInChildren("haze2"); return mirror; } diff --git a/SSMP/Animation/Effects/BindInterrupt.cs b/SSMP/Animation/Effects/BindInterrupt.cs index 5dc9c6e..d2b103b 100644 --- a/SSMP/Animation/Effects/BindInterrupt.cs +++ b/SSMP/Animation/Effects/BindInterrupt.cs @@ -55,10 +55,7 @@ private void PlayBindInterrupt(GameObject bindEffects) { PlaySound(bindEffects.transform.parent.gameObject, audio); // Remove haze and camera controls - var haze = burst.FindGameObjectInChildren("haze2"); - if (haze != null) { - Object.Destroy(haze); - } + burst.DestroyGameObjectInChildren("haze2"); var shaker = burst.GetComponentInChildren(); if (shaker != null) { @@ -100,10 +97,7 @@ private void PlayBellExplode(GameObject bindEffects) { Object.DestroyImmediate(shaker); } - var haze = bindBell.FindGameObjectInChildren("haze2 (1)"); - if (haze != null) { - Object.Destroy(haze); - } + bindBell.DestroyGameObjectInChildren("haze2 (1)"); // Add hitbox if appropriate if (ServerSettings.IsPvpEnabled && ShouldDoDamage) { diff --git a/SSMP/Util/GameObjectUtil.cs b/SSMP/Util/GameObjectUtil.cs index 18dd6f1..a1a54bc 100644 --- a/SSMP/Util/GameObjectUtil.cs +++ b/SSMP/Util/GameObjectUtil.cs @@ -37,6 +37,26 @@ string name return null; } + /// + /// Destroys a GameObject with the given name in the children of the given GameObject. + /// + /// The GameObject to search in. + /// The name of the GameObject to search for. + /// Returns true if the object was destroyed, false otherwise. + public static bool DestroyGameObjectInChildren(this GameObject gameObject, string name) { + if (gameObject == null) { + return false; + } + + var child = FindGameObjectInChildren(gameObject, name); + if (child != null) { + Object.Destroy(child.gameObject); + return true; + } + + return false; + } + /// /// Get a list of the children of the given GameObject. /// From 5726cbdf0b092bac15692e034565d3802dc0d27d Mon Sep 17 00:00:00 2001 From: Extremelyd1 <10898310+Extremelyd1@users.noreply.github.com> Date: Sat, 4 Apr 2026 11:54:44 +0200 Subject: [PATCH 33/33] Final cleanup - Rename audio util methods - Remove unused imports - Change unneeded accessibility of variables - Various style formatting changes - Fix doc comment cref --- SSMP/Animation/AnimationEffect.cs | 35 ------------------ SSMP/Animation/DamageAnimationEffect.cs | 2 +- SSMP/Animation/Effects/Bind.cs | 45 +++++++++++------------ SSMP/Animation/Effects/BindBurst.cs | 14 ++----- SSMP/Animation/Effects/BindInterrupt.cs | 4 +- SSMP/Animation/Effects/DashSlash.cs | 2 +- SSMP/Animation/Effects/DashSlashAntic.cs | 10 ++--- SSMP/Animation/Effects/DashSlashReaper.cs | 2 +- SSMP/Animation/Effects/EffectUtils.cs | 2 +- SSMP/Animation/Effects/NeedleStrike.cs | 12 +++--- SSMP/Util/AudioUtil.cs | 8 ++-- 11 files changed, 47 insertions(+), 89 deletions(-) diff --git a/SSMP/Animation/AnimationEffect.cs b/SSMP/Animation/AnimationEffect.cs index fdce536..56e6960 100644 --- a/SSMP/Animation/AnimationEffect.cs +++ b/SSMP/Animation/AnimationEffect.cs @@ -1,7 +1,5 @@ -using HutongGames.PlayMaker.Actions; using SSMP.Game.Settings; using SSMP.Internals; -using SSMP.Util; using UnityEngine; namespace SSMP.Animation; @@ -42,37 +40,4 @@ protected static void ChangeAttackDirection(GameObject targetObject, float direc var directionVar = damageFsm.FsmVariables.GetFsmFloat("direction"); directionVar.Value = direction; } - - - /// - /// Plays a one-shot audio clip at the specified GameObject source. - /// - /// The object to play the sound at. - /// The audio clip to be played. - protected static void PlaySound(GameObject source, AudioPlayerOneShotSingle audio) { - AudioUtil.PlayAudioOneShotSingleAtPlayerObject(audio, source); - } - - /// - /// Plays a specified audio event at the specified GameObject source. - /// - /// The object to play the sound at. - /// The FSM action with the audio clip to be played. - protected static void PlaySound(GameObject source, PlayAudioEvent audio) { - AudioUtil.PlayAudioEventAtPlayerObject(audio, source); - } - - /// - /// Plays a random sound effect at the specified GameObject source - /// - /// The object to play the sound at. - /// The FSM action with the audio table. - /// The FSM action with the audio playing function. - protected static void PlaySound(GameObject source, GetRandomAudioClipFromTable getAction, PlayAudioEvent playAction) { - AudioUtil.PlayAudioEventWithRandomAudioClipFromTableAtPlayerObject( - playAction, - getAction, - source - ); - } } diff --git a/SSMP/Animation/DamageAnimationEffect.cs b/SSMP/Animation/DamageAnimationEffect.cs index e7707cc..aedc240 100644 --- a/SSMP/Animation/DamageAnimationEffect.cs +++ b/SSMP/Animation/DamageAnimationEffect.cs @@ -45,7 +45,7 @@ protected static DamageHero AddDamageHeroComponent(GameObject target, int damage /// /// Removes a component from the given game object. /// - /// The target game object to detatch the component to. + /// The target game object to detach the component from. protected static void RemoveDamageHeroComponent(GameObject target) { var damageHero = target.GetComponent(); if (damageHero == null) { diff --git a/SSMP/Animation/Effects/Bind.cs b/SSMP/Animation/Effects/Bind.cs index 70e0421..d3d9174 100644 --- a/SSMP/Animation/Effects/Bind.cs +++ b/SSMP/Animation/Effects/Bind.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Linq; using HutongGames.PlayMaker.Actions; @@ -31,12 +30,12 @@ internal class Bind : DamageAnimationEffect { /// /// Cached FSM for Hornet's bind ability. /// - protected static PlayMakerFSM? BindFsm; + private static PlayMakerFSM? _bindFsm; /// /// Cached effects object for Hornet's bind ability. /// - protected static GameObject? LocalBindEffects; + private static GameObject? _localBindEffects; /// public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { @@ -55,10 +54,10 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? var playAudioAction = GetOrFindBindFsm().GetFirstAction("Bind Start"); if (BindState == State.Normal) { - PlaySound(playerObject, randomClipAction, playAudioAction); + AudioUtil.PlayAudio(playAudioAction, randomClipAction, playerObject); var oneShotSingleAction = GetOrFindBindFsm().GetFirstAction("Check Grounded"); - PlaySound(playerObject, oneShotSingleAction); + AudioUtil.PlayAudio(oneShotSingleAction, playerObject); } @@ -278,7 +277,7 @@ private void PlayShamanCancel(GameObject playerObject, GameObject bindEffects) { var silkPuffSpawner = GetOrFindBindFsm().GetFirstAction(cancelStateName); var audio = GetOrFindBindFsm().GetFirstAction(cancelStateName); - PlaySound(playerObject, audio); + AudioUtil.PlayAudio(audio, playerObject); EffectUtils.SpawnGlobalPoolObject(silkPuffSpawner, playerObject.transform, 5f); @@ -301,34 +300,34 @@ private void PlayShamanFallEnd(GameObject bindEffects) { /// - public override byte[]? GetEffectInfo() { - byte[] effectInfo = { + public override byte[] GetEffectInfo() { + byte[] effectInfo = [ (byte) ((ToolItemManager.IsToolEquipped("Bell Bind") && !ToolItemManager.GetToolByName("Bell Bind").IsEmpty) ? 1 : 0), (byte) (ToolItemManager.IsToolEquipped("Dazzle Bind") ? 1 : 0), (byte) (ToolItemManager.IsToolEquipped("Dazzle Bind Upgraded") ? 1 : 0), (byte) (ToolItemManager.IsToolEquipped("Quickbind") ? 1 : 0), (byte) ((ToolItemManager.IsToolEquipped("Reserve Bind") && !ToolItemManager.GetToolByName("Reserve Bind").IsEmpty) ? 1 : 0), (byte) (HeroController.instance.cState.isMaggoted ? 1 : 0) - }; + ]; return effectInfo; } /// - /// Get or find the Bind FSM on the hero object. Will be cached to . + /// Get or find the Bind FSM on the hero object. Will be cached to . /// /// The FSM for Bind. /// Thrown if the FSM cannot be found, which shouldn't happen. /// protected PlayMakerFSM GetOrFindBindFsm() { - if (BindFsm != null) { - return BindFsm; + if (_bindFsm != null) { + return _bindFsm; } var heroFsms = HeroController.instance.GetComponents(); foreach (var heroFsm in heroFsms) { if (heroFsm.FsmName == "Bind") { - BindFsm = heroFsm; - return BindFsm; + _bindFsm = heroFsm; + return _bindFsm; } } @@ -342,8 +341,8 @@ protected PlayMakerFSM GetOrFindBindFsm() { /// The player's 'Bind Effects' object, or null if not found. /// true if the 'Bind Effects' GameObject is successfully found and bound; otherwise, false. protected bool CreateObjects(GameObject playerObject, [MaybeNullWhen(false)] out GameObject bindEffects) { - LocalBindEffects ??= HeroController.instance.gameObject.FindGameObjectInChildren("Bind Effects"); - if (LocalBindEffects == null) { + _localBindEffects ??= HeroController.instance.gameObject.FindGameObjectInChildren("Bind Effects"); + if (_localBindEffects == null) { Logger.Warn("Could not find local Bind Effects object in hero object"); bindEffects = null; return false; @@ -371,7 +370,7 @@ protected bool CreateEffectIfNotExists(GameObject bindEffects, string objectName return false; } - var localObj = LocalBindEffects!.FindGameObjectInChildren(objectName); + var localObj = _localBindEffects!.FindGameObjectInChildren(objectName); if (localObj == null) { Logger.Warn($"Could not find local {objectName} object, cannot play bind effect"); return false; @@ -439,12 +438,12 @@ public enum State { /// Effect flags sent by the other player. Mostly items they have equipped. /// protected class Flags { - public readonly bool BindBell = false; - public readonly bool BaseMirror = false; - public readonly bool UpgradedMirror = false; - public readonly bool QuickBind = false; - public readonly bool ReserveBind = false; - public bool Maggoted = false; // Needs to be writable by BindBurst + public readonly bool BindBell; + public readonly bool BaseMirror; + public readonly bool UpgradedMirror; + public readonly bool QuickBind; + public readonly bool ReserveBind; + public bool Maggoted; // Needs to be writable by BindBurst public Flags(byte[]? info) { if (info == null) return; diff --git a/SSMP/Animation/Effects/BindBurst.cs b/SSMP/Animation/Effects/BindBurst.cs index 367c3de..193caa5 100644 --- a/SSMP/Animation/Effects/BindBurst.cs +++ b/SSMP/Animation/Effects/BindBurst.cs @@ -26,7 +26,7 @@ internal class BindBurst : Bind { /// /// A set of players who recently bound while maggoted /// - public static readonly HashSet MaggotedPlayers = new(); + public static readonly HashSet MaggotedPlayers = []; /// public override void Play(GameObject playerObject, CrestType crestType, byte[]? effectInfo) { @@ -87,9 +87,7 @@ private void PlayMaggotCleanse(GameObject bindEffects, GameObject playerObject) EffectUtils.SpawnGlobalPoolObject(maggotFlash, bindEffects.transform, 5f); // Play audio - if (maggotAudio != null) { - PlaySound(playerObject, maggotAudio); - } + AudioUtil.PlayAudio(maggotAudio, playerObject); } /// @@ -214,9 +212,7 @@ private void PlayWitchEnd(GameObject bindEffects) { // Play tentacles audio var audio = GetOrFindBindFsm().GetFirstAction("Witch Tentancles!"); - if (audio != null) { - PlaySound(bindEffects.transform.parent.gameObject, audio); - } + AudioUtil.PlayAudio(audio, bindEffects.transform.parent.gameObject); witchBind.SetActive(false); witchBind.SetActive(true); @@ -274,8 +270,6 @@ private void PlayNormalEnd(GameObject bindEffects) { // Play audio var audio = GetOrFindBindFsm().GetFirstAction("Bind Burst"); - if (audio != null) { - PlaySound(bindEffects.transform.parent.gameObject, audio); - } + AudioUtil.PlayAudio(audio, bindEffects.transform.parent.gameObject); } } diff --git a/SSMP/Animation/Effects/BindInterrupt.cs b/SSMP/Animation/Effects/BindInterrupt.cs index d2b103b..fe8b979 100644 --- a/SSMP/Animation/Effects/BindInterrupt.cs +++ b/SSMP/Animation/Effects/BindInterrupt.cs @@ -52,7 +52,7 @@ private void PlayBindInterrupt(GameObject bindEffects) { // Play audio var audio = GetOrFindBindFsm().GetFirstAction(stateName); - PlaySound(bindEffects.transform.parent.gameObject, audio); + AudioUtil.PlayAudio(audio, bindEffects.transform.parent.gameObject); // Remove haze and camera controls burst.DestroyGameObjectInChildren("haze2"); @@ -88,7 +88,7 @@ private void PlayBellExplode(GameObject bindEffects) { // Play sound var audio = bellFsm.GetFirstAction(stateName); - PlaySound(bindBell, audio); + AudioUtil.PlayAudio(audio, bindBell); // Remove camera control and haze diff --git a/SSMP/Animation/Effects/DashSlash.cs b/SSMP/Animation/Effects/DashSlash.cs index 8eac3f5..38ba946 100644 --- a/SSMP/Animation/Effects/DashSlash.cs +++ b/SSMP/Animation/Effects/DashSlash.cs @@ -124,7 +124,7 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? // TODO: Add charged dash stab/slash, see alternative states in Sprint FSM for Architect/Toolmaster var playAudioEvent = sprintFsm.GetFirstAction("Drill Attack Unch"); - AudioUtil.PlayAudioEventAtPlayerObject(playAudioEvent, playerObject); + AudioUtil.PlayAudio(playAudioEvent, playerObject); } } diff --git a/SSMP/Animation/Effects/DashSlashAntic.cs b/SSMP/Animation/Effects/DashSlashAntic.cs index 57e0add..adf5511 100644 --- a/SSMP/Animation/Effects/DashSlashAntic.cs +++ b/SSMP/Animation/Effects/DashSlashAntic.cs @@ -30,23 +30,23 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? case CrestType.Hunter: case CrestType.Witch: playAudioAction = sprintFsm.GetFirstAction("Attack Antic"); - AudioUtil.PlayAudioEventAtPlayerObject(playAudioAction, playerObject); + AudioUtil.PlayAudio(playAudioAction, playerObject); break; case CrestType.Reaper: var playRandomAudioClipAction = sprintFsm.GetFirstAction("Reaper Antic"); - AudioUtil.PlayRandomAudioClipAtPlayerObject(playRandomAudioClipAction, playerObject); + AudioUtil.PlayAudio(playRandomAudioClipAction, playerObject); break; case CrestType.Beast: playAudioAction = sprintFsm.GetFirstAction("Warrior Antic"); - AudioUtil.PlayAudioEventAtPlayerObject(playAudioAction, playerObject); + AudioUtil.PlayAudio(playAudioAction, playerObject); break; case CrestType.Architect: playAudioAction = sprintFsm.GetFirstAction("Drill Charge Start"); - AudioUtil.PlayAudioEventAtPlayerObject(playAudioAction, playerObject); + AudioUtil.PlayAudio(playAudioAction, playerObject); break; case CrestType.Shaman: playAudioAction = sprintFsm.GetFirstAction("Shaman Antic"); - AudioUtil.PlayAudioEventAtPlayerObject(playAudioAction, playerObject); + AudioUtil.PlayAudio(playAudioAction, playerObject); break; default: return; diff --git a/SSMP/Animation/Effects/DashSlashReaper.cs b/SSMP/Animation/Effects/DashSlashReaper.cs index 72c4190..8795dc3 100644 --- a/SSMP/Animation/Effects/DashSlashReaper.cs +++ b/SSMP/Animation/Effects/DashSlashReaper.cs @@ -16,6 +16,6 @@ public override void Play(GameObject playerObject, CrestType crestType, byte[]? // Also play an additional sound from the Sprint FSM var sprintFsm = HeroController.instance.sprintFSM; var playAudioAction = sprintFsm.GetFirstAction("Reaper Upper"); - AudioUtil.PlayAudioEventAtPlayerObject(playAudioAction, playerObject); + AudioUtil.PlayAudio(playAudioAction, playerObject); } } diff --git a/SSMP/Animation/Effects/EffectUtils.cs b/SSMP/Animation/Effects/EffectUtils.cs index 940899a..8abd61f 100644 --- a/SSMP/Animation/Effects/EffectUtils.cs +++ b/SSMP/Animation/Effects/EffectUtils.cs @@ -40,7 +40,7 @@ public static void SafelyRemoveAutoRecycle(GameObject obj) { return SpawnGlobalPoolObject(spawner.gameObject.Value, spawnLocation, destroyAfterDelay, keepParent); } - /// + /// /// The GameObject to spawn /// The location where the object will be spawned. /// The duration, in seconds, after which the spawned object will be destroyed. diff --git a/SSMP/Animation/Effects/NeedleStrike.cs b/SSMP/Animation/Effects/NeedleStrike.cs index e6e03f1..7dd5ca4 100644 --- a/SSMP/Animation/Effects/NeedleStrike.cs +++ b/SSMP/Animation/Effects/NeedleStrike.cs @@ -165,7 +165,7 @@ out _ } _reaperAudioEvent ??= GetOrFindNailArtsFsm().GetFirstAction("Antic Rpr"); - AudioUtil.PlayAudioEventAtPlayerObject(_reaperAudioEvent, playerObject); + AudioUtil.PlayAudio(_reaperAudioEvent, playerObject); var strikeObj = Object.Instantiate(_chargeSlashScythe, playerAttacks.transform); strikeObj.layer = 17; @@ -198,7 +198,7 @@ out _ strikeObj.SetActive(true); _wandererAudioEvent ??= GetOrFindNailArtsFsm().GetFirstAction("Wanderer Attack"); - AudioUtil.PlayAudioEventAtPlayerObject(_wandererAudioEvent, playerObject); + AudioUtil.PlayAudio(_wandererAudioEvent, playerObject); }, 0.1666667f); strikeObj.DestroyAfterTime(5f); @@ -214,7 +214,7 @@ out _ AnimationUtil.ExecuteActionAfterDelay(() => { _beastLeapAudioEvent ??= GetOrFindNailArtsFsm().GetFirstAction("Warrior2 Leap"); - AudioUtil.PlayAudioEventAtPlayerObject(_beastLeapAudioEvent, playerObject); + AudioUtil.PlayAudio(_beastLeapAudioEvent, playerObject); }, 0.1666667f); AnimationUtil.ExecuteActionAfterDelay(() => { @@ -254,7 +254,7 @@ out _ strikeObj.SetActive(true); _beastSlashAudioEvent ??= GetOrFindNailArtsFsm().GetFirstAction("Warrior2 Slash"); - AudioUtil.PlayAudioEventAtPlayerObject(_beastSlashAudioEvent, playerObject); + AudioUtil.PlayAudio(_beastSlashAudioEvent, playerObject); }, 0.4166667f); return; @@ -289,7 +289,7 @@ out _ ModifyDamagingSlashObject(strikeObj.FindGameObjectInChildren("damager 02"), damage); AnimationUtil.ExecuteActionAfterDelay(() => { - AudioUtil.PlayAudioEventAtPlayerObject(_witchAudioEvent, playerObject); + AudioUtil.PlayAudio(_witchAudioEvent, playerObject); strikeObj.SetActive(true); }, 0.08f); @@ -324,7 +324,7 @@ out _ ); AnimationUtil.ExecuteActionAfterDelay(() => { - AudioUtil.PlayAudioEventAtPlayerObject(_architectAudioEvent, playerObject); + AudioUtil.PlayAudio(_architectAudioEvent, playerObject); strikeObj.SetActive(true); }, 0.2777778f); diff --git a/SSMP/Util/AudioUtil.cs b/SSMP/Util/AudioUtil.cs index c675bb9..7abc643 100644 --- a/SSMP/Util/AudioUtil.cs +++ b/SSMP/Util/AudioUtil.cs @@ -31,7 +31,7 @@ public static AudioSource GetAudioSourceObject(GameObject gameObject) { /// The PlayAudioEvent instance from an FSM. /// The action to get a random audio clip from. /// The player object to play the audio at. - public static void PlayAudioEventWithRandomAudioClipFromTableAtPlayerObject( + public static void PlayAudio( PlayAudioEvent playAudioEvent, GetRandomAudioClipFromTable getRandomAudioClipFromTable, GameObject playerObject @@ -53,7 +53,7 @@ GameObject playerObject /// /// The PlayAudioEvent instance from an FSM. /// The player object to play the audio at. - public static void PlayAudioEventAtPlayerObject(PlayAudioEvent playAudioEvent, GameObject playerObject) { + public static void PlayAudio(PlayAudioEvent playAudioEvent, GameObject playerObject) { var audioClip = playAudioEvent.audioClip.value as AudioClip; if (audioClip == null) { Logger.Warn("Audio clip for PlayAudioEvent is null"); @@ -99,7 +99,7 @@ GameObject playerObject /// /// The action instance from an FSM. /// The player object to play the audio at. - public static void PlayRandomAudioClipAtPlayerObject( + public static void PlayAudio( PlayRandomAudioClipTableV2 playAudioClip, GameObject playerObject ) { @@ -127,7 +127,7 @@ GameObject playerObject /// /// The audio player action. /// The game object for the player. - public static void PlayAudioOneShotSingleAtPlayerObject( + public static void PlayAudio( AudioPlayerOneShotSingle action, GameObject playerObject ) {