1+ if type (ScriptHawk ) ~= " table" then -- An error message to inform the user that this is a game module, not a standalone script
2+ print (" This script is not designed to run by itself" );
3+ print (" Please run ScriptHawk.lua from the parent directory instead" );
4+ print (" Thanks for using ScriptHawk :)" );
5+ return ;
6+ end
7+
8+ local Game = {
9+ Memory = {
10+ obj_array_base = {Domain = " IWRAM" , Address = 0x2920 },
11+ obj_array_size = {Domain = " IWRAM" , Address = 0x5428 },
12+ grabbed_obj_index = {Domain = " IWRAM" , Address = 0x5262 }
13+ },
14+ speedy_speeds = { .001 , .01 , .1 , 1 , 5 , 10 , 20 , 50 , 100 }, -- D-Pad speeds, scale these appropriately with your game's coordinate system
15+ speedy_index = 7 , -- Default speed, index into the speedy_speeds table
16+ };
17+
18+ ---- ----------------
19+ -- Region/Version --
20+ ---- ----------------
21+ local obj_struct_size = 0x1C ;
22+ local obj_struct = {
23+ [0x00 ] = {type = " s16_le" , name = " XPosition" },
24+ [0x02 ] = {type = " s16_le" , name = " YPosition" },
25+ [0x04 ] = {type = " s16_le" , name = " XScreenOffset" },
26+ [0x06 ] = {type = " s16_le" , name = " YScreenOffset" },
27+ [0x08 ] = {type = " u8_le" , name = " InvincTimer" },
28+ };
29+
30+ function Game .detectVersion (romName , romHash )
31+ ScriptHawk .dpad .joypad .enabled = false ;
32+ ScriptHawk .dpad .key .enabled = false ;
33+ return true ;
34+ end
35+
36+ local function parsePointer (inPtr )
37+ if (inPtr >= 0x02000000 ) and (inPtr < 0x02040000 ) then
38+ return {
39+ Domain = " EWRAM" ,
40+ Address = inPtr - 0x02000000 ,
41+ };
42+ elseif (inPtr >= 0x03000000 ) and (inPtr < 0x03008000 ) then
43+ return {
44+ Domain = " IWRAM" ,
45+ Address = inPtr - 0x03000000 ,
46+ };
47+ end
48+ end
49+
50+ function dereferencePointer (inPtr )
51+ return parsePointer (memory .read_u32_le (inPtr .Address , inPtr .Domain ));
52+ end
53+
54+
55+ ---- ---------------
56+ -- Physics/Scale --
57+ ---- ---------------
58+
59+ -- Optional: If lag in your game is more complicated than a simple emu.islagged() call you should add the logic to detect it here
60+ function Game .isPhysicsFrame ()
61+ -- Implementing this logic will result in smooth dY/dXZ calculation (no more flickering between 0 and the correct value)
62+ return not emu .islagged ();
63+ end
64+
65+ ---- ----------
66+ -- Objects --
67+ ---- ----------
68+ function Game .getObjXPos (obj_index )
69+ local objArraySize = memory .read_u8 (Game .Memory .obj_array_size .Address , Game .Memory .obj_array_size .Domain );
70+ if obj_index < objArraySize then
71+ local objAddr = {Address = Game .Memory .obj_array_base .Address ,
72+ Domain = Game .Memory .obj_array_base .Domain
73+ };
74+ objAddr .Address = objAddr .Address + (obj_index * obj_struct_size );
75+ return memory .read_s16_le (objAddr .Address , objAddr .Domain );
76+ else
77+ return 0 ;
78+ end
79+ end
80+
81+ function Game .getObjYPos (obj_index )
82+ local objArraySize = memory .read_u8 (Game .Memory .obj_array_size .Address , Game .Memory .obj_array_size .Domain );
83+ if obj_index < objArraySize then
84+ local objAddr = {Address = Game .Memory .obj_array_base .Address ,
85+ Domain = Game .Memory .obj_array_base .Domain
86+ };
87+ objAddr .Address = objAddr .Address + (obj_index * obj_struct_size );
88+ return memory .read_s16_le (objAddr .Address + 0x02 , objAddr .Domain );
89+ else
90+ return 0 ;
91+ end
92+ end
93+
94+ function Game .getObjXOffset (obj_index )
95+ local objArraySize = memory .read_u8 (Game .Memory .obj_array_size .Address , Game .Memory .obj_array_size .Domain );
96+ if obj_index < objArraySize then
97+ local objAddr = {Address = Game .Memory .obj_array_base .Address ,
98+ Domain = Game .Memory .obj_array_base .Domain
99+ };
100+ objAddr .Address = objAddr .Address + (obj_index * obj_struct_size );
101+ return memory .read_s16_le (objAddr .Address + 0x04 , objAddr .Domain );
102+ else
103+ return 0 ;
104+ end
105+ end
106+
107+ function Game .getObjYOffset (obj_index )
108+ local objArraySize = memory .read_u8 (Game .Memory .obj_array_size .Address , Game .Memory .obj_array_size .Domain );
109+ if obj_index < objArraySize then
110+ local objAddr = {Address = Game .Memory .obj_array_base .Address ,
111+ Domain = Game .Memory .obj_array_base .Domain
112+ };
113+ objAddr .Address = objAddr .Address + (obj_index * obj_struct_size );
114+ return memory .read_s16_le (objAddr .Address + 0x06 , objAddr .Domain );
115+ else
116+ return 0 ;
117+ end
118+ end
119+
120+ function Game .setObjXPos (obj_index , value )
121+ local objArraySize = memory .read_u8 (Game .Memory .obj_array_size .Address , Game .Memory .obj_array_size .Domain );
122+ if obj_index < objArraySize then
123+ local objAddr = {Address = Game .Memory .obj_array_base .Address ,
124+ Domain = Game .Memory .obj_array_base .Domain
125+ };
126+ objAddr .Address = objAddr .Address + (obj_index * obj_struct_size );
127+ return memory .write_s16_le (objAddr .Address , value , objAddr .Domain );
128+ else
129+ return 0 ;
130+ end
131+ end
132+
133+ function Game .setObjYPos (obj_index , value )
134+ local objArraySize = memory .read_u8 (Game .Memory .obj_array_size .Address , Game .Memory .obj_array_size .Domain );
135+ if obj_index < objArraySize then
136+ local objAddr = {Address = Game .Memory .obj_array_base .Address ,
137+ Domain = Game .Memory .obj_array_base .Domain
138+ };
139+ objAddr .Address = objAddr .Address + (obj_index * obj_struct_size );
140+ return memory .read_s16_le (objAddr .Address + 0x04 , value , objAddr .Domain );
141+ else
142+ return 0 ;
143+ end
144+ end
145+
146+ ---- ----------
147+ -- Position --
148+ ---- ----------
149+ function Game .getXPosition ()
150+ return Game .getObjXPos (0 );
151+ end
152+
153+ function Game .getYPosition ()
154+ return Game .getObjYPos (0 );
155+ end
156+
157+ function Game .setXPosition (value )
158+ Game .setObjXPos (0 , value );
159+ end
160+
161+ function Game .setYPosition (value )
162+ Game .setObjYPos (0 , value );
163+ end
164+
165+
166+ function Game .drawObjectPositions ()
167+ local objArraySize = memory .read_u8 (Game .Memory .obj_array_size .Address , Game .Memory .obj_array_size .Domain );
168+ if objArraySize ~= 0 then
169+ for i = 0 , objArraySize - 1 do
170+ local xOffset = Game .getObjXOffset (i );
171+ local yOffset = Game .getObjYOffset (i );
172+
173+ if xOffset >= 0 and xOffset < 240 then
174+ if yOffset >= 0 and yOffset < 160 then
175+ gui .drawLine (xOffset , yOffset - 2 , xOffset , yOffset + 2 );
176+ gui .drawLine (xOffset - 2 , yOffset , xOffset + 2 , yOffset );
177+ gui .drawText (xOffset , yOffset , toHexString (i ), null , null , 9 );
178+ end
179+ end
180+ end
181+ end
182+ end
183+
184+ ---- --------
185+ -- Events --
186+ ---- --------
187+
188+ function Game .buttonHandler ()
189+ print (" Example button was pressed!" );
190+ end
191+
192+ local labelValue = 0 ;
193+ function Game .initUI () -- Optional: Init any UI state here, mainly useful for setting up your form controls. Runs once at startup after successful version detection.
194+ -- Here are some examples for the most common UI control types
195+ ScriptHawk .UI .form_controls [" Example Dropdown" ] = forms .dropdown (ScriptHawk .UI .options_form , {" Option 1" , " Option 2" , " Option 3" }, ScriptHawk .UI .col (0 ) + ScriptHawk .UI .dropdown_offset , ScriptHawk .UI .row (7 ) + ScriptHawk .UI .dropdown_offset , ScriptHawk .UI .col (9 ) + 7 , ScriptHawk .UI .button_height );
196+ ScriptHawk .UI .button (10 , 7 , {59 }, nil , " Example Button" , " Label" , Game .buttonHandler );
197+ ScriptHawk .UI .button ({13 , - 7 }, 6 , {ScriptHawk .UI .button_height }, nil , " Example Plus Button" , " -" , function () labelValue = labelValue + 1 end );
198+ ScriptHawk .UI .button ({13 , ScriptHawk .UI .button_height - 7 }, 6 , {ScriptHawk .UI .button_height }, nil , " Example Minus Button" , " +" , function () labelValue = labelValue - 1 end );
199+ ScriptHawk .UI .form_controls [" Example Value Label" ] = forms .label (ScriptHawk .UI .options_form , " 0" , ScriptHawk .UI .col (13 ) + ScriptHawk .UI .button_height + 21 , ScriptHawk .UI .row (6 ) + ScriptHawk .UI .label_offset , 54 , 14 );
200+ ScriptHawk .UI .checkbox (10 , 6 , " Example Checkbox" , " Label" );
201+ end
202+
203+ -- Optional: This function should be used to draw to the screen or update form controls
204+ -- When emulation is running it will be called once per frame
205+ -- When emulation is paused it will be called as fast as possible
206+ -- function Game.drawUI()
207+ -- forms.settext(ScriptHawk.UI.form_controls["Example Value Label"], labelValue);
208+ -- end
209+
210+
211+ function Game .eachFrame ()
212+ Game .drawObjectPositions ();
213+ end
214+
215+ Game .OSD = {
216+ {" X" , category = " position" },
217+ {" Y" , category = " position" },
218+ {" Separator" },
219+ {" dY" , category = " positionStats" },
220+ {" dXZ" , category = " positionStats" },
221+ {" Separator" },
222+ {" Max dY" , category = " positionStatsMore" },
223+ {" Max dXZ" , category = " positionStatsMore" },
224+ {" Odometer" , category = " positionStatsMore" },
225+ {" Separator" }
226+ };
227+
228+ return Game ; -- Return your Game table to ScriptHawk
0 commit comments