Skip to content

Commit 60d93a4

Browse files
authored
Development Merge (0.2.0) Fixed Restoring StoryMode state
* Initial work on reversing gamemode state (dev) * Fixed Level State Restoring Plus Bug Fixes - Added new transpiler to Restore Story mode state correctly when Switching back - Removed Useless levelManagerType Method for custom levels (No level index so dont touch saves at all) - Removed "currentLevelIndex" for custom levels (No level index so no need to pass a dummy - This was used at beginning of mod development to spoof story mode level to load levels that way, I have wrote a custom gamemode now so no need) - Removed Useless Log and tweaked some - Added Null checks on director instance for loading custom levels * 0.2.0 Bump Due to Fixed Major Issue with loading back into story mode from custom level. Just don't load into level editor when in custom level (You should be able to anyway unless you have a conflicting mod)
1 parent d6a9608 commit 60d93a4

File tree

8 files changed

+148
-55
lines changed

8 files changed

+148
-55
lines changed

CustomLevels/CustomLevels.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,8 @@
317317
<Compile Include="LevelGameBuilderExtension.cs" />
318318
<Compile Include="LevelManagerPatches.cs" />
319319
<Compile Include="main.cs" />
320+
<Compile Include="MenuButtonSandboxExtensions.cs" />
321+
<Compile Include="MenuButtonSandboxPatches.cs" />
320322
<Compile Include="Properties\AssemblyInfo.cs" />
321323
</ItemGroup>
322324
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

CustomLevels/DirectorExtensions.cs

Lines changed: 30 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -49,45 +49,15 @@ private static void ExecuteLoadCustomLevel(object directorInstance, string Level
4949
return;
5050
}
5151

52-
int currentLevelIndex;
5352
object gameLevel;
54-
levelBuilder.InitCustomLevelFromCode(LevelCode, out currentLevelIndex, out gameLevel);
53+
levelBuilder.InitCustomLevelFromCode(LevelCode, out gameLevel);
5554

5655
if (gameLevel == null)
5756
{
5857
Debug.LogError("Failed to load custom level — aborting LoadCustomLevel!");
5958
return;
6059
}
6160

62-
Type levelManagerType = AccessTools.TypeByName("LevelManager");
63-
levelManagerType?.GetMethod("Init")?.Invoke(null, new object[] { currentLevelIndex });
64-
65-
var ui = UIManager.instance;
66-
if (ui != null)
67-
{
68-
var menuLayer = AccessTools.Field(ui.GetType(), "menuLayer")?.GetValue(ui);
69-
if (menuLayer != null)
70-
{
71-
var levelButtons = AccessTools.Field(menuLayer.GetType(), "levelButtons")?.GetValue(menuLayer) as IEnumerable<object>;
72-
if (levelButtons != null)
73-
{
74-
foreach (var btn in levelButtons)
75-
{
76-
if (btn is Component comp)
77-
{
78-
AccessTools.Method(comp.GetType(), "Init")?.Invoke(comp, new object[] { currentLevelIndex });
79-
}
80-
}
81-
}
82-
else Debug.LogWarning("UIManager menuLayer.levelButtons is null!");
83-
}
84-
else Debug.LogWarning("UIManager menuLayer is null!");
85-
}
86-
else
87-
{
88-
Debug.LogError("UIManager instance still null after waiting!");
89-
return;
90-
}
9161
AccessTools.TypeByName("LevelNumber")?.GetMethod("ChangeLevelText")?.Invoke(null, new object[] { LevelCode });
9262

9363
AccessTools.Method(directorInstance.GetType(), "InitCommonSystems")?.Invoke(directorInstance, null);
@@ -128,15 +98,13 @@ private static void ExecuteLoadCustomLevel(object directorInstance, string Level
12898
Debug.LogWarning("ReplayManager is null!");
12999

130100
AccessTools.Method(directorInstance.GetType(), "InitVisuals")?.Invoke(directorInstance, null);
131-
132-
Debug.Log($"<color=green>Custom level loaded successfully: Index {currentLevelIndex}</color>");
133101
}
134102

135103

