Skip to content

Commit f659cf4

Browse files
committed
chore: reorganize internals and remove obsolete code for v3.0
- Move internal bootstrap code into clearer namespaces for entities and map systems - Make legacy internal utility wrappers truly internal instead of keeping obsolete public surfaces around - Simplify reflection, scene cleanup, quest, shop, and NPC helper paths as part of the initial 3.0 cleanup pass - Prepare the codebase for further 3.0 API and organization cleanup still to come
1 parent ad843ea commit f659cf4

25 files changed

Lines changed: 586 additions & 796 deletions

S1API/Entities/NPCAppearance.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
using S1API.Entities.Appearances.BodyLayerFields;
2323
using S1API.Entities.Appearances.CustomizationFields;
2424
using S1API.Entities.Appearances.FaceLayerFields;
25-
using S1API.Internal.Utils;
25+
using S1API.Utils;
2626
using S1API.Logging;
2727
using MelonLoader;
2828

S1API/Entities/NPCInventory.cs

Lines changed: 71 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#endif
1212

1313
using System;
14-
using System.Collections.Generic;
1514
using System.Reflection;
1615
using UnityEngine;
1716
using UnityEngine.Events;
@@ -26,7 +25,7 @@ namespace S1API.Entities
2625
public sealed class NPCInventory
2726
{
2827
private static readonly Logging.Log Logger = new Logging.Log("NPCInventory");
29-
internal readonly NPC NPC;
28+
private readonly NPC NPC;
3029

3130
internal NPCInventory(NPC npc)
3231
{
@@ -41,8 +40,6 @@ public bool CanItemFit(string itemId, int quantity = 1)
4140
if (string.IsNullOrEmpty(itemId) || quantity <= 0)
4241
return false;
4342
var temp = BuildTempItem(itemId, quantity);
44-
if (temp == null)
45-
return false;
4643
return CanItemFitInternal(temp);
4744
}
4845

@@ -54,8 +51,6 @@ public int GetCapacityForItem(string itemId, int quantity = 1)
5451
if (string.IsNullOrEmpty(itemId) || quantity <= 0)
5552
return 0;
5653
var temp = BuildTempItem(itemId, quantity);
57-
if (temp == null)
58-
return 0;
5954
return GetCapacityForItemInternal(temp);
6055
}
6156

