Skip to content

Commit 1f9a4fd

Browse files
committed
Custom Sandwich Lava (custom speed/width/direction)
1 parent 135b00b commit 1f9a4fd

File tree

5 files changed

+311
-1
lines changed

5 files changed

+311
-1
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module SpringCollab2020CustomSandwichLava
2+
3+
using ..Ahorn, Maple
4+
5+
@mapdef Entity "SpringCollab2020/CustomSandwichLava" CustomSandwichLava(x::Integer, y::Integer,
6+
direction::String="CoreModeBased", speed::Number=20, sandwichGap::Number=160)
7+
8+
const directions = String["AlwaysUp", "AlwaysDown", "CoreModeBased"]
9+
10+
const placements = Ahorn.PlacementDict(
11+
"Custom Sandwich Lava (Spring Collab 2020)" => Ahorn.EntityPlacement(
12+
CustomSandwichLava
13+
)
14+
)
15+
16+
Ahorn.editingOptions(entity::CustomSandwichLava) = Dict{String, Any}(
17+
"direction" => directions
18+
)
19+
20+
function Ahorn.selection(entity::CustomSandwichLava)
21+
x, y = Ahorn.position(entity)
22+
23+
return Ahorn.Rectangle(x - 12, y - 12, 24, 24)
24+
end
25+
26+
function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::CustomSandwichLava, room::Maple.Room)
27+
direction = get(entity.data, "direction", "CoreModeBased")
28+
29+
if direction == "AlwaysUp"
30+
Ahorn.drawSprite(ctx, "ahorn/SpringCollab2020/lava_sandwich_up", 0, 0)
31+
elseif direction == "AlwaysDown"
32+
Ahorn.drawSprite(ctx, "ahorn/SpringCollab2020/lava_sandwich_down", 0, 0)
33+
else
34+
Ahorn.drawImage(ctx, Ahorn.Assets.lavaSanwitch, -12, -12)
35+
end
36+
end
37+
38+
end

Ahorn/lang/en_gb.lang

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,9 @@ placements.entities.SpringCollab2020/UpsideDownJumpThru.tooltips.texture=Changes
5353

5454
# Sideways Jump Thru
5555
placements.entities.SpringCollab2020/SidewaysJumpThru.tooltips.texture=Changes the appearance of the platform.
56-
placements.entities.SpringCollab2020/SidewaysJumpThru.tooltips.left=Whether the solid side of the jumpthru is the left side.
56+
placements.entities.SpringCollab2020/SidewaysJumpThru.tooltips.left=Whether the solid side of the jumpthru is the left side.
57+
58+
# Custom Sandwich Lava
59+
placements.entities.SpringCollab2020/CustomSandwichLava.tooltips.direction=Changes the sandwich lava direction. CoreModeBased is the same behaviour as vanilla, depending on core mode.
60+
placements.entities.SpringCollab2020/CustomSandwichLava.tooltips.speed=Changes the speed the lava is moving at, in pixels per second. Vanilla value is 20.
61+
placements.entities.SpringCollab2020/CustomSandwichLava.tooltips.sandwichGap=The gap in pixels between both sides of the lava. Vanilla value is 160.