136104
public static void SafeInitVisuals(object directorInstance)
137105
{
138106
var traverse = Traverse.Create(directorInstance);
139-
var gameMode = (GameMode)traverse.Field("gameMode").GetValue();
107+
var gameMode = Director.gameMode; //(GameMode)traverse.Field("gameMode").GetValue();
140108

141109
(float, int, bool, float) tuple;
142110

@@ -145,30 +113,44 @@ public static void SafeInitVisuals(object directorInstance)
145113
Debug.Log("[Patch] Using fallback tuple for GameMode 3");
146114
tuple = (0.75f, 1, false, 4.5f);
147115
}
116+
else if (SandboxStateMapping.mapping.TryGetValue(gameMode, out var mappedTuple))
117+
{
118+
Debug.Log("[Patch] gamemode: " + gameMode.ToString());
119+
tuple = mappedTuple;
120+
}
148121
else
149122
{
150-
tuple = SandboxStateMapping.mapping[gameMode];
123+
Debug.LogWarning($"[Patch] Unknown GameMode: {gameMode}, using default fallback");
124+
tuple = (0.75f, 1, false, 4.5f);
151125
}
152126

127+
128+
153129
CameraSizeManager.SetAdditiveSize(tuple.Item1);
154130

155-
var gridLines = AccessTools.Field(AccessTools.TypeByName("GridLines"), "instance")?.GetValue(null);
156-
if (gridLines != null)
157-
{
158-
AccessTools.Method(gridLines.GetType(), "SetGameGrid")?.Invoke(gridLines, new object[] { tuple.Item2 });
131+
//var gridLines = AccessTools.Field(AccessTools.TypeByName("GridLines"), "instance")?.GetValue(null);
132+
GridLines.instance?.SetGameGrid(tuple.Item2);
133+
//if (gridLines != null)
134+
//{
135+
//AccessTools.Method(gridLines.GetType(), "SetGameGrid")?.Invoke(gridLines, new object[] { tuple.Item2 });
159136

160137
if (tuple.Item3)
161138
{
162-
var selectedButton = AccessTools.Field(AccessTools.TypeByName("LayerSandbox"), "selectedButton")?.GetValue(null);
163-
bool willPlaceOut = selectedButton is SandboxButtonEntity entity && entity.willPlaceOut;
164-
AccessTools.Method(gridLines.GetType(), "ShowCantPlace")?.Invoke(gridLines, new object[] { willPlaceOut });
139+
//var selectedButton = AccessTools.Field(AccessTools.TypeByName("LayerSandbox"), "selectedButton")?.GetValue(null);
140+
//bool willPlaceOut = selectedButton is SandboxButtonEntity entity && entity.willPlaceOut;
141+
//AccessTools.Method(gridLines.GetType(), "ShowCantPlace")?.Invoke(gridLines, new object[] { willPlaceOut });
142+
GridLines.instance?.ShowCantPlace(LayerSandbox.selectedButton is SandboxButtonEntity sandboxButtonEntity && sandboxButtonEntity.willPlaceOut);
165143
}
166144
else
167145
{
168-
AccessTools.Method(gridLines.GetType(), "ResetCantPlace")?.Invoke(gridLines, null);
169-
}
170-
}
171-
146+
//AccessTools.Method(gridLines.GetType(), "ResetCantPlace")?.Invoke(gridLines, null);
147+
GridLines.instance?.ResetCantPlace();
148+
}
149+
(UIManager.instance.sandboxLayer.allEntityButtons.Last() as SandboxButtonEntity).SetSprite();
150+
var heartContainer = traverse.Field("heartContainer").GetValue();
151+
AccessTools.Method(heartContainer.GetType(), "MoveContainerY")?.Invoke(heartContainer, new object[] { tuple.Item4 });
152+
//}
153+
/*
172154
var ui = UIManager.instance;
173155
var lastButton = ui?.sandboxLayer?.allEntityButtons?.LastOrDefault();
174156
if (lastButton is SandboxButtonEntity sbEntity)
@@ -178,6 +160,8 @@ public static void SafeInitVisuals(object directorInstance)
178160
179161
var heartContainer = traverse.Field("heartContainer").GetValue();
180162
AccessTools.Method(heartContainer.GetType(), "MoveContainerY")?.Invoke(heartContainer, new object[] { tuple.Item4 });
163+
*/
164+
UIManager.instance?.menuLayer.CalculateMenu();
181165
}
182166

183167
}

CustomLevels/DirectorPatches.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public static void ApplyPatch(HarmonyLib.Harmony harmony)
2121
var transpiler = AccessTools.Method(typeof(DirectorPatches), nameof(InitTranspiler));
2222
harmony.Patch(original, transpiler: new HarmonyMethod(transpiler));
2323

24+
var originalInitGameMode = AccessTools.Method(typeof(Director), "InitGameMode");
25+
var transpilerInitGameMode = AccessTools.Method(typeof(DirectorPatches), nameof(InitGameModeTranspiler));
26+
harmony.Patch(originalInitGameMode, transpiler: new HarmonyMethod(transpilerInitGameMode));
27+
2428
var originalVisuals = AccessTools.Method(typeof(Director), "InitVisuals");
2529
var prefixVisuals = AccessTools.Method(typeof(DirectorPatches), nameof(InitVisualsPrefix));
2630
harmony.Patch(originalVisuals, prefix: new HarmonyMethod(prefixVisuals));
@@ -43,6 +47,27 @@ private static void ParseCommandLineArgs()
4347
}
4448
}
4549