@@ -68,8 +63,6 @@ public bool TryInsert(string itemId, int quantity = 1, bool network = true)
6863
if (string.IsNullOrEmpty(itemId) || quantity <= 0)
6964
return false;
7065
var temp = BuildTempItem(itemId, quantity);
71-
if (temp == null)
72-
return false;
7366
if (!CanItemFitInternal(temp))
7467
return false;
7568
InsertItemInternal(temp, network);
@@ -84,16 +77,17 @@ public bool TryInsert(string itemId, int quantity = 1, bool network = true)
8477
/// </summary>
8578
public void EnsureInitialized()
8679
{
87-
string npcId = NPC?.S1NPC?.ID ?? "<null>";
80+
var npcId = NPC?.S1NPC?.ID ?? "<null>";
8881
var inv = Component;
89-
if (inv == null)
82+
if (inv == null && NPC != null)
9083
{
91-
// Attach inventory if missing
9284
var comp = NPC.gameObject.GetComponent<S1NPCs.NPCInventory>() ?? NPC.gameObject.AddComponent<S1NPCs.NPCInventory>();
9385
inv = comp;
9486
}
9587

96-
// Ensure ItemSlots list exists
88+
if (inv == null)
89+
return;
90+
9791
if (inv.ItemSlots == null)
9892
{
9993
#if (IL2CPPMELON || IL2CPPBEPINEX)
@@ -102,64 +96,67 @@ public void EnsureInitialized()
10296
inv.ItemSlots = new System.Collections.Generic.List<S1Items.ItemSlot>();
10397
#endif
10498
}
105-
106-
// Remove duplicate slots (same instance appearing multiple times)
107-
int duplicatesRemoved = DeduplicateSlots(inv);
108-
109-
// Ensure we have the correct number of slots
110-
int currentCount = inv.ItemSlots.Count;
111-
int targetCount = inv.SlotCount;
112-
113-
// Remove excess slots if we have too many
99+
100+
var currentCount = inv.ItemSlots.Count;
101+
var targetCount = inv.SlotCount;
102+
114103
if (currentCount > targetCount)
115104
{
116-
int trimmed = currentCount - targetCount;
117-
for (int i = currentCount - 1; i >= targetCount; i--)
105+
for (var i = currentCount - 1; i >= targetCount; i--)
118106
{
119107
var slot = inv.ItemSlots[i];
120108
if (slot != null)
121109
{
122-
try { slot.ClearStoredInstance(true); } catch { }
110+
try { slot.ClearStoredInstance(true); }
111+
catch
112+
{
113+
// ignored
114+
}
123115
}
124116
inv.ItemSlots.RemoveAt(i);
125117
}
126118
currentCount = inv.ItemSlots.Count;
127119
}
128-
129-
// CRITICAL: Unlock all existing slots before creating new ones
130-
// Base game NPCs may have locked slots, which prevents AddCash/InsertItem from working
131-
for (int i = 0; i < inv.ItemSlots.Count; i++)
120+
121+
foreach (var slot in inv.ItemSlots)
132122
{
133-
var slot = inv.ItemSlots[i];
134-
if (slot != null)
123+
if (slot == null) continue;
124+
try
125+
{
126+
ReflectionUtils.TrySetFieldOrProperty(slot, "ActiveLock", null);
127+
}
128+
catch
129+
{
130+
// ignored
131+
}
132+
133+
try
134+
{
135+
ReflectionUtils.TrySetFieldOrProperty(slot, "IsAddLocked", false);
136+
}
137+
catch
135138
{
136-
try
137-
{
138-
ReflectionUtils.TrySetFieldOrProperty(slot, "ActiveLock", null);
139-
}
140-
catch { }
141-
try
142-
{
143-
ReflectionUtils.TrySetFieldOrProperty(slot, "IsAddLocked", false);
144-
}
145-
catch { }
139+
// ignored
146140
}
147141
}
148142

149143
// Create missing slots if we have too few
150144
// Note: SetSlotOwner automatically adds the slot to ItemSlots, so we don't call Add() manually
151145
if (currentCount < targetCount)
152146
{
153-
int slotsToCreate = targetCount - currentCount;
154-
for (int i = 0; i < slotsToCreate; i++)
147+
var slotsToCreate = targetCount - currentCount;
148+
for (var i = 0; i < slotsToCreate; i++)
155149
{
156150
var slot = new S1Items.ItemSlot();
157151

158-
// Set up event handler before SetSlotOwner (which adds to list)
159152
#if (IL2CPPMELON || IL2CPPBEPINEX)
160-
System.Action handler = new System.Action(() =>
153+
var handler = new System.Action(() =>
161154
{
162-
try { inv.onContentsChanged?.Invoke(); } catch { }
155+
try { inv.onContentsChanged?.Invoke(); }
156+
catch
157+
{
158+
// ignored
159+
}
163160
});
164161
slot.onItemDataChanged = (Il2CppSystem.Action)Il2CppSystem.Delegate.Combine(
165162
slot.onItemDataChanged,
@@ -184,27 +181,35 @@ public void EnsureInitialized()
184181
})
185182
);
186183
#endif
187-
188-
// SetSlotOwner automatically adds slot to inv.ItemSlots - DO NOT call Add() manually
184+
189185
#if MONOMELON
190186
slot.SetSlotOwner(inv);
191187
#else
192188
slot.SetSlotOwner(inv.Cast<S1Items.IItemSlotOwner>());
193189
#endif
194190

195191
// Ensure slot is unlocked
196-
try { ReflectionUtils.TrySetFieldOrProperty(slot, "ActiveLock", null); } catch { }
197-
try { ReflectionUtils.TrySetFieldOrProperty(slot, "IsAddLocked", false); } catch { }
192+
try { ReflectionUtils.TrySetFieldOrProperty(slot, "ActiveLock", null); }
193+
catch
194+
{
195+
// ignored
196+
}
197+
198+
try { ReflectionUtils.TrySetFieldOrProperty(slot, "IsAddLocked", false); }
199+
catch
200+
{
201+
// ignored
202+
}
198203
}
199204
}
200205