Entities/CustomSandwichLava.cs

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
using Celeste.Mod.Entities;
2+
using Microsoft.Xna.Framework;
3+
using Monocle;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
7+
8+
namespace Celeste.Mod.SpringCollab2020.Entities {
9+
/// <summary>
10+
/// Almost a copypaste of the vanilla SandwichLava class, but:
11+
/// - plays nicely with vertical rooms
12+
/// - can be made one-way
13+
/// - has customizable speed and gap.
14+
/// </summary>
15+
[CustomEntity("SpringCollab2020/CustomSandwichLava")]
16+
class CustomSandwichLava : Entity {
17+
private static FieldInfo lavaBlockerTriggerEnabled = typeof(LavaBlockerTrigger).GetField("enabled", BindingFlags.NonPublic | BindingFlags.Instance);
18+
19+
// if the player collides with one of those, lava should be forced into waiting.
20+
private List<LavaBlockerTrigger> lavaBlockerTriggers;
21+
22+
private enum Direction {
23+
AlwaysUp, AlwaysDown, CoreModeBased
24+
};
25+
26+
private const float TopOffset = -160f;
27+
28+
private bool iceMode;
29+
30+
private float startX;
31+
private Direction direction;
32+
private float speed;
33+
// the extra pixels each side has to be shifted towards each other compared to vanilla
34+
// to comply with the "sandwichGap" setting.
35+
private float sandwichDisplacement;
36+
37+
private float lerp;
38+
39+
private float transitionStartY;
40+
private float transitionStartTopRectY;
41+
private float transitionStartBottomRectY;
42+
43+
public bool Waiting;
44+
private bool leaving = false;
45+
private float delay = 0f;
46+
47+
private LavaRect bottomRect;
48+
private LavaRect topRect;
49+
50+
private bool persistent;
51+
private bool entering = true;
52+
53+
private SoundSource loopSfx;
54+
55+
// in vanilla, this is SceneAs<Level>().Bounds.Bottom. This is bad with vertical rooms.
56+
private float centerY => SceneAs<Level>().Camera.Bottom - 10f;
57+
58+
public CustomSandwichLava(EntityData data, Vector2 offset) {
59+
startX = data.Position.X + offset.X;
60+
direction = data.Enum("direction", Direction.CoreModeBased);
61+
speed = data.Float("speed", 20f);
62+
63+
// vanilla is 160. so, setting sandwichGap to 120 requires each side to be shifted by 20 pixels towards the other ((160 - 120) / 2).
64+
sandwichDisplacement = (160 - data.Float("sandwichGap", 160f)) / 2;
65+
66+
Depth = -1000000;
67+
Collider = new ColliderList(new Hitbox(340f, 120f, 0f, -sandwichDisplacement), new Hitbox(340f, 120f, 0f, -280f + sandwichDisplacement));
68+
Visible = false;
69+
70+
Add(loopSfx = new SoundSource());
71+
Add(new PlayerCollider(OnPlayer));
72+
Add(new CoreModeListener(OnChangeMode));
73+
74+
Add(bottomRect = new LavaRect(400f, 200f, 4));
75+
bottomRect.Position = new Vector2(-40f, 0f);
76+
bottomRect.OnlyMode = LavaRect.OnlyModes.OnlyTop;
77+
bottomRect.SmallWaveAmplitude = 2f;
78+
79+
Add(topRect = new LavaRect(400f, 200f, 4));
80+
topRect.Position = new Vector2(-40f, -360f);
81+
topRect.OnlyMode = LavaRect.OnlyModes.OnlyBottom;
82+
topRect.SmallWaveAmplitude = 2f;
83+
topRect.BigWaveAmplitude = (bottomRect.BigWaveAmplitude = 2f);
84+
topRect.CurveAmplitude = (bottomRect.CurveAmplitude = 4f);
85+
86+
Add(new TransitionListener {
87+
OnOutBegin = () => {
88+
transitionStartY = Y;
89+
transitionStartTopRectY = topRect.Position.Y;
90+
transitionStartBottomRectY = bottomRect.Position.Y;
91+
if (persistent && Scene != null && Scene.Entities.FindAll<CustomSandwichLava>().Count <= 1) {
92+
Leave();
93+
} else {
94+
// look up for all lava blocker triggers in the next room.
95+
lavaBlockerTriggers = Scene.Entities.OfType<LavaBlockerTrigger>().ToList();
96+
}
97+
},
98+
OnOut = progress => {
99+
if (Scene != null) {
100+
X = (Scene as Level).Camera.X;
101+
if (!leaving) {
102+
Y = MathHelper.Lerp(transitionStartY, centerY, progress);
103+
topRect.Position.Y = MathHelper.Lerp(transitionStartTopRectY, TopOffset - topRect.Height + sandwichDisplacement, progress);
104+
bottomRect.Position.Y = MathHelper.Lerp(transitionStartBottomRectY, -sandwichDisplacement, progress);
105+
}
106+
}
107+
if ((progress > 0.95f) & leaving) {
108+
RemoveSelf();
109+
}
110+
},
111+
OnInEnd = () => {
112+
if (entering) {
113+
Y = centerY;
114+
entering = false;
115+
}
116+
}
117+
});
118+
}
119+
120+
public override void Added(Scene scene) {
121+
base.Added(scene);
122+
X = SceneAs<Level>().Bounds.Left - 10;
123+
Y = centerY;
124+
iceMode = (SceneAs<Level>().Session.CoreMode == Session.CoreModes.Cold);
125+
}
126+
127+
public override void Awake(Scene scene) {
128+
base.Awake(scene);
129+
130+
Player entity = Scene.Tracker.GetEntity<Player>();
131+
if (entity != null && (entity.JustRespawned || entity.X < startX)) {
132+
Waiting = true;
133+
}
134+
135+
List<CustomSandwichLava> existingLavas = Scene.Entities.FindAll<CustomSandwichLava>();
136+
137+
bool removedSelf = false;
138+
if (!persistent && existingLavas.Count >= 2) {
139+
CustomSandwichLava sandwichLava = (existingLavas[0] == this) ? existingLavas[1] : existingLavas[0];
140+
if (!sandwichLava.leaving) {
141+
// transfer new settings to the existing lava.
142+
sandwichLava.startX = startX;
143+
sandwichLava.Waiting = true;
144+
sandwichLava.direction = direction;
145+
sandwichLava.speed = speed;
146+
sandwichLava.sandwichDisplacement = sandwichDisplacement;
147+
sandwichLava.Collider = Collider;
148+
entering = false;
149+
RemoveSelf();
150+
removedSelf = true;
151+
}
152+
}
153+
154+
if (!removedSelf) {
155+
persistent = true;
156+
Tag = Tags.Persistent;
157+
if ((scene as Level).LastIntroType != Player.IntroTypes.Respawn) {
158+
topRect.Position.Y -= 60f;
159+
bottomRect.Position.Y += 60f;
160+
} else {
161+
Visible = true;
162+
}
163+
loopSfx.Play("event:/game/09_core/rising_threat", "room_state", iceMode ? 1 : 0);
164+
loopSfx.Position = new Vector2(Width / 2f, 0f);
165+
166+
// look up for all lava blocker triggers in the room.
167+
lavaBlockerTriggers = scene.Entities.OfType<LavaBlockerTrigger>().ToList();
168+
}
169+
}
170+
171+
private void OnChangeMode(Session.CoreModes mode) {
172+
iceMode = (mode == Session.CoreModes.Cold);
173+
loopSfx.Param("room_state", iceMode ? 1 : 0);
174+
}
175+
176+
private void OnPlayer(Player player) {
177+
if (Waiting) {
178+
return;
179+
}
180+
if (SaveData.Instance.Assists.Invincible) {
181+
if (delay <= 0f) {
182+
int direction = (player.Y > Y + bottomRect.Position.Y - 32f) ? 1 : -1;
183+
float from = Y;
184+
float to = Y + (direction * 48);
185+
player.Speed.Y = -direction * 200;
186+
if (direction > 0) {
187+
player.RefillDash();
188+
}
189+
Tween.Set(this, Tween.TweenMode.Oneshot, 0.4f, Ease.CubeOut, delegate (Tween t) {
190+
Y = MathHelper.Lerp(from, to, t.Eased);
191+
});
192+
delay = 0.5f;
193+
loopSfx.Param("rising", 0f);
194+
Audio.Play("event:/game/general/assist_screenbottom", player.Position);
195+
}
196+
} else {
197+
player.Die(-Vector2.UnitY);
198+
}
199+
}
200+
201+
public void Leave() {
202+
AddTag(Tags.TransitionUpdate);
203+
leaving = true;
204+
Collidable = false;
205+
Alarm.Set(this, 2f, delegate {
206+
RemoveSelf();
207+
});
208+
}
209+
210+
public override void Update() {
211+
if (entering) {
212+
Y = centerY;
213+
entering = false;
214+
}
215+
216+
Level level = Scene as Level;
217+
X = level.Camera.X;
218+
delay -= Engine.DeltaTime;
219+
base.Update();
220+
Visible = true;
221+
222+
Player player = Scene.Tracker.GetEntity<Player>();
223+
if (player != null) {
224+
LavaBlockerTrigger collidedTrigger = lavaBlockerTriggers.Find(trigger => player.CollideCheck(trigger));
225+
226+
if (collidedTrigger != null && (bool) lavaBlockerTriggerEnabled.GetValue(collidedTrigger)) {
227+
// player is in a lava blocker trigger and it is enabled; block the lava.
228+
Waiting = true;
229+
}
230+
}
231+
232+
if (Waiting) {
233+
Y = Calc.Approach(Y, centerY, 128f * Engine.DeltaTime);
234+
loopSfx.Param("rising", 0f);
235+
if (player != null && player.X >= startX && !player.JustRespawned && player.StateMachine.State != 11) {
236+
Waiting = false;
237+
}
238+
} else if (!leaving && delay <= 0f) {
239+
loopSfx.Param("rising", 1f);
240+
if (direction == Direction.AlwaysDown || (direction == Direction.CoreModeBased && iceMode)) {
241+
Y += speed * Engine.DeltaTime;
242+
} else {
243+
Y -= speed * Engine.DeltaTime;
244+
}
245+
}
246+
247+
topRect.Position.Y = Calc.Approach(topRect.Position.Y, TopOffset - topRect.Height + (leaving ? (-512) : sandwichDisplacement), (leaving ? 256 : 64) * Engine.DeltaTime);
248+
bottomRect.Position.Y = Calc.Approach(bottomRect.Position.Y, leaving ? 512 : -sandwichDisplacement, (leaving ? 256 : 64) * Engine.DeltaTime);
249+
250+
lerp = Calc.Approach(lerp, iceMode ? 1 : 0, Engine.DeltaTime * 4f);
251+
252+
bottomRect.SurfaceColor = Color.Lerp(RisingLava.Hot[0], RisingLava.Cold[0], lerp);
253+
bottomRect.EdgeColor = Color.Lerp(RisingLava.Hot[1], RisingLava.Cold[1], lerp);
254+
bottomRect.CenterColor = Color.Lerp(RisingLava.Hot[2], RisingLava.Cold[2], lerp);
255+
bottomRect.Spikey = lerp * 5f;
256+
bottomRect.UpdateMultiplier = (1f - lerp) * 2f;
257+
bottomRect.Fade = (iceMode ? 128 : 32);
258+
259+
topRect.SurfaceColor = bottomRect.SurfaceColor;
260+
topRect.EdgeColor = bottomRect.EdgeColor;
261+
topRect.CenterColor = bottomRect.CenterColor;
262+
topRect.Spikey = bottomRect.Spikey;
263+
topRect.UpdateMultiplier = bottomRect.UpdateMultiplier;
264+
topRect.Fade = bottomRect.Fade;
265+
}
266+
}
267+
}
564 Bytes
Loading
561 Bytes
Loading

0 commit comments

Comments
 (0)