@@ -24,7 +24,11 @@ You should have received a copy of the GNU General Public License
2424using MonoMod ;
2525using MonoMod . Cil ;
2626using System ;
27+ using System . Collections . Generic ;
2728using 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