50+
public static IEnumerable<CodeInstruction> InitGameModeTranspiler(IEnumerable<CodeInstruction> instructions)
51+
{
52+
var debugLogMethod = AccessTools.Method(typeof(Debug), "Log", new[] { typeof(object) });
53+
var levelBuilderField = AccessTools.Field(typeof(Director), "levelBuilder");
54+
var customLevelField = AccessTools.Field(typeof(LevelGameBuilder), "customLevel");
55+
56+
57+
yield return new CodeInstruction(OpCodes.Ldarg_0);
58+
yield return new CodeInstruction(OpCodes.Ldfld, levelBuilderField);
59+
60+
yield return new CodeInstruction(OpCodes.Ldc_I4_0);
61+
62+
yield return new CodeInstruction(OpCodes.Stfld, customLevelField);
63+
64+
foreach (var instruction in instructions)
65+
{
66+
yield return instruction;
67+
}
68+
}
69+
70+
4671
public static IEnumerable<CodeInstruction> InitTranspiler(IEnumerable<CodeInstruction> instructions)
4772
{
4873
var codes = new List<CodeInstruction>(instructions);

CustomLevels/LayerMenuExtension.cs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using HarmonyLib;
33
using QuadrataPatcher;
44
using Steamworks;
5+
using System.Collections;
56
using System.Collections.Generic;
67
using System.Diagnostics.Eventing.Reader;
78
using System.Linq;
@@ -10,6 +11,16 @@
1011

1112
public static class LayerMenuExtensions
1213
{
14+
private static IEnumerator WaitAndChangeSprite(MenuButtonSandbox sandboxBtn, Sprite sprite)
15+
{
16+
yield return new WaitWhile(() => UIManager.isMenuMoving);
17+
18+
var traverse = Traverse.Create(sandboxBtn);
19+
var buttonIcon = traverse.Field("buttonIcon").GetValue<Image>();
20+
21+
if (buttonIcon != null){buttonIcon.sprite = sprite;}
22+
}
23+
1324
public static void ChangeLevelSelectSizeExtended(this object layerMenuInstance, Vector2 size, float time, Ease ease)
1425
{
1526
var traverse = Traverse.Create(layerMenuInstance);
@@ -121,10 +132,10 @@ public static void CalculateMenuExtended(this object layerMenuInstance)
121132

122133
if (gameMode != 3 && levelButtons != null)
123134
{
124-
levelButtons.ForEach(button =>
135+
foreach (var button in levelButtons)
125136
{
126-
button.navigationButtons[Vector2.up] = activeButtons[0];
127-
});
137+
button.gameObject.SetActive(true);
138+
}
128139
}
129140
else if (levelButtons != null)
130141
{
@@ -228,14 +239,32 @@ public static void CalculateMenuExtended(this object layerMenuInstance)
228239
Button loadButton = loadButtonObj.GetComponent<Button>();
229240
loadButton.onClick.AddListener(() =>
230241
{
242+
243+
var sandboxBtn = GameObject.FindObjectsOfType<MenuButtonSandbox>().FirstOrDefault();
244+
245+
var sandboxTraverse = Traverse.Create(sandboxBtn);
246+
var toggleMapping = sandboxTraverse.Field("toggleMapping").GetValue<Dictionary<GameMode, (GameMode targetMode, Sprite targetIcon)>>();
247+
248+
GameMode currentMode = (GameMode)Traverse.Create(typeof(Director)).Field("gameMode").GetValue();
249+
/*
250+
if (toggleMapping.TryGetValue(currentMode, out var mapping) && !(Director.gameMode == (GameMode)3)) {
251+
Debug.Log("Got value of pre: " + Director.gameMode.ToString());
252+
Director.gameMode = mapping.targetMode;
253+
Debug.Log("Got value of: " + mapping.targetMode.ToString());
254+
}
255+
*/
256+
231257
Director.gameMode = (GameMode)3;
232-
DirectorPatches.customLevelCode = inputField.text;
233258
UIManager.instance?.menuLayer.CloseMenu();
234-
Director.instance.Init();
259+
UIManager.instance?.sandboxLayer.sideButtons.Last().ResetButton();
260+
Director.instance?.StartCoroutine(WaitAndChangeSprite(sandboxBtn, toggleMapping[Director.gameMode].targetIcon));
261+
DirectorPatches.customLevelCode = inputField.text;
262+
Director.instance?.Init();
235263

236264
Debug.Log($"[Patch] Loaded custom level code: {inputField.text}");
237265
});
238266

267+
239268
Debug.Log("[Patch] Menu Load button and InputField added.");
240269
}
241270

CustomLevels/LevelGameBuilderExtension.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ namespace QuadrataPatcher
88
{
99
public static class LevelGameBuilderExtensions
1010
{
11-
public static void InitCustomLevelFromCode(this object instance, string levelCode, out int currentLevelIndex, out object currentGameLevel)
11+
public static void InitCustomLevelFromCode(this object instance, string levelCode, out object currentGameLevel)
1212
{
13-
currentLevelIndex = 90;
1413
currentGameLevel = null;
1514

1615
try
@@ -66,7 +65,7 @@ public static void InitCustomLevelFromCode(this object instance, string levelCod
6665

6766
currentGameLevel = gameLevel;
6867

69-
Debug.Log($"<color=green>Custom level loaded from code '{levelCode}' with index {currentLevelIndex}!</color>");
68+
Debug.Log($"<color=green>Custom level loaded from code '{levelCode}'!</color>");
7069
}
7170
catch (Exception ex)
7271
{
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using UnityEngine;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
5+
namespace CustomLevels
6+
{
7+
internal static class MenuButtonSandboxExtensions
8+
{
9+
public static void ToggleMappingSet(MenuButtonSandbox instance)
10+
{
11+
var toggleMappingField = typeof(MenuButtonSandbox).GetField("toggleMapping", BindingFlags.NonPublic | BindingFlags.Instance);
12+
var editorIconField = typeof(MenuButtonSandbox).GetField("editorIcon", BindingFlags.NonPublic | BindingFlags.Instance);
13+
var gameIconField = typeof(MenuButtonSandbox).GetField("gameIcon", BindingFlags.NonPublic | BindingFlags.Instance);
14+
15+
var editorIcon = (Sprite)editorIconField.GetValue(instance);
16+
var gameIcon = (Sprite)gameIconField.GetValue(instance);
17+
18+
var mapping = new Dictionary<GameMode, (GameMode, Sprite)>
19+
{
20+
{ GameMode.Game, (GameMode.SandboxEdit, editorIcon) },
21+
{ GameMode.SandboxEdit, (GameMode.Game, gameIcon) },
22+
{ GameMode.SandboxPlay, (GameMode.Game, gameIcon) },
23+
{ (GameMode)3, (GameMode.Game, gameIcon) }
24+
};
25+
26+
toggleMappingField.SetValue(instance, mapping);
27+
}
28+
}
29+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using HarmonyLib;
2+
3+
namespace CustomLevels
4+
{
5+
[HarmonyPatch(typeof(MenuButtonSandbox), "Awake")]
6+
internal static class MenuButtonSandboxPatches
7+
{
8+
static bool Prefix(MenuButtonSandbox __instance)
9+
{
10+
MenuButtonSandboxExtensions.ToggleMappingSet(__instance);
11+
return false;
12+
}
13+
14+
public static void ApplyPatch(HarmonyLib.Harmony harmony)
15+
{
16+
var originalAwake = AccessTools.Method(typeof(MenuButtonSandbox), "Awake");
17+
var prefix = AccessTools.Method(typeof(MenuButtonSandboxPatches), nameof(Prefix));
18+
harmony.Patch(originalAwake, prefix: new HarmonyMethod(prefix));
19+
}
20+
}
21+
}

CustomLevels/main.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using MelonLoader;
22
using HarmonyLib;
33
using System.Reflection;
4+
using CustomLevels;
45

5-
[assembly: MelonInfo(typeof(QuadrataPatcher.Main), "Custom Levels", "0.1.0", "Bud3699")]
6+
[assembly: MelonInfo(typeof(QuadrataPatcher.Main), "Custom Levels", "0.2.0", "Bud3699")]
67
[assembly: MelonGame("Mindlabor", "Quadrata")]
78

89
namespace QuadrataPatcher
@@ -23,6 +24,9 @@ public override void OnInitializeMelon()
2324
LevelManagerPatches.ApplyPatch(harmony);
2425
MelonLogger.Msg("Patched Level Manger to allow gameplay");
2526

27+
MenuButtonSandboxPatches.ApplyPatch(harmony);
28+
MelonLogger.Msg("Patched MenuButtonSandbox to allow icon changes");
29+
2630
string[] args = System.Environment.GetCommandLineArgs();
2731
bool patchedLayerMenu = false;
2832

0 commit comments

Comments
 (0)