Skip to content

Commit bb8f334

Browse files
committed
Adjustments for the new Grandmaster Heart Side
1 parent 6cdc0d4 commit bb8f334

File tree

4 files changed

+188
-23
lines changed

4 files changed

+188
-23
lines changed

GrandmasterHeartSideHelper.cs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
using Microsoft.Xna.Framework;
2+
using Mono.Cecil.Cil;
3+
using Monocle;
4+
using MonoMod.Cil;
5+
using System;
6+
using System.Reflection;
7+
using System.Linq;
8+
using MonoMod.RuntimeDetour;
9+
using System.Collections.Generic;
10+
using Mono.Cecil;
11+
using MonoMod.Utils;
12+
13+
namespace Celeste.Mod.SpringCollab2020 {
14+
// The new Grandmaster Heart Side is called ZZ-NewHeartSide and the old one is hidden at ZZ-HeartSide.
15+
// This requires some specific handling from the Collab Utils:
16+
// - hiding old GMHS from the journals, and not making it behave like a heart side
17+
// - making new GMHS behave like a heart side
18+
// We also want the ending heart of old GMHS to send back to the new GMHS, instead of displaying an endscreen.
19+
static class GrandmasterHeartSideHelper {
20+
private static Hook hookIsHeartSide;
21+
private static ILHook hookLobbyJournal;
22+
private static ILHook hookOverworldJournal;
23+
private static Hook hookLevelExitToLobby;
24+
25+
public static void Load() {
26+
Assembly collabUtils = typeof(CollabUtils2.CollabModule).Assembly;
27+
28+
hookIsHeartSide = new Hook(
29+
collabUtils.GetType("Celeste.Mod.CollabUtils2.LobbyHelper").GetMethod("IsHeartSide"),
30+
typeof(GrandmasterHeartSideHelper).GetMethod("modIsHeartSide", BindingFlags.NonPublic | BindingFlags.Static));
31+
32+
hookOverworldJournal = new ILHook(
33+
collabUtils.GetType("Celeste.Mod.CollabUtils2.UI.OuiJournalCollabProgressInOverworld").GetConstructor(new Type[] { typeof(OuiJournal) }),
34+
modOverworldJournal);
35+
36+
hookLobbyJournal = new ILHook(
37+
collabUtils.GetType("Celeste.Mod.CollabUtils2.UI.OuiJournalCollabProgressInLobby").GetMethod("GeneratePages"),
38+
modLobbyJournal);
39+
40+
IL.Celeste.Level.CompleteArea_bool_bool_bool += modLevelComplete;
41+
IL.Celeste.OuiChapterPanel.Render += renderOldGMHSCompletionStamp;
42+
}
43+
44+
public static void Unload() {
45+
hookIsHeartSide?.Dispose();
46+
hookIsHeartSide = null;
47+
48+
hookLobbyJournal?.Dispose();
49+
hookLobbyJournal = null;
50+
51+
hookOverworldJournal?.Dispose();
52+
hookOverworldJournal = null;
53+
54+
IL.Celeste.Level.CompleteArea_bool_bool_bool -= modLevelComplete;
55+
IL.Celeste.OuiChapterPanel.Render -= renderOldGMHSCompletionStamp;
56+
}
57+
58+
private static bool modIsHeartSide(Func<string, bool> orig, string sid) {
59+
if (sid == "SpringCollab2020/5-Grandmaster/ZZ-HeartSide") {
60+
return false; // old GMHS
61+
} else if (sid == "SpringCollab2020/5-Grandmaster/ZZ-NewHeartSide") {
62+
return true; // new GMHS
63+
}
64+
65+
return orig(sid); // ... not GMHS
66+
}
67+
68+
private static void modLobbyJournal(ILContext il) {
69+
ILCursor cursor = new ILCursor(il);
70+
71+
while (cursor.TryGotoNext(MoveType.After, instr => instr.MatchCallvirt<SaveData>("get_Areas_Safe"))) {
72+
Logger.Log("SpringCollab2020/GrandmasterHeartSideHelper", $"Filtering out old GMHS from journal at {cursor.Index} in IL for OuiJournalProgressInLobby.GeneratePages");
73+
cursor.EmitDelegate<Func<List<AreaStats>, List<AreaStats>>>(orig => orig.Where(area => area.SID != "SpringCollab2020/5-Grandmaster/ZZ-HeartSide").ToList());
74+
}
75+
}
76+
77+
private static void modOverworldJournal(ILContext il) {
78+
ILCursor cursor = new ILCursor(il);
79+
80+
while (cursor.TryGotoNext(MoveType.After, instr => instr.MatchLdfld<LevelSetStats>("Areas"))) {
81+
Logger.Log("SpringCollab2020/GrandmasterHeartSideHelper", $"Filtering out old GMHS from journal at {cursor.Index} in IL for OuiJournalCollabProgressInOverworld ctor");
82+
cursor.EmitDelegate<Func<List<AreaStats>, List<AreaStats>>>(orig => orig.Where(area => area.SID != "SpringCollab2020/5-Grandmaster/ZZ-HeartSide").ToList());
83+
}
84+
}
85+
86+
87+
private static void modLevelComplete(ILContext il) {
88+
ILCursor cursor = new ILCursor(il);
89+
90+
while (cursor.TryGotoNext(MoveType.After,
91+
instr => instr.OpCode == OpCodes.Ldftn && ((instr.Operand as MethodReference)?.Name.StartsWith("<CompleteArea>") ?? false),
92+
instr => instr.MatchNewobj<Action>())) {
93+
94+
Logger.Log("SpringCollab2020/GrandmasterHeartSideHelper", $"Redirecting old GMHS ending to new GMHS at {cursor.Index} in IL for Level.CompleteArea");
95+
96+
cursor.Emit(OpCodes.Ldarg_0);
97+
cursor.EmitDelegate<Func<Action, Level, Action>>((orig, self) => {
98+
if (self.Session.Area.GetSID() == "SpringCollab2020/5-Grandmaster/ZZ-HeartSide") {
99+
// "return to lobby" but it actually returns to the new GM heart side.
100+
return () => {
101+
hookLevelExitToLobby = new Hook(
102+
typeof(CollabUtils2.UI.LevelExitToLobby).GetMethod("Begin"),
103+
typeof(GrandmasterHeartSideHelper).GetMethod("modLevelExitToLobby", BindingFlags.NonPublic | BindingFlags.Static));
104+
On.Celeste.LevelEnter.Go += modLevelEnter;
105+
106+
Engine.Scene = new CollabUtils2.UI.LevelExitToLobby(LevelExit.Mode.Completed, self.Session);
107+
};
108+
} else {
109+
// do the usual.
110+
return orig;
111+
}
112+
});
113+
}
114+
}
115+
116+
private static void modLevelExitToLobby(Action<CollabUtils2.UI.LevelExitToLobby> orig, CollabUtils2.UI.LevelExitToLobby self) {
117+
// back up return location.
118+
string bakSID = CollabUtils2.CollabModule.Instance.Session.LobbySID;
119+
string bakRoom = CollabUtils2.CollabModule.Instance.Session.LobbyRoom;
120+
float bakX = CollabUtils2.CollabModule.Instance.Session.LobbySpawnPointX;
121+
float bakY = CollabUtils2.CollabModule.Instance.Session.LobbySpawnPointY;
122+
123+
// modify it to the new gmhs.
124+
CollabUtils2.CollabModule.Instance.Session.LobbySID = "SpringCollab2020/5-Grandmaster/ZZ-NewHeartSide";
125+
CollabUtils2.CollabModule.Instance.Session.LobbyRoom = null;
126+
CollabUtils2.CollabModule.Instance.Session.LobbySpawnPointX = 0;
127+
CollabUtils2.CollabModule.Instance.Session.LobbySpawnPointY = 0;
128+
129+
// run collab utils code.
130+
orig(self);
131+
132+
// restore old values.
133+
CollabUtils2.CollabModule.Instance.Session.LobbySID = bakSID;
134+
CollabUtils2.CollabModule.Instance.Session.LobbyRoom = bakRoom;
135+
CollabUtils2.CollabModule.Instance.Session.LobbySpawnPointX = bakX;
136+
CollabUtils2.CollabModule.Instance.Session.LobbySpawnPointY = bakY;
137+
138+
// undo the hook so that future returns to lobby behave normally.
139+
hookLevelExitToLobby?.Dispose();
140+
hookLevelExitToLobby = null;
141+
}
142+
143+
private static void modLevelEnter(On.Celeste.LevelEnter.orig_Go orig, Session session, bool fromSaveData) {
144+
// This hook is only applied when returning from the old GMHS.
145+
// We know we are returning to the start of new GMHS, so set up the session properly for that.
146+
session.FirstLevel = true;
147+
session.StartedFromBeginning = true;
148+
new DynData<Session>(session)["pauseTimerUntilAction"] = false;
149+
150+
orig(session, fromSaveData);
151+
152+
// and undo the hook.
153+
On.Celeste.LevelEnter.Go -= modLevelEnter;
154+
}
155+
156+
private static void renderOldGMHSCompletionStamp(ILContext il) {
157+
ILCursor cursor = new ILCursor(il);
158+
159+
// draw the stamp just after the chapter card.
160+
if (cursor.TryGotoNext(instr => instr.MatchStfld<OuiChapterPanel>("card"))
161+
&& cursor.TryGotoNext(MoveType.After, instr => instr.MatchCallvirt<MTexture>("Draw"))) {
162+
163+
Logger.Log("SpringCollab2020/GrandmasterHeartSideHelper", $"Injecting GMHS stamp rendering at {cursor.Index} in IL for OuiChapterPanel.Render");
164+
165+
cursor.Emit(OpCodes.Ldarg_0);
166+
cursor.EmitDelegate<Action<OuiChapterPanel>>(self => {
167+
// draw it only if the player beat old grandmaster heart side, and we're actually looking at it.
168+
if (self.Area.GetSID() == "SpringCollab2020/5-Grandmaster/ZZ-NewHeartSide"
169+
&& (SaveData.Instance.GetAreaStatsFor(AreaData.Get("SpringCollab2020/5-Grandmaster/ZZ-HeartSide").ToKey())?.Modes[0].Completed ?? false)) {
170+
171+
MTN.FileSelect["heart"].Draw(self.Position + new Vector2(-580f, 130f));
172+
}
173+
});
174+
}
175+
}
176+
}
177+
}

