Skip to content

Commit 5f89dcc

Browse files
authored
Merge pull request #48 from EverestAPI/aerial_respawn_crumble
Safe Respawn Crumble a la Mario Maker
2 parents 058b567 + a0c5334 commit 5f89dcc

File tree

5 files changed

+254
-0
lines changed

5 files changed

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