Skip to content

Commit 81d640d

Browse files
Merge pull request #157 from Arthri/implement-quick-stack-hook
Implement Quick Stack hook
2 parents 3bd0c76 + 234a9a0 commit 81d640d

1 file changed

Lines changed: 119 additions & 1 deletion

File tree

OTAPI.Scripts/Patches/HookChestQuickStack.Server.cs

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ You should have received a copy of the GNU General Public License
2424
using MonoMod;
2525
using MonoMod.Cil;
2626
using System;
27+
using System.Collections.Generic;
2728
using System.Linq;
29+
using Terraria;
30+
using Terraria.GameContent;
31+
using Terraria.ID;
2832

2933
/// <summary>
3034
/// @doc Creates Hooks.Chest.QuickStack.
@@ -39,7 +43,121 @@ partial class ChestHooks
3943
static void HookChestQuickStack(ModFwModder modder)
4044
{
4145
#if TerrariaServer_1450_OrAbove || Terraria__1450_OrAbove || tModLoader_1450_OrAbove
42-
Console.WriteLine("[TODO] reimplement HookChestQuickStack for 1.4.5+");
46+
{
47+
// DO NOT use GetILCursor(). The instruction body does not have jumps transformed into labels,
48+
// a process which is required to happen for the edits below to work.
49+
var ctx = new ILContext(modder.GetMethodDefinition(() => QuickStacking.BuildDestinationMetricsAndStackItems(default, default, default)));
50+
51+
// Hooks onto quick stacking into existing slot
52+
ctx.Invoke((ctx) =>
53+
{
54+
var csr = new ILCursor(ctx);
55+
56+
ILLabel endLabel = null!;
57+
csr.GotoNext(
58+
MoveType.After,
59+
i => i.MatchLdarg(1),
60+
i => i.MatchLdcI4(1),
61+
i => i.MatchStfld(typeof(QuickStacking.DestinationHelper), nameof(QuickStacking.DestinationHelper.transferBlocked)),
62+
i => i.MatchBr(out endLabel)
63+
);
64+
// AfterLabel is not the same as After + MoveAfterLabels for some reason
65+
csr.MoveAfterLabels();
66+
67+
// Load player ID (source.slots[0].Player.whoAmI)
68+
// source
69+
csr.Emit(OpCodes.Ldarg_0);
70+
// ^.slots
71+
csr.Emit(OpCodes.Ldfld, modder.GetFieldDefinition(() => default(QuickStacking.SourceInventory).slots));
72+
// ^[0]
73+
csr.Emit(OpCodes.Ldc_I4_0);
74+
csr.Emit(OpCodes.Ldelem_Any, modder.GetDefinition<PlayerItemSlotID.SlotReference>());
75+
// ^.Player
76+
csr.Emit(OpCodes.Ldfld, modder.GetFieldDefinition(() => default(PlayerItemSlotID.SlotReference).Player));
77+
// ^.whoAmI
78+
csr.Emit(OpCodes.Ldfld, modder.GetFieldDefinition(() => default(Player)!.whoAmI));
79+
80+
// Load item
81+
// NOTE: this is very fragile, but I can't think of a way to write it better without significantly bloating the code
82+
csr.Emit(OpCodes.Ldloc_S, (byte)3);
83+
84+
// Load chest index
85+
csr.Emit(OpCodes.Ldarg_S, (byte)1);
86+
// ^.ChestIndex (property get)
87+
csr.Emit(OpCodes.Callvirt, modder.GetDefinition<QuickStacking.DestinationHelper>().Properties.Single(p => p.Name == "ChestIndex")!.GetMethod);
88+
89+
// Call hook
90+
csr.Emit(OpCodes.Call, modder.GetMethodDefinition(() => OTAPI.Hooks.Chest.InvokeQuickStack(default, default!, default)));
91+
92+
// Continue if handled
93+
csr.Emit(OpCodes.Brfalse, endLabel);
94+
});
95+
}
96+
{
97+
// used for the out parameter only
98+
List<int> _blockedChests;
99+
100+
// DO NOT use GetILCursor(). The instruction body does not have jumps transformed into labels,
101+
// a process which is required to happen for the edits below to work.
102+
var ctx = new ILContext(modder.GetMethodDefinition(() => QuickStacking.Transfer(default, default, out _blockedChests, default)));
103+
104+
// Hooks onto quick stacking overflowing into a new stack
105+
ctx.Invoke((ctx) =>
106+
{
107+
var csr = new ILCursor(ctx);
108+
int count = 0;
109+
110+
while (csr.TryGotoNext(
111+
MoveType.Before,
112+
i => i.MatchLdarg(0),
113+
i => i.MatchLdloc(out _),
114+
i => i.MatchCall(typeof(QuickStacking), nameof(QuickStacking.Consolidate))
115+
))
116+
{
117+
if (++count > 2)
118+
{
119+
throw new Exception($"More than two matches of {nameof(QuickStacking.Consolidate)}.");
120+
}
121+
122+
// Both targets are preceded by a jump instruction exiting the loop
123+
var endInstruction = csr.Prev.Operand;
124+
125+
csr.MoveAfterLabels();
126+
127+
// Load player ID (source.slots[0].Player.whoAmI)
128+
// source
129+
csr.Emit(OpCodes.Ldarg_0);
130+
// ^.slots
131+
csr.Emit(OpCodes.Ldfld, modder.GetFieldDefinition(() => default(QuickStacking.SourceInventory).slots));
132+
// ^[0]
133+
csr.Emit(OpCodes.Ldc_I4_0);
134+
csr.Emit(OpCodes.Ldelem_Any, modder.GetDefinition<PlayerItemSlotID.SlotReference>());
135+
// ^.Player
136+
csr.Emit(OpCodes.Ldfld, modder.GetFieldDefinition(() => default(PlayerItemSlotID.SlotReference).Player));
137+
// ^.whoAmI
138+
csr.Emit(OpCodes.Ldfld, modder.GetFieldDefinition(() => default(Player)!.whoAmI));
139+
140+
// Load item
141+
// NOTE: this is very fragile, but I can't think of a way to write it better without significantly bloating the code
142+
csr.Emit(OpCodes.Ldloc_S, (byte)(count == 1 ? 6 : 9));
143+
144+
// Load chest index
145+
// NOTE: this is very fragile, but I can't think of a way to write it better without significantly bloating the code
146+
csr.Emit(OpCodes.Ldloc_S, (byte)(count == 1 ? 7 : 10));
147+
// ^.ChestIndex (property get)
148+
csr.Emit(OpCodes.Callvirt, modder.GetDefinition<QuickStacking.DestinationHelper>().Properties.Single(p => p.Name == "ChestIndex")!.GetMethod);
149+
150+
// Call hook
151+
csr.Emit(OpCodes.Call, modder.GetMethodDefinition(() => OTAPI.Hooks.Chest.InvokeQuickStack(default, default!, default)));
152+
153+
// Continue if handled
154+
csr.Emit(OpCodes.Brfalse, endInstruction);
155+
156+
// Move after Consolidate call
157+
csr.Goto(csr.Index + 3);
158+
}
159+
});
160+
}
43161
#else
44162
var csr = modder.GetILCursor(() => Terraria.Chest.PutItemInNearbyChest(null, default));
45163
PutItemInNearbyChest = csr.Method;

0 commit comments

Comments
 (0)