-
Notifications
You must be signed in to change notification settings - Fork 1
/
Main.cs
406 lines (363 loc) · 15.1 KB
/
Main.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// Copyright (c) 2019 [email protected]
// Copyright (c) 2019 Jennifer Messerly
// This code is licensed under MIT license (see LICENSE for details)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Kingmaker;
using Kingmaker.Blueprints;
using Kingmaker.Blueprints.Root.Strings.GameLog;
using Kingmaker.GameModes;
using Kingmaker.Globalmap;
using Kingmaker.Globalmap.Blueprints;
using Kingmaker.PubSubSystem;
using Kingmaker.UI.ServiceWindow.LocalMap;
using Kingmaker.Utility;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityModManagerNet;
namespace CustomMapMarkers
{
public class Main
{
[Harmony12.HarmonyPatch(typeof(LibraryScriptableObject), "LoadDictionary", new Type[0])]
static class LibraryScriptableObject_LoadDictionary_Patch
{
static void Postfix(LibraryScriptableObject __instance)
{
var self = __instance;
if (Main.library != null) return;
Main.library = self;
//EnableGameLogging();
#if DEBUG
// Perform extra sanity checks in debug builds.
SafeLoad(CheckPatchingSuccess, "Check that all patches are used, and were loaded");
Log.Write("Load finished.");
#endif
}
}
[Harmony12.HarmonyPatch(typeof(LocalMap), "OnPointerClick")]
static class LocalMap_OnPointerClick_Patch
{
private static bool Prefix(LocalMap __instance, PointerEventData eventData)
{
if (eventData.button == PointerEventData.InputButton.Left)
{
if (IsShiftPressed)
{
CustomMapMarkers.CreateMarker(__instance, eventData);
}
}
// Don't pass the click through to the map if control or shift are pressed
return !(IsControlPressed || IsShiftPressed);
}
}
[Harmony12.HarmonyPatch(typeof(LocalMap), "OnShow")]
static class LocalMap_OnShow_Patch
{
private static bool Prefix(LocalMap __instance)
{
IsLocalMapActive = true;
CustomMapMarkers.OnShowLocalMap();
return true;
}
}
[Harmony12.HarmonyPatch(typeof(LocalMap), "OnHide")]
static class LocalMap_OnHide_Patch
{
private static void Postfix()
{
IsLocalMapActive = false;
}
}
[Harmony12.HarmonyPatch(typeof(GlobalMapLocation), "HandleClick")]
static class GlobalMapLocation_HandleClick_Patch
{
private static bool Prefix(GlobalMapLocation __instance)
{
if (IsShiftPressed)
{
CustomGlobalMapLocations.CustomizeGlobalMapLocation(__instance);
}
// Don't pass the click through to the map if control or shift are pressed
return !(IsControlPressed || IsShiftPressed);
}
}
[Harmony12.HarmonyPatch(typeof(GlobalMapLocation), "HandleHoverChange")]
static class GlobalMapLocation_HandleHoverChange_Patch
{
private static void Postfix(GlobalMapLocation __instance, bool isHover)
{
CustomGlobalMapLocations.PostHandleHoverchange(__instance, isHover);
}
}
[Harmony12.HarmonyPatch(typeof(BlueprintLocation), "GetDescription")]
static class BlueprintLocation_GetDescription_Patch
{
private static void Postfix(BlueprintLocation __instance, ref string __result)
{
__result = ModGlobalMapLocation.GetModifiedDescription(__instance, __result);
}
}
[Harmony12.HarmonyPatch(typeof(UnityModManager.UI), "Update")]
static class UnityModManager_UI_Update_Patch
{
private static void Postfix()
{
if (IsLocalMapActive || Game.Instance.CurrentMode == GameModeType.GlobalMap)
{
try
{
IsControlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
IsShiftPressed = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
}
catch (Exception e)
{
Log.Write($"Key read: {e}");
}
}
}
}
private static bool IsLocalMapActive = false;
private static bool IsControlPressed = false;
private static bool IsShiftPressed = false;
static LibraryScriptableObject library;
public static bool enabled;
public static UnityModManager.ModEntry.ModLogger logger;
static Settings settings;
static Harmony12.HarmonyInstance harmonyInstance;
static readonly Dictionary<Type, bool> typesPatched = new Dictionary<Type, bool>();
static readonly List<String> failedPatches = new List<String>();
static readonly List<String> failedLoading = new List<String>();
[System.Diagnostics.Conditional("DEBUG")]
static void EnableGameLogging()
{
if (UberLogger.Logger.Enabled) return;
// Code taken from GameStarter.Awake(). PF:K logging can be enabled with command line flags,
// but when developing the mod it's easier to force it on.
var dataPath = ApplicationPaths.persistentDataPath;
Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None);
UberLogger.Logger.Enabled = true;
var text = Path.Combine(dataPath, "GameLog.txt");
if (File.Exists(text))
{
File.Copy(text, Path.Combine(dataPath, "GameLogPrev.txt"), overwrite: true);
File.Delete(text);
}
UberLogger.Logger.AddLogger(new UberLoggerFile("GameLogFull.txt", dataPath));
UberLogger.Logger.AddLogger(new UberLoggerFilter(new UberLoggerFile("GameLog.txt", dataPath), UberLogger.LogSeverity.Warning, "MatchLight"));
UberLogger.Logger.Enabled = true;
}
internal static void NotifyPlayer(string message, bool warning = false)
{
if (warning)
{
EventBus.RaiseEvent<IWarningNotificationUIHandler>((IWarningNotificationUIHandler h) => h.HandleWarning(message, true));
}
else
{
Game.Instance.UI.BattleLogManager.LogView.AddLogEntry(message, GameLogStrings.Instance.DefaultColor);
}
}
// We don't want one patch failure to take down the entire mod, so they're applied individually.
//
// Also, in general the return value should be ignored. If a patch fails, we still want to create
// blueprints, otherwise the save won't load. Better to have something be non-functional.
static bool ApplyPatch(Type type, String featureName)
{
try
{
if (typesPatched.ContainsKey(type)) return typesPatched[type];
var patchInfo = Harmony12.HarmonyMethodExtensions.GetHarmonyMethods(type);
if (patchInfo == null || patchInfo.Count() == 0)
{
Log.Error($"Failed to apply patch {type}: could not find Harmony attributes");
failedPatches.Add(featureName);
typesPatched.Add(type, false);
return false;
}
var processor = new Harmony12.PatchProcessor(harmonyInstance, type, Harmony12.HarmonyMethod.Merge(patchInfo));
var patch = processor.Patch().FirstOrDefault();
if (patch == null)
{
Log.Error($"Failed to apply patch {type}: no dynamic method generated");
failedPatches.Add(featureName);
typesPatched.Add(type, false);
return false;
}
typesPatched.Add(type, true);
return true;
}
catch (Exception e)
{
Log.Error($"Failed to apply patch {type}: {e}");
failedPatches.Add(featureName);
typesPatched.Add(type, false);
return false;
}
}
static void CheckPatchingSuccess()
{
// Check to make sure we didn't forget to patch something.
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
var infos = Harmony12.HarmonyMethodExtensions.GetHarmonyMethods(type);
if (infos != null && infos.Count() > 0 && !typesPatched.ContainsKey(type))
{
Log.Write($"Did not apply patch for {type}");
}
}
}
// Mod entry point, invoked from UMM
static bool Load(UnityModManager.ModEntry modEntry)
{
logger = modEntry.Logger;
modEntry.OnToggle = OnToggle;
modEntry.OnGUI = OnGUI;
modEntry.OnSaveGUI = OnSaveGUI;
settings = UnityModManager.ModSettings.Load<Settings>(modEntry);
harmonyInstance = Harmony12.HarmonyInstance.Create(modEntry.Info.Id);
if (!ApplyPatch(typeof(LibraryScriptableObject_LoadDictionary_Patch), "Load library"))
{
throw Error("Failed to patch LibraryScriptableObject.LoadDictionary(), cannot load mod");
}
if (!ApplyPatch(typeof(UnityModManager_UI_Update_Patch), "Read keys"))
{
throw Error("Failed to patch LibraryScriptableObject.LoadDictionary(), cannot load mod");
}
if (!ApplyPatch(typeof(LocalMap_OnPointerClick_Patch), "Local map click"))
{
throw Error("Failed to patch LocalMap.OnPointerClick(), cannot load mod");
}
if (!ApplyPatch(typeof(LocalMap_OnShow_Patch), "Local map show"))
{
throw Error("Failed to patch LocalMap.OnShow(), cannot load mod");
}
if (!ApplyPatch(typeof(LocalMap_OnHide_Patch), "Local map hide"))
{
throw Error("Failed to patch LocalMap.OnHide(), cannot load mod");
}
if (!ApplyPatch(typeof(GlobalMapLocation_HandleClick_Patch), "Global map location click"))
{
throw Error("Failed to patch GlobalMapLocation.HandleClick(), cannot load mod");
}
if (!ApplyPatch(typeof(GlobalMapLocation_HandleHoverChange_Patch), "Global map hover change"))
{
throw Error("Failed to patch GlobalMapLocation.HandleHoverChange(), cannot load mod");
}
if (!ApplyPatch(typeof(BlueprintLocation_GetDescription_Patch), "Blueprint location description"))
{
throw Error("Failed to patch BlueprintLocation.GetDescription(), cannot load mod");
}
StartMod();
return true;
}
static void StartMod()
{
SafeLoad(StateManager.Load, "State Manager");
SafeLoad(CustomMapMarkers.Load, "Local Map Markers");
SafeLoad(CustomGlobalMapLocations.Load, "Global Map Locations");
}
static bool OnToggle(UnityModManager.ModEntry modEntry, bool value)
{
enabled = value;
return true;
}
private static bool editingGlobalMapLocations = true;
static void OnGUI(UnityModManager.ModEntry modEntry)
{
if (!enabled) return;
var fixedWidth = new GUILayoutOption[1] { GUILayout.ExpandWidth(false) };
if (failedPatches.Count > 0)
{
GUILayout.BeginVertical();
GUILayout.Label("<b><color=#ff5454>Error: Some patches failed to apply. These features may not work:</color></b>", fixedWidth);
foreach (var featureName in failedPatches)
{
GUILayout.Label($" • <b><color=#ff5454>{featureName}</b>", fixedWidth);
}
GUILayout.EndVertical();
}
if (failedLoading.Count > 0)
{
GUILayout.BeginVertical();
GUILayout.Label("<b><color=#ff5454>Error: Some assets failed to load. Saves using these features won't work:</color></b>", fixedWidth);
foreach (var featureName in failedLoading)
{
GUILayout.Label($" • <b><color=#ff5454>{featureName}</color></b>", fixedWidth);
}
GUILayout.EndVertical();
}
#if DEBUG
GUILayout.BeginVertical();
GUILayout.Label("<b>DEBUG build!</b>", fixedWidth);
GUILayout.Space(10f);
GUILayout.EndVertical();
#endif
string gameCharacterName = Game.Instance.Player.MainCharacter.Value?.CharacterName;
if (gameCharacterName == null || gameCharacterName.Length == 0)
{
GUILayout.Label("<b><color=#ff5454>Load a save to edit map notes and markers.</color></b>", fixedWidth);
return;
}
GUILayout.BeginHorizontal();
GUILayout.Label("<b><color=cyan>Edit: </color></b>", fixedWidth);
bool doEditLocalMap = GUILayout.Toggle(!editingGlobalMapLocations, "<b><color=cyan>Local Map Areas</color></b>", fixedWidth);
editingGlobalMapLocations = GUILayout.Toggle(!doEditLocalMap, "<b><color=cyan>Global Map Locations</color></b>", fixedWidth);
GUILayout.EndHorizontal();
if (editingGlobalMapLocations)
{
CustomGlobalMapLocationsMenu.Layout();
}
else
{
CustomMapMarkersMenu.Layout();
}
}
static void OnSaveGUI(UnityModManager.ModEntry modEntry)
{
settings.Save(modEntry);
StateManager.SaveState();
}
static void SafeLoad(Action load, String name)
{
try
{
load();
}
catch (Exception e)
{
failedLoading.Add(name);
Log.Error(e);
}
}
static T SafeLoad<T>(Func<T> load, String name)
{
try
{
return load();
}
catch (Exception e)
{
failedLoading.Add(name);
Log.Error(e);
return default(T);
}
}
static Exception Error(String message)
{
logger?.Log(message);
return new InvalidOperationException(message);
}
}
public class Settings : UnityModManager.ModSettings
{
public bool SaveAfterEveryChange = false;
public override void Save(UnityModManager.ModEntry modEntry)
{
UnityModManager.ModSettings.Save<Settings>(this, modEntry);
}
}
}