Skip to content

Commit b597233

Browse files
authored
Mod menu (#61)
* [Major] two new functions to inject loot tables, still wip * [Major] rework json parsing for loot table * [Major] weapons/armors are handled with quality and durability * [Major] Loot table are now saved in the same floder as the data.win, InjectLoot is called during the patching, there is no need to call it in the mod, the loot table abstraction inside MSL is reset each time a data.win is loaded to avoid conflicts, unique items are now properly handled, they wont diluate the loot table once looted * [Minor] some renaming and handle unique items * [Major] adding reference table * [Major] Mod menu api for combobox and checkbox * [Minor] adding slider support * [Major] Rework of mod menu, unique mod menu in settings but each mod adds a submenu. Slider still wip * [Major] slider are working, sub menu also, wip init globals and default values * [Major] handling of default value for global used in settings, globals are defined in the first room to be sure they always exist
1 parent 447eb7d commit b597233

5 files changed

Lines changed: 687 additions & 2 deletions

File tree

Controls/ModInfos.xaml.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
34
using System.Threading.Tasks;
45
using System.Windows;
56
using System.Windows.Controls;
@@ -55,6 +56,8 @@ private async void Save_Click(object sender, EventArgs e)
5556
Task<bool> save = DataLoader.DoSaveDialog();
5657
await save;
5758
if (!save.Result) Log.Information("Saved cancelled.");
59+
// copy the dataloot.json in the stoneshard directory
60+
LootUtils.SaveLootTables(Msl.ThrowIfNull(Path.GetDirectoryName(DataLoader.dataPath)));
5861
}
5962

6063
// reload the data

DataLoader.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ private static void ExportData()
6969
File.WriteAllText("json_dump_rooms.json", JsonConvert.SerializeObject(data.Rooms.Select(t => t.Name.Content)));
7070
Msl.GenerateNRandomLinesFromCode(data.Code, new GlobalDecompileContext(data, false), 100, 1, 0);
7171
}
72+
private static void ExportPreset()
73+
{
74+
GlobalDecompileContext context = new(ModLoader.Data, false);
75+
File.WriteAllText("json_preset_bastion.json", Decompiler.Decompile(data.Code.First(t => t.Name.Content.Contains("scr_preset_bastion_1")), context));
76+
File.WriteAllText("json_preset_catacombs.json", Decompiler.Decompile(data.Code.First(t => t.Name.Content.Contains("scr_preset_catacombs")), context));
77+
File.WriteAllText("json_preset_crypt.json", Decompiler.Decompile(data.Code.First(t => t.Name.Content.Contains("scr_preset_crypt_1")), context));
78+
}
7279
private static bool LoadUmt(string filename)
7380
{
7481
bool hadWarnings = false;
@@ -127,6 +134,8 @@ public static async Task LoadFile(string filename, bool re = false)
127134
dialog.ShowDialog();
128135
await taskLoadDataWinWithUmt;
129136
ModLoader.Initalize();
137+
// cleaning loot table
138+
LootUtils.ResetLootTables();
130139
}
131140
public static async Task<bool> DoSaveDialog()
132141
{

ModLoader.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static class ModLoader
2626
public static Dictionary<string, ModFile> Mods = new();
2727
public static Dictionary<string, ModSource> ModSources = new();
2828
private static List<Assembly> Assemblies = new();
29+
private static List<Menu> Menus = new();
2930
public static List<string> Weapons = new();
3031
public static List<string> WeaponDescriptions = new();
3132
private static List<(string, string[])> Credits = new();
@@ -48,6 +49,10 @@ internal static void AddDisclaimer(string modNameShort, UndertaleRoom.GameObject
4849
{
4950
Disclaimers.Add((modNameShort, overlay));
5051
}
52+
public static void AddMenu(string name, params UIComponent[] components)
53+
{
54+
Menus.Add(new Menu(name, components));
55+
}
5156
public static List<string>? GetTable(string name)
5257
{
5358
try
@@ -177,6 +182,7 @@ public static void PatchMods()
177182
Credits = new();
178183
Disclaimers = new();
179184
List<ModFile> mods = ModInfos.Instance.Mods;
185+
Menus = new();
180186
foreach (ModFile mod in mods)
181187
{
182188
if (!mod.isEnabled) continue;
@@ -214,6 +220,7 @@ public static void PatchMods()
214220
}
215221
Msl.AddDisclaimerRoom(Credits.Select(x => x.Item1).ToArray(), Credits.SelectMany(x => x.Item2).Distinct().ToArray());
216222
Msl.ChainDisclaimerRooms(Disclaimers);
223+
Msl.CreateMenu(Menus);
217224
}
218225
public static void LoadWeapon(Type type)
219226
{
@@ -231,8 +238,8 @@ public static void PatchFile()
231238
{
232239
PatchInnerFile();
233240
PatchMods();
234-
// SetTable(Weapons, "gml_GlobalScript_table_weapons");
235-
// SetTable(WeaponDescriptions, "gml_GlobalScript_table_weapons_text");
241+
// add the new loot related functions if there is any
242+
LootUtils.InjectLootScripts();
236243
}
237244
internal static void PatchInnerFile()
238245
{

ModUtils/LootUtils.cs

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Serilog;
5+
using UndertaleModLib.Models;
6+
using Newtonsoft.Json;
7+
using System.IO;
8+
using Microsoft.Win32;
9+
10+
namespace ModShardLauncher
11+
{
12+
public class RandomLootTable
13+
{
14+
public int[] TableLootWeight { get; }
15+
public string[] TableLootItems { get; }
16+
public int[] TableLootRarity { get; }
17+
public int[] TableLootDurability { get; }
18+
19+
public RandomLootTable(int[] tableLootWeight, string[] tableLootItems, int[] tableLootRarity, int[] tableLootDurability)
20+
{
21+
TableLootWeight = tableLootWeight;
22+
TableLootItems = tableLootItems;
23+
TableLootRarity = tableLootRarity;
24+
TableLootDurability = tableLootDurability;
25+
}
26+
}
27+
public class ReferenceTable
28+
{
29+
public int[] Ids { get; }
30+
public string[] Refs { get; }
31+
public ReferenceTable(string[] refs, int[] ids)
32+
{
33+
Ids = ids;
34+
Refs = refs;
35+
}
36+
}
37+
public class LootTable
38+
{
39+
public string[] GuaranteedItems { get; }
40+
public int RandomLootMin { get; }
41+
public int RandomLootMax { get; }
42+
public int EmptyWeight { get; }
43+
public RandomLootTable RandomLootTable { get; }
44+
public LootTable(string[] guaranteedItems, int randomLootMin, int randomLootMax, int emptyWeight, RandomLootTable randomLootTable)
45+
{
46+
GuaranteedItems = guaranteedItems;
47+
RandomLootMin = randomLootMin;
48+
RandomLootMax = randomLootMax;
49+
EmptyWeight = emptyWeight;
50+
RandomLootTable = randomLootTable;
51+
}
52+
}
53+
public static class LootUtils
54+
{
55+
public static Dictionary<string, ReferenceTable> ReferenceTables = new();
56+
public static Dictionary<string, LootTable> LootTables = new();
57+
public static void ResetLootTables()
58+
{
59+
ReferenceTables.Clear();
60+
LootTables.Clear();
61+
}
62+
public static void SaveLootTables(string DirPath)
63+
{
64+
if (LootTables.Count > 0)
65+
{
66+
File.WriteAllText(Path.Combine(DirPath, "loot_table.json"), JsonConvert.SerializeObject(LootTables));
67+
Log.Information("Successfully saving the loot table json.");
68+
}
69+
if (ReferenceTables.Count > 0)
70+
{
71+
File.WriteAllText(Path.Combine(DirPath, "reference_table.json"), JsonConvert.SerializeObject(ReferenceTables));
72+
Log.Information("Successfully saving the reference table json.");
73+
}
74+
}
75+
public static void InjectLootScripts()
76+
{
77+
if (LootTables.Count == 0 && ReferenceTables.Count == 0) return;
78+
79+
string lootFunction = @"function scr_resolve_loot_table(argument0)
80+
{
81+
var objectName = object_get_name(argument0.object_index);
82+
scr_actionsLogUpdate(""instance: "" + string(argument0.id) + "" of "" + objectName);
83+
84+
var refFile = file_text_open_read(""reference_table.json"");
85+
var refJson = file_text_read_string(refFile);
86+
var refData = json_parse(refJson);
87+
88+
var min_lvl = scr_globaltile_dungeon_get(""mob_lvl_min"");
89+
var max_lvl = scr_globaltile_dungeon_get(""mob_lvl_max"");
90+
var tier = floor(((max_lvl + min_lvl) / 2));
91+
92+
if (!variable_struct_exists(refData, objectName))
93+
{
94+
scr_actionsLogUpdate(""cant find object "" + objectName);
95+
return 0;
96+
}
97+
var refStruct = variable_struct_get(refData, objectName);
98+
var referenceLootTableIndex = -1;
99+
100+
if (!variable_struct_exists(refStruct, ""Ids""))
101+
{
102+
scr_actionsLogUpdate(""cant find Ids"");
103+
return 0;
104+
}
105+
var idsArray = variable_struct_get(refStruct, ""Ids"");
106+
107+
for (var i = 0; i < array_length(idsArray); i += 1)
108+
{
109+
if (idsArray[i] == argument0.id)
110+
{
111+
referenceLootTableIndex = i;
112+
break;
113+
}
114+
}
115+
scr_actionsLogUpdate(""id ref: "" + string(referenceLootTableIndex));
116+
117+
if (!variable_struct_exists(refStruct, ""Refs""))
118+
{
119+
scr_actionsLogUpdate(""cant find Refs"");
120+
return 0;
121+
}
122+
var refsTable = variable_struct_get(refStruct, ""Refs"");
123+
var referenceLootTable = refsTable[referenceLootTableIndex + 1];
124+
125+
scr_actionsLogUpdate(""ref: "" + referenceLootTable);
126+
127+
if (!variable_struct_exists(refStruct, ""Refs""))
128+
{
129+
scr_actionsLogUpdate(""cant find Refs"");
130+
return 0;
131+
}
132+
var refsTable = variable_struct_get(refStruct, ""Refs"");
133+
var referenceLootTable = refsTable[referenceLootTableIndex + 1];
134+
135+
var file = file_text_open_read(""loot_table.json"");
136+
var json = file_text_read_string(file);
137+
var data = json_parse(json);
138+
139+
if (!variable_struct_exists(data, referenceLootTable))
140+
{
141+
scr_actionsLogUpdate(""cant find ref "" + referenceLootTable);
142+
return 0;
143+
}
144+
var lootStruct = variable_struct_get(data, referenceLootTable);
145+
146+
var objectName = """";
147+
var obj = -1;
148+
149+
if (!variable_struct_exists(lootStruct, ""GuaranteedItems""))
150+
{
151+
scr_actionsLogUpdate(""no guaranteedItems"");
152+
return 0;
153+
}
154+
var guaranteedItems = variable_struct_get(lootStruct, ""GuaranteedItems"");
155+
var sizeGuaranteedItems = array_length(guaranteedItems);
156+
157+
for (var i = 0; i < sizeGuaranteedItems; i += 1)
158+
{
159+
objectName = guaranteedItems[i];
160+
obj = asset_get_index(objectName)
161+
if (obj > -1)
162+
{
163+
scr_inventory_add_item(obj);
164+
}
165+
else
166+
{
167+
scr_actionsLogUpdate(""invalid object "" + string(objectName));
168+
}
169+
}
170+
171+
if (!variable_struct_exists(lootStruct, ""RandomLootMin"") || !variable_struct_exists(lootStruct, ""RandomLootMax"") || !variable_struct_exists(lootStruct, ""EmptyWeight""))
172+
{
173+
scr_actionsLogUpdate(""no int"");
174+
return 0;
175+
}
176+
177+
var randomLootMin = variable_struct_get(lootStruct, ""RandomLootMin"");
178+
var randomLootMax = variable_struct_get(lootStruct, ""RandomLootMax"");
179+
var emptyWeight = variable_struct_get(lootStruct, ""EmptyWeight"");
180+
181+
var iteration = randomLootMin + irandom(randomLootMax - randomLootMin);
182+
scr_actionsLogUpdate(""iteration "" + string(iteration));
183+
184+
if (!variable_struct_exists(lootStruct, ""RandomLootTable""))
185+
{
186+
scr_actionsLogUpdate(""no RandomLootTable"");
187+
return 0;
188+
}
189+
190+
var randomLootTable = variable_struct_get(lootStruct, ""RandomLootTable"");
191+
192+
if (!variable_struct_exists(randomLootTable, ""TableLootWeight"")
193+
|| !variable_struct_exists(randomLootTable, ""TableLootItems"")
194+
|| !variable_struct_exists(randomLootTable, ""TableLootRarity"")
195+
|| !variable_struct_exists(randomLootTable, ""TableLootDurability""))
196+
{
197+
scr_actionsLogUpdate(""no tableLoot data"");
198+
return 0;
199+
}
200+
201+
var tableLootWeight = variable_struct_get(randomLootTable, ""TableLootWeight"");
202+
var tableLootItems = variable_struct_get(randomLootTable, ""TableLootItems"");
203+
var tableLootRarity = variable_struct_get(randomLootTable, ""TableLootRarity"");
204+
var tableLootDurability = variable_struct_get(randomLootTable, ""TableLootDurability"");
205+
206+
var sizeTableLoot = array_length(tableLootWeight);
207+
var tableLootSpecialLootAlready = array_create(sizeTableLoot, 0);
208+
209+
for (var j = 0; j < iteration; j += 1)
210+
{
211+
var totalWeight = emptyWeight;
212+
for (var i = 0; i < sizeTableLoot; i += 1)
213+
{
214+
if (ds_list_find_index(scr_atr(""specialItemsPool""), tableLootItems[i]) != -1)
215+
{
216+
tableLootSpecialLootAlready[i] = 1;
217+
}
218+
else
219+
{
220+
totalWeight += tableLootWeight[i];
221+
}
222+
}
223+
224+
scr_actionsLogUpdate(""totalWeight "" + string(totalWeight));
225+
226+
var randomWeight = irandom(totalWeight - 1);
227+
scr_actionsLogUpdate(""randomWeight "" + string(randomWeight));
228+
var cumulativeWeight = 0;
229+
var index = -1;
230+
231+
for (var i = 0; i < sizeTableLoot; i += 1)
232+
{
233+
if (tableLootSpecialLootAlready[i] == 1)
234+
{
235+
continue;
236+
}
237+
cumulativeWeight += tableLootWeight[i]
238+
if (randomWeight < cumulativeWeight)
239+
{
240+
index = i;
241+
break;
242+
}
243+
}
244+
245+
if (index != -1)
246+
{
247+
scr_actionsLogUpdate(""found "" + string(index));
248+
249+
if (tableLootRarity[index] == -1)
250+
{
251+
objectName = tableLootItems[index];
252+
obj = asset_get_index(objectName)
253+
if (obj > -1)
254+
{
255+
scr_inventory_add_item(obj);
256+
}
257+
else
258+
{
259+
scr_actionsLogUpdate(""invalid object "" + string(objectName));
260+
}
261+
}
262+
else
263+
{
264+
with (scr_inventory_add_weapon(tableLootItems[index], (tableLootRarity[index] << 0)))
265+
{
266+
scr_inv_atr_set(""Duration"", tableLootDurability[index]);
267+
}
268+
}
269+
270+
}
271+
else
272+
{
273+
scr_actionsLogUpdate(""found empty"");
274+
}
275+
}
276+
}";
277+
278+
Msl.AddFunction(lootFunction, "scr_resolve_loot_table");
279+
280+
Msl.LoadGML("gml_Object_o_chest_p_Alarm_1")
281+
.MatchFrom("script_execute")
282+
.InsertBelow("scr_resolve_loot_table(other)")
283+
.Save();
284+
285+
Msl.LoadGML("gml_Object_c_container_Other_10")
286+
.MatchFrom("script_execute")
287+
.InsertBelow("scr_resolve_loot_table(other)")
288+
.Save();
289+
}
290+
}
291+
public static partial class Msl
292+
{
293+
public static void AddLootTable(string lootTableID, string[] guaranteedItems, int randomLootMin, int randomLootMax, int emptyWeight, RandomLootTable randomLootTable)
294+
{
295+
LootTable lootTable = new(guaranteedItems, randomLootMin, randomLootMax, emptyWeight, randomLootTable);
296+
LootUtils.LootTables.Add(lootTableID, lootTable);
297+
}
298+
public static void AddReferenceTable(string nameObject, string[] refs, int[]? ids = null)
299+
{
300+
ids ??= Array.Empty<int>();
301+
ReferenceTable referenceTable = new(refs, ids);
302+
LootUtils.ReferenceTables.Add(nameObject, referenceTable);
303+
}
304+
}
305+
}

0 commit comments

Comments
 (0)