1+ using Celeste . Mod . CommunalHelper . Components ;
2+ using Celeste . Mod . CommunalHelper . Imports ;
3+ using Mono . Cecil . Cil ;
4+ using MonoMod . Cil ;
5+ using System . Collections ;
6+ using System . Reflection ;
7+
8+ namespace Celeste . Mod . CommunalHelper . Entities ;
9+
10+ [ CustomEntity ( "CommunalHelper/DreamTheoCrystal" ) ]
11+ [ TrackedAs ( typeof ( TheoCrystal ) ) ]
12+ [ Tracked ( true ) ]
13+ internal class DreamTheoCrystal : TheoCrystal
14+ {
15+ public static readonly ParticleType [ ] P_DreamImpact = new ParticleType [ CustomDreamBlock . DreamColors . Length ] ;
16+
17+ private static readonly Rectangle particleBounds = new ( - 12 , - 20 , 24 , 40 ) ;
18+ public DreamSprite DreamSprite ;
19+
20+ public DreamHoldable DreamHold ;
21+
22+ public DreamTheoCrystal ( EntityData data , Vector2 offset ) : base ( data , offset )
23+ {
24+ Remove ( sprite ) ;
25+ Add ( sprite = DreamSprite = new DreamSprite ( CommunalHelperGFX . SpriteBank . Create ( "dreamTheoCrystal" ) , particleBounds ) ) ;
26+
27+ Remove ( Hold ) ;
28+ Add ( Hold = DreamHold = new DreamHoldable (
29+ new Hitbox ( 20 , 20 , - 10 , - 20 ) ,
30+ ( ) =>
31+ {
32+ DreamSprite . Enabled = true ;
33+ DreamSprite . Flash = 0.5f ;
34+ Audio . Play ( CustomSFX . game_dreamJellyfish_jelly_refill ) ;
35+ } ,
36+ ( ) =>
37+ {
38+ DreamSprite . Enabled = false ;
39+ DreamSprite . Flash = 1f ;
40+ Audio . Play ( CustomSFX . game_dreamJellyfish_jelly_use ) ;
41+ } ,
42+ 0.1f
43+ )
44+ {
45+ PickupCollider = new Hitbox ( 16f , 22f , - 8f , - 16f ) ,
46+ SlowFall = false ,
47+ SlowRun = true ,
48+ OnPickup = OnPickup ,
49+ OnRelease = OnRelease ,
50+ DangerousCheck = Dangerous ,
51+ OnHitSeeker = HitSeeker ,
52+ OnSwat = Swat ,
53+ OnHitSpring = HitSpring ,
54+ OnHitSpinner = HitSpinner ,
55+ SpeedGetter = ( ) => Speed ,
56+ SpeedSetter = delegate ( Vector2 speed )
57+ {
58+ Speed = speed ;
59+ } ,
60+ } ) ;
61+
62+ // The Dreamdash Collider does not shift down when this entity is inverted (via GravityHelper)
63+ // So let's add a listener that does this for us.
64+ Component listener = GravityHelper . CreateGravityListener ? . Invoke ( this , ( _ , value , _ ) =>
65+ {
66+ bool inverted = value == ( int ) GravityType . Inverted ;
67+ DreamHold . DreamDashCollider . Collider . Position . Y = inverted ? 0 : - 18 ; // a bit hacky
68+ } ) ;
69+ if ( listener is not null )
70+ Add ( listener ) ;
71+ }
72+
73+ internal static void InitializeParticles ( )
74+ {
75+ for ( int i = 0 ; i < CustomDreamBlock . DreamColors . Length ; i ++ )
76+ {
77+ P_DreamImpact [ i ] = new ParticleType ( P_Impact )
78+ {
79+ Color = CustomDreamBlock . DreamColors [ i ] ,
80+ Color2 = Color . Lerp ( CustomDreamBlock . DreamColors [ ( i + 2 ) % CustomDreamBlock . DreamColors . Length ] , P_Impact . Color2 , 0.4f ) ,
81+ } ;
82+ }
83+ }
84+
85+ #region Hooks
86+
87+ private static readonly MethodInfo m_ParticleSystem_Emit = typeof ( ParticleSystem ) . GetMethod ( "Emit" , BindingFlags . Public | BindingFlags . Instance , new Type [ ] { typeof ( ParticleType ) , typeof ( int ) , typeof ( Vector2 ) , typeof ( Vector2 ) , typeof ( float ) } ) ;
88+
89+ internal static void Load ( )
90+ {
91+ On . Celeste . TheoCrystal . Shatter += TheoCrystal_Shatter ;
92+
93+ // Change particles
94+ IL . Celeste . TheoCrystal . ImpactParticles += TheoCrystal_ImpactParticles ;
95+ }
96+
97+ internal static void Unload ( )
98+ {
99+ On . Celeste . TheoCrystal . Shatter -= TheoCrystal_Shatter ;
100+
101+ IL . Celeste . TheoCrystal . ImpactParticles -= TheoCrystal_ImpactParticles ;
102+ }
103+
104+ private static IEnumerator TheoCrystal_Shatter ( On . Celeste . TheoCrystal . orig_Shatter orig , TheoCrystal self )
105+ {
106+ if ( self is DreamTheoCrystal )
107+ yield break ;
108+
109+ yield return new SwapImmediately ( orig ( self ) ) ;
110+ }
111+
112+ private static void TheoCrystal_ImpactParticles ( ILContext il ) {
113+ ILCursor cursor = new ( il ) ;
114+
115+ if ( cursor . TryGotoNext ( MoveType . Before , instr => instr . MatchCallvirt ( m_ParticleSystem_Emit ) ) )
116+ {
117+ ILLabel normalBehavior = cursor . DefineLabel ( ) ;
118+ ILLabel afterNormalBehavior = cursor . DefineLabel ( ) ;
119+
120+ cursor . Emit ( OpCodes . Ldarg_0 ) ;
121+ cursor . EmitDelegate < Func < TheoCrystal , bool > > ( theo => theo is DreamTheoCrystal ) ;
122+ cursor . Emit ( OpCodes . Brfalse , normalBehavior ) ;
123+ cursor . EmitDelegate < Action < ParticleSystem , ParticleType , int , Vector2 , Vector2 , float > > ( ( particleSystem , _ , num , position , positionRange , direction ) =>
124+ {
125+ for ( int i = 0 ; i < num ; i ++ )
126+ {
127+ particleSystem . Emit ( Calc . Random . Choose ( P_DreamImpact ) , 1 , position , positionRange , direction ) ;
128+ }
129+ } ) ;
130+ cursor . Emit ( OpCodes . Br , afterNormalBehavior ) ;
131+ cursor . MarkLabel ( normalBehavior ) ;
132+ cursor . Index ++ ;
133+ cursor . MarkLabel ( afterNormalBehavior ) ;
134+ }
135+ }
136+
137+ #endregion
138+ }
0 commit comments