Skip to content

Commit 22f6c41

Browse files
committed
Add Dream Theo Crystals
1 parent 8b5d9d2 commit 22f6c41

File tree

8 files changed

+189
-16
lines changed

8 files changed

+189
-16
lines changed
301 Bytes
Loading
504 Bytes
Loading

Graphics/CommunalHelper/Sprites.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,11 @@
506506
<Anim id="death" path="death" delay="0.06"/>
507507
</dreamJellyfish>
508508

509+
<dreamTheoCrystal path="objects/CommunalHelper/dreamTheoCrystal/mask/" start="idle">
510+
<Origin x="32" y="42"/>
511+
<Loop id="idle" path="idle" delay="0.08"/>
512+
</dreamTheoCrystal>
513+
509514
<!--DreamStrawberry-->
510515
<dreamStrawberry path="collectables/CommunalHelper/dreamberry/" start="idle">
511516
<Center />
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
local drawableSprite = require("structs.drawable_sprite")
2+
local utils = require("utils")
3+
4+
local dreamTheoCrystal = {}
5+
6+
dreamTheoCrystal.name = "CommunalHelper/DreamTheoCrystal"
7+
dreamTheoCrystal.depth = 100
8+
dreamTheoCrystal.placements = {
9+
name = "dream_theo_crystal",
10+
}
11+
12+
-- Offset is from sprites.xml, not justifications
13+
local offsetY = -10
14+
local texture = "objects/CommunalHelper/dreamTheoCrystal/theo"
15+
16+
function dreamTheoCrystal.sprite(room, entity)
17+
local sprite = drawableSprite.fromTexture(texture, entity)
18+
19+
sprite.y += offsetY
20+
21+
return sprite
22+
end
23+
24+
function dreamTheoCrystal.rectangle(room, entity)
25+
return utils.rectangle(entity.x - 11, entity.y - 21, 21, 21)
26+
end
27+
28+
return dreamTheoCrystal

Loenn/lang/en_gb.lang

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ entities.CommunalHelper/DreamJellyfish.placements.name.floating=Dream Jellyfish
1313
entities.CommunalHelper/DreamJellyfish.attributes.description.bubble=Whether the entity should hover in the air before being grabbed.
1414
entities.CommunalHelper/DreamJellyfish.attributes.description.tutorial=Whether the entity should show the usage tutorial.
1515

16+
# Dream Theo Crystal
17+
entities.CommunalHelper/DreamTheoCrystal.placements.name.dream_theo_crystal=Dream Theo Crystal
18+
1619
# Dream Booster
1720
entities.CommunalHelper/DreamBoosterAny.placements.name.dream_booster=Dream Booster (Any Direction)
1821

src/CommunalHelperModule.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public override void Load()
8181

8282
DreamHoldable.Load();
8383
DreamJellyfish.Load();
84+
DreamTheoCrystal.Load();
8485

8586
ChainedKevin.Load();
8687

@@ -169,6 +170,7 @@ public override void Unload()
169170

170171
DreamHoldable.Unload();
171172
DreamJellyfish.Unload();
173+
DreamTheoCrystal.Unload();
172174

173175
ChainedKevin.Unload();
174176

@@ -286,6 +288,7 @@ public override void LoadContent(bool firstLoad)
286288
DreamSpriteRenderer.InitializeTextures();
287289

288290
DreamJellyfish.InitializeParticles();
291+
DreamTheoCrystal.InitializeParticles();
289292

290293
Chain.InitializeTextures();
291294

src/Entities/DreamBlocks/DreamJellyfish.cs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,19 @@ internal class DreamJellyfish : Glider
1515
public static readonly ParticleType[] P_DreamGlideUp = new ParticleType[CustomDreamBlock.DreamColors.Length];
1616
public static readonly ParticleType[] P_DreamGlide = new ParticleType[CustomDreamBlock.DreamColors.Length];
1717

18-
private readonly DynamicData gliderData;
19-
2018
private static readonly Rectangle particleBounds = new(-23, -35, 48, 60);
21-
public DreamSprite Sprite;
19+
public DreamSprite DreamSprite;
2220

23-
public DreamHoldable Holdable;
21+
public DreamHoldable DreamHold;
2422

2523
public DreamJellyfish(EntityData data, Vector2 offset)
2624
: this(data.Position + offset, data.Bool("bubble"), data.Bool("tutorial")) { }
2725

2826
public DreamJellyfish(Vector2 position, bool bubble, bool tutorial)
2927
: base(position, bubble, tutorial)
3028
{
31-
gliderData = new DynamicData(typeof(Glider), this);
32-
33-
Remove(gliderData.Get<Sprite>("sprite"));
34-
gliderData.Set("sprite", Sprite = new DreamSprite(
29+
Remove(sprite);
30+
Add(sprite = DreamSprite = new DreamSprite(
3531
CommunalHelperGFX.SpriteBank.Create("dreamJellyfish"),
3632
particleBounds,
3733
delegate (ref Vector2 position, ref Vector2 scale, ref float rotation)
@@ -40,22 +36,21 @@ public DreamJellyfish(Vector2 position, bool bubble, bool tutorial)
4036
rotation = -rotation;
4137
}
4238
));
43-
Add(Sprite);
4439

4540
Remove(Hold);
46-
Add(Hold = Holdable = new DreamHoldable(
41+
Add(Hold = DreamHold = new DreamHoldable(
4742
new Hitbox(28, 16, -13, -18),
4843
() =>
4944
{
50-
Sprite.Enabled = true;
51-
Sprite.Flash = 0.5f;
52-
Sprite.Scale = new Vector2(1.3f, 1.2f);
45+
DreamSprite.Enabled = true;
46+
DreamSprite.Flash = 0.5f;
47+
DreamSprite.Scale = new Vector2(1.3f, 1.2f);
5348
Audio.Play(CustomSFX.game_dreamJellyfish_jelly_refill);
5449
},
5550
() =>
5651
{
57-
Sprite.Enabled = false;
58-
Sprite.Flash = 1f;
52+
DreamSprite.Enabled = false;
53+
DreamSprite.Flash = 1f;
5954
Audio.Play(CustomSFX.game_dreamJellyfish_jelly_use);
6055
},
6156
0.3f
@@ -79,7 +74,7 @@ public DreamJellyfish(Vector2 position, bool bubble, bool tutorial)
7974
Component listener = GravityHelper.CreateGravityListener?.Invoke(this, (_, value, _) =>
8075
{
8176
bool inverted = value == (int) GravityType.Inverted;
82-
Holdable.DreamDashCollider.Collider.Position.Y = inverted ? 1 : -18; // a bit hacky
77+
DreamHold.DreamDashCollider.Collider.Position.Y = inverted ? 1 : -18; // a bit hacky
8378
});
8479
if (listener is not null)
8580
Add(listener);
@@ -132,6 +127,7 @@ internal static void Unload()
132127
private static void Glider_Update(ILContext il)
133128
{
134129
ILCursor cursor = new(il);
130+
135131
if (cursor.TryGotoNext(MoveType.After, instr => instr.MatchLdsfld(f_Glider_P_Glow)))
136132
{
137133
cursor.Emit(OpCodes.Ldarg_0);
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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

Comments
 (0)