1+ if type (ScriptHawk ) ~= " table" then
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+ screen_x_position = 0x70 , -- Fixed u16.8 LE
11+ level_width = 0x7C , -- Single byte, measured in tiles
12+ max_health = 0x98 ,
13+ health = 0x99 ,
14+ lives = 0x9F ,
15+ time_paused = 0xA0 ,
16+ time_frames = 0xA1 , -- u8, maxes at 0x19 and counts down
17+ time_seconds = 0xA2 , -- BCD
18+ time_hundred_seconds = 0xA3 , -- BCD - Anything with 0xA-F in the upper digit is instakill, weird
19+ grabbed_object_pointer = 0x11A , -- 2 byte pointer onto system bus
20+ x_position_level = 0x214 , -- Fixed u16.8 LE
21+ x_position = 0x20C , -- Fixed u8.8 LE
22+ y_position = 0x20A , -- Fixed u8.8 LE
23+ x_velocity = 0x210 , -- Fixed s8.8 LE
24+ y_velocity = 0x20E , -- Fixed s8.8 LE
25+ level_index = 0x9FF , -- Single byte
26+ --[[
27+ Tornados RNG (Level 2) 0x04BA
28+ Boss Health 0x041F
29+ Boss Cycle and RNG 0x041A
30+ --]]
31+ },
32+ };
33+
34+ local enemy_array = 0x200 ;
35+ local enemy_array_capacity = 32 ;
36+ local enemy_size = 0x20 ;
37+ local enemy = {
38+ x_screen = 0x0C ,
39+ y_screen = 0x12 , -- Single byte?
40+ x = 0x14 ,
41+ y = 0x0A ,
42+ };
43+
44+ function Game .read_u16_8 (base )
45+ local major = mainmemory .read_u16_le (base + 1 );
46+ local sub = mainmemory .readbyte (base ) / 256 ;
47+ return major + sub ;
48+ end
49+
50+ function Game .read_s16_8 (base )
51+ local major = mainmemory .read_s16_le (base + 1 );
52+ local sub = mainmemory .readbyte (base ) / 256 ;
53+ return major + sub ;
54+ end
55+
56+ function Game .read_hitbox_y (base )
57+ local major = mainmemory .read_s8 (base + enemy .y_screen ) * 256 ;
58+ local minor = mainmemory .read_u16_le (base + enemy .y ) / 256 ;
59+ return major + minor ;
60+ end
61+
62+ function Game .write_hitbox_y (base , value )
63+ local major = math.floor (value / 256 );
64+ local minor = (value * 256 ) % 0xFFFF ;
65+ mainmemory .write_s8 (base + enemy .y_screen , major );
66+ mainmemory .write_u16_le (base + enemy .y , minor );
67+ end
68+
69+ function Game .write_u16_8 (base , value )
70+ local major = math.floor (value );
71+ local sub = value - major ;
72+ mainmemory .writebyte (base , sub * 256 );
73+ mainmemory .write_u16_le (base + 1 , major );
74+ end
75+
76+ function Game .detectVersion (romName , romHash )
77+ ScriptHawk .dpad .joypad .enabled = false ;
78+ ScriptHawk .dpad .key .enabled = false ;
79+ ScriptHawk .hitboxDefaultMode = ScriptHawk .hitboxModeWHCentered ;
80+ return true ;
81+ end
82+
83+ function Game .getXPosition ()
84+ return Game .read_u16_8 (Game .Memory .x_position_level );
85+ end
86+
87+ function Game .getYPosition ()
88+ return Game .read_hitbox_y (0x200 );
89+ end
90+
91+ function Game .getScreenXPosition ()
92+ return Game .read_u16_8 (Game .Memory .screen_x_position );
93+ end
94+
95+ function Game .getXVelocity ()
96+ return mainmemory .read_s16_le (Game .Memory .x_velocity ) / 256 ;
97+ end
98+
99+ function Game .getYVelocity ()
100+ return mainmemory .read_s16_le (Game .Memory .y_velocity ) / 256 ;
101+ end
102+
103+ function Game .blueWhenInfinites ()
104+ if ScriptHawk .UI .ischecked (" Toggle Infinites Checkbox" ) then
105+ return colors .blue ;
106+ end
107+ end
108+
109+ function Game .applyInfinites ()
110+ -- Set lives to max
111+ mainmemory .writebyte (Game .Memory .lives , 0x10 );
112+
113+ -- Set health to max
114+ mainmemory .writebyte (Game .Memory .health , mainmemory .readbyte (Game .Memory .max_health ));
115+
116+ -- Set time to max
117+ mainmemory .writebyte (Game .Memory .time_seconds , 0x00 );
118+ mainmemory .writebyte (Game .Memory .time_hundred_seconds , 0x10 );
119+ end
120+
121+ function Game .getIGT ()
122+ local frames = mainmemory .readbyte (Game .Memory .time_frames );
123+ local seconds = toHexString (mainmemory .readbyte (Game .Memory .time_seconds ), 2 , " " );
124+ local hundredSeconds = toHexString (mainmemory .readbyte (Game .Memory .time_hundred_seconds ), 2 , " " );
125+ return hundredSeconds .. seconds .. " ." .. frames ;
126+ end
127+
128+ function Game .getHealth ()
129+ return mainmemory .readbyte (Game .Memory .health );
130+ end
131+
132+ function Game .getMaxHealth ()
133+ return mainmemory .readbyte (Game .Memory .max_health );
134+ end
135+
136+ function Game .getHealthOSD ()
137+ return Game .getHealth ().. " /" .. Game .getMaxHealth ();
138+ end
139+
140+ function Game .getBossHealth ()
141+ return 0 ; -- TODO
142+ end
143+
144+ function Game .getHitboxes ()
145+ local hitboxes = {};
146+
147+ local screenX = Game .getScreenXPosition ();
148+ ScriptHawk .hitboxDefaultXOffset = - screenX ;
149+
150+ for base = enemy_array , enemy_array + enemy_array_capacity * enemy_size , enemy_size do
151+ local enemyType = mainmemory .readbyte (base );
152+ if enemyType ~= 0 then
153+ local hitbox = {
154+ base = base ,
155+ dragTag = base ,
156+ enemyType = enemyType ,
157+ x = Game .read_u16_8 (base + enemy .x ),
158+ y = Game .read_hitbox_y (base ),
159+ width = 16 ,
160+ height = 16 ,
161+ };
162+ table.insert (hitboxes , hitbox );
163+ end
164+ end
165+ return hitboxes ;
166+ end
167+
168+ function Game .getHitboxListText (hitbox )
169+ return round (hitbox .x ).. " , " .. round (hitbox .y ).. " - " .. toHexString (hitbox .base );
170+ end
171+
172+ function Game .getHitboxStaticText (hitbox )
173+ return toHexString (hitbox .base );
174+ end
175+
176+ function Game .setHitboxPosition (hitbox , x , y )
177+ Game .write_u16_8 (hitbox .base + enemy .x , x );
178+ Game .write_hitbox_y (hitbox .base , y );
179+ end
180+
181+ function Game .getGrabbedObject ()
182+ return mainmemory .read_u16_le (Game .Memory .grabbed_object_pointer );
183+ end
184+
185+ -- Map Data
186+ local tilemap_start = 0x1600 ;
187+ local screen_height = 0x0A ; -- tiles
188+ local tile_size = 16 ;
189+
190+ function Game .drawMap ()
191+ local screenXPos = Game .getScreenXPosition ();
192+ local startX = math.floor (screenXPos / tile_size );
193+ local endX = startX + 16 ;
194+ local levelWidth = mainmemory .readbyte (Game .Memory .level_width );
195+ local xOffset = ScriptHawk .overscan_compensation .x + (tile_size - screenXPos % tile_size ) - tile_size ;
196+ local yOffset = ScriptHawk .overscan_compensation .y ;
197+ for x = startX , endX do
198+ for y = 0x00 , screen_height - 1 do
199+ local tile = tilemap_start + y * levelWidth + x ;
200+ local tileValue = mainmemory .readbyte (tile );
201+ ScriptHawk .drawText ((x - startX ) * tile_size + xOffset , y * tile_size + yOffset , toHexString (tileValue , 2 , " " ), nil , 0 , true );
202+ end
203+ end
204+ -- print_deferred();
205+ end
206+
207+ function Game .drawUI ()
208+ if (ScriptHawk .UI .isChecked (" Draw Map Checkbox" )) then
209+ Game .drawMap ();
210+ end
211+ end
212+
213+ function Game .initUI ()
214+ ScriptHawk .UI .checkbox (10 , 4 , " Draw Map Checkbox" , " Draw Map" );
215+ end
216+
217+ Game .OSD = {
218+ {" IGT" , Game .getIGT , Game .blueWhenInfinites },
219+ {" Health" , Game .getHealthOSD , Game .blueWhenInfinites },
220+ {" Separator" },
221+ {" X" },
222+ {" Y" },
223+ {" dX" },
224+ {" dY" },
225+ {" X Velocity" , Game .getXVelocity },
226+ {" Y Velocity" , Game .getYVelocity },
227+ {" Separator" },
228+ {" Screen X" , Game .getScreenXPosition },
229+ {" Grabbed" , hexifyOSD (Game .getGrabbedObject )},
230+ };
231+
232+ return Game ;
0 commit comments