Skip to content

Commit 759cdeb

Browse files
authored
Fix(IsekaiRpgLeveling): add animal stats tab patching from update 1.1.3 (#550)
1 parent 7d44640 commit 759cdeb

File tree

1 file changed

+175
-52
lines changed

1 file changed

+175
-52
lines changed

Source/Mods/IsekaiRPG.cs

Lines changed: 175 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ public class IsekaiRPGCompat
1717
private static readonly string[] StatAllocationFieldNames = ["strength", "vitality", "dexterity", "intelligence", "wisdom", "charisma"];
1818
private static readonly string[] PendingFieldNames = ["pendingSTR", "pendingVIT", "pendingDEX", "pendingINT", "pendingWIS", "pendingCHA"];
1919

20-
// ── Types ──────────────────────────────────────────────────────────
20+
#region Fields
21+
2122
private static Type isekaiComponentType;
2223
private static Type isekaiStatAllocationType;
2324
private static Type passiveTreeTrackerType;
@@ -27,24 +28,32 @@ public class IsekaiRPGCompat
2728
private static Type pawnStatGeneratorType;
2829
private static Type treeAutoAssignerType;
2930
private static Type manaCoreCompType;
31+
private static Type windowCreatureStatsType;
32+
private static Type iTabCreatureStatsType;
33+
private static Type mobRankComponentType;
3034

31-
// ── Field accessors ────────────────────────────────────────────────
3235
private static FieldInfo[] statAllocationFields;
3336
private static FieldInfo statAllocationAvailablePoints;
3437
private static FieldInfo isekaiCompStatsField;
3538
private static FieldInfo isekaiCompPassiveTreeField;
3639
private static FieldInfo raidRankSystemRandomField;
3740
private static FieldInfo pawnStatGeneratorRandomField;
3841
private static FieldInfo treeAutoAssignerRngField;
42+
private static FieldInfo mobRankStatsField;
3943

4044
private static AccessTools.FieldRef<object, Pawn> statsWindowPawn;
4145
private static AccessTools.FieldRef<object, int>[] statsWindowPending;
4246
private static AccessTools.FieldRef<object, int> statsWindowPointsSpent;
47+
private static AccessTools.FieldRef<object, Pawn> creatureWindowPawn;
48+
private static AccessTools.FieldRef<object, int>[] creatureWindowPending;
49+
private static AccessTools.FieldRef<object, int> creatureWindowPointsSpent;
50+
51+
private static ISyncField[] statSyncFields;
52+
53+
#endregion
4354

44-
// ── MP sync fields ─────────────────────────────────────────────────
45-
private static ISyncField[] statSyncFields; // [0..5] stats, [6] availableStatPoints
55+
#region Constructor
4656

47-
// ── Constructor ────────────────────────────────────────────────────
4857
public IsekaiRPGCompat(ModContentPack mod)
4958
{
5059
isekaiComponentType = Resolve("IsekaiLeveling.IsekaiComponent");
@@ -77,43 +86,74 @@ public IsekaiRPGCompat(ModContentPack mod)
7786

7887
if (raidRankSystemRandomField == null || pawnStatGeneratorRandomField == null || treeAutoAssignerRngField == null)
7988
{
80-
Log.Error("[IsekaiMP] One or more required fields could not be resolved — patches will NOT be applied.");
89+
Log.Warning("[IsekaiMP] One or more required fields could not be resolved — patches will NOT be applied.");
8190
return;
8291
}
92+
else
93+
{
8394

84-
isekaiCompStatsField = AccessTools.Field(isekaiComponentType, "stats");
85-
isekaiCompPassiveTreeField = AccessTools.Field(isekaiComponentType, "passiveTree");
86-
87-
statAllocationFields = new FieldInfo[StatAllocationFieldNames.Length];
88-
for (int i = 0; i < StatAllocationFieldNames.Length; i++)
89-
statAllocationFields[i] = AccessTools.Field(isekaiStatAllocationType, StatAllocationFieldNames[i]);
90-
statAllocationAvailablePoints = AccessTools.Field(isekaiStatAllocationType, "availableStatPoints");
91-
92-
statSyncFields = new ISyncField[StatAllocationFieldNames.Length + 1];
93-
for (int i = 0; i < StatAllocationFieldNames.Length; i++)
94-
statSyncFields[i] = MP.RegisterSyncField(isekaiStatAllocationType, StatAllocationFieldNames[i]);
95-
statSyncFields[StatAllocationFieldNames.Length] = MP.RegisterSyncField(isekaiStatAllocationType, "availableStatPoints");
96-
97-
statsWindowPawn = AccessTools.FieldRefAccess<Pawn>(windowStatsType, "pawn");
98-
statsWindowPending = new AccessTools.FieldRef<object, int>[PendingFieldNames.Length];
99-
for (int i = 0; i < PendingFieldNames.Length; i++)
100-
statsWindowPending[i] = AccessTools.FieldRefAccess<int>(windowStatsType, PendingFieldNames[i]);
101-
statsWindowPointsSpent = AccessTools.FieldRefAccess<int>(windowStatsType, "pointsSpent");
102-
103-
PatchAndLog(isekaiComponentType, "DevAddLevel", prefix: nameof(DevAddLevelPrefix));
104-
PatchAndLog(iTabType, "FillTab", prefix: nameof(ITabFillTabPrefix), postfix: nameof(ITabFillTabPostfix));
105-
PatchAndLog(windowStatsType, "ApplyChanges", prefix: nameof(ApplyChangesPrefix));
106-
PatchAndLog(passiveTreeTrackerType, "Unlock", prefix: nameof(UnlockNodePrefix));
107-
PatchAndLog(passiveTreeTrackerType, "Respec", prefix: nameof(RespecPrefix));
108-
109-
MP.RegisterSyncWorker<object>(SyncIsekaiStatAllocation, isekaiStatAllocationType);
110-
MP.RegisterSyncMethod(typeof(IsekaiRPGCompat), nameof(SyncedDevAddLevel));
111-
MP.RegisterSyncMethod(typeof(IsekaiRPGCompat), nameof(SyncedApplyStats));
112-
MP.RegisterSyncMethod(typeof(IsekaiRPGCompat), nameof(SyncedUnlockNode));
113-
MP.RegisterSyncMethod(typeof(IsekaiRPGCompat), nameof(SyncedRespec));
114-
MP.RegisterSyncDelegateLambda(manaCoreCompType, "GetBulkAbsorbOptions", 0);
95+
isekaiCompStatsField = AccessTools.Field(isekaiComponentType, "stats");
96+
isekaiCompPassiveTreeField = AccessTools.Field(isekaiComponentType, "passiveTree");
97+
98+
statAllocationFields = new FieldInfo[StatAllocationFieldNames.Length];
99+
for (int i = 0; i < StatAllocationFieldNames.Length; i++)
100+
statAllocationFields[i] = AccessTools.Field(isekaiStatAllocationType, StatAllocationFieldNames[i]);
101+
statAllocationAvailablePoints = AccessTools.Field(isekaiStatAllocationType, "availableStatPoints");
102+
103+
statSyncFields = new ISyncField[StatAllocationFieldNames.Length + 1];
104+
for (int i = 0; i < StatAllocationFieldNames.Length; i++)
105+
statSyncFields[i] = MP.RegisterSyncField(isekaiStatAllocationType, StatAllocationFieldNames[i]);
106+
statSyncFields[StatAllocationFieldNames.Length] = MP.RegisterSyncField(isekaiStatAllocationType, "availableStatPoints");
107+
108+
statsWindowPawn = AccessTools.FieldRefAccess<Pawn>(windowStatsType, "pawn");
109+
statsWindowPending = new AccessTools.FieldRef<object, int>[PendingFieldNames.Length];
110+
for (int i = 0; i < PendingFieldNames.Length; i++)
111+
statsWindowPending[i] = AccessTools.FieldRefAccess<int>(windowStatsType, PendingFieldNames[i]);
112+
statsWindowPointsSpent = AccessTools.FieldRefAccess<int>(windowStatsType, "pointsSpent");
113+
114+
PatchAndLog(isekaiComponentType, "DevAddLevel", prefix: nameof(DevAddLevelPrefix));
115+
PatchAndLog(iTabType, "FillTab", prefix: nameof(ITabFillTabPrefix), postfix: nameof(ITabFillTabPostfix));
116+
PatchAndLog(windowStatsType, "ApplyChanges", prefix: nameof(ApplyChangesPrefix));
117+
PatchAndLog(passiveTreeTrackerType, "Unlock", prefix: nameof(UnlockNodePrefix));
118+
PatchAndLog(passiveTreeTrackerType, "Respec", prefix: nameof(RespecPrefix));
119+
120+
MP.RegisterSyncWorker<object>(SyncIsekaiStatAllocation, isekaiStatAllocationType);
121+
MP.RegisterSyncMethod(typeof(IsekaiRPGCompat), nameof(SyncedDevAddLevel));
122+
MP.RegisterSyncMethod(typeof(IsekaiRPGCompat), nameof(SyncedApplyStats));
123+
MP.RegisterSyncMethod(typeof(IsekaiRPGCompat), nameof(SyncedUnlockNode));
124+
MP.RegisterSyncMethod(typeof(IsekaiRPGCompat), nameof(SyncedRespec));
125+
MP.RegisterSyncDelegateLambda(manaCoreCompType, "GetBulkAbsorbOptions", 0);
126+
}
127+
128+
windowCreatureStatsType = AccessTools.TypeByName("IsekaiLeveling.UI.Window_CreatureStats");
129+
iTabCreatureStatsType = AccessTools.TypeByName("IsekaiLeveling.UI.ITab_CreatureStats");
130+
mobRankComponentType = AccessTools.TypeByName("IsekaiLeveling.MobRanking.MobRankComponent");
131+
132+
if (windowCreatureStatsType == null || iTabCreatureStatsType == null || mobRankComponentType == null)
133+
{
134+
Log.Warning("[IsekaiMP] Animal stats types not found — animal stat sync will not be applied.");
135+
}
136+
else
137+
{
138+
mobRankStatsField = AccessTools.Field(mobRankComponentType, "stats");
139+
140+
creatureWindowPawn = AccessTools.FieldRefAccess<Pawn>(windowCreatureStatsType, "pawn");
141+
creatureWindowPending = new AccessTools.FieldRef<object, int>[PendingFieldNames.Length];
142+
for (int i = 0; i < PendingFieldNames.Length; i++)
143+
creatureWindowPending[i] = AccessTools.FieldRefAccess<int>(windowCreatureStatsType, PendingFieldNames[i]);
144+
creatureWindowPointsSpent = AccessTools.FieldRefAccess<int>(windowCreatureStatsType, "pointsSpent");
145+
146+
PatchAndLog(windowCreatureStatsType, "ApplyChanges", prefix: nameof(CreatureApplyChangesPrefix));
147+
PatchAndLog(iTabCreatureStatsType, "FillTab", prefix: nameof(ITabCreatureFillTabPrefix), postfix: nameof(ITabFillTabPostfix));
148+
149+
MP.RegisterSyncMethod(typeof(IsekaiRPGCompat), nameof(SyncedApplyCreatureStats));
150+
}
115151
}
116152

153+
#endregion
154+
155+
#region Utility
156+
117157
private static Type Resolve(string typeName)
118158
{
119159
var type = AccessTools.TypeByName(typeName);
@@ -165,9 +205,9 @@ private static Pawn FindPawnByComp(Func<ThingComp, bool> match)
165205
return null;
166206
}
167207

168-
// ═══════════════════════════════════════════════════════════════════
169-
// STAT ALLOCATION
170-
// ═══════════════════════════════════════════════════════════════════
208+
#endregion
209+
210+
#region Stat Allocation
171211

172212
private static bool ApplyChangesPrefix(object __instance)
173213
{
@@ -230,9 +270,9 @@ private static void RefreshStatsWindows(Pawn pawn, object statsObj)
230270
}
231271
}
232272

