Skip to content

Commit a16ed29

Browse files
committed
Safe Respawn Crumble a la Mario Maker
Spawning or bubbling atop this object, within range, will activate it and allow you to stand. Jumping off will disable it again. (Also forced C# version by request)
1 parent 058b567 commit a16ed29

File tree

5 files changed

+214
-0
lines changed

5 files changed

+214
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module SpringCollab2020SafeRespawnCrumble
2+
3+
using ..Ahorn, Maple
4+
5+
@mapdef Entity "SpringCollab2020/safeRespawnCrumble" SafeRespawnCrumble(x::Integer, y::Integer, width::Integer=Maple.defaultBlockWidth)
6+
7+
const placements = Ahorn.PlacementDict(
8+
"Safe Respawn Crumble (Spring Collab 2020)" => Ahorn.EntityPlacement(
9+
SafeRespawnCrumble,
10+
"rectangle"
11+
)
12+
)
13+
14+
Ahorn.minimumSize(entity::SafeRespawnCrumble) = 8, 0
15+
Ahorn.resizable(entity::SafeRespawnCrumble) = true, false
16+
17+
function Ahorn.selection(entity::SafeRespawnCrumble)
18+
x, y = Ahorn.position(entity)
19+
width = Int(get(entity.data, "width", 8))
20+
21+
return Ahorn.Rectangle(x, y, width, 8)
22+
end
23+
24+
function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::SafeRespawnCrumble, room::Maple.Room)
25+
texture = "objects/SpringCollab2020/safeRespawnCrumble/tile"
26+
27+
# Values need to be system specific integer
28+
x = Int(get(entity.data, "x", 0))
29+
y = Int(get(entity.data, "y", 0))
30+
31+
width = Int(get(entity.data, "width", 8))
32+
tilesWidth = div(width, 8)
33+
34+
Ahorn.Cairo.save(ctx)
35+
36+
Ahorn.rectangle(ctx, 0, 0, width, 8)
37+
Ahorn.clip(ctx)
38+
39+
for i in 0:ceil(Int, tilesWidth)
40+
Ahorn.drawImage(ctx, texture, 8 * i, 0, 0, 0, 8, 8)
41+
end
42+
43+
Ahorn.restore(ctx)
44+
end
45+
46+
end

