-
Notifications
You must be signed in to change notification settings - Fork 6
/
LoadStep.cs
266 lines (230 loc) · 11.6 KB
/
LoadStep.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
using Gajatko.IniFiles;
using SevenZip;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using tConfigWrapper.Common;
using tConfigWrapper.Common.DataTemplates;
using tConfigWrapper.Loaders;
using static tConfigWrapper.Common.Utilities;
using Terraria.ID;
using Terraria.ModLoader;
namespace tConfigWrapper {
public static class LoadStep {
private static Action<string> _loadProgressText; // Heading text during loading
private static Action<float> _loadProgress; // Progress bar during loading: 0-1 scale
private static Action<string> _loadSubProgressText; // Subtext during loading
public static int TaskCompletedCount; // Int used for tracking load progress during content loading
internal static ConcurrentDictionary<int, ItemInfo> globalItemInfos = new ConcurrentDictionary<int, ItemInfo>(); // Dictionaries are selfexplanatory, concurrent so that multiple threads can access them without dying
internal static ConcurrentDictionary<string, IniFileSection> recipeDict = new ConcurrentDictionary<string, IniFileSection>();
internal static ConcurrentBag<ModPrefix> suffixes = new ConcurrentBag<ModPrefix>();
internal static ConcurrentDictionary<string, MemoryStream> streamsGlobal = new ConcurrentDictionary<string, MemoryStream>();
internal static string CurrentLoadingMod;
internal static Mod Mod => ModContent.GetInstance<tConfigWrapper>();
public static void Setup() {
recipeDict.TryGetValue("", out _); // Sanity check to make sure it's initialized
// Cringe reflection
Assembly assembly = Assembly.GetAssembly(typeof(Mod));
Type uiLoadModsType = assembly.GetType("Terraria.ModLoader.UI.UILoadMods");
object loadModsValue = assembly.GetType("Terraria.ModLoader.UI.Interface")
.GetField("loadMods", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
MethodInfo loadStageMethod = uiLoadModsType.GetMethod("SetLoadStage", BindingFlags.Instance | BindingFlags.Public);
PropertyInfo progressProperty = uiLoadModsType.GetProperty("Progress", BindingFlags.Instance | BindingFlags.Public);
PropertyInfo subProgressTextProperty =
uiLoadModsType.GetProperty("SubProgressText", BindingFlags.Instance | BindingFlags.Public);
_loadProgressText = (string s) => loadStageMethod.Invoke(loadModsValue, new object[] {s, -1});
_loadProgress = (float f) => progressProperty.SetValue(loadModsValue, f);
_loadSubProgressText = (string s) => subProgressTextProperty.SetValue(loadModsValue, s);
_loadProgressText?.Invoke("tConfig Wrapper: Loading Mods");
_loadProgress?.Invoke(0f);
foreach (var modName in ModState.AllMods) {
//using (MemoryStream stream = new MemoryStream())
//using (SevenZipExtractor extractor = new SevenZipExtractor(stream)) {
// extractor.ExtractFile("get the description, and the references, and anything else I need to get from a mod", stream);
//}
if (ModState.EnabledMods.Contains(Path.GetFileNameWithoutExtension(modName)))
Mod.Logger.Debug($"tConfig Mod: {Path.GetFileNameWithoutExtension(modName)} is enabled!"); // Writes all mod names to logs
}
for (int i = 0; i < ModState.EnabledMods.Count; i++) {
// Iterates through every mod
string currentMod = ModState.EnabledMods[i];
string currentModNoExt = Path.GetFileNameWithoutExtension(ModState.EnabledMods[i]);
CurrentLoadingMod = currentModNoExt;
_loadProgressText?.Invoke($"tConfig Wrapper: Loading {currentModNoExt}"); // Sets heading text to display the mod being loaded
Mod.Logger.Debug($"Loading tConfig Mod: {currentModNoExt}"); // Logs the mod being loaded
Mod.Logger.Debug($"Loading Content: {currentModNoExt}");
ConcurrentDictionary<string, MemoryStream> streams = new ConcurrentDictionary<string, MemoryStream>();
Decompressor.DecompressMod(currentMod, streams); // Decompresses mods since .obj files are literally just 7z files
streamsGlobal.Clear();
streamsGlobal = streams;
TaskCompletedCount = 0;
// Get the first stream that is an obj file
var obj = streams.First(s => s.Key.EndsWith(".obj"));
BinaryReader reader = new BinaryReader(obj.Value);
// Create an Obj Loader and load the obj
var loader = new ObjLoader(reader, currentModNoExt);
loader.LoadObj(); // This was causing errors for some reason.
// Get all classes that extend BaseLoader and make an instance of them
var loaderInstances = GetLoaders(currentModNoExt, streams).ToArray();
int contentCount = 0;
// Call AddFiles for all loaders and add to the content amount
CallMethodAsync(loaderInstances, baseLoader => {
int addedFiles = baseLoader.AddFiles(streams.Keys);
Interlocked.Add(ref contentCount, addedFiles);
});
//// Make a task for each loader and call IterateFiles
CallMethodAsync(loaderInstances, baseLoader => baseLoader.IterateFiles(contentCount));
// Call RegisterContent for each loader
CallMethodAsync(loaderInstances, baseLoader => baseLoader.RegisterContent());
// Dispose the streams
foreach (var memoryStream in streams) {
memoryStream.Value.Dispose();
}
}
//Reset progress bar
_loadSubProgressText?.Invoke("");
_loadProgressText?.Invoke("Loading mod");
_loadProgress?.Invoke(0f);
CurrentLoadingMod = null;
}
private static IEnumerable<BaseLoader> GetLoaders(string modName, ConcurrentDictionary<string, MemoryStream> fileStreams) {
Type baseType = typeof(BaseLoader);
var childTypes = Mod.Code.GetTypes().Where(p => baseType.IsAssignableFrom(p) && !p.IsAbstract);
foreach (Type childType in childTypes) {
yield return (BaseLoader)Activator.CreateInstance(childType, modName, fileStreams);
}
}
/// <summary>
/// Calls <paramref name="method"/> for every T in <paramref name="objects"/>
/// </summary>
private static void CallMethodAsync<T>(IEnumerable<T> objects, Action<T> method) {
var objArr = objects.ToArray();
List<Task> tasks = new List<Task>(objArr.Length);
// Make a task and run method for every thing in objects
foreach (T thing in objArr) {
tasks.Add(Task.Run(() => method.Invoke(thing)));
}
// Wait for all tasks to finish
Task.WaitAll(tasks.ToArray());
}
internal static void AddRecipes() {
SetupRecipes();
var loaders = GetLoaders(null, null);
CallMethodAsync(loaders, loader => loader.AddRecipes());
}
internal static void PostSetupContent() {
var loaders = GetLoaders(null, null);
CallMethodAsync(loaders, loader => loader.PostSetupContent());
}
private static void SetupRecipes() { // Sets up recipes, what were you expecting?
_loadProgressText.Invoke("tConfig Wrapper: Adding Recipes"); // Ah yes, more reflection
_loadProgress.Invoke(0f);
int progressCount = 0;
bool initialized = (bool)Assembly.GetAssembly(typeof(Mod)).GetType("Terraria.ModLoader.MapLoader").GetField("initialized", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null); // Check if the map is already initialized
foreach (var iniFileSection in recipeDict) { // Load every recipe in the recipe dict
progressCount++; // Count the number of recipes, still broken somehow :(
string modName = iniFileSection.Key.Split(':')[0];
ModRecipe recipe = null;
if (initialized) // Only make the recipe if the maps have already been initialized. The checks for initialized are because this method is run in GetTileMapEntires() to see what tiles are used in recipes and need to have a name in their map entry
recipe = new ModRecipe(Mod);
foreach (var element in iniFileSection.Value.elements) { // ini recipe loading, code is barely readable enough.
string[] splitElement = element.Content.Split('=');
string key = splitElement[0];
string value = splitElement[1];
switch (key) {
case "Amount" when initialized: {
int id;
string[] splitKey = iniFileSection.Key.Split(':');
string itemName = splitKey.Length == 1 ? splitKey[0] : splitKey[1];
if ((id = ItemID.FromLegacyName(itemName, 4)) != 0)
recipe?.SetResult(id, int.Parse(value));
else
recipe?.SetResult(Mod, iniFileSection.Key, int.Parse(value));
break;
}
case "needWater" when initialized:
recipe.needWater = bool.Parse(value);
break;
case "Items" when initialized: {
foreach (string recipeItem in value.Split(',')) {
var recipeItemInfo = recipeItem.Split(null, 2);
int amount = int.Parse(recipeItemInfo[0]);
int itemID = Mod.ItemType($"{modName}:{recipeItemInfo[1].RemoveIllegalCharacters()}");
if (itemID == 0)
itemID = ItemID.FromLegacyName(recipeItemInfo[1], 4);
var numberIngredients =
recipe?.requiredItem.Count(i => i != null & i.type != ItemID.None);
if (numberIngredients < 14)
recipe?.AddIngredient(itemID, amount);
else {
Mod.Logger.Debug($"The following item has exceeded the max ingredient limit! -> {iniFileSection.Key}");
tConfigWrapper.ReportErrors = true;
}
}
break;
}
case "Tiles": { // Does stuff to check for modtiles and vanilla tiles that have changed their name since 1.1.2
foreach (string recipeTile in value.Split(',')) {
string recipeTileIR = recipeTile.RemoveIllegalCharacters();
int tileInt = Mod.TileType($"{modName}:{recipeTileIR}");
var tileModTile = Mod.GetTile($"{modName}:{recipeTileIR}");
if (!TileID.Search.ContainsName(recipeTileIR) && !CheckIDConversion(recipeTileIR) && tileInt == 0 && tileModTile == null) { // Would love to replace this with Utilities.StringToContent() but this one is special and needs to add stuff to a dictionary so I can't
if (initialized) {
Mod.Logger.Debug($"TileID {modName}:{recipeTileIR} does not exist"); // We will have to manually convert anything that breaks lmao
tConfigWrapper.ReportErrors = true;
}
}
else if (CheckIDConversion(recipeTileIR) || TileID.Search.ContainsName(recipeTileIR)) {
string converted = ConvertIDTo13(recipeTileIR);
if (initialized)
recipe?.AddTile(TileID.Search.GetId(converted));
}
else if (tileInt != 0) {
if (initialized) {
recipe?.AddTile(tileModTile);
Mod.Logger.Debug($"{modName}:{recipeTileIR} added to recipe through mod.TileType!");
}
//else {
// tileMapData[tileModTile] = (true, tileMapData[tileModTile].Item2); // I do this because either I can't just change Item1 directly to true OR because I am very not smart and couldn't figure out how to set it individually.
//}
}
}
break;
}
}
}
if (recipe?.createItem != null && recipe?.createItem.type != ItemID.None && initialized)
recipe?.AddRecipe();
if (initialized)
_loadProgress.Invoke(progressCount / recipeDict.Count);
}
}
// TODO: Move this method
public static void UpdateSubProgressText(string newText) {
_loadSubProgressText?.Invoke(newText);
}
public static void UpdateProgress(float newProgress) {
_loadProgress?.Invoke(newProgress);
}
internal static void LoadStaticFields() {
globalItemInfos = new ConcurrentDictionary<int, ItemInfo>();
recipeDict = new ConcurrentDictionary<string, IniFileSection>();
suffixes = new ConcurrentBag<ModPrefix>();
var loaders = GetLoaders(null, null);
CallMethodAsync(loaders, loader => loader.InitStatic());
}
internal static void UnloadStaticFields() {
_loadProgressText = null;
_loadProgress = null;
_loadSubProgressText = null;
globalItemInfos = null;
recipeDict = null;
suffixes = null;
}
}
}