From f2eba6a784a9c6e0af96a127ec49440d3cc46584 Mon Sep 17 00:00:00 2001 From: SilverDorian46 <86711559+SilverDorian46@users.noreply.github.com> Date: Fri, 9 Jan 2026 20:29:58 +0400 Subject: [PATCH] Add support for custom Scenery Tiles --- Celeste.Mod.mm/Mod/Meta/MapMeta.cs | 8 ++++++++ Celeste.Mod.mm/Patches/Level.cs | 23 +++++++++++++++++++++++ Celeste.Mod.mm/Patches/LevelLoader.cs | 6 ++++++ 3 files changed, 37 insertions(+) diff --git a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs index 6614d80ee..baa07146d 100644 --- a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs +++ b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs @@ -61,6 +61,8 @@ public MapMeta(BinaryPacker.Element meta) { public string Sprites { get; set; } public string Portraits { get; set; } + public string SceneryTiles { get; set; } + public bool? OverrideASideMeta { get; set; } public MapMetaModeProperties[] Modes { get; set; } @@ -114,6 +116,9 @@ public void Parse(BinaryPacker.Element meta) { meta.AttrIf("AnimatedTiles", v => AnimatedTiles = v); meta.AttrIf("Sprites", v => Sprites = v); meta.AttrIf("Portraits", v => Portraits = v); + + meta.AttrIf("SceneryTiles", v => SceneryTiles = v); + meta.AttrIfBool("OverrideASideMeta", v => OverrideASideMeta = v); BinaryPacker.Element child; @@ -224,6 +229,9 @@ public void ApplyTo(patch_AreaData area) { if (!string.IsNullOrEmpty(Portraits)) meta.Portraits = Portraits; + if (!string.IsNullOrEmpty(SceneryTiles)) + meta.SceneryTiles = SceneryTiles; + if (OverrideASideMeta != null) meta.OverrideASideMeta = OverrideASideMeta; diff --git a/Celeste.Mod.mm/Patches/Level.cs b/Celeste.Mod.mm/Patches/Level.cs index b32cfaf6e..d0e7cff3f 100644 --- a/Celeste.Mod.mm/Patches/Level.cs +++ b/Celeste.Mod.mm/Patches/Level.cs @@ -758,6 +758,9 @@ public static void PatchLevelLoader(ILContext context, CustomAttribute attrib) { FieldReference f_currentEntityId = context.Method.DeclaringType.FindField("_currentEntityId")!; MethodReference m_IsInDoNotLoadIncreased = context.Method.DeclaringType.FindMethod("_IsInDoNotLoadIncreased")!; + TypeDefinition t_GFX = MonoModRule.Modder.FindType("Celeste.GFX").Resolve(); + FieldDefinition f_GFX_SceneryTiles = t_GFX.FindField("SceneryTiles"); + ILCursor cursor = new ILCursor(context); // Insert our custom entity loader and use it for levelData.Entities and levelData.Triggers @@ -882,6 +885,26 @@ public static void PatchLevelLoader(ILContext context, CustomAttribute attrib) { cursor.Next.OpCode = OpCodes.Call; cursor.Next.Operand = m_LoadNewPlayer; + // Reset to apply patch for Scenery Tiles + cursor.Index = 0; + + // Patch this bit of code to use GFX.SceneryTiles instead of creating the same tileset again + // before: Tileset tileset = new Tileset(GFX.Game["tilesets/scenery"], 8, 8); + // after: Tileset tileset = GFX.SceneryTiles; + cursor.GotoNext(MoveType.AfterLabel, + instr => instr.MatchLdsfld("Celeste.GFX", "Game"), + instr => instr.MatchLdstr("tilesets/scenery") + ); + // to remove: + // - ldsfld Celeste.GFX::Game + // - ldstr "tilesets/scenery" + // - callvirt Monocle.Atlas::get_Item(string) + // - ldc.i4.8 + // - ldc.i4.8 + // - newobj Monocle.Tileset::.ctor(class Monocle.MTexture, int32, int32) + cursor.RemoveRange(6); + cursor.Emit(OpCodes.Ldsfld, f_GFX_SceneryTiles); + // Reset to apply static constructor patch cursor.Index = 0; diff --git a/Celeste.Mod.mm/Patches/LevelLoader.cs b/Celeste.Mod.mm/Patches/LevelLoader.cs index 5334289f9..cbff1d929 100644 --- a/Celeste.Mod.mm/Patches/LevelLoader.cs +++ b/Celeste.Mod.mm/Patches/LevelLoader.cs @@ -207,6 +207,12 @@ public void ctor(Session session, Vector2? startPosition = default) { if (string.IsNullOrEmpty(path)) path = Path.Combine("Graphics", "Portraits.xml"); GFX.PortraitsSpriteBank = new SpriteBank(GFX.Portraits, path); + + path = meta?.SceneryTiles; + if (string.IsNullOrEmpty(path)) + path = "tilesets/scenery"; + GFX.SceneryTiles = new Tileset(GFX.Game[path], 8, 8); + } catch (Exception e) { if (patch_LevelEnter.ErrorMessage == null) { if (e is XmlException) {