233-
// ═══════════════════════════════════════════════════════════════════
234-
// ITAB — stat field watch
235-
// ═══════════════════════════════════════════════════════════════════
273+
#endregion
274+
275+
#region ITab
236276

237277
private static void ITabFillTabPrefix(object __instance, ref bool __state)
238278
{
@@ -255,9 +295,9 @@ private static void ITabFillTabPostfix(bool __state)
255295
if (__state) MP.WatchEnd();
256296
}
257297

258-
// ═══════════════════════════════════════════════════════════════════
259-
// SKILL TREE
260-
// ═══════════════════════════════════════════════════════════════════
298+
#endregion
299+
300+
#region Skill Tree
261301

262302
private static bool _suppressUnlockPrefix = false;
263303

@@ -312,9 +352,9 @@ private static void SyncedRespec(Pawn pawn)
312352
finally { _suppressRespecPrefix = false; }
313353
}
314354

315-
// ═══════════════════════════════════════════════════════════════════
316-
// DEV TOOLS
317-
// ═══════════════════════════════════════════════════════════════════
355+
#endregion
356+
357+
#region Dev Tools
318358

319359
private static bool _suppressDevAddLevelPrefix = false;
320360

@@ -343,9 +383,9 @@ private static void SyncedDevAddLevel(Pawn pawn, int levels)
343383
finally { _suppressDevAddLevelPrefix = false; }
344384
}
345385