Entities/SafeRespawnCrumble.cs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using Microsoft.Xna.Framework;
4+
using Monocle;
5+
using Celeste.Mod.Entities;
6+
7+
/*
8+
* Safe Respawn Crumble (Spring Collab 2020)
9+
* https://github.com/EverestAPI/SpringCollab2020/
10+
*
11+
* A Crumble Platform which is always "crumbled",
12+
* unless the player respawns in its vicinity
13+
* or is bubbled to it via a CassetteReturn routine.
14+
*
15+
* It performs a similar role to Mario Maker's pink block platform,
16+
* which only spawns when a user playtests their level from an aerial position
17+
* and disappears once the user jumps off of it.
18+
*/
19+
namespace Celeste.Mod.SpringCollab2020.Entities {
20+
[Tracked(false)]
21+
[CustomEntity("SpringCollab2020/safeRespawnCrumble")]
22+
class SafeRespawnCrumble : Solid {
23+
// We'll want to declare the SafeRespawnCrumble as "safe ground" so that Return Strawberries can be collected on it.
24+
public SafeRespawnCrumble(EntityData data, Vector2 offset) : base(data.Position + offset, (float) data.Width, 8f, true) {
25+
EnableAssistModeChecks = false;
26+
}
27+
28+
public override void Added(Scene scene) {
29+
base.Added(scene);
30+
31+
// Set up the outline and block tiles and first-initialize the fader coroutines
32+
MTexture outlineTexture = GFX.Game["objects/crumbleBlock/outline"];
33+
MTexture tileTexture = GFX.Game["objects/SpringCollab2020/safeRespawnCrumble/tile"];
34+
35+
tiles = new List<Image>();
36+
outlineTiles = new List<Image>();
37+
38+
if (Width <= 8f) {
39+
Image toDraw = new Image(outlineTexture.GetSubtexture(24, 0, 8, 8, null));
40+
toDraw.Color = Color.White;
41+
outlineTiles.Add(toDraw);
42+
} else {
43+
// Select left, middle, or right
44+
for (int tile = 0; (float)tile < base.Width; tile += 8) {
45+
int tileTex;
46+
if (tile == 0)
47+
tileTex = 0;
48+
else if (tile > 0 && (float) tile < base.Width - 8f)
49+
tileTex = 1;
50+
else
51+
tileTex = 2;
52+
53+
Image toDraw = new Image(outlineTexture.GetSubtexture(tileTex * 8, 0, 8, 8));
54+
toDraw.Position = new Vector2((float) tile, 0f);
55+
outlineTiles.Add(toDraw);
56+
Add(toDraw);
57+
}
58+
}
59+
60+
// Add pink-block tiles
61+
for (int tile = 0; (float) tile < base.Width; tile += 8) {
62+
Image toDraw = new Image(tileTexture);
63+
toDraw.Position = new Vector2((float) tile, 0f);
64+
toDraw.Color = Color.White * 0f;
65+
tiles.Add(toDraw);
66+
Add(toDraw);
67+
}
68+
69+
Add(outlineFader = new Coroutine(false));
70+
Add(tileFader = new Coroutine(false));
71+
72+
Add(new Coroutine(Sequence(), true));
73+
}
74+
75+
private IEnumerator Sequence() {
76+
for (;;) {
77+
// Wait until activated
78+
Collidable = false;
79+
while (!activated)
80+
yield return null;
81+
82+
// Fade out the outline, fade in the tiles. Oh, and make ourselves collidable.
83+
tileFader.Replace(TileFade(1f, tiles));
84+
outlineFader.Replace(TileFade(0f, outlineTiles));
85+
Collidable = true;
86+
87+
// Wait until player is found
88+
while (GetPlayerOnTop() == null)
89+
yield return null;
90+
91+
// Wait until player leaves
92+
while (GetPlayerOnTop() != null)
93+
yield return null;
94+
95+
// Fade out tiles, fade in outline.
96+
tileFader.Replace(TileFade(0f, tiles));
97+
outlineFader.Replace(TileFade(1f, outlineTiles));
98+
99+
// Do nothing until next activation
100+
activated = false;
101+
}
102+
}
103+
104+
// Fade the passed tiles in or out.
105+
private IEnumerator TileFade(float to, List<Image> targetTiles) {
106+
float from = 1f - to;
107+
for (float t = 0f; t < 1f; t += Engine.DeltaTime * 2f) {
108+
foreach (Image img in targetTiles)
109+
img.Color = Color.White * (from + (to - from) * Ease.CubeInOut(t));
110+
yield return null;
111+
}
112+
yield break;
113+
}
114+
115+
// Bubble and player detection hooks
116+
public static void Load() {
117+
On.Celeste.Player.CassetteFlyEnd += SafeActivatorCasetteFly;
118+
On.Celeste.Player.IntroRespawnEnd += SafeActivatorRespawn;
119+
}
120+
public static void Unload() {
121+
On.Celeste.Player.CassetteFlyEnd -= SafeActivatorCasetteFly;
122+
On.Celeste.Player.IntroRespawnEnd -= SafeActivatorRespawn;
123+
}
124+
125+
private static void SafeActivatorCasetteFly(On.Celeste.Player.orig_CassetteFlyEnd orig, Player self) {
126+
orig(self);
127+
SafeActivate(self);
128+
}
129+
private static void SafeActivatorRespawn(On.Celeste.Player.orig_IntroRespawnEnd orig, Player self) {
130+
orig(self);
131+
SafeActivate(self);
132+
}
133+
134+
private static void SafeActivate(Player player) {
135+
SafeRespawnCrumble target = player.Scene.Tracker.GetNearestEntity<SafeRespawnCrumble>(player.Position);
136+
if (target == null)
137+
return;
138+
139+
if (target.Left < player.X &&
140+
target.Right > player.X &&
141+
target.Top - 12f <= player.Y &&
142+
target.Bottom > player.Y)
143+
target.activated = true;
144+
}
145+
146+
private List<Image> tiles;
147+
private List<Image> outlineTiles;
148+
private Coroutine tileFader;
149+
private Coroutine outlineFader;
150+
public bool activated = false;
151+
}
152+
}
144 Bytes
Loading

SpringCollab2020.csproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,26 @@
44
<TargetFrameworks>net452</TargetFrameworks>
55
<AssemblyName>SpringCollab2020</AssemblyName>
66
<RootNamespace>Celeste.Mod.SpringCollab2020</RootNamespace>
7+
<LangVersion>7.3</LangVersion>
78
</PropertyGroup>
89

910
<ItemGroup>
1011
<PackageReference Include="MonoMod.RuntimeDetour" Version="20.1.1.4" PrivateAssets="all" ExcludeAssets="runtime" />
1112
</ItemGroup>
1213

14+
<ItemGroup>
15+
<Folder Include="Graphics\Atlases\Gameplay\objects\SpringCollab2020\safeRespawnCrumble\" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<None Update="everest.yaml">
20+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
21+
</None>
22+
<None Update="Graphics\Atlases\Gameplay\objects\SpringCollab2020\safeRespawnCrumble\tile.png">
23+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
24+
</None>
25+
</ItemGroup>
26+
1327
<Choose>
1428
<When Condition="Exists('..\..\Celeste.exe')">
1529
<ItemGroup>

SpringCollab2020Module.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ public override void Load() {
1414
NoRefillField.Load();
1515
FloatierSpaceBlock.Load();
1616
RemoveLightSourcesTrigger.Load();
17+
SafeRespawnCrumble.Load();
1718
}
1819

1920
public override void Unload() {
2021
NoRefillField.Unload();
2122
FloatierSpaceBlock.Unload();
2223
RemoveLightSourcesTrigger.Unload();
24+
SafeRespawnCrumble.Unload();
2325
}
2426
}
2527
}

0 commit comments

Comments
 (0)