diff --git a/Celeste.Mod.mm/Patches/LevelData.cs b/Celeste.Mod.mm/Patches/LevelData.cs index 5557b5d97..709a95782 100644 --- a/Celeste.Mod.mm/Patches/LevelData.cs +++ b/Celeste.Mod.mm/Patches/LevelData.cs @@ -4,6 +4,7 @@ using System; using Mono.Cecil; using Mono.Cecil.Cil; +using Monocle; using MonoMod; using MonoMod.Cil; using MonoMod.InlineRT; @@ -16,6 +17,10 @@ public class patch_LevelData : LevelData { public Vector2? DefaultSpawn; + // custom room color + // if null, `EditorColorIndex` will be used to determine the room color + public Color? CustomEditorColor; + public patch_LevelData(BinaryPacker.Element data) : base(data) { // no-op. MonoMod ignores this - we only need this to make the compiler shut up. } @@ -29,6 +34,9 @@ public patch_LevelData(BinaryPacker.Element data) : base(data) { [MonoModConstructor] public void ctor(BinaryPacker.Element data) { orig_ctor(data); + + string color = data.Attr("color"); + CustomEditorColor = string.IsNullOrEmpty(color) ? null : Calc.HexToColor(color); } private void CheckForDefaultSpawn(BinaryPacker.Element spawn, Vector2 coords) { diff --git a/Celeste.Mod.mm/Patches/LevelTemplate.cs b/Celeste.Mod.mm/Patches/LevelTemplate.cs index 8068e9749..e46b8d913 100755 --- a/Celeste.Mod.mm/Patches/LevelTemplate.cs +++ b/Celeste.Mod.mm/Patches/LevelTemplate.cs @@ -1,4 +1,5 @@ using Celeste.Mod.Helpers; +using Microsoft.Xna.Framework; using Mono.Cecil; using Mono.Cecil.Cil; using Monocle; @@ -10,26 +11,41 @@ namespace Celeste.Editor { class patch_LevelTemplate : LevelTemplate { + // expose this private field to our patch + private static new Color[] fgTilesColor; + + // custom room color + // if null, `EditorColorIndex` will be used to determine the room color + public Color? CustomEditorColor; + public Color EditorColor => CustomEditorColor ?? fgTilesColor[EditorColorIndex]; + public patch_LevelTemplate(LevelData data) : base(data) { // no-op. MonoMod ignores this - we only need this to make the compiler shut up. } - - [MonoModConstructor] + [MonoModIgnore] // we don't want to change anything in the method... [PatchTrackableStrawberryCheck] // except manipulating it with MonoModRules - public extern void ctor(LevelData data); + public extern void orig_ctor(LevelData data); + + [MonoModConstructor] + public void ctor(LevelData data) { + orig_ctor(data); - [AddLevelTemplateCulling] + CustomEditorColor = ((patch_LevelData) data).CustomEditorColor; + } + [MonoModIgnore] - public new extern void RenderContents(Camera camera, System.Collections.Generic.List allLevels); - [AddLevelTemplateCulling] + [PatchLevelTemplateRenderContents] + public new extern void RenderContents(Camera camera, System.Collections.Generic.List allLevels); + [MonoModIgnore] - public new extern void RenderOutline(Camera camera); - [AddLevelTemplateCulling] + public new extern void RenderOutline(Camera camera); + [MonoModIgnore] + [AddLevelTemplateCulling] public new extern void RenderHighlight(Camera camera, bool hovered, bool selected); [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -46,6 +62,12 @@ namespace MonoMod { [MonoModCustomMethodAttribute(nameof(MonoModRules.AddLevelTemplateCulling))] class AddLevelTemplateCullingAttribute : Attribute { } + /// + /// Patch `LevelTemplate.RenderContent` to support custom template colors. + /// + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchLevelTemplateRenderContents))] + class PatchLevelTemplateRenderContents : Attribute { } + static partial class MonoModRules { public static void AddLevelTemplateCulling(ILContext il, CustomAttribute attrib) { var cursor = new ILCursor(il); @@ -63,5 +85,34 @@ public static void AddLevelTemplateCulling(ILContext il, CustomAttribute attrib) cursor.Emit(OpCodes.Ret); cursor.MarkLabel(label); } + + public static void PatchLevelTemplateRenderContents(ILContext il, CustomAttribute attrib) { + /* + * patch the `fgTilesColor` lookup to use `EditorColor` instead + * ```diff + * - Draw.Rect(X + solid.X, Y + solid.Y, solid.Width, solid.Height, Dummy ? dummyFgTilesColor : fgTilesColor[EditorColorIndex]); + * + Draw.Rect(X + solid.X, Y + solid.Y, solid.Width, solid.Height, Dummy ? dummyFgTilesColor : EditorColor); + * ``` + */ + + MethodDefinition m_LevelTemplate_get_EditorColor = il.Method.DeclaringType.FindMethod("get_EditorColor")!; + + ILCursor cursor = new(il); + + /* + * IL_0170: ldsfld valuetype [FNA]Microsoft.Xna.Framework.Color[] Celeste.Editor.LevelTemplate::fgTilesColor + * IL_0175: ldarg.0 + * IL_0176: ldfld int32 Celeste.Editor.LevelTemplate::EditorColorIndex + * IL_017b: ldelem [FNA]Microsoft.Xna.Framework.Color + */ + cursor.GotoNext(MoveType.Before, + instr => instr.MatchLdsfld("Celeste.Editor.LevelTemplate", "fgTilesColor"), + instr => instr.MatchLdarg0(), + instr => instr.MatchLdfld("Celeste.Editor.LevelTemplate", "EditorColorIndex"), + instr => instr.MatchLdelemAny("Microsoft.Xna.Framework.Color")); + cursor.RemoveRange(4); + cursor.EmitLdarg0(); + cursor.EmitCall(m_LevelTemplate_get_EditorColor); + } } } \ No newline at end of file