346-
// ═══════════════════════════════════════════════════════════════════
347-
// SYNC WORKER — IsekaiStatAllocation
348-
// ═══════════════════════════════════════════════════════════════════
386+
#endregion
387+
388+
#region Sync Worker
349389

350390
private static void SyncIsekaiStatAllocation(SyncWorker sync, ref object statsAllocation)
351391
{
@@ -366,5 +406,88 @@ private static void SyncIsekaiStatAllocation(SyncWorker sync, ref object statsAl
366406
}
367407
}
368408

409+
#endregion
410+
411+
#region Animal Stats
412+
413+
private static bool CreatureApplyChangesPrefix(object __instance)
414+
{
415+
if (!MP.IsInMultiplayer) return true;
416+
417+
Pawn pawn = creatureWindowPawn(__instance);
418+
if (pawn == null) return true;
419+
420+
var pending = new int[StatAllocationFieldNames.Length];
421+
for (int i = 0; i < pending.Length; i++)
422+
pending[i] = creatureWindowPending[i](__instance);
423+
424+
int pointsSpent = 0;
425+
bool godMode = Prefs.DevMode && DebugSettings.godMode;
426+
if (!godMode)
427+
{
428+
var comp = GetCompByType(pawn, mobRankComponentType);
429+
if (comp != null)
430+
{
431+
object statsObj = mobRankStatsField.GetValue(comp);
432+
for (int i = 0; i < pending.Length; i++)
433+
pointsSpent += pending[i] - (int)statAllocationFields[i].GetValue(statsObj);
434+
}
435+
}
436+
437+
SyncedApplyCreatureStats(pawn, pending, pointsSpent, godMode);
438+
return false;
439+
}
440+
441+
private static void SyncedApplyCreatureStats(Pawn pawn, int[] statValues, int pointsSpent, bool godMode)
442+
{
443+
var comp = GetCompByType(pawn, mobRankComponentType);
444+
if (comp == null) return;
445+
446+
object statsObj = mobRankStatsField.GetValue(comp);
447+
if (statsObj == null) return;
448+
449+
for (int i = 0; i < statAllocationFields.Length; i++)
450+
statAllocationFields[i].SetValue(statsObj, statValues[i]);
451+
452+
if (!godMode && pointsSpent > 0)
453+
{
454+
int remaining = (int)statAllocationAvailablePoints.GetValue(statsObj);
455+
statAllocationAvailablePoints.SetValue(statsObj, remaining - pointsSpent);
456+
}
457+
458+
RefreshCreatureStatsWindows(pawn, statsObj);
459+
}
460+
461+
private static void RefreshCreatureStatsWindows(Pawn pawn, object statsObj)
462+
{
463+
foreach (var window in Find.WindowStack.Windows)
464+
{
465+
if (!windowCreatureStatsType.IsInstanceOfType(window)) continue;
466+
if (!ReferenceEquals(creatureWindowPawn(window), pawn)) continue;
467+
468+
for (int i = 0; i < statAllocationFields.Length; i++)
469+
creatureWindowPending[i](window) = (int)statAllocationFields[i].GetValue(statsObj);
470+
471+
creatureWindowPointsSpent(window) = 0;
472+
}
473+
}
474+
475+
private static void ITabCreatureFillTabPrefix(object __instance, ref bool __state)
476+
{
477+
if (!MP.IsInMultiplayer) return;
478+
479+
if (Find.Selector.SingleSelectedThing is not Pawn selectedPawn) return;
480+
481+
var comp = GetCompByType(selectedPawn, mobRankComponentType);
482+
var stats = comp != null ? mobRankStatsField.GetValue(comp) : null;
483+
if (stats == null) return;
484+
485+
__state = true;
486+
MP.WatchBegin();
487+
foreach (var field in statSyncFields)
488+
field.Watch(stats);
489+
}
490+
491+
#endregion
369492
}
370493
}

0 commit comments

Comments
 (0)