SpringCollab2020.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
<Folder Include="Graphics\Atlases\Gameplay\objects\SpringCollab2020\safeRespawnCrumble\" />
1616
</ItemGroup>
1717

18+
<ItemGroup>
19+
<Reference Include="CollabUtils2">
20+
<HintPath>lib-stripped\CollabUtils2.dll</HintPath>
21+
<Private>false</Private>
22+
</Reference>
23+
</ItemGroup>
24+
1825
<ItemGroup>
1926
<None Update="everest.yaml">
2027
<CopyToOutputDirectory>Never</CopyToOutputDirectory>

SpringCollab2020Module.cs

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ public override void Load() {
5252
SpikeJumpThroughController.Load();
5353
Everest.Events.Level.OnLoadBackdrop += onLoadBackdrop;
5454

55+
GrandmasterHeartSideHelper.Load();
56+
5557
IL.Celeste.Level.Reload += resetFlagsOnTimerResets;
56-
IL.Celeste.OuiChapterPanel.Render += renderOldGMHSCompletionStamp;
5758

5859
DecalRegistry.AddPropertyHandler("scale", (decal, attrs) => {
5960
Vector2 scale = decal.Scale;
@@ -102,8 +103,9 @@ public override void Unload() {
102103
SpikeJumpThroughController.Unload();
103104
Everest.Events.Level.OnLoadBackdrop -= onLoadBackdrop;
104105

106+
GrandmasterHeartSideHelper.Unload();
107+
105108
IL.Celeste.Level.Reload -= resetFlagsOnTimerResets;
106-
IL.Celeste.OuiChapterPanel.Render -= renderOldGMHSCompletionStamp;
107109
}
108110

109111
private Backdrop onLoadBackdrop(MapData map, BinaryPacker.Element child, BinaryPacker.Element above) {
@@ -146,26 +148,5 @@ private void resetFlagsOnTimerResets(ILContext il) {
146148
});
147149
}
148150
}
149-
150-
private void renderOldGMHSCompletionStamp(ILContext il) {
151-
ILCursor cursor = new ILCursor(il);
152-
153-
// draw the stamp just after the chapter card.
154-
if (cursor.TryGotoNext(instr => instr.MatchStfld<OuiChapterPanel>("card"))
155-
&& cursor.TryGotoNext(MoveType.After, instr => instr.MatchCallvirt<MTexture>("Draw"))) {
156-
157-
Logger.Log("SpringCollab2020", $"Injecting GMHS stamp rendering at {cursor.Index} in IL for OuiChapterPanel.Render");
158-
159-
cursor.Emit(OpCodes.Ldarg_0);
160-
cursor.EmitDelegate<Action<OuiChapterPanel>>(self => {
161-
// draw it only if the player beat old grandmaster heart side, and we're actually looking at it.
162-
if (self.Area.GetSID() == "SpringCollab2020/5-Grandmaster/ZZ-NewHeartSide"
163-
&& (global::Celeste.SaveData.Instance.GetAreaStatsFor(AreaData.Get("SpringCollab2020/5-Grandmaster/ZZ-HeartSide").ToKey())?.Modes[0].Completed ?? false)) {
164-
165-
MTN.FileSelect["heart"].Draw(self.Position + new Vector2(-580f, 130f));
166-
}
167-
});
168-
}
169-
}
170151
}
171152
}

lib-stripped/CollabUtils2.dll

85 KB
Binary file not shown.

0 commit comments

Comments
 (0)