From a5622499ef3aae789807e1383151f84d281e9812 Mon Sep 17 00:00:00 2001 From: coloursofnoise <53288101+coloursofnoise@users.noreply.github.com> Date: Sat, 18 Sep 2021 23:25:43 -0700 Subject: [PATCH] Hide C-Side/Golden postcard if LevelSet has no C-Sides --- Celeste.Mod.mm/MonoModRules.cs | 43 +++++++++++++++-------- Celeste.Mod.mm/Patches/LevelSetStats.cs | 22 ++++++++++++ Celeste.Mod.mm/Patches/OverworldLoader.cs | 2 +- 3 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 Celeste.Mod.mm/Patches/LevelSetStats.cs diff --git a/Celeste.Mod.mm/MonoModRules.cs b/Celeste.Mod.mm/MonoModRules.cs index c7ad21b5d..ad49673d9 100644 --- a/Celeste.Mod.mm/MonoModRules.cs +++ b/Celeste.Mod.mm/MonoModRules.cs @@ -206,10 +206,10 @@ class PatchPathfinderRenderAttribute : Attribute { }; class PatchTotalHeartGemChecksAttribute : Attribute { }; /// - /// Same as above, but for references in routines. + /// Patch TotalHeartGems to refer to TotalHeartGemsInVanilla, and whether to show the UnlockCSide postcard /// - [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchTotalHeartGemChecksInRoutine))] - class PatchTotalHeartGemChecksInRoutineAttribute : Attribute { }; + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchTotalHeartGemCSidePostcard))] + class PatchTotalHeartGemCSidePostcardAttribute : Attribute { }; /// /// Patch a reference to TotalHeartGems in the OuiJournalGlobal constructor to unharcode the check for golden berry unlock. @@ -1475,20 +1475,21 @@ public static void PatchPathfinderRender(ILContext context, CustomAttribute attr } - public static void PatchTotalHeartGemChecks(MethodDefinition method, CustomAttribute attrib) { - MethodDefinition m_getTotalHeartGemsInVanilla = method.Module.GetType("Celeste.SaveData").FindMethod("System.Int32 get_TotalHeartGemsInVanilla()"); + public static void PatchTotalHeartGemChecks(ILContext context, CustomAttribute attrib) { + MethodDefinition m_getTotalHeartGemsInVanilla = context.Module.GetType("Celeste.SaveData").FindMethod("System.Int32 get_TotalHeartGemsInVanilla()"); - Mono.Collections.Generic.Collection instrs = method.Body.Instructions; - for (int instri = 0; instri < instrs.Count; instri++) { - Instruction instr = instrs[instri]; + ILCursor cursor = new ILCursor(context); - if (instr.MatchCallvirt(out MethodReference m) && m.Name == "get_TotalHeartGems") { - // replace the call to the TotalHeartGems property with a call to TotalHeartGemsInVanilla. - instr.Operand = m_getTotalHeartGemsInVanilla; - } - } + cursor.GotoNext(instr => instr.MatchCallvirt("Celeste.SaveData", "get_TotalHeartGems")); + cursor.Next.Operand = m_getTotalHeartGemsInVanilla; } - public static void PatchTotalHeartGemChecksInRoutine(MethodDefinition method, CustomAttribute attrib) { + + public static void PatchTotalHeartGemCSidePostcard(MethodDefinition method, CustomAttribute attrib) { + FieldDefinition f_SaveData_Instance = method.Module.GetType("Celeste.SaveData").FindField("Instance"); + MethodDefinition m_SaveData_get_LevelSetStats = method.Module.GetType("Celeste.SaveData").FindMethod("Celeste.LevelSetStats get_LevelSetStats()"); + MethodDefinition m_LevelSetStats_get_MaxAreaMode = method.Module.GetType("Celeste.LevelSetStats").FindMethod("System.Int32 get_MaxAreaMode()"); + + // Routines are stored in compiler-generated methods. foreach (TypeDefinition nest in method.DeclaringType.NestedTypes) { if (!nest.Name.StartsWith("<" + method.Name + ">d__")) @@ -1497,7 +1498,19 @@ public static void PatchTotalHeartGemChecksInRoutine(MethodDefinition method, Cu break; } - PatchTotalHeartGemChecks(method, attrib); + new ILContext(method).Invoke(il => { + ILCursor cursor = new ILCursor(il); + + cursor.GotoNext(MoveType.After, instr => instr.MatchLdfld("Celeste.Session", "UnlockedCSide")); + cursor.Emit(cursor.Next.OpCode, cursor.Next.Operand); + cursor.Emit(OpCodes.Ldsfld, f_SaveData_Instance); + cursor.Emit(OpCodes.Callvirt, m_SaveData_get_LevelSetStats); + cursor.Emit(OpCodes.Callvirt, m_LevelSetStats_get_MaxAreaMode); + cursor.Emit(OpCodes.Ldc_I4_2); + cursor.Next.OpCode = OpCodes.Blt_S; + + PatchTotalHeartGemChecks(il, attrib); + }); } public static void PatchOuiJournalStatsHeartGemCheck(ILContext context, CustomAttribute attrib) { diff --git a/Celeste.Mod.mm/Patches/LevelSetStats.cs b/Celeste.Mod.mm/Patches/LevelSetStats.cs new file mode 100644 index 000000000..58ac2431a --- /dev/null +++ b/Celeste.Mod.mm/Patches/LevelSetStats.cs @@ -0,0 +1,22 @@ +namespace Celeste { + class patch_LevelSetStats : LevelSetStats { + public int MaxAreaMode { + get { + if (Name == "Celeste") { + return (int) AreaMode.CSide; + } + int areaOffset = AreaOffset; + int maxAreaMode = 0; + for (int i = 0; i <= MaxArea; i++) { + ModeProperties[] mode = AreaData.Areas[areaOffset + i].Mode; + foreach (ModeProperties modeProperties in mode) { + if ((int) modeProperties.MapData.Area.Mode > maxAreaMode) { + maxAreaMode = (int) modeProperties.MapData.Area.Mode; + } + } + } + return maxAreaMode; + } + } + } +} \ No newline at end of file diff --git a/Celeste.Mod.mm/Patches/OverworldLoader.cs b/Celeste.Mod.mm/Patches/OverworldLoader.cs index b687d372f..6d57dc8a6 100644 --- a/Celeste.Mod.mm/Patches/OverworldLoader.cs +++ b/Celeste.Mod.mm/Patches/OverworldLoader.cs @@ -18,7 +18,7 @@ public patch_OverworldLoader(Overworld.StartMode startMode, HiresSnow snow = nul private extern void CheckVariantsPostcardAtLaunch(); [MonoModIgnore] // don't change anything in the method... - [PatchTotalHeartGemChecksInRoutine] // except for replacing TotalHeartGems with TotalHeartGemsInVanilla through MonoModRules + [PatchTotalHeartGemCSidePostcard] // except for replacing TotalHeartGems with TotalHeartGemsInVanilla through MonoModRules private extern IEnumerator Routine(Session session); } }