201-
if (inv.PickpocketIntObj == null)
206+
if (inv.PickpocketIntObj == null && NPC != null)
202207
{
203208
var talk = NPC.GetType().GetMethod("GetPrimaryInteractable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
204209
var primary = talk?.Invoke(NPC, null) as S1Interaction.InteractableObject;
205210
var interactables = NPC.gameObject.GetComponentsInChildren<S1Interaction.InteractableObject>(true);
206-
S1Interaction.InteractableObject pick = null;
207-
for (int i = 0; i < interactables.Length; i++)
211+
S1Interaction.InteractableObject? pick = null;
212+
for (var i = 0; i < interactables.Length; i++)
208213
{
209214
if (interactables[i] != null && interactables[i] != primary)
210215
{
@@ -221,13 +226,12 @@ public void EnsureInitialized()
221226

222227
try
223228
{
224-
ReflectionUtils.TrySetFieldOrProperty(inv, "npc", NPC.S1NPC);
225-
226-
// NOTE: Do NOT add listeners here - the game's NPCInventory.Awake() already adds
227-
// onHovered and onInteractStart listeners. Adding them again causes duplicate
228-
// event firing which breaks the pickpocket screen.
229+
ReflectionUtils.TrySetFieldOrProperty(inv, "npc", NPC?.S1NPC);
230+
}
231+
catch
232+
{
233+
// ignored
229234
}
230-
catch { }
231235

232236
try
233237
{
@@ -237,14 +241,17 @@ public void EnsureInitialized()
237241
ReflectionUtils.TrySetFieldOrProperty(inv, "onContentsChanged", new UnityEvent());
238242
}
239243
}
240-
catch { }
244+
catch
245+
{
246+
// ignored
247+
}
241248

242249
try { inv.NetworkInitializeIfDisabled(); } catch (Exception ex) { Logger.Warning($"[NPCInventory] EnsureInitialized: NetworkInitializeIfDisabled threw for '{npcId}': {ex.Message}"); }
243250
}
244251

245-
internal S1NPCs.NPCInventory Component => NPC.gameObject.GetComponent<S1NPCs.NPCInventory>();
252+
private S1NPCs.NPCInventory Component => NPC.gameObject.GetComponent<S1NPCs.NPCInventory>();
246253

247-
private S1Items.ItemInstance BuildTempItem(string itemId, int quantity)
254+
private S1Items.ItemInstance? BuildTempItem(string itemId, int quantity)
248255
{
249256
try
250257
{
@@ -261,81 +268,24 @@ private S1Items.ItemInstance BuildTempItem(string itemId, int quantity)
261268
}
262269
}
263270

264-
internal bool CanItemFitInternal(S1Items.ItemInstance item)
271+
private bool CanItemFitInternal(S1Items.ItemInstance item)
265272
{
266-
if (item == null) return false;
267273
EnsureInitialized();
268274
var inv = Component;
269275
return inv != null && inv.CanItemFit(item);
270276
}
271277

272-
internal int GetCapacityForItemInternal(S1Items.ItemInstance item)
278+
private int GetCapacityForItemInternal(S1Items.ItemInstance item)
273279
{
274-
if (item == null) return 0;
275280
EnsureInitialized();
276281
var inv = Component;
277282
return inv != null ? inv.GetCapacityForItem(item) : 0;
278283
}
279284

280-
internal void InsertItemInternal(S1Items.ItemInstance item, bool network = true)
285+
private void InsertItemInternal(S1Items.ItemInstance item, bool network = true)
281286
{
282-
if (item == null) return;
283287
EnsureInitialized();
284288
Component?.InsertItem(item, network);
285289
}
286-
287-
/// <summary>
288-
/// Removes duplicate slot instances from the inventory's ItemSlots list.
289-
/// Duplicates can occur if slots are added manually after SetSlotOwner (which already adds them).
290-
/// </summary>
291-
private int DeduplicateSlots(S1NPCs.NPCInventory inv)
292-
{
293-
if (inv?.ItemSlots == null)
294-
return 0;
295-
296-
try
297-
{
298-
// Use a set to track seen slot instances (by reference equality)
299-
var seen = new HashSet<object>();
300-
var toRemove = new List<int>();
301-
302-
for (int i = 0; i < inv.ItemSlots.Count; i++)
303-
{
304-
var slot = inv.ItemSlots[i];
305-
if (slot == null)
306-
{
307-
toRemove.Add(i);
308-
continue;
309-
}
310-
311-
// Check if we've seen this exact slot instance before
312-
if (seen.Contains(slot))
313-
{
314-
// This is a duplicate - clear it and mark for removal
315-
try { slot.ClearStoredInstance(true); } catch { }
316-
toRemove.Add(i);
317-
}
318-
else
319-
{
320-
seen.Add(slot);
321-
}
322-
}
323-
324-
// Remove duplicates from the end to preserve indices
325-
for (int i = toRemove.Count - 1; i >= 0; i--)
326-
{
327-
inv.ItemSlots.RemoveAt(toRemove[i]);
328-
}
329-
330-
return toRemove.Count;
331-
}
332-
catch (Exception ex)
333-
{
334-
// If deduplication fails, continue - better to have duplicates than crash
335-
Logger.Warning($"[NPCInventory] DeduplicateSlots: Failed with exception: {ex.Message}");
336-
Logger.Warning($"[NPCInventory] DeduplicateSlots: Stack trace: {ex.StackTrace}");
337-
return 0;
338-
}
339-
}
340290
}
341291
}

S1API/Entities/NPCMovement.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class NPCMovement
1919
/// <summary>
2020
/// INTERNAL: Reference to the NPC on API side.
2121
/// </summary>
22-
internal readonly NPC NPC;
22+
private readonly NPC NPC;
2323

2424
/// <summary>
2525
/// INTERNAL: Constructor used for assigning the NPC instance.
@@ -151,11 +151,9 @@ public float SpeedMultiplier
151151
/// <param name="speed">Movement speed multiplier.</param>
152152
public void AddSpeedControl(string id, int priority, float speed)
153153
{
154-
if (SpeedController != null)
155-
{
156-
var control = new S1NPCs.NPCSpeedController.SpeedControl(id, priority, speed);
157-
SpeedController.AddSpeedControl(control);
158-
}
154+
if (SpeedController == null) return;
155+
var control = new S1NPCs.NPCSpeedController.SpeedControl(id, priority, speed);
156+
SpeedController.AddSpeedControl(control);
159157
}
160158

161159
/// <summary>
@@ -180,7 +178,7 @@ public bool DoesSpeedControlExist(string id)
180178
/// <summary>
181179
/// INTERNAL: Gets the speed controller component from the NPC.
182180
/// </summary>
183-
internal S1NPCs.NPCSpeedController? SpeedController =>
181+
private S1NPCs.NPCSpeedController? SpeedController =>
184182
NPC.S1NPC.Movement?.SpeedController;
185183

186184
#endregion

0 commit comments

Comments
 (0)