· 9 years ago · Nov 29, 2016, 09:54 PM
1---------------------------------------------------------------------------
2-- Super Mario World (U) Utility Script for BizHawk
3-- http://tasvideos.org/Bizhawk.html
4--
5-- Author: Rodrigo A. do Amaral (Amaraticando)
6-- Git repository: https://github.com/rodamaral/smw-tas
7---------------------------------------------------------------------------
8
9--#############################################################################
10-- CONFIG:
11
12local INI_CONFIG_FILENAME = "config.ini" -- relative to the folder of the script
13local OLD_EMU_VERSION
14
15local DEFAULT_OPTIONS = {
16 -- Display
17 display_movie_info = false, -- BizHawk: has good built-in movie info with custom positioning
18 display_misc_info = true,
19 display_player_info = true,
20 display_player_hitbox = true, -- can be changed by right-clicking on player
21 display_interaction_points = true, -- can be changed by right-clicking on player
22 display_sprite_info = true,
23 display_sprite_hitbox = true, -- you still have to select the sprite with the mouse
24 display_extended_sprite_info = false,
25 display_cluster_sprite_info = true,
26 display_minor_extended_sprite_info = true,
27 display_bounce_sprite_info = true,
28 display_level_info = false,
29 display_yoshi_info = true,
30 display_counters = true,
31 display_static_camera_region = false, -- shows the region in which the camera won't scroll horizontally
32 draw_tiles_with_click = false,
33
34 -- Some extra/debug info
35 display_debug_info = false, -- shows useful info while investigating the game, but not very useful while TASing
36 display_debug_player_extra = true,
37 display_debug_sprite_extra = true,
38 display_debug_sprite_tweakers = true,
39 display_debug_extended_sprite = true,
40 display_debug_cluster_sprite = true,
41 display_debug_minor_extended_sprite = true,
42 display_debug_bounce_sprite = true,
43 display_debug_controller_data = true,
44 display_miscellaneous_sprite_table = false,
45 miscellaneous_sprite_table_number = {[1] = true, [2] = true, [3] = true, [4] = true, [5] = true, [6] = true, [7] = true, [8] = true, [9] = true,
46 [10] = true, [11] = true, [12] = true, [13] = true, [14] = true, [15] = true, [16] = true, [17] = true, [18] = true, [19] = true
47 },
48
49 -- Script settings
50 max_tiles_drawn = 20, -- the max number of tiles to be drawn/registered by the script
51
52 -- Lateral gaps (initial values) / bizhawk specific
53 left_gap = 100,
54 right_gap = 100,
55 top_gap = 20,
56 bottom_gap = 8,
57}
58
59-- Colour settings
60local DEFAULT_COLOUR = {
61 -- Text
62 default_text_opacity = 1.0,
63 default_bg_opacity = 0.4,
64 text = "#ffffffff",
65 background = "#000000ff",
66 outline = "#000040ff",
67 warning = "#ff0000ff",
68 warning_bg = "#0000ffff",
69 warning2 = "#ff00ffff",
70 weak = "#a9a9a9ff",
71 very_weak = "#ffffff60",
72 joystick_input = "#ffff00ff",
73 joystick_input_bg = "#ffffff30",
74 button_text = "#300030ff",
75 mainmenu_outline = "#ffffffc0",
76 mainmenu_bg = "#000000c0",
77
78 -- Counters
79 counter_pipe = "#00ff00ff",
80 counter_multicoin = "#ffff00ff",
81 counter_gray_pow = "#a5a5a5ff",
82 counter_blue_pow = "#4242deff",
83 counter_dircoin = "#8c5a19ff",
84 counter_pballoon = "#f8d870ff",
85 counter_star = "#ffd773ff",
86 counter_fireflower = "#ff8c00ff",
87
88 -- hitbox and related text
89 mario = "#ff0000ff",
90 mario_bg = "#00000000",
91 mario_mounted_bg = "#00000000",
92 interaction = "#ffffffff",
93 interaction_bg = "#00000020",
94 interaction_nohitbox = "#000000a0",
95 interaction_nohitbox_bg = "#00000070",
96
97 sprites = {"#00ff00ff", "#0000ffff", "#ffff00ff", "#ff00ffff", "#b00040ff"},
98 sprites_interaction_pts = "#ffffffff",
99 sprites_bg = "#0000b050",
100 sprites_clipping_bg = "#000000a0",
101 extended_sprites = "#ff8000ff",
102 extended_sprites_bg = "#00ff0050",
103 special_extended_sprite_bg = "#00ff0060",
104 goal_tape_bg = "#ffff0050",
105 fireball = "#b0d0ffff",
106 baseball = "#0040a0ff",
107 cluster_sprites = "#ff80a0ff",
108 sumo_brother_flame = "#0040a0ff",
109 minor_extended_sprites = "#ff90b0ff",
110 awkward_hitbox = "#204060ff",
111 awkward_hitbox_bg = "#ff800060",
112
113 yoshi = "#00ffffff",
114 yoshi_bg = "#00ffff40",
115 yoshi_mounted_bg = "#00000000",
116 tongue_line = "#ffa000ff",
117 tongue_bg = "#00000060",
118
119 cape = "#ffd700ff",
120 cape_bg = "#ffd70060",
121
122 block = "#00008bff",
123 blank_tile = "#ffffff70",
124 block_bg = "#22cc88a0",
125 layer2_line = "#ff2060ff",
126 layer2_bg = "#ff206040",
127 static_camera_region = "#40002040",
128}
129
130-- Font settings
131local BIZHAWK_FONT_HEIGHT = 14
132local BIZHAWK_FONT_WIDTH = 10
133
134-- Symbols
135local LEFT_ARROW = "<-"
136local RIGHT_ARROW = "->"
137
138-- Others
139local Y_CAMERA_OFF = 1 -- small adjustment to display the tiles according to their actual graphics
140
141-- Input key names
142local INPUT_KEYNAMES = { -- BizHawk
143
144 A=false, Add=false, Alt=false, Apps=false, Attn=false, B=false, Back=false, BrowserBack=false, BrowserFavorites=false,
145 BrowserForward=false, BrowserHome=false, BrowserRefresh=false, BrowserSearch=false, BrowserStop=false, C=false,
146 Cancel=false, Capital=false, CapsLock=false, Clear=false, Control=false, ControlKey=false, Crsel=false, D=false, D0=false,
147 D1=false, D2=false, D3=false, D4=false, D5=false, D6=false, D7=false, D8=false, D9=false, Decimal=false, Delete=false,
148 Divide=false, Down=false, E=false, End=false, Enter=false, EraseEof=false, Escape=false, Execute=false, Exsel=false,
149 F=false, F1=false, F10=false, F11=false, F12=false, F13=false, F14=false, F15=false, F16=false, F17=false, F18=false,
150 F19=false, F2=false, F20=false, F21=false, F22=false, F23=false, F24=false, F3=false, F4=false, F5=false, F6=false,
151 F7=false, F8=false, F9=false, FinalMode=false, G=false, H=false, HanguelMode=false, HangulMode=false, HanjaMode=false,
152 Help=false, Home=false, I=false, IMEAccept=false, IMEAceept=false, IMEConvert=false, IMEModeChange=false,
153 IMENonconvert=false, Insert=false, J=false, JunjaMode=false, K=false, KanaMode=false, KanjiMode=false, KeyCode=false,
154 L=false, LaunchApplication1=false, LaunchApplication2=false, LaunchMail=false, LButton=false, LControlKey=false,
155 Left=false, LineFeed=false, LMenu=false, LShiftKey=false, LWin=false, M=false, MButton=false, MediaNextTrack=false,
156 MediaPlayPause=false, MediaPreviousTrack=false, MediaStop=false, Menu=false, Modifiers=false, Multiply=false, N=false,
157 Next=false, NoName=false, None=false, NumLock=false, NumPad0=false, NumPad1=false, NumPad2=false, NumPad3=false,
158 NumPad4=false, NumPad5=false, NumPad6=false, NumPad7=false, NumPad8=false, NumPad9=false, O=false, Oem1=false,
159 Oem102=false, Oem2=false, Oem3=false, Oem4=false, Oem5=false, Oem6=false, Oem7=false, Oem8=false, OemBackslash=false,
160 OemClear=false, OemCloseBrackets=false, Oemcomma=false, OemMinus=false, OemOpenBrackets=false, OemPeriod=false,
161 OemPipe=false, Oemplus=false, OemQuestion=false, OemQuotes=false, OemSemicolon=false, Oemtilde=false, P=false, Pa1=false,
162 Packet=false, PageDown=false, PageUp=false, Pause=false, Play=false, Print=false, PrintScreen=false, Prior=false,
163 ProcessKey=false, Q=false, R=false, RButton=false, RControlKey=false, Return=false, Right=false, RMenu=false, RShiftKey=false,
164 RWin=false, S=false, Scroll=false, Select=false, SelectMedia=false, Separator=false, Shift=false, ShiftKey=false,
165 Sleep=false, Snapshot=false, Space=false, Subtract=false, T=false, Tab=false, U=false, Up=false, V=false, VolumeDown=false,
166 VolumeMute=false, VolumeUp=false, W=false, X=false, XButton1=false, XButton2=false, Y=false, Z=false, Zoom=false
167}
168
169-- END OF CONFIG < < < < < < <
170--#############################################################################
171-- INITIAL STATEMENTS:
172
173
174-- Load environment
175local gui, input, joypad, emu, movie, memory, mainmemory, bit = gui, input, joypad, emu, movie, memory, mainmemory, bit
176local unpack = unpack or table.unpack
177local string, math, table, next, ipairs, pairs, io, os, type = string, math, table, next, ipairs, pairs, io, os, type
178
179-- Script tries to verify whether the emulator is indeed BizHawk
180if tastudio == nil then
181 gui.text(0, 0, "This script works with BizHawk emulator.")
182 error("This script works with BizHawk emulator.")
183elseif gui.drawAxis == nil then
184 gui.text(0, 0, "This script works with BizHawk 1.11.0 or superior.")
185 gui.text(0, 16, "Your version seems to be older.")
186 gui.text(0, 32, "Visit http://tasvideos.org/Bizhawk.html to download the latest version.")
187 error("This script works with BizHawk 1.11.0 or superior.")
188else
189 OLD_EMU_VERSION = client.SetGameExtraPadding == nil
190end
191
192print("\nStarting smw-bizhawk script.")
193
194-- TEST: INI library for handling an ini configuration file
195function file_exists(name)
196 local f = io.open(name, "r")
197 if f ~= nil then io.close(f) return true else return false end
198end
199
200function copytable(orig)
201 local orig_type = type(orig)
202 local copy
203 if orig_type == 'table' then
204 copy = {}
205 for orig_key, orig_value in next, orig, nil do
206 copy[copytable(orig_key)] = copytable(orig_value) -- possible stack overflow
207 end
208 setmetatable(copy, copytable(getmetatable(orig)))
209 else -- number, string, boolean, etc
210 copy = orig
211 end
212 return copy
213end
214
215function mergetable(source, t2)
216 for key, value in pairs(t2) do
217 if type(value) == "table" then
218 if type(source[key] or false) == "table" then
219 mergetable(source[key] or {}, t2[key] or {}) -- possible stack overflow
220 else
221 source[key] = value
222 end
223 else
224 source[key] = value
225 end
226 end
227 return source
228end
229
230-- Creates a set from a list
231local function make_set(list)
232 local set = {}
233 for _, l in ipairs(list) do set[l] = true end
234 return set
235end
236
237local INI = {}
238
239function INI.arg_to_string(value)
240 local str
241 if type(value) == "string" then
242 str = "\"" .. value .. "\""
243 elseif type(value) == "number" or type(value) == "boolean" or value == nil then
244 str = tostring(value)
245 elseif type(value) == "table" then
246 local tmp = {"{"} -- only arrays
247 for a, b in ipairs(value) do
248 table.insert(tmp, ("%s%s"):format(INI.arg_to_string(b), a ~= #value and ", " or "")) -- possible stack overflow
249 end
250 table.insert(tmp, "}")
251 str = table.concat(tmp)
252 else
253 str = "#BAD_VALUE"
254 end
255
256 return str
257end
258
259-- creates the string for ini
260function INI.data_to_string(data)
261 local sections = {}
262
263 for section, prop in pairs(data) do
264 local properties = {}
265
266 for key, value in pairs(prop) do
267 table.insert(properties, ("%s = %s\n"):format(key, INI.arg_to_string(value))) -- properties
268 end
269
270 table.sort(properties)
271 table.insert(sections, ("[%s]\n"):format(section) .. table.concat(properties) .. "\n")
272 end
273
274 table.sort(sections)
275 return table.concat(sections)
276end
277
278function INI.string_to_data(value)
279 local data
280
281 if tonumber(value) then
282 data = tonumber(value)
283 elseif value == "true" then
284 data = true
285 elseif value == "false" then
286 data = false
287 elseif value == "nil" then
288 data = nil
289 else
290 local quote1, text, quote2 = value:match("(['\"{])(.+)(['\"}])") -- value is surrounded by "", '' or {}?
291 if quote1 and quote2 and text then
292 if (quote1 == '"' or quote1 == "'") and quote1 == quote2 then
293 data = text
294 elseif quote1 == "{" and quote2 == "}" then
295 local tmp = {} -- test
296 for words in text:gmatch("[^,%s]+") do
297 tmp[#tmp + 1] = INI.string_to_data(words) -- possible stack overflow
298 end
299
300 data = tmp
301 else
302 data = value
303 end
304 else
305 data = value
306 end
307 end
308
309 return data
310end
311
312function INI.load(filename)
313 local file = io.open(filename, "r")
314 if not file then return false end
315
316 local data, section = {}, nil
317
318 for line in file:lines() do
319 local new_section = line:match("^%[([^%[%]]+)%]$")
320
321 if new_section then
322 section = INI.string_to_data(new_section) and INI.string_to_data(new_section) or new_section
323 if data[section] then print("Duplicated section") end
324 data[section] = data[section] or {}
325 else
326
327 local prop, value = line:match("^([%w_%-%.]+)%s*=%s*(.+)%s*$") -- prop = value
328
329 if prop and value then
330 value = INI.string_to_data(value)
331 prop = INI.string_to_data(prop) and INI.string_to_data(prop) or prop
332
333 if data[section] == nil then print(prop, value) ; error("Property outside section") end
334 data[section][prop] = value
335 else
336 local ignore = line:match("^;") or line == ""
337 if not ignore then
338 print("BAD LINE:", line, prop, value)
339 end
340 end
341
342 end
343
344 end
345
346 file:close()
347 return data
348end
349
350function INI.retrieve(filename, data)
351 if type(data) ~= "table" then error"data must be a table" end
352 local data, previous_data = copytable(data), nil
353
354 -- Verifies if file already exists
355 if file_exists(filename) then
356 ini_data = INI.load(filename)
357 else return data
358 end
359
360 -- Adds previous values to the new ini
361 local union_data = mergetable(data, ini_data)
362 return union_data
363end
364
365function INI.overwrite(filename, data)
366 local file, err = assert(io.open(filename, "w"), "Error loading file :" .. filename)
367 if not file then print(err) ; return end
368
369 file:write(INI.data_to_string(data))
370 file:close()
371end
372
373function INI.save(filename, data)
374 if type(data) ~= "table" then error"data must be a table" end
375
376 local tmp, previous_data
377 if file_exists(filename) then
378 previous_data = INI.load(filename)
379 tmp = mergetable(previous_data, data)
380 else
381 tmp = data
382 end
383
384 INI.overwrite(filename, tmp)
385end
386
387local function color_number(str)
388 local r, g, b, a = str:match("^#(%x+%x+)(%x+%x+)(%x+%x+)(%x+%x+)$")
389 if not a then print(str) return gui.color(str) end -- lsnes specific
390
391 r, g, b, a = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), tonumber(a, 16)
392 return 0x1000000*a + 0x10000*r + 0x100*g + b -- BizHawk specific
393end
394
395local OPTIONS = file_exists(INI_CONFIG_FILENAME) and
396 INI.retrieve(INI_CONFIG_FILENAME, {["BIZHAWK OPTIONS"] = DEFAULT_OPTIONS})["BIZHAWK OPTIONS"] or DEFAULT_OPTIONS
397local COLOUR = file_exists(INI_CONFIG_FILENAME) and
398 INI.retrieve(INI_CONFIG_FILENAME, {["BIZHAWK COLOURS"] = DEFAULT_COLOUR})["BIZHAWK COLOURS"] or DEFAULT_COLOUR
399INI.save(INI_CONFIG_FILENAME, {["BIZHAWK COLOURS"] = COLOUR})
400INI.save(INI_CONFIG_FILENAME, {["BIZHAWK OPTIONS"] = OPTIONS})
401
402function interpret_color(data)
403 for k, v in pairs(data) do
404 if type(v) == "string" then
405 data[k] = type(v) == "string" and color_number(v) or v
406 elseif type(v) == "table" then
407 interpret_color(data[k]) -- possible stack overflow
408 end
409 end
410end
411interpret_color(COLOUR)
412
413function INI.save_options()
414 INI.save(INI_CONFIG_FILENAME, {["BIZHAWK OPTIONS"] = OPTIONS})
415end
416
417--######################## -- end of test
418
419-- Text/Background_max_opacity is only changed by the player using the hotkeys
420-- Text/Bg_opacity must be used locally inside the functions
421local Text_max_opacity = COLOUR.default_text_opacity
422local Background_max_opacity = COLOUR.default_bg_opacity
423local Text_opacity = 1
424local Bg_opacity = 1
425
426local fmt = string.format
427local floor = math.floor
428
429-- unsigned to signed (based in <bits> bits)
430local function signed(num, bits)
431 local maxval = 2^(bits - 1)
432 if num < maxval then return num else return num - 2*maxval end
433end
434
435-- Compatibility of the memory read/write functions
436local u8 = mainmemory.read_u8
437local s8 = mainmemory.read_s8
438local w8 = mainmemory.write_u8
439local u16 = mainmemory.read_u16_le
440local s16 = mainmemory.read_s16_le
441local w16 = mainmemory.write_u16_le
442local u24 = mainmemory.read_u24_le
443local s24 = mainmemory.read_s24_le
444local w24 = mainmemory.write_u32_le
445
446
447--#############################################################################
448-- GAME AND SNES SPECIFIC MACROS:
449
450
451local NTSC_FRAMERATE = 60.0988138974405
452local PAL_FRAMERATE = 50.0069789081886
453
454local SMW = {
455 -- Game Modes
456 game_mode_overworld = 0x0e,
457 game_mode_level = 0x14,
458
459 -- Sprites
460 sprite_max = 12,
461 extended_sprite_max = 10,
462 cluster_sprite_max = 20,
463 minor_extended_sprite_max = 12,
464 bounce_sprite_max = 4,
465 null_sprite_id = 0xff,
466
467 -- Blocks
468 blank_tile_map16 = 0x25,
469}
470
471WRAM = {
472 -- I/O
473 ctrl_1_1 = 0x0015,
474 ctrl_1_2 = 0x0017,
475 firstctrl_1_1 = 0x0016,
476 firstctrl_1_2 = 0x0018,
477
478 -- General
479 game_mode = 0x0100,
480 real_frame = 0x0013,
481 effective_frame = 0x0014,
482 lag_indicator = 0x01fe,
483 timer_frame_counter = 0x0f30,
484 RNG = 0x148d,
485 current_level = 0x00fe, -- plus 1
486 sprite_memory_header = 0x1692,
487 lock_animation_flag = 0x009d, -- Most codes will still run if this is set, but almost nothing will move or animate.
488 level_mode_settings = 0x1925,
489 star_road_speed = 0x1df7,
490 star_road_timer = 0x1df8,
491
492 -- Cheats
493 frozen = 0x13fb,
494 level_paused = 0x13d4,
495 level_index = 0x13bf,
496 room_index = 0x00ce,
497 level_flag_table = 0x1ea2,
498 level_exit_type = 0x0dd5,
499 midway_point = 0x13ce,
500
501 -- Camera
502 camera_x = 0x1462,
503 camera_y = 0x1464,
504 screens_number = 0x005d,
505 hscreen_number = 0x005e,
506 vscreen_number = 0x005f,
507 vertical_scroll_flag_header = 0x1412, -- #$00 = Disable; #$01 = Enable; #$02 = Enable if flying/climbing/etc.
508 vertical_scroll_enabled = 0x13f1,
509 camera_scroll_timer = 0x1401,
510
511 -- Sprites
512 sprite_status = 0x14c8,
513 sprite_number = 0x009e,
514 sprite_x_high = 0x14e0,
515 sprite_x_low = 0x00e4,
516 sprite_y_high = 0x14d4,
517 sprite_y_low = 0x00d8,
518 sprite_x_sub = 0x14f8,
519 sprite_y_sub = 0x14ec,
520 sprite_x_speed = 0x00b6,
521 sprite_y_speed = 0x00aa,
522 sprite_x_offscreen = 0x15a0,
523 sprite_y_offscreen = 0x186c,
524 sprite_miscellaneous1 = 0x00c2,
525 sprite_miscellaneous2 = 0x1504,
526 sprite_miscellaneous3 = 0x1510,
527 sprite_miscellaneous4 = 0x151c,
528 sprite_miscellaneous5 = 0x1528,
529 sprite_miscellaneous6 = 0x1534,
530 sprite_miscellaneous7 = 0x1540,
531 sprite_miscellaneous8 = 0x154c,
532 sprite_miscellaneous9 = 0x1558,
533 sprite_miscellaneous10 = 0x1564,
534 sprite_miscellaneous11 = 0x1570,
535 sprite_miscellaneous12 = 0x157c,
536 sprite_miscellaneous13 = 0x1594,
537 sprite_miscellaneous14 = 0x15ac,
538 sprite_miscellaneous15 = 0x1602,
539 sprite_miscellaneous16 = 0x160e,
540 sprite_miscellaneous17 = 0x1626,
541 sprite_miscellaneous18 = 0x163e,
542 sprite_miscellaneous19 = 0x187b,
543 sprite_underwater = 0x164a,
544 sprite_disable_cape = 0x1fe2,
545 sprite_1_tweaker = 0x1656,
546 sprite_2_tweaker = 0x1662,
547 sprite_3_tweaker = 0x166e,
548 sprite_4_tweaker = 0x167a,
549 sprite_5_tweaker = 0x1686,
550 sprite_6_tweaker = 0x190f,
551 sprite_tongue_wait = 0x14a3,
552 sprite_yoshi_squatting = 0x18af,
553 sprite_buoyancy = 0x190e,
554
555 -- Extended sprites
556 extspr_number = 0x170b,
557 extspr_x_high = 0x1733,
558 extspr_x_low = 0x171f,
559 extspr_y_high = 0x1729,
560 extspr_y_low = 0x1715,
561 extspr_x_speed = 0x1747,
562 extspr_y_speed = 0x173d,
563 extspr_suby = 0x1751,
564 extspr_subx = 0x175b,
565 extspr_table = 0x1765,
566 extspr_table2 = 0x176f,
567
568 -- Cluster sprites
569 cluspr_flag = 0x18b8,
570 cluspr_number = 0x1892,
571 cluspr_x_high = 0x1e3e,
572 cluspr_x_low = 0x1e16,
573 cluspr_y_high = 0x1e2a,
574 cluspr_y_low = 0x1e02,
575 cluspr_timer = 0x0f9a,
576 cluspr_table_1 = 0x0f4a,
577 cluspr_table_2 = 0x0f72,
578 cluspr_table_3 = 0x0f86,
579 reappearing_boo_counter = 0x190a,
580
581 -- Minor extended sprites
582 minorspr_number = 0x17f0,
583 minorspr_x_high = 0x18ea,
584 minorspr_x_low = 0x1808,
585 minorspr_y_high = 0x1814,
586 minorspr_y_low = 0x17fc,
587 minorspr_xspeed = 0x182c,
588 minorspr_yspeed = 0x1820,
589 minorspr_x_sub = 0x1844,
590 minorspr_y_sub = 0x1838,
591 minorspr_timer = 0x1850,
592
593 -- Bounce sprites
594 bouncespr_number = 0x1699,
595 bouncespr_x_high = 0x16ad,
596 bouncespr_x_low = 0x16a5,
597 bouncespr_y_high = 0x16a9,
598 bouncespr_y_low = 0x16a1,
599 bouncespr_timer = 0x16c5,
600 bouncespr_last_id = 0x18cd,
601 turn_block_timer = 0x18ce,
602
603 -- Player
604 x = 0x0094,
605 y = 0x0096,
606 previous_x = 0x00d1,
607 previous_y = 0x00d3,
608 x_sub = 0x13da,
609 y_sub = 0x13dc,
610 x_speed = 0x007b,
611 x_subspeed = 0x007a,
612 y_speed = 0x007d,
613 direction = 0x0076,
614 is_ducking = 0x0073,
615 p_meter = 0x13e4,
616 take_off = 0x149f,
617 powerup = 0x0019,
618 cape_spin = 0x14a6,
619 cape_fall = 0x14a5,
620 cape_interaction = 0x13e8,
621 flight_animation = 0x1407,
622 diving_status = 0x1409,
623 player_animation_trigger = 0x0071,
624 climbing_status = 0x0074,
625 spinjump_flag = 0x140d,
626 player_blocked_status = 0x0077,
627 player_item = 0x0dc2, --hex
628 cape_x = 0x13e9,
629 cape_y = 0x13eb,
630 on_ground = 0x13ef,
631 on_ground_delay = 0x008d,
632 on_air = 0x0072,
633 can_jump_from_water = 0x13fa,
634 carrying_item = 0x148f,
635 mario_score = 0x0f34,
636 player_coin = 0x0dbf,
637 player_looking_up = 0x13de,
638
639 -- Yoshi
640 yoshi_riding_flag = 0x187a, -- #$00 = No, #$01 = Yes, #$02 = Yes, and turning around.
641 yoshi_tile_pos = 0x0d8c,
642
643 -- Timer
644 --keep_mode_active = 0x0db1,
645 pipe_entrance_timer = 0x0088,
646 score_incrementing = 0x13d6,
647 end_level_timer = 0x1493,
648 multicoin_block_timer = 0x186b,
649 gray_pow_timer = 0x14ae,
650 blue_pow_timer = 0x14ad,
651 dircoin_timer = 0x190c,
652 pballoon_timer = 0x1891,
653 star_timer = 0x1490,
654 animation_timer = 0x1496,--
655 invisibility_timer = 0x1497,
656 fireflower_timer = 0x149b,
657 yoshi_timer = 0x18e8,
658 swallow_timer = 0x18ac,
659 lakitu_timer = 0x18e0,
660 spinjump_fireball_timer = 0x13e2,
661
662 -- Layers
663 layer2_x_nextframe = 0x1466,
664 layer2_y_nextframe = 0x1468,
665}
666local WRAM = WRAM
667
668local X_INTERACTION_POINTS = {center = 0x8, left_side = 0x2 + 1, left_foot = 0x5, right_side = 0xe - 1, right_foot = 0xb}
669
670local Y_INTERACTION_POINTS = {
671 {head = 0x10, center = 0x18, shoulder = 0x16, side = 0x1a, foot = 0x20, sprite = 0x15},
672 {head = 0x08, center = 0x12, shoulder = 0x0f, side = 0x1a, foot = 0x20, sprite = 0x07},
673 {head = 0x13, center = 0x1d, shoulder = 0x19, side = 0x28, foot = 0x30, sprite = 0x19},
674 {head = 0x10, center = 0x1a, shoulder = 0x16, side = 0x28, foot = 0x30, sprite = 0x11}
675}
676
677local HITBOX_SPRITE = { -- sprites' hitbox against player and other sprites
678 [0x00] = { xoff = 2, yoff = 3, width = 12, height = 10, oscillation = true },
679 [0x01] = { xoff = 2, yoff = 3, width = 12, height = 21, oscillation = true },
680 [0x02] = { xoff = 16, yoff = -2, width = 16, height = 18, oscillation = true },
681 [0x03] = { xoff = 20, yoff = 8, width = 8, height = 8, oscillation = true },
682 [0x04] = { xoff = 0, yoff = -2, width = 48, height = 14, oscillation = true },
683 [0x05] = { xoff = 0, yoff = -2, width = 80, height = 14, oscillation = true },
684 [0x06] = { xoff = 1, yoff = 2, width = 14, height = 24, oscillation = true },
685 [0x07] = { xoff = 8, yoff = 8, width = 40, height = 48, oscillation = true },
686 [0x08] = { xoff = -8, yoff = -2, width = 32, height = 16, oscillation = true },
687 [0x09] = { xoff = -2, yoff = 8, width = 20, height = 30, oscillation = true },
688 [0x0a] = { xoff = 3, yoff = 7, width = 1, height = 2, oscillation = true },
689 [0x0b] = { xoff = 6, yoff = 6, width = 3, height = 3, oscillation = true },
690 [0x0c] = { xoff = 1, yoff = -2, width = 13, height = 22, oscillation = true },
691 [0x0d] = { xoff = 0, yoff = -4, width = 15, height = 16, oscillation = true },
692 [0x0e] = { xoff = 6, yoff = 6, width = 20, height = 20, oscillation = true },
693 [0x0f] = { xoff = 2, yoff = -2, width = 36, height = 18, oscillation = true },
694 [0x10] = { xoff = 0, yoff = -2, width = 15, height = 32, oscillation = true },
695 [0x11] = { xoff = -24, yoff = -24, width = 64, height = 64, oscillation = true },
696 [0x12] = { xoff = -4, yoff = 16, width = 8, height = 52, oscillation = true },
697 [0x13] = { xoff = -4, yoff = 16, width = 8, height = 116, oscillation = true },
698 [0x14] = { xoff = 4, yoff = 2, width = 24, height = 12, oscillation = true },
699 [0x15] = { xoff = 0, yoff = -2, width = 15, height = 14, oscillation = true },
700 [0x16] = { xoff = -4, yoff = -12, width = 24, height = 24, oscillation = true },
701 [0x17] = { xoff = 2, yoff = 8, width = 12, height = 69, oscillation = true },
702 [0x18] = { xoff = 2, yoff = 19, width = 12, height = 58, oscillation = true },
703 [0x19] = { xoff = 2, yoff = 35, width = 12, height = 42, oscillation = true },
704 [0x1a] = { xoff = 2, yoff = 51, width = 12, height = 26, oscillation = true },
705 [0x1b] = { xoff = 2, yoff = 67, width = 12, height = 10, oscillation = true },
706 [0x1c] = { xoff = 0, yoff = 10, width = 10, height = 48, oscillation = true },
707 [0x1d] = { xoff = 2, yoff = -3, width = 28, height = 27, oscillation = true },
708 [0x1e] = { xoff = 6, yoff = -8, width = 3, height = 32, oscillation = true }, -- default: { xoff = -32, yoff = -8, width = 48, height = 32, oscillation = true },
709 [0x1f] = { xoff = -16, yoff = -4, width = 48, height = 18, oscillation = true },
710 [0x20] = { xoff = -4, yoff = -24, width = 8, height = 24, oscillation = true },
711 [0x21] = { xoff = -4, yoff = 16, width = 8, height = 24, oscillation = true },
712 [0x22] = { xoff = 0, yoff = 0, width = 16, height = 16, oscillation = true },
713 [0x23] = { xoff = -8, yoff = -24, width = 32, height = 32, oscillation = true },
714 [0x24] = { xoff = -12, yoff = 32, width = 56, height = 56, oscillation = true },
715 [0x25] = { xoff = -14, yoff = 4, width = 60, height = 20, oscillation = true },
716 [0x26] = { xoff = 0, yoff = 88, width = 32, height = 8, oscillation = true },
717 [0x27] = { xoff = -4, yoff = -4, width = 24, height = 24, oscillation = true },
718 [0x28] = { xoff = -14, yoff = -24, width = 28, height = 40, oscillation = true },
719 [0x29] = { xoff = -16, yoff = -4, width = 32, height = 27, oscillation = true },
720 [0x2a] = { xoff = 2, yoff = -8, width = 12, height = 19, oscillation = true },
721 [0x2b] = { xoff = 0, yoff = 2, width = 16, height = 76, oscillation = true },
722 [0x2c] = { xoff = -8, yoff = -8, width = 16, height = 16, oscillation = true },
723 [0x2d] = { xoff = 4, yoff = 4, width = 8, height = 4, oscillation = true },
724 [0x2e] = { xoff = 2, yoff = -2, width = 28, height = 34, oscillation = true },
725 [0x2f] = { xoff = 2, yoff = -2, width = 28, height = 32, oscillation = true },
726 [0x30] = { xoff = 8, yoff = -14, width = 16, height = 28, oscillation = true },
727 [0x31] = { xoff = 0, yoff = -2, width = 48, height = 18, oscillation = true },
728 [0x32] = { xoff = 0, yoff = -2, width = 48, height = 18, oscillation = true },
729 [0x33] = { xoff = 0, yoff = -2, width = 64, height = 18, oscillation = true },
730 [0x34] = { xoff = -4, yoff = -4, width = 8, height = 8, oscillation = true },
731 [0x35] = { xoff = 3, yoff = 0, width = 18, height = 32, oscillation = true },
732 [0x36] = { xoff = 8, yoff = 8, width = 52, height = 46, oscillation = true },
733 [0x37] = { xoff = 0, yoff = -8, width = 15, height = 20, oscillation = true },
734 [0x38] = { xoff = 8, yoff = 16, width = 32, height = 40, oscillation = true },
735 [0x39] = { xoff = 4, yoff = 3, width = 8, height = 10, oscillation = true },
736 [0x3a] = { xoff = -8, yoff = 16, width = 32, height = 16, oscillation = true },
737 [0x3b] = { xoff = 0, yoff = 0, width = 16, height = 13, oscillation = true },
738 [0x3c] = { xoff = 12, yoff = 10, width = 3, height = 6, oscillation = true },
739 [0x3d] = { xoff = 12, yoff = 21, width = 3, height = 20, oscillation = true },
740 [0x3e] = { xoff = 16, yoff = 18, width = 254, height = 16, oscillation = true },
741 [0x3f] = { xoff = 8, yoff = 8, width = 8, height = 24, oscillation = true }
742}
743
744local OBJ_CLIPPING_SPRITE = { -- sprites' interaction points against objects
745 [0x0] = {xright = 14, xleft = 2, xdown = 8, xup = 8, yright = 8, yleft = 8, ydown = 16, yup = 2},
746 [0x1] = {xright = 14, xleft = 2, xdown = 7, xup = 7, yright = 18, yleft = 18, ydown = 32, yup = 2},
747 [0x2] = {xright = 7, xleft = 7, xdown = 7, xup = 7, yright = 7, yleft = 7, ydown = 7, yup = 7},
748 [0x3] = {xright = 14, xleft = 2, xdown = 8, xup = 8, yright = 16, yleft = 16, ydown = 32, yup = 11},
749 [0x4] = {xright = 16, xleft = 0, xdown = 8, xup = 8, yright = 18, yleft = 18, ydown = 32, yup = 2},
750 [0x5] = {xright = 13, xleft = 2, xdown = 8, xup = 8, yright = 24, yleft = 24, ydown = 32, yup = 16},
751 [0x6] = {xright = 7, xleft = 0, xdown = 4, xup = 4, yright = 4, yleft = 4, ydown = 8, yup = 0},
752 [0x7] = {xright = 31, xleft = 1, xdown = 16, xup = 16, yright = 16, yleft = 16, ydown = 31, yup = 1},
753 [0x8] = {xright = 15, xleft = 0, xdown = 8, xup = 8, yright = 8, yleft = 8, ydown = 15, yup = 0},
754 [0x9] = {xright = 16, xleft = 0, xdown = 8, xup = 8, yright = 8, yleft = 8, ydown = 16, yup = 0},
755 [0xa] = {xright = 13, xleft = 2, xdown = 8, xup = 8, yright = 72, yleft = 72, ydown = 80, yup = 66},
756 [0xb] = {xright = 14, xleft = 2, xdown = 8, xup = 8, yright = 4, yleft = 4, ydown = 8, yup = 0},
757 [0xc] = {xright = 13, xleft = 2, xdown = 8, xup = 8, yright = 0, yleft = 0, ydown = 0, yup = 0},
758 [0xd] = {xright = 16, xleft = 0, xdown = 8, xup = 8, yright = 8, yleft = 8, ydown = 16, yup = 0},
759 [0xe] = {xright = 31, xleft = 0, xdown = 16, xup = 16, yright = 8, yleft = 8, ydown = 16, yup = 0},
760 [0xf] = {xright = 8, xleft = 8, xdown = 8, xup = 16, yright = 4, yleft = 1, ydown = 2, yup = 4}
761}
762
763local HITBOX_EXTENDED_SPRITE = { -- extended sprites' hitbox
764 -- To fill the slots...
765 --[0] ={ xoff = 3, yoff = 3, width = 64, height = 64}, -- Free slot
766 [0x01] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Puff of smoke with various objects
767 [0x0e] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Wiggler's flower
768 [0x0f] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Trail of smoke -- TODO: add experimental hitbox
769 [0x10] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Spinjump stars
770 [0x12] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Water bubble
771 -- extracted from ROM:
772 [0x02] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball }, -- Reznor fireball
773 [0x03] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball}, -- Flame left by hopping flame
774 [0x04] = { xoff = 4, yoff = 4, width = 8, height = 8}, -- Hammer
775 [0x05] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball }, -- Player fireball
776 [0x06] = { xoff = 4, yoff = 4, width = 8, height = 8}, -- Bone from Dry Bones
777 [0x07] = { xoff = 0, yoff = 0, width = 0, height = 0}, -- Lava splash
778 [0x08] = { xoff = 0, yoff = 0, width = 0, height = 0}, -- Torpedo Ted shooter's arm
779 [0x09] = { xoff = 0, yoff = 0, width = 15, height = 15}, -- Unknown flickering object
780 [0x0a] = { xoff = 4, yoff = 2, width = 8, height = 12}, -- Coin from coin cloud game
781 [0x0b] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball }, -- Piranha Plant fireball
782 [0x0c] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball }, -- Lava Lotus's fiery objects
783 [0x0d] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.baseball }, -- Baseball
784 -- got experimentally:
785 [0x11] = { xoff = -0x1, yoff = -0x4, width = 11, height = 19}, -- Yoshi fireballs
786}
787
788local HITBOX_CLUSTER_SPRITE = { -- got experimentally
789 --[0] -- Free slot
790 [0x01] = { xoff = 2, yoff = 0, width = 17, height = 21, oscillation = 2, phase = 1, color = COLOUR.awkward_hitbox, bg = COLOUR.awkward_hitbox_bg}, -- 1-Up from bonus game (glitched hitbox area)
791 [0x02] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Unused
792 [0x03] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Boo from Boo Ceiling
793 [0x04] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Boo from Boo Ring
794 [0x05] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Castle candle flame (meaningless hitbox)
795 [0x06] = { xoff = 2, yoff = 2, width = 12, height = 20, oscillation = 4, color = COLOUR.sumo_brother_flame}, -- Sumo Brother lightning flames
796 [0x07] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Reappearing Boo
797 [0x08] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Swooper bat from Swooper Death Bat Ceiling (untested)
798}
799
800; -- 0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12
801local SPRITE_MEMORY_MAX = {[0] = 10, 6, 7, 6, 7, 5, 8, 5, 7, 9, 9, 4, 8, 6, 8, 9, 10, 6, 6} -- the max of sprites in a room
802
803-- Creates a set from a list
804local function make_set(list)
805 local set = {}
806 for _, l in ipairs(list) do set[l] = true end
807 return set
808end
809
810-- from sprite number, returns oscillation flag
811-- A sprite must be here iff it processes interaction with player every frame AND this bit is not working in the sprite_4_tweaker WRAM(0x167a)
812local OSCILLATION_SPRITES = make_set{0x0e, 0x21, 0x29, 0x35, 0x54, 0x74, 0x75, 0x76, 0x77, 0x78, 0x81, 0x83, 0x87}
813
814-- Sprites that have a custom hitbox drawing
815local ABNORMAL_HITBOX_SPRITES = make_set{0x62, 0x63, 0x6b, 0x6c}
816
817-- Sprites whose clipping interaction points usually matter
818local GOOD_SPRITES_CLIPPING = make_set{
8190x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xf, 0x10, 0x11, 0x13, 0x14, 0x18,
8200x1b, 0x1d, 0x1f, 0x20, 0x26, 0x27, 0x29, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31,
8210x32, 0x34, 0x35, 0x3d, 0x3e, 0x3f, 0x40, 0x46, 0x47, 0x48, 0x4d, 0x4e,
8220x51, 0x53, 0x6e, 0x6f, 0x70, 0x80, 0x81, 0x86,
8230x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa1, 0xa2, 0xa5, 0xa6, 0xa7, 0xab, 0xb2,
8240xb4, 0xbb, 0xbc, 0xbd, 0xbf, 0xc3, 0xda, 0xdb, 0xdc, 0xdd, 0xdf
825}
826
827-- Extended sprites that don't interact with the player
828local UNINTERESTING_EXTENDED_SPRITES = make_set{1, 7, 8, 0x0e, 0x10, 0x12}
829
830
831--#############################################################################
832-- SCRIPT UTILITIES:
833
834
835-- Variables used in various functions
836local Cheat = {} -- family of cheat functions and variables
837local Previous = {}
838local User_input = INPUT_KEYNAMES -- BizHawk
839local Joypad = {}
840local Layer1_tiles = {}
841local Layer2_tiles = {}
842local Is_lagged = nil
843local Mario_boost_indicator = nil
844local Show_player_point_position = false
845local Sprites_info = {} -- keeps track of useful sprite info that might be used outside the main sprite function
846local Sprite_hitbox = {} -- keeps track of what sprite slots must display the hitbox
847local Options_form = {} -- BizHawk
848local Bizhawk_loop_counter = 1 -- BizHawk specific, a hack for saving the ini regularly
849
850-- Initialization of some tables
851for i = 0, SMW.sprite_max -1 do
852 Sprites_info[i] = {}
853end
854for key = 0, SMW.sprite_max - 1 do
855 Sprite_hitbox[key] = {}
856 for number = 0, 0xff do
857 Sprite_hitbox[key][number] = {["sprite"] = true, ["block"] = GOOD_SPRITES_CLIPPING[number]}
858 end
859end
860
861
862local function copytable(orig)
863 local orig_type = type(orig)
864 local copy
865 if orig_type == 'table' then
866 copy = {}
867 for orig_key, orig_value in pairs(orig) do
868 copy[orig_key] = orig_value
869 end
870 else -- number, string, boolean, etc
871 copy = orig
872 end
873 return copy
874end
875
876
877-- Sum of the digits of a integer
878local function sum_digits(number)
879 local sum = 0
880 while number > 0 do
881 sum = sum + number%10
882 number = floor(number*0.1)
883 end
884
885 return sum
886end
887
888
889-- Transform the binary representation of base into a string
890-- For instance, if each bit of a number represents a char of base, then this function verifies what chars are on
891local function decode_bits(data, base)
892 local i = 1
893 local size = base:len()
894 local direct_concatenation = size <= 45 -- Performance: I found out that the .. operator is faster for 45 operations or less
895 local result
896
897 if direct_concatenation then
898 result = ""
899 for ch in base:gmatch(".") do
900 if bit.test(data, size - i) then
901 result = result .. ch
902 else
903 result = result .. " "
904 end
905 i = i + 1
906 end
907 else
908 result = {}
909 for ch in base:gmatch(".") do
910 if bit.test(data, size-i) then
911 result[i] = ch
912 else
913 result[i] = " "
914 end
915 i = i + 1
916 end
917 result = table.concat(result)
918 end
919
920 return result
921end
922
923
924bit.test = bit.check -- BizHawk
925
926
927-- Register a function to be executed on key press or release
928-- execution happens in the main loop
929local Keys = {}
930Keys.press = {}
931Keys.release = {}
932Keys.down, Keys.up, Keys.pressed, Keys.released = {}, {}, {}, {}
933function Keys.registerkeypress(key, fn)
934 Keys.press[key] = fn
935end
936function Keys.registerkeyrelease(key, fn)
937 Keys.release[key] = fn
938end
939
940
941-- A cross sign with pos and size
942gui.crosshair = gui.drawAxis
943
944
945local Movie_active, Readonly, Framecount, Lagcount, Rerecords, Game_region
946local Lastframe_emulated, Starting_subframe_last_frame, Size_last_frame, Final_subframe_last_frame
947local Nextframe, Starting_subframe_next_frame, Starting_subframe_next_frame, Final_subframe_next_frame
948local function bizhawk_status()
949 Movie_active = movie.isloaded() -- BizHawk
950 Readonly = movie.getreadonly() -- BizHawk
951 Framecount = movie.length() -- BizHawk
952 Lagcount = emu.lagcount() -- BizHawk
953 Rerecords = movie.getrerecordcount() -- BizHawk
954 Is_lagged = emu.islagged() -- BizHawk
955 Game_region = emu.getdisplaytype() -- BizHawk
956
957 -- Last frame info
958 Lastframe_emulated = emu.framecount()
959
960 -- Next frame info (only relevant in readonly mode)
961 Nextframe = Lastframe_emulated + 1
962end
963
964
965-- Get screen values of the game and emulator areas
966local Left_gap, Right_gap, Top_gap, Bottom_gap
967local Border_left, Border_right, Border_top, Border_bottom
968local Buffer_width, Buffer_height, Buffer_middle_x, Buffer_middle_y
969local Screen_width, Screen_height, AR_x, AR_y
970local function bizhawk_screen_info()
971 -- zero gaps in old versions
972 if OLD_EMU_VERSION then
973 Left_gap = 0
974 Top_gap = 0
975 Right_gap = 0
976 Bottom_gap = 0
977 else
978 Left_gap = OPTIONS.left_gap
979 Top_gap = OPTIONS.top_gap
980 Right_gap = OPTIONS.right_gap
981 Bottom_gap = OPTIONS.bottom_gap
982 end
983
984 Screen_width = client.screenwidth() -- Screen area
985 Screen_height = client.screenheight()
986 Buffer_width = client.bufferwidth() -- Game area
987 Buffer_height = client.bufferheight()
988 Border_left = client.borderwidth() -- Borders' dimensions
989 Border_top = client.borderheight()
990
991 -- BizHawk bug: buffer dimensions go crazy when emu is minimized
992 if Buffer_width == 0 then
993 Buffer_width, Screen_width = 256, 256
994 Buffer_height, Screen_height = 224, 224
995 Border_left, Border_top = 0, 0
996 end
997
998 -- Derived dimensions
999 Buffer_middle_x = floor(Buffer_width/2)
1000 Buffer_middle_y = floor(Buffer_height/2)
1001 Border_right = Screen_width - Buffer_width - Border_left
1002 Border_bottom = Screen_height - Buffer_height - Border_top
1003 AR_x = Buffer_width/256
1004 AR_y = Buffer_height/224
1005end
1006
1007
1008local function mouse_onregion(x1, y1, x2, y2)
1009 -- Reads external mouse coordinates
1010 local mouse_x = User_input.xmouse*AR_x
1011 local mouse_y = User_input.ymouse*AR_y
1012
1013 -- From top-left to bottom-right
1014 if x2 < x1 then
1015 x1, x2 = x2, x1
1016 end
1017 if y2 < y1 then
1018 y1, y2 = y2, y1
1019 end
1020
1021 if mouse_x >= x1 and mouse_x <= x2 and mouse_y >= y1 and mouse_y <= y2 then
1022 return true
1023 else
1024 return false
1025 end
1026end
1027
1028
1029-- draw a pixel given (x,y) with SNES' pixel sizes
1030local draw_pixel = function(x, y, color) gui.drawPixel(x + Left_gap, y + Top_gap, color) end
1031
1032
1033-- draws a line given (x,y) and (x',y') with given scale and SNES' pixel thickness (whose scale is 2)
1034local function draw_line(x1, y1, x2, y2, scale, color)
1035 -- Draw from top-left to bottom-right
1036 if x2 < x1 then
1037 x1, x2 = x2, x1
1038 end
1039 if y2 < y1 then
1040 y1, y2 = y2, y1
1041 end
1042
1043 x1, y1, x2, y2 = scale*x1, scale*y1, scale*x2, scale*y2
1044 gui.drawLine(x1 + Left_gap, y1 + Top_gap, x2 + Left_gap, y2 + Top_gap, color)
1045end
1046
1047
1048-- draws a box given (x,y) and (x',y') with SNES' pixel sizes
1049local draw_box = function(x1, y1, x2, y2, line, bg) gui.drawBox(x1 + Left_gap, y1 + Top_gap, x2 + Left_gap, y2 + Top_gap, line, bg) end
1050
1051
1052-- draws a rectangle given (x,y) and dimensions, with SNES' pixel sizes
1053local draw_rectangle = function(x, y, w, h, line, bg) gui.drawRectangle(x + Left_gap, y + Top_gap, w, h, line, bg) end
1054
1055
1056-- Changes transparency of a color: result is opaque original * transparency level (0.0 to 1.0).
1057local function change_transparency(color, transparency)
1058 -- Sane transparency
1059 if transparency >= 1 then return color end -- no transparency
1060 if transparency <= 0 then return 0 end -- total transparency
1061
1062 -- Sane colour
1063 if color == 0 then return 0 end
1064 if type(color) ~= "number" then
1065 print(color)
1066 error"Wrong color"
1067 end
1068
1069 local a = floor(color/0x1000000)
1070 local rgb = color - a*0x1000000
1071 local new_a = floor(a*transparency)
1072 return new_a*0x1000000 + rgb
1073end
1074
1075
1076-- returns the (x, y) position to start the text and its length:
1077-- number, number, number text_position(x, y, text, font_width, font_height[[[[, always_on_client], always_on_game], ref_x], ref_y])
1078-- x, y: the coordinates that the refereed point of the text must have
1079-- text: a string, don't make it bigger than the buffer area width and don't include escape characters
1080-- font_width, font_height: the sizes of the font
1081-- always_on_client, always_on_game: boolean
1082-- ref_x and ref_y: refer to the relative point of the text that must occupy the origin (x,y), from 0% to 100%
1083-- for instance, if you want to display the middle of the text in (x, y), then use 0.5, 0.5
1084local function text_position(x, y, text, font_width, font_height, always_on_client, always_on_game, ref_x, ref_y)
1085 -- Reads external variables
1086 local border_left = Border_left
1087 local border_right = Border_right
1088 local border_top = Border_top
1089 local border_bottom = Border_bottom
1090 local buffer_width = Buffer_width
1091 local buffer_height = Buffer_height
1092
1093 -- text processing
1094 local text_length = text and string.len(text)*font_width or font_width -- considering another objects, like bitmaps
1095
1096 -- actual position, relative to game area origin
1097 x = (not ref_x and x) or (ref_x == 0 and x) or x - floor(text_length*ref_x)
1098 y = (not ref_y and y) or (ref_y == 0 and y) or y - floor(font_height*ref_y)
1099
1100 -- adjustment needed if text is supposed to be on screen area
1101 local x_end = x + text_length
1102 local y_end = y + font_height
1103
1104 if always_on_game then
1105 if x < 0 then x = 0 end
1106 if y < 0 then y = 0 end
1107
1108 if x_end > buffer_width then x = buffer_width - text_length end
1109 if y_end > buffer_height then y = buffer_height - font_height end
1110
1111 elseif always_on_client then
1112 if x < -border_left then x = -border_left end
1113 if y < -border_top then y = -border_top end
1114
1115 if x_end > buffer_width + border_right then x = buffer_width + border_right - text_length end
1116 if y_end > buffer_height + border_bottom then y = buffer_height + border_bottom - font_height end
1117 end
1118
1119 return x, y, text_length
1120end
1121
1122
1123-- Complex function for drawing, that uses text_position
1124local function draw_text(x, y, text, ...)
1125 -- Reads external variables
1126 local font_name = Font or false
1127 local font_width = BIZHAWK_FONT_WIDTH
1128 local font_height = BIZHAWK_FONT_HEIGHT
1129 local bg_default_color = font_name and COLOUR.outline or COLOUR.background
1130 local text_color, bg_color, always_on_client, always_on_game, ref_x, ref_y
1131 local arg1, arg2, arg3, arg4, arg5, arg6 = ...
1132
1133 if not arg1 or arg1 == true then
1134
1135 text_color = COLOUR.text
1136 bg_color = bg_default_color
1137 always_on_client, always_on_game, ref_x, ref_y = arg1, arg2, arg3, arg4
1138
1139 elseif not arg2 or arg2 == true then
1140
1141 text_color = arg1
1142 bg_color = bg_default_color
1143 always_on_client, always_on_game, ref_x, ref_y = arg2, arg3, arg4, arg5
1144
1145 else
1146
1147 text_color, bg_color = arg1, arg2
1148 always_on_client, always_on_game, ref_x, ref_y = arg3, arg4, arg5, arg6
1149
1150 end
1151
1152 local x_pos, y_pos, length = text_position(x, y, text, font_width, font_height,
1153 always_on_client, always_on_game, ref_x, ref_y)
1154 ;
1155
1156 text_color = change_transparency(text_color, Text_max_opacity * Text_opacity)
1157 bg_color = change_transparency(bg_color, Text_max_opacity * Text_opacity)
1158 gui.text(x_pos + Border_left, y_pos + Border_top, text, OLD_EMU_VERSION and bg_color or text_color, OLD_EMU_VERSION and text_color or bg_color)
1159
1160 return x_pos + length, y_pos + font_height, length
1161end
1162
1163
1164local function alert_text(x, y, text, text_color, bg_color, always_on_game, ref_x, ref_y)
1165 -- Reads external variables
1166 local font_width = BIZHAWK_FONT_WIDTH
1167 local font_height = BIZHAWK_FONT_HEIGHT
1168
1169 local x_pos, y_pos, text_length = text_position(x, y, text, font_width, font_height, false, always_on_game, ref_x, ref_y)
1170
1171 if not bg_color then bg_color = BACKGROUND_COLOR end
1172 text_color = change_transparency(text_color, Text_max_opacity * Text_opacity)
1173 bg_color = change_transparency(bg_color, Background_max_opacity * Bg_opacity)
1174
1175 draw_box(x_pos/AR_x, y_pos/AR_y, (x_pos + text_length)/AR_x + 2, (y_pos + font_height)/AR_y + 1, 0, bg_color)
1176 gui.text(x_pos + Border_left, y_pos + Border_top, text, OLD_EMU_VERSION and 0 or text_color, OLD_EMU_VERSION and text_color or 0)
1177end
1178
1179
1180local function draw_over_text(x, y, value, base, color_base, color_value, color_bg, always_on_client, always_on_game, ref_x, ref_y)
1181 value = decode_bits(value, base)
1182 local x_end, y_end, length = draw_text(x, y, base, color_base, color_bg, always_on_client, always_on_game, ref_x, ref_y)
1183
1184 change_transparency(color_value or COLOUR.text, Text_max_opacity * Text_opacity)
1185 gui.text(x_end + Border_left - length, y_end + Border_top - BIZHAWK_FONT_HEIGHT, value,
1186 OLD_EMU_VERSION and 0 or color_value, OLD_EMU_VERSION and color_value or 0) -- BizHawk
1187 ;
1188
1189 return x_end, y_end, length
1190end
1191
1192
1193-- Returns frames-time conversion
1194local function frame_time(frame)
1195 local total_seconds = frame/(Game_region == "NTSC" and NTSC_FRAMERATE or PAL_FRAMERATE)
1196 local hours = floor(total_seconds/3600)
1197 local tmp = total_seconds - 3600*hours
1198 local minutes = floor(tmp/60)
1199 tmp = tmp - 60*minutes
1200 local seconds = floor(tmp)
1201
1202 local miliseconds = 1000* (total_seconds%1)
1203 if hours == 0 then hours = "" else hours = string.format("%d:", hours) end
1204 local str = string.format("%s%.2d:%.2d.%03.0f", hours, minutes, seconds, miliseconds)
1205 return str
1206end
1207
1208
1209-- Background opacity functions
1210local function increase_opacity()
1211 if Text_max_opacity <= 0.9 then Text_max_opacity = Text_max_opacity + 0.1
1212 else
1213 if Background_max_opacity <= 0.9 then Background_max_opacity = Background_max_opacity + 0.1 end
1214 end
1215end
1216
1217
1218local function decrease_opacity()
1219 if Background_max_opacity >= 0.1 then Background_max_opacity = Background_max_opacity - 0.1
1220 else
1221 if Text_max_opacity >= 0.1 then Text_max_opacity = Text_max_opacity - 0.1 end
1222 end
1223end
1224
1225
1226
1227--#############################################################################
1228-- SMW FUNCTIONS:
1229
1230
1231-- Returns the id of Yoshi; if more than one, the lowest sprite slot
1232local function get_yoshi_id()
1233 for i = 0, SMW.sprite_max - 1 do
1234 local id = u8(WRAM.sprite_number + i)
1235 local status = u8(WRAM.sprite_status + i)
1236 if id == 0x35 and status ~= 0 then return i end
1237 end
1238
1239 return nil
1240end
1241
1242
1243local Real_frame, Previous_real_frame, Effective_frame, Game_mode
1244local Level_index, Room_index, Level_flag, Current_level
1245local Is_paused, Lock_animation_flag, Player_powerup, Player_animation_trigger
1246local Camera_x, Camera_y
1247local function scan_smw()
1248 Previous_real_frame = Real_frame or u8(WRAM.real_frame)
1249 Real_frame = u8(WRAM.real_frame)
1250 Effective_frame = u8(WRAM.effective_frame)
1251 Game_mode = u8(WRAM.game_mode)
1252 Level_index = u8(WRAM.level_index)
1253 Level_flag = u8(WRAM.level_flag_table + Level_index)
1254 Is_paused = u8(WRAM.level_paused) == 1
1255 Lock_animation_flag = u8(WRAM.lock_animation_flag)
1256 Room_index = u24(WRAM.room_index)
1257
1258 -- In level frequently used info
1259 Player_animation_trigger = u8(WRAM.player_animation_trigger)
1260 Player_powerup = u8(WRAM.powerup)
1261 Camera_x = s16(WRAM.camera_x)
1262 Camera_y = s16(WRAM.camera_y)
1263 Yoshi_riding_flag = u8(WRAM.yoshi_riding_flag) ~= 0
1264 Yoshi_id = get_yoshi_id()
1265end
1266
1267
1268-- Converts the in-game (x, y) to SNES-screen coordinates
1269local function screen_coordinates(x, y, camera_x, camera_y)
1270 -- Sane values
1271 camera_x = camera_x or Camera_x or u8(WRAM.camera_x)
1272 camera_y = camera_y or Camera_y or u8(WRAM.camera_y)
1273
1274 local x_screen = (x - camera_x)
1275 local y_screen = (y - camera_y) - Y_CAMERA_OFF
1276
1277 return x_screen, y_screen
1278end
1279
1280
1281-- Converts BizHawk/emu-screen coordinates to in-game (x, y)
1282local function game_coordinates(x_emu, y_emu, camera_x, camera_y)
1283 -- Sane values
1284 camera_x = camera_x or Camera_x or u8(WRAM.camera_x)
1285 camera_y = camera_y or Camera_y or u8(WRAM.camera_y)
1286
1287 local x_game = x_emu + camera_x
1288 local y_game = y_emu + Y_CAMERA_OFF + camera_y
1289
1290 return x_game, y_game
1291end
1292
1293
1294-- Returns the extreme values that Mario needs to have in order to NOT touch a rectangular object
1295local function display_boundaries(x_game, y_game, width, height, camera_x, camera_y)
1296 -- Font
1297 Text_opacity = 0.6
1298 Bg_opacity = 0.4
1299
1300 -- Coordinates around the rectangle
1301 local left = width*floor(x_game/width)
1302 local top = height*floor(y_game/height)
1303 left, top = screen_coordinates(left, top, camera_x, camera_y)
1304 local right = left + width - 1
1305 local bottom = top + height - 1
1306
1307 -- Reads WRAM values of the player
1308 local is_ducking = u8(WRAM.is_ducking)
1309 local powerup = Player_powerup
1310 local is_small = is_ducking ~= 0 or powerup == 0
1311
1312 -- Left
1313 local left_text = string.format("%4d.0", width*floor(x_game/width) - 13)
1314 draw_text(AR_x*left, AR_y*(top+bottom)/2, left_text, false, false, 1.0, 0.5)
1315
1316 -- Right
1317 local right_text = string.format("%d.f", width*floor(x_game/width) + 12)
1318 draw_text(AR_x*right, AR_y*(top+bottom)/2, right_text, false, false, 0.0, 0.5)
1319
1320 -- Top
1321 local value = (Yoshi_riding_flag and y_game - 16) or y_game
1322 local top_text = fmt("%d.0", width*floor(value/width) - 32)
1323 draw_text(AR_x*(left+right)/2, AR_y*top, top_text, false, false, 0.5, 1.0)
1324
1325 -- Bottom
1326 value = height*floor(y_game/height)
1327 if not is_small and not Yoshi_riding_flag then
1328 value = value + 0x07
1329 elseif is_small and Yoshi_riding_flag then
1330 value = value - 4
1331 else
1332 value = value - 1 -- the 2 remaining cases are equal
1333 end
1334
1335 local bottom_text = fmt("%d.f", value)
1336 draw_text(AR_x*(left+right)/2, AR_y*bottom, bottom_text, false, false, 0.5, 0.0)
1337
1338 return left, top
1339end
1340
1341
1342local function read_screens()
1343 local screens_number = u8(WRAM.screens_number)
1344 local vscreen_number = u8(WRAM.vscreen_number)
1345 local hscreen_number = u8(WRAM.hscreen_number) - 1
1346 local vscreen_current = s8(WRAM.y + 1)
1347 local hscreen_current = s8(WRAM.x + 1)
1348 local level_mode_settings = u8(WRAM.level_mode_settings)
1349 --local b1, b2, b3, b4, b5, b6, b7, b8 = bit.multidiv(level_mode_settings, 128, 64, 32, 16, 8, 4, 2)
1350 --draw_text(Buffer_middle_x, Buffer_middle_y, {"%x: %x%x%x%x%x%x%x%x", level_mode_settings, b1, b2, b3, b4, b5, b6, b7, b8}, COLOUR.text, COLOUR.background)
1351
1352 local level_type
1353 if (level_mode_settings ~= 0) and (level_mode_settings == 0x3 or level_mode_settings == 0x4 or level_mode_settings == 0x7
1354 or level_mode_settings == 0x8 or level_mode_settings == 0xa or level_mode_settings == 0xd) then
1355 level_type = "Vertical"
1356 ;
1357 else
1358 level_type = "Horizontal"
1359 end
1360
1361 return level_type, screens_number, hscreen_current, hscreen_number, vscreen_current, vscreen_number
1362end
1363
1364
1365local function get_map16_value(x_game, y_game)
1366 local num_x = floor(x_game/16)
1367 local num_y = floor(y_game/16)
1368 if num_x < 0 or num_y < 0 then return end -- 1st breakpoint
1369
1370 local level_type, screens, _, hscreen_number, _, vscreen_number = read_screens()
1371 local max_x, max_y
1372 if level_type == "Horizontal" then
1373 max_x = 16*(hscreen_number + 1)
1374 max_y = 27
1375 else
1376 max_x = 32
1377 max_y = 16*(vscreen_number + 1)
1378 end
1379
1380 if num_x > max_x or num_y > max_y then return end -- 2nd breakpoint
1381
1382 local num_id, kind, address
1383 if level_type == "Horizontal" then
1384 num_id = 16*27*floor(num_x/16) + 16*num_y + num_x%16
1385 else
1386 local nx = floor(num_x/16)
1387 local ny = floor(num_y/16)
1388 local n = 2*ny + nx
1389 num_id = 16*16*n + 16*(num_y%16) + num_x%16
1390 end
1391 if (num_id >= 0 and num_id <= 0x37ff) then
1392 address = fmt(" $%4.x", 0xc800 + num_id)
1393 kind = 256*u8(0x1c800 + num_id) + u8(0xc800 + num_id)
1394 end
1395
1396 if kind then return num_x, num_y, kind, address end
1397end
1398
1399
1400local function draw_layer1_tiles(camera_x, camera_y)
1401 local x_origin, y_origin = screen_coordinates(0, 0, camera_x, camera_y)
1402 local x_mouse, y_mouse = game_coordinates(User_input.xmouse, User_input.ymouse, camera_x, camera_y)
1403 x_mouse = 16*floor(x_mouse/16)
1404 y_mouse = 16*floor(y_mouse/16)
1405 local push_direction = Real_frame%2 == 0 and 0 or 7 -- block pushes sprites to left or right?
1406
1407 for number, positions in ipairs(Layer1_tiles) do
1408 -- Calculate the Lsnes coordinates
1409 local left = positions[1] + x_origin
1410 local top = positions[2] + y_origin
1411 local right = left + 15
1412 local bottom = top + 15
1413 local x_game, y_game = game_coordinates(left, top, camera_x, camera_y)
1414
1415 -- Returns if block is way too outside the screen
1416 if left > - Border_left - 32 and top > - Border_top - 32 and
1417 right < Screen_width + Border_right + 32 and bottom < Screen_height + Border_bottom + 32 then
1418
1419 -- Drawings
1420 Text_opacity = 1.0
1421 local num_x, num_y, kind, address = get_map16_value(x_game, y_game)
1422 if kind then
1423 if kind >= 0x111 and kind <= 0x16d or kind == 0x2b then
1424 -- default solid blocks, don't know how to include custom blocks
1425 draw_rectangle(left + push_direction, top, 8, 15, 0, COLOUR.block_bg)
1426 end
1427 draw_rectangle(left, top, 15, 15, kind == SMW.blank_tile_map16 and COLOUR.blank_tile or COLOUR.block, 0)
1428
1429 if Layer1_tiles[number][3] then
1430 display_boundaries(x_game, y_game, 16, 16, camera_x, camera_y) -- the text around it
1431 end
1432
1433 -- Draw Map16 id
1434 Text_opacity = 1.0
1435 if kind and x_mouse == positions[1] and y_mouse == positions[2] then
1436 draw_text(AR_x*(left + 4), AR_y*top - BIZHAWK_FONT_HEIGHT, fmt("Map16 (%d, %d), %x%s", num_x, num_y, kind, address),
1437 false, false, 0.5, 1.0)
1438 end
1439 end
1440
1441 end
1442
1443 end
1444
1445end
1446
1447
1448local function draw_layer2_tiles()
1449 local layer2x = s16(WRAM.layer2_x_nextframe)
1450 local layer2y = s16(WRAM.layer2_y_nextframe)
1451
1452 for number, positions in ipairs(Layer2_tiles) do
1453 draw_rectangle(-layer2x + positions[1], -layer2y + positions[2], 15, 15, COLOUR.layer2_line, COLOUR.layer2_bg)
1454 end
1455end
1456
1457
1458-- if the user clicks in a tile, it will be be drawn
1459-- if click is onto drawn region, it'll be erased
1460-- there's a max of possible tiles
1461-- layer_table[n] is an array {x, y, [draw info?]}
1462local function select_tile(x, y, layer_table)
1463 if not OPTIONS.draw_tiles_with_click then return end
1464 if Game_mode ~= SMW.game_mode_level then return end
1465
1466 for number, positions in ipairs(layer_table) do -- if mouse points a drawn tile, erase it
1467 if x == positions[1] and y == positions[2] then
1468 -- Layer 1
1469 if layer_table == Layer1_tiles then
1470 if layer_table[number][3] == false then
1471 layer_table[number][3] = true
1472 else
1473 table.remove(layer_table, number)
1474 end
1475 -- Layer 2
1476 elseif layer_table == Layer2_tiles then
1477 table.remove(layer_table, number)
1478 end
1479
1480 return
1481 end
1482 end
1483
1484 -- otherwise, draw a new tile
1485 if #layer_table == OPTIONS.max_tiles_drawn then
1486 table.remove(layer_table, 1)
1487 layer_table[OPTIONS.max_tiles_drawn] = {x, y, false}
1488 else
1489 table.insert(layer_table, {x, y, false})
1490 end
1491
1492end
1493
1494
1495-- uses the mouse to select an object
1496local function select_object(mouse_x, mouse_y, camera_x, camera_y)
1497 -- Font
1498 Text_opacity = 1.0
1499 Bg_opacity = 0.5
1500
1501 local x_game, y_game = game_coordinates(mouse_x, mouse_y, camera_x, camera_y)
1502 local obj_id
1503
1504 -- Checks if the mouse is over Mario
1505 local x_player = s16(WRAM.x)
1506 local y_player = s16(WRAM.y)
1507 if x_player + 0xe >= x_game and x_player + 0x2 <= x_game and y_player + 0x30 >= y_game and y_player + 0x8 <= y_game then
1508 obj_id = "Mario"
1509 end
1510
1511 if not obj_id and OPTIONS.display_sprite_info then
1512 for id = 0, SMW.sprite_max - 1 do
1513 local sprite_status = u8(WRAM.sprite_status + id)
1514 if sprite_status ~= 0 and Sprites_info[id].x then -- TODO: see why the script gets here without exporting Sprites_info
1515 -- Import some values
1516 local x_sprite, y_sprite = Sprites_info[id].x, Sprites_info[id].y
1517 local x_screen, y_screen = Sprites_info[id].x_screen, Sprites_info[id].y_screen
1518 local boxid = Sprites_info[id].boxid
1519 local xoff, yoff = Sprites_info[id].xoff, Sprites_info[id].yoff
1520 local width, height = Sprites_info[id].width, Sprites_info[id].height
1521
1522 if x_sprite + xoff + width >= x_game and x_sprite + xoff <= x_game and
1523 y_sprite + yoff + height >= y_game and y_sprite + yoff <= y_game then
1524 obj_id = id
1525 break
1526 end
1527 end
1528 end
1529 end
1530
1531 if not obj_id then return end
1532
1533 draw_text(AR_x*User_input.xmouse, AR_y*(User_input.ymouse - 8), obj_id, true, false, 0.5, 1.0)
1534 return obj_id, x_game, y_game
1535end
1536
1537
1538-- This function sees if the mouse if over some object, to change its hitbox mode
1539-- The order is: 1) player, 2) sprite.
1540local function right_click()
1541 local id = select_object(User_input.xmouse, User_input.ymouse, Camera_x, Camera_y)
1542
1543 if tostring(id) == "Mario" then
1544
1545 if OPTIONS.display_player_hitbox and OPTIONS.display_interaction_points then
1546 OPTIONS.display_interaction_points = false
1547 OPTIONS.display_player_hitbox = false
1548 elseif OPTIONS.display_player_hitbox then
1549 OPTIONS.display_interaction_points = true
1550 OPTIONS.display_player_hitbox = false
1551 elseif OPTIONS.display_interaction_points then
1552 OPTIONS.display_player_hitbox = true
1553 else
1554 OPTIONS.display_player_hitbox = true
1555 end
1556
1557 end
1558 if id then return end
1559
1560 local spr_id = tonumber(id)
1561 if spr_id and spr_id >= 0 and spr_id <= SMW.sprite_max - 1 then
1562
1563 local number = Sprites_info[spr_id].number
1564 if Sprite_hitbox[spr_id][number].sprite and Sprite_hitbox[spr_id][number].block then
1565 Sprite_hitbox[spr_id][number].sprite = false
1566 Sprite_hitbox[spr_id][number].block = false
1567 elseif Sprite_hitbox[spr_id][number].sprite then
1568 Sprite_hitbox[spr_id][number].block = true
1569 Sprite_hitbox[spr_id][number].sprite = false
1570 elseif Sprite_hitbox[spr_id][number].block then
1571 Sprite_hitbox[spr_id][number].sprite = true
1572 else
1573 Sprite_hitbox[spr_id][number].sprite = true
1574 end
1575
1576 end
1577 if id then return end
1578
1579 -- Select layer 2 tiles
1580 local layer2x = s16(WRAM.layer2_x_nextframe)
1581 local layer2y = s16(WRAM.layer2_y_nextframe)
1582 local x_mouse, y_mouse = User_input.xmouse + layer2x, User_input.ymouse + layer2y
1583 select_tile(16*floor(x_mouse/16), 16*floor(y_mouse/16) - Y_CAMERA_OFF, Layer2_tiles)
1584end
1585
1586
1587local function show_movie_info()
1588 if not OPTIONS.display_movie_info then
1589 return
1590 end
1591
1592 -- Font
1593 Text_opacity = 1.0
1594 Bg_opacity = 1.0
1595 local y_text = - Border_top
1596 local x_text = 0
1597 local width = BIZHAWK_FONT_WIDTH
1598
1599 local rec_color = (Readonly or not Movie_active) and COLOUR.text or COLOUR.warning
1600 local recording_bg = (Readonly or not Movie_active) and COLOUR.background or COLOUR.warning_bg
1601
1602 -- Read-only or read-write?
1603 local movie_type = (not Movie_active and "No movie ") or (Readonly and "Movie " or "REC ")
1604 alert_text(x_text, y_text, movie_type, rec_color, recording_bg)
1605
1606 if Movie_active then
1607 -- Frame count
1608 x_text = x_text + width*string.len(movie_type)
1609 local movie_info
1610 if Readonly then
1611 movie_info = string.format("%d/%d", Lastframe_emulated, Framecount)
1612 else
1613 movie_info = string.format("%d", Lastframe_emulated)
1614 end
1615 draw_text(x_text, y_text, movie_info) -- Shows the latest frame emulated, not the frame being run now
1616
1617 -- Rerecord count
1618 x_text = x_text + width*string.len(movie_info)
1619 local rr_info = string.format(" %d ", Rerecords)
1620 draw_text(x_text, y_text, rr_info, COLOUR.weak)
1621
1622 -- Lag count
1623 x_text = x_text + width*string.len(rr_info)
1624 draw_text(x_text, y_text, Lagcount, COLOUR.warning)
1625 end
1626
1627 local str = frame_time(Lastframe_emulated) -- Shows the latest frame emulated, not the frame being run now
1628 alert_text(Buffer_width, Buffer_height, str, COLOUR.text, recording_bg, false, 1.0, 1.0)
1629
1630end
1631
1632
1633local function show_misc_info()
1634 if not OPTIONS.display_misc_info then
1635 return
1636 end
1637
1638 -- Font
1639 Text_opacity = 1.0
1640 Bg_opacity = 1.0
1641
1642 -- Display
1643 local RNG = u16(WRAM.RNG)
1644 local main_info = string.format("Frame(%02x, %02x) RNG(%04x) Mode(%02x)",
1645 Real_frame, Effective_frame, RNG, Game_mode)
1646 ;
1647
1648 draw_text(Buffer_width + Border_right, -Border_top, main_info, true, false)
1649
1650 if Game_mode == SMW.game_mode_level then
1651 -- Time frame counter of the clock
1652 Text_opacity = 1.0
1653 local timer_frame_counter = u8(WRAM.timer_frame_counter)
1654 draw_text(AR_x*161, AR_y*15, fmt("%.2d", timer_frame_counter))
1655
1656 -- Score: sum of digits, useful for avoiding lag
1657 Text_opacity = 0.5
1658 local score = u24(WRAM.mario_score)
1659 draw_text(AR_x*240, AR_y*24, fmt("=%d", sum_digits(score)), COLOUR.weak)
1660 end
1661end
1662
1663
1664-- Shows the controller input as the RAM and SNES registers store it
1665local function show_controller_data()
1666 if not (OPTIONS.display_debug_info and OPTIONS.display_debug_controller_data) then return end
1667
1668 -- Font
1669 Text_opacity = 0.9
1670 local height = BIZHAWK_FONT_HEIGHT
1671 local x_pos, y_pos, x, y, _ = 0, 0, 0, BIZHAWK_FONT_HEIGHT
1672
1673 x = draw_over_text(x_pos, y_pos, 256*u8(WRAM.ctrl_1_1) + u8(WRAM.ctrl_1_2), "BYsS^v<>AXLR0123", COLOUR.weak)
1674 _, y = draw_text(x, y_pos, " (RAM data)", COLOUR.weak, false, true)
1675
1676 x = x_pos
1677 draw_over_text(x, y, 256*u8(WRAM.firstctrl_1_1) + u8(WRAM.firstctrl_1_2), "BYsS^v<>AXLR0123", 0, 0xff0000ff, 0)
1678end
1679
1680
1681local function level_info()
1682 if not OPTIONS.display_level_info then
1683 return
1684 end
1685 -- Font
1686 local x_pos = Buffer_width + Border_right
1687 local y_pos = - Border_top + BIZHAWK_FONT_HEIGHT
1688 local color = COLOUR.text
1689 Text_opacity = 1.0
1690 Bg_opacity = 1.0
1691
1692 local sprite_buoyancy = floor(u8(WRAM.sprite_buoyancy)/64)
1693 if sprite_buoyancy == 0 then sprite_buoyancy = "" else
1694 sprite_buoyancy = fmt(" %.2x", sprite_buoyancy)
1695 color = COLOUR.warning
1696 end
1697
1698 -- converts the level number to the Lunar Magic number; should not be used outside here
1699 local lm_level_number = Level_index
1700 if Level_index > 0x24 then lm_level_number = Level_index + 0xdc end
1701
1702 -- Number of screens within the level
1703 local level_type, screens_number, hscreen_current, hscreen_number, vscreen_current, vscreen_number = read_screens()
1704
1705 draw_text(x_pos, y_pos, fmt("%.1sLevel(%.2x)%s", level_type, lm_level_number, sprite_buoyancy),
1706 color, true, false)
1707 ;
1708
1709 draw_text(x_pos, y_pos + BIZHAWK_FONT_HEIGHT, fmt("Screens(%d):", screens_number), true)
1710
1711 draw_text(x_pos, y_pos + 2*BIZHAWK_FONT_HEIGHT, fmt("(%d/%d, %d/%d)", hscreen_current, hscreen_number,
1712 vscreen_current, vscreen_number), true)
1713 ;
1714end
1715
1716
1717function draw_blocked_status(x_text, y_text, player_blocked_status, x_speed, y_speed)
1718 local block_width = 9 -- BizHawk
1719 local block_height = 9 -- BizHawk
1720 local block_str = "Block:"
1721 local str_len = string.len(block_str)
1722 local xoffset = (x_text + str_len*BIZHAWK_FONT_WIDTH)/AR_x
1723 local yoffset = y_text/AR_y
1724 local color_line = COLOUR.warning
1725
1726 gui.drawRectangle(xoffset + Left_gap, yoffset + Top_gap, block_width - 1, block_height - 1, 0x40000000, 0x40ff0000)
1727
1728 local blocked_status = {}
1729 local was_boosted = false
1730
1731 if bit.test(player_blocked_status, 0) then -- Right
1732 draw_line(xoffset + block_width - 1, yoffset, xoffset + block_width - 1, yoffset + block_height - 1, 1, color_line)
1733 if x_speed < 0 then was_boosted = true end
1734 end
1735
1736 if bit.test(player_blocked_status, 1) then -- Left
1737 draw_line(xoffset, yoffset, xoffset, yoffset + block_height - 1, 1, color_line)
1738 if x_speed > 0 then was_boosted = true end
1739 end
1740
1741 if bit.test(player_blocked_status, 2) then -- Down
1742 draw_line(xoffset, yoffset + block_height - 1, xoffset + block_width - 1, yoffset + block_height - 1, 1, color_line)
1743 end
1744
1745 if bit.test(player_blocked_status, 3) then -- Up
1746 draw_line(xoffset, yoffset, xoffset + block_width - 1, yoffset, 1, color_line)
1747 if y_speed > 6 then was_boosted = true end
1748 end
1749
1750 if bit.test(player_blocked_status, 4) then -- Middle
1751 gui.crosshair(xoffset + floor(block_width/2) + Left_gap, yoffset + floor(block_height/2) + Top_gap, -- BizHawk
1752 math.min(block_width/3, block_height/3), color_line)
1753 end
1754
1755 draw_text(x_text, y_text, block_str, COLOUR.text, was_boosted and COLOUR.warning_bg or nil)
1756
1757end
1758
1759
1760-- displays player's hitbox
1761local function player_hitbox(x, y, is_ducking, powerup, transparency_level)
1762 local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
1763 local is_small = is_ducking ~= 0 or powerup == 0
1764
1765 -- Colors BizHawk
1766 local is_transparent = transparency_level == 1
1767 local interaction_bg = is_transparent and COLOUR.interaction_bg or 0
1768 local mario_bg = is_transparent and COLOUR.mario_bg or 0
1769 local mario_mounted_bg = is_transparent and COLOUR.mario_mounted_bg or 0
1770 local mario = is_transparent and COLOUR.mario or change_transparency(COLOUR.mario, transparency_level)
1771 local interaction_nohitbox = is_transparent and COLOUR.interaction_nohitbox or change_transparency(COLOUR.interaction_nohitbox, transparency_level)
1772 local interaction_nohitbox_bg = is_transparent and COLOUR.interaction_nohitbox_bg or 0
1773 local interaction = is_transparent and COLOUR.interaction or change_transparency(COLOUR.interaction, transparency_level)
1774
1775 local x_points = X_INTERACTION_POINTS
1776 local y_points
1777 if is_small and not Yoshi_riding_flag then
1778 y_points = Y_INTERACTION_POINTS[1]
1779 elseif not is_small and not Yoshi_riding_flag then
1780 y_points = Y_INTERACTION_POINTS[2]
1781 elseif is_small and Yoshi_riding_flag then
1782 y_points = Y_INTERACTION_POINTS[3]
1783 else
1784 y_points = Y_INTERACTION_POINTS[4]
1785 end
1786
1787 draw_box(x_screen + x_points.left_side, y_screen + y_points.head, x_screen + x_points.right_side, y_screen + y_points.foot,
1788 interaction_bg, interaction_bg) -- background for block interaction
1789 ;
1790
1791 if OPTIONS.display_player_hitbox then
1792
1793 -- Collision with sprites
1794 local mario_bg = (not Yoshi_riding_flag and mario_bg) or mario_mounted_bg
1795
1796 draw_box(x_screen + x_points.left_side - 1, y_screen + y_points.sprite,
1797 x_screen + x_points.right_side + 1, y_screen + y_points.foot + 1, mario, mario_bg)
1798 ;
1799
1800 end
1801
1802 -- interaction points (collision with blocks)
1803 if OPTIONS.display_interaction_points then
1804
1805 if not SHOW_PLAYER_HITBOX then
1806 draw_box(x_screen + x_points.left_side , y_screen + y_points.head,
1807 x_screen + x_points.right_side, y_screen + y_points.foot, interaction_nohitbox, interaction_nohitbox_bg)
1808 end
1809
1810 draw_line(x_screen + x_points.left_side, y_screen + y_points.side, x_screen + x_points.left_foot, y_screen + y_points.side, 1, interaction) -- left side
1811 draw_line(x_screen + x_points.right_side, y_screen + y_points.side, x_screen + x_points.right_foot, y_screen + y_points.side, 1, interaction) -- right side
1812 draw_line(x_screen + x_points.left_foot, y_screen + y_points.foot - 2, x_screen + x_points.left_foot, y_screen + y_points.foot, 1, interaction) -- left foot bottom
1813 draw_line(x_screen + x_points.right_foot, y_screen + y_points.foot - 2, x_screen + x_points.right_foot, y_screen + y_points.foot, 1, interaction) -- right foot bottom
1814 draw_line(x_screen + x_points.left_side, y_screen + y_points.shoulder, x_screen + x_points.left_side + 2, y_screen + y_points.shoulder, 1, interaction) -- head left point
1815 draw_line(x_screen + x_points.right_side - 2, y_screen + y_points.shoulder, x_screen + x_points.right_side, y_screen + y_points.shoulder, 1, interaction) -- head right point
1816 draw_line(x_screen + x_points.center, y_screen + y_points.head, x_screen + x_points.center, y_screen + y_points.head + 2, 1, interaction) -- head point
1817 draw_line(x_screen + x_points.center - 1, y_screen + y_points.center, x_screen + x_points.center + 1, y_screen + y_points.center, 1, interaction) -- center point
1818 draw_line(x_screen + x_points.center, y_screen + y_points.center - 1, x_screen + x_points.center, y_screen + y_points.center + 1, 1, interaction) -- center point
1819 end
1820
1821 -- That's the pixel that appears when Mario dies in the pit
1822 Show_player_point_position = Show_player_point_position or y_screen >= 200 or
1823 (OPTIONS.display_debug_info and OPTIONS.display_debug_player_extra)
1824 if Show_player_point_position then
1825 draw_rectangle(x_screen - 1, y_screen - 1, 2, 2, interaction_bg, interaction)
1826 Show_player_point_position = false
1827 end
1828
1829 return x_points, y_points
1830end
1831
1832
1833-- displays the hitbox of the cape while spinning
1834local function cape_hitbox(spin_direction)
1835 local cape_interaction = u8(WRAM.cape_interaction)
1836 if cape_interaction == 0 then return end
1837
1838 local cape_x = u16(WRAM.cape_x)
1839 local cape_y = u16(WRAM.cape_y)
1840
1841 local cape_x_screen, cape_y_screen = screen_coordinates(cape_x, cape_y, Camera_x, Camera_y)
1842 local cape_left = -2
1843 local cape_right = 0x12
1844 local cape_up = 0x01
1845 local cape_down = 0x11
1846 local cape_middle = 0x08
1847 local block_interaction_cape = (spin_direction < 0 and cape_left + 4) or cape_right - 4
1848 local active_frame_sprites = Real_frame%2 == 1 -- active iff the cape can hit a sprite
1849 local active_frame_blocks = Real_frame%2 == (spin_direction < 0 and 0 or 1) -- active iff the cape can hit a block
1850
1851 if active_frame_sprites then bg_color = COLOUR.cape_bg else bg_color = 0 end
1852 draw_box(cape_x_screen + cape_left, cape_y_screen + cape_up, cape_x_screen + cape_right, cape_y_screen + cape_down, COLOUR.cape, bg_color)
1853
1854 if active_frame_blocks then
1855 draw_pixel(cape_x_screen + block_interaction_cape, cape_y_screen + cape_middle, COLOUR.warning)
1856 else
1857 draw_pixel(cape_x_screen + block_interaction_cape, cape_y_screen + cape_middle, COLOUR.text)
1858 end
1859end
1860
1861
1862local function player()
1863 if not OPTIONS.display_player_info then
1864 return
1865 end
1866
1867 -- Font
1868 Text_opacity = 1.0
1869
1870 -- Reads WRAM
1871 local x = s16(WRAM.x)
1872 local y = s16(WRAM.y)
1873 local previous_x = s16(WRAM.previous_x)
1874 local previous_y = s16(WRAM.previous_y)
1875 local x_sub = u8(WRAM.x_sub)
1876 local y_sub = u8(WRAM.y_sub)
1877 local x_speed = s8(WRAM.x_speed)
1878 local x_subspeed = u8(WRAM.x_subspeed)
1879 local y_speed = s8(WRAM.y_speed)
1880 local p_meter = u8(WRAM.p_meter)
1881 local take_off = u8(WRAM.take_off)
1882 local powerup = Player_powerup
1883 local direction = u8(WRAM.direction)
1884 local cape_spin = u8(WRAM.cape_spin)
1885 local cape_fall = u8(WRAM.cape_fall)
1886 local flight_animation = u8(WRAM.flight_animation)
1887 local diving_status = s8(WRAM.diving_status)
1888 local player_blocked_status = u8(WRAM.player_blocked_status)
1889 local player_item = u8(WRAM.player_item)
1890 local is_ducking = u8(WRAM.is_ducking)
1891 local on_ground = u8(WRAM.on_ground)
1892 local spinjump_flag = u8(WRAM.spinjump_flag)
1893 local can_jump_from_water = u8(WRAM.can_jump_from_water)
1894 local carrying_item = u8(WRAM.carrying_item)
1895 local scroll_timer = u8(WRAM.camera_scroll_timer)
1896 local vertical_scroll_flag_header = u8(WRAM.vertical_scroll_flag_header)
1897 local vertical_scroll_enabled = u8(WRAM.vertical_scroll_enabled)
1898
1899 -- Transformations
1900 if direction == 0 then direction = LEFT_ARROW else direction = RIGHT_ARROW end
1901 local x_sub_simple, y_sub_simple-- = x_sub, y_sub
1902 if x_sub%0x10 == 0 then x_sub_simple = fmt("%x", x_sub/0x10) else x_sub_simple = fmt("%.2x", x_sub) end
1903 if y_sub%0x10 == 0 then y_sub_simple = fmt("%x", y_sub/0x10) else y_sub_simple = fmt("%.2x", y_sub) end
1904
1905 local x_speed_int, x_speed_frac = math.modf(x_speed + x_subspeed/0x100)
1906 x_speed_frac = math.abs(x_speed_frac*100)
1907
1908 local spin_direction = (Effective_frame)%8
1909 if spin_direction < 4 then
1910 spin_direction = spin_direction + 1
1911 else
1912 spin_direction = 3 - spin_direction
1913 end
1914
1915 local is_caped = powerup == 0x2
1916 local is_spinning = cape_spin ~= 0 or spinjump_flag ~= 0
1917
1918 -- Display info
1919 local i = 0
1920 local delta_x = BIZHAWK_FONT_WIDTH
1921 local delta_y = BIZHAWK_FONT_HEIGHT
1922 local table_x = - Border_left
1923 local table_y = AR_y*32
1924
1925 draw_text(table_x, table_y + i*delta_y, fmt("Meter (%03d, %02d) %s", p_meter, take_off, direction))
1926 draw_text(table_x + 18*delta_x, table_y + i*delta_y, fmt(" %+d", spin_direction),
1927 (is_spinning and COLOUR.text) or COLOUR.weak)
1928 i = i + 1
1929
1930 draw_text(table_x, table_y + i*delta_y, fmt("Pos (%+d.%s, %+d.%s)", x, x_sub_simple, y, y_sub_simple))
1931 i = i + 1
1932
1933 draw_text(table_x, table_y + i*delta_y, fmt("Speed (%+d(%d.%02.0f), %+d)", x_speed, x_speed_int, x_speed_frac, y_speed))
1934 i = i + 1
1935
1936 if is_caped then
1937 draw_text(table_x, table_y + i*delta_y, fmt("Cape (%.2d, %.2d)/(%d, %d)", cape_spin, cape_fall, flight_animation, diving_status), COLOUR.cape)
1938 i = i + 1
1939 end
1940
1941 local x_txt = draw_text(table_x, table_y + i*delta_y, fmt("Camera (%d, %d)", Camera_x, Camera_y))
1942 if scroll_timer ~= 0 then x_txt = draw_text(x_txt, table_y + i*delta_y, 16 - scroll_timer, COLOUR.warning) end
1943 if vertical_scroll_flag_header ~=0 and vertical_scroll_enabled ~= 0 then
1944 draw_text(x_txt, table_y + i*delta_y, vertical_scroll_enabled, COLOUR.warning2)
1945 end
1946 i = i + 1
1947
1948 if OPTIONS.display_static_camera_region then
1949 Show_player_point_position = true
1950 local left_cam, right_cam = u16(0x142c), u16(0x142e) -- unlisted WRAM
1951 draw_box(left_cam, 0, right_cam, 224, COLOUR.static_camera_region, COLOUR.static_camera_region)
1952 end
1953
1954 draw_blocked_status(table_x, table_y + i*delta_y, player_blocked_status, x_speed, y_speed)
1955
1956 -- Mario boost indicator (experimental)
1957 -- This looks for differences between the expected x position and the actual x position, after a frame advance
1958 -- Fails during a loadstate and has false positives if the game is paused or lagged
1959 Previous.player_x = 256*x + x_sub -- the total amount of 256-based subpixels
1960 Previous.x_speed = 16*x_speed -- the speed in 256-based subpixels
1961
1962 if Mario_boost_indicator and not Cheat.under_free_move then
1963 local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
1964 draw_text(AR_x*(x_screen + 4), AR_y*(y_screen + 60), Mario_boost_indicator, COLOUR.warning, 0x20000000) -- unlisted color
1965 end
1966
1967 -- shows hitbox and interaction points for player
1968 if not (OPTIONS.display_player_hitbox or OPTIONS.display_interaction_points) then return end
1969
1970 cape_hitbox(spin_direction)
1971 player_hitbox(x, y, is_ducking, powerup, 1.0)
1972
1973 -- Shows where Mario is expected to be in the next frame, if he's not boosted or stopped (DEBUG)
1974 if OPTIONS.display_debug_info and OPTIONS.display_debug_player_extra then
1975 player_hitbox( floor((256*x + x_sub + 16*x_speed)/256),
1976 floor((256*y + y_sub + 16*y_speed)/256), is_ducking, powerup, 0.3) -- BizHawk
1977 end
1978
1979end
1980
1981
1982local function extended_sprites()
1983 if not OPTIONS.display_extended_sprite_info then
1984 return
1985 end
1986
1987 -- Font
1988 Text_opacity = 1.0
1989 local height = BIZHAWK_FONT_HEIGHT
1990
1991 local y_pos = AR_y*144
1992 local counter = 0
1993 for id = 0, SMW.extended_sprite_max - 1 do
1994 local extspr_number = u8(WRAM.extspr_number + id)
1995
1996 if extspr_number ~= 0 then
1997 -- Reads WRAM addresses
1998 local x = 256*u8(WRAM.extspr_x_high + id) + u8(WRAM.extspr_x_low + id)
1999 local y = 256*u8(WRAM.extspr_y_high + id) + u8(WRAM.extspr_y_low + id)
2000 local sub_x = bit.rshift(u8(WRAM.extspr_subx + id), 4)
2001 local sub_y = bit.rshift(u8(WRAM.extspr_suby + id), 4)
2002 local x_speed = s8(WRAM.extspr_x_speed + id)
2003 local y_speed = s8(WRAM.extspr_y_speed + id)
2004 local extspr_table = u8(WRAM.extspr_table + id)
2005 local extspr_table2 = u8(WRAM.extspr_table2 + id)
2006
2007 -- Reduction of useless info
2008 local special_info = ""
2009 if OPTIONS.display_debug_info and OPTIONS.display_debug_extended_sprite and (extspr_table ~= 0 or extspr_table2 ~= 0) then
2010 special_info = fmt("(%x, %x) ", extspr_table, extspr_table2)
2011 end
2012
2013 -- x speed for Fireballs
2014 if extspr_number == 5 then x_speed = 16*x_speed end
2015
2016 if OPTIONS.display_extended_sprite_info then
2017 draw_text(Buffer_width + Border_right, y_pos + counter*height, fmt("#%.2d %.2x %s(%d.%x(%+.2d), %d.%x(%+.2d))",
2018 id, extspr_number, special_info, x, sub_x, x_speed, y, sub_y, y_speed),
2019 COLOUR.extended_sprites, true, false)
2020 end
2021
2022 if (OPTIONS.display_debug_info and OPTIONS.display_debug_extended_sprite) or not UNINTERESTING_EXTENDED_SPRITES[extspr_number]
2023 or (extspr_number == 1 and extspr_table2 == 0xf)
2024 then
2025 local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
2026
2027 local t = HITBOX_EXTENDED_SPRITE[extspr_number] or
2028 {xoff = 0, yoff = 0, width = 16, height = 16, color_line = COLOUR.awkward_hitbox, color_bg = COLOUR.awkward_hitbox_bg}
2029 local xoff = t.xoff
2030 local yoff = t.yoff + Y_CAMERA_OFF
2031 local xrad = t.width
2032 local yrad = t.height
2033
2034 local color_line = t.color_line or COLOUR.extended_sprites
2035 local color_bg = t.color_bg or COLOUR.extended_sprites_bg
2036 if extspr_number == 0x5 or extspr_number == 0x11 then
2037 color_bg = (Real_frame - id)%4 == 0 and COLOUR.special_extended_sprite_bg or 0
2038 end
2039 draw_rectangle(x_screen+xoff, y_screen+yoff, xrad, yrad, color_line, color_bg) -- regular hitbox
2040
2041 -- Experimental: attempt to show Mario's fireball vs sprites
2042 -- this is likely wrong in some situation, but I can't solve this yet
2043 if extspr_number == 5 or extspr_number == 1 then
2044 local xoff_spr = x_speed >= 0 and -5 or 1
2045 local yoff_spr = - floor(y_speed/16) - 4 + (y_speed >= -40 and 1 or 0)
2046 local yrad_spr = y_speed >= -40 and 19 or 20
2047 draw_rectangle(x_screen + xoff_spr, y_screen + yoff_spr, 12, yrad_spr, color_line, color_bg)
2048 end
2049 end
2050
2051 counter = counter + 1
2052 end
2053 end
2054
2055 Text_opacity = 0.5
2056 local x_pos, y_pos, length = draw_text(Buffer_width + Border_right, y_pos, fmt("Ext. spr:%2d ", counter), COLOUR.weak, true, false, 0.0, 1.0)
2057
2058 if u8(WRAM.spinjump_flag) ~= 0 and u8(WRAM.powerup) == 3 then
2059 local fireball_timer = u8(WRAM.spinjump_fireball_timer)
2060 draw_text(x_pos - length - BIZHAWK_FONT_WIDTH, y_pos, fmt("%d %s",
2061 fireball_timer%16, bit.test(fireball_timer, 4) and RIGHT_ARROW or LEFT_ARROW), COLOUR.extended_sprites, true, false, 1.0, 1.0)
2062 end
2063
2064end
2065
2066
2067local function cluster_sprites()
2068 if not OPTIONS.display_cluster_sprite_info or u8(WRAM.cluspr_flag) == 0 then return end
2069
2070 -- Font
2071 Text_opacity = 1.0
2072 local height = BIZHAWK_FONT_HEIGHT
2073 local x_pos, y_pos = AR_x*90, AR_y*77 -- BizHawk
2074 local counter = 0
2075
2076 if OPTIONS.display_debug_info and OPTIONS.display_debug_cluster_sprite then
2077 draw_text(x_pos, y_pos, "Cluster Spr.", COLOUR.weak)
2078 counter = counter + 1
2079 end
2080
2081 local reappearing_boo_counter
2082
2083 for id = 0, SMW.cluster_sprite_max - 1 do
2084 local clusterspr_number = u8(WRAM.cluspr_number + id)
2085
2086 if clusterspr_number ~= 0 then
2087 if not HITBOX_CLUSTER_SPRITE[clusterspr_number] then
2088 print("Warning: wrong cluster sprite number:", clusterspr_number) -- should not happen without cheats
2089 return
2090 end
2091
2092 -- Reads WRAM addresses
2093 local x = signed(256*u8(WRAM.cluspr_x_high + id) + u8(WRAM.cluspr_x_low + id), 16)
2094 local y = signed(256*u8(WRAM.cluspr_y_high + id) + u8(WRAM.cluspr_y_low + id), 16)
2095 local clusterspr_timer, special_info, table_1, table_2, table_3
2096
2097 -- Reads cluster's table
2098 local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
2099 local t = HITBOX_CLUSTER_SPRITE[clusterspr_number] or
2100 {xoff = 0, yoff = 0, width = 16, height = 16, color_line = COLOUR.awkward_hitbox, color_bg = COLOUR.awkward_hitbox_bg, oscillation = 1}
2101 local xoff = t.xoff
2102 local yoff = t.yoff + Y_CAMERA_OFF
2103 local xrad = t.width
2104 local yrad = t.height
2105 local phase = t.phase or 0
2106 local oscillation = (Real_frame - id)%t.oscillation == phase
2107 local color = t.color or COLOUR.cluster_sprites
2108 local color_bg = t.bg or COLOUR.sprites_bg
2109 local invencibility_hitbox = nil
2110
2111 if OPTIONS.display_debug_info and OPTIONS.display_debug_cluster_sprite then
2112 table_1 = u8(WRAM.cluspr_table_1 + id)
2113 table_2 = u8(WRAM.cluspr_table_2 + id)
2114 table_3 = u8(WRAM.cluspr_table_3 + id)
2115 draw_text(x_pos, y_pos + counter*height, ("#%d(%d): (%d, %d) %d, %d, %d")
2116 :format(id, clusterspr_number, x, y, table_1, table_2, table_3), color)
2117 counter = counter + 1
2118 end
2119
2120 -- Case analysis
2121 if clusterspr_number == 3 or clusterspr_number == 8 then
2122 clusterspr_timer = u8(WRAM.cluspr_timer + id)
2123 if clusterspr_timer ~= 0 then special_info = " " .. clusterspr_timer end
2124 elseif clusterspr_number == 6 then
2125 table_1 = table_1 or u8(WRAM.cluspr_table_1 + id)
2126 if table_1 >= 111 or (table_1 < 31 and table_1 >= 16) then
2127 yoff = yoff + 17
2128 elseif table_1 >= 103 or table_1 < 16 then
2129 invencibility_hitbox = true
2130 elseif table_1 >= 95 or (table_1 < 47 and table_1 >= 31) then
2131 yoff = yoff + 16
2132 end
2133 elseif clusterspr_number == 7 then
2134 reappearing_boo_counter = reappearing_boo_counter or u8(WRAM.reappearing_boo_counter)
2135 invencibility_hitbox = (reappearing_boo_counter > 0xde) or (reappearing_boo_counter < 0x3f)
2136 special_info = " " .. reappearing_boo_counter
2137 end
2138
2139 -- Hitbox and sprite id
2140 color = invencibility_hitbox and COLOUR.weak or color
2141 color_bg = (invencibility_hitbox and 0) or (oscillation and color_bg) or 0
2142 draw_rectangle(x_screen + xoff, y_screen + yoff, xrad, yrad, color, color_bg)
2143 draw_text(AR_x*(x_screen + xoff) + xrad, AR_y*(y_screen + yoff), special_info and id .. special_info or id,
2144 color, false, false, 0.5, 1.0)
2145 end
2146 end
2147end
2148
2149
2150local function minor_extended_sprites()
2151 if not OPTIONS.display_minor_extended_sprite_info then return end
2152
2153 -- Font
2154 Text_opacity = 1.0
2155 local height = BIZHAWK_FONT_HEIGHT
2156 local x_pos, y_pos = 0, Buffer_height - height*SMW.minor_extended_sprite_max
2157 local counter = 0
2158
2159 for id = 0, SMW.minor_extended_sprite_max - 1 do
2160 local minorspr_number = u8(WRAM.minorspr_number + id)
2161
2162 if minorspr_number ~= 0 then
2163 -- Reads WRAM addresses
2164 local x = signed(256*u8(WRAM.minorspr_x_high + id) + u8(WRAM.minorspr_x_low + id), 16)
2165 local y = signed(256*u8(WRAM.minorspr_y_high + id) + u8(WRAM.minorspr_y_low + id), 16)
2166 local xspeed, yspeed = s8(WRAM.minorspr_xspeed + id), s8(WRAM.minorspr_yspeed + id)
2167 local x_sub, y_sub = u8(WRAM.minorspr_x_sub + id), u8(WRAM.minorspr_y_sub + id)
2168 local timer = u8(WRAM.minorspr_timer + id)
2169
2170 -- Only sprites 1 and 10 use the higher byte
2171 local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
2172 if minorspr_number ~= 1 and minorspr_number ~= 10 then -- Boo stream and Piece of brick block
2173 x_screen = x_screen%0x100
2174 y_screen = y_screen%0x100
2175 end
2176
2177 -- Draw next to the sprite
2178 local text = "#" .. id .. (timer ~= 0 and (" " .. timer) or "")
2179 draw_text(AR_x*(x_screen + 8), AR_y*(y_screen + 4), text, COLOUR.minor_extended_sprites, false, false, 0.5, 1.0)
2180 if minorspr_number == 10 then -- Boo stream
2181 draw_rectangle(x_screen + 4, y_screen + 4 + Y_CAMERA_OFF, 8, 8, COLOUR.minor_extended_sprites, COLOUR.sprites_bg)
2182 end
2183
2184 -- Draw in the table
2185 if OPTIONS.display_debug_info and OPTIONS.display_debug_minor_extended_sprite then
2186 draw_text(x_pos, y_pos + counter*height, ("#%d(%d): %d.%x(%d), %d.%x(%d)")
2187 :format(id, minorspr_number, x, floor(x_sub/16), xspeed, y, floor(y_sub/16), yspeed), COLOUR.minor_extended_sprites)
2188 end
2189 counter = counter + 1
2190 end
2191 end
2192
2193 if OPTIONS.display_debug_info and OPTIONS.display_debug_minor_extended_sprite then
2194 draw_text(x_pos, y_pos - height, "Minor Ext Spr:" .. counter, COLOUR.weak)
2195 end
2196end
2197
2198
2199local function bounce_sprite_info()
2200 if not OPTIONS.display_bounce_sprite_info then return end
2201
2202 -- Debug info
2203 local x_txt, y_txt = AR_x*90, AR_y*37
2204 if OPTIONS.display_debug_info and OPTIONS.display_debug_bounce_sprite then
2205 Text_opacity = 0.5
2206 draw_text(x_txt, y_txt, "Bounce Spr.", COLOUR.weak)
2207 end
2208
2209 -- Font
2210 Text_opacity = 1.0
2211 local height = BIZHAWK_FONT_HEIGHT
2212
2213 local stop_id = (u8(WRAM.bouncespr_last_id) - 1)%SMW.bounce_sprite_max
2214 for id = 0, SMW.bounce_sprite_max - 1 do
2215 local bounce_sprite_number = u8(WRAM.bouncespr_number + id)
2216 if bounce_sprite_number ~= 0 then
2217 local x = 256*u8(WRAM.bouncespr_x_high + id) + u8(WRAM.bouncespr_x_low + id)
2218 local y = 256*u8(WRAM.bouncespr_y_high + id) + u8(WRAM.bouncespr_y_low + id)
2219 local bounce_timer = u8(WRAM.bouncespr_timer + id)
2220
2221 if OPTIONS.display_debug_info and OPTIONS.display_debug_bounce_sprite then
2222 draw_text(x_txt, y_txt + height*(id + 1), fmt("#%d:%d (%d, %d)", id, bounce_sprite_number, x, y))
2223 end
2224
2225 local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
2226 x_screen, y_screen = AR_x*(x_screen + 8), AR_y*y_screen
2227 local color = id == stop_id and COLOUR.warning or COLOUR.text
2228 draw_text(x_screen , y_screen, fmt("#%d:%d", id, bounce_timer), color, false, false, 0.5) -- timer
2229
2230 -- Turn blocks
2231 if bounce_sprite_number == 7 then
2232 turn_block_timer = u8(WRAM.turn_block_timer + id)
2233 draw_text(x_screen, y_screen + height, turn_block_timer, color, false, false, 0.5)
2234 end
2235 end
2236 end
2237end
2238
2239
2240local function sprite_info(id, counter, table_position)
2241 Text_opacity = 1.0
2242
2243 local sprite_status = u8(WRAM.sprite_status + id)
2244 if sprite_status == 0 then return 0 end -- returns if the slot is empty
2245
2246 local x = 256*u8(WRAM.sprite_x_high + id) + u8(WRAM.sprite_x_low + id)
2247 local y = 256*u8(WRAM.sprite_y_high + id) + u8(WRAM.sprite_y_low + id)
2248 local x_sub = u8(WRAM.sprite_x_sub + id)
2249 local y_sub = u8(WRAM.sprite_y_sub + id)
2250 local number = u8(WRAM.sprite_number + id)
2251 local stun = u8(WRAM.sprite_miscellaneous7 + id)
2252 local x_speed = s8(WRAM.sprite_x_speed + id)
2253 local y_speed = s8(WRAM.sprite_y_speed + id)
2254 local contact_mario = u8(WRAM.sprite_miscellaneous8 + id)
2255 local underwater = u8(WRAM.sprite_underwater + id)
2256 local x_offscreen = s8(WRAM.sprite_x_offscreen + id)
2257 local y_offscreen = s8(WRAM.sprite_y_offscreen + id)
2258
2259 local special = ""
2260 if (OPTIONS.display_debug_info and OPTIONS.display_debug_sprite_extra) or
2261 ((sprite_status ~= 0x8 and sprite_status ~= 0x9 and sprite_status ~= 0xa and sprite_status ~= 0xb) or stun ~= 0) then
2262 special = string.format("(%d %d) ", sprite_status, stun)
2263 end
2264
2265 -- Let x and y be 16-bit signed
2266 x = signed(x, 16)
2267 y = signed(y, 16)
2268
2269 ---**********************************************
2270 -- Calculates the sprites dimensions and screen positions
2271
2272 local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
2273
2274 -- Sprite clipping vs mario and sprites
2275 local boxid = bit.band(u8(WRAM.sprite_2_tweaker + id), 0x3f) -- This is the type of box of the sprite
2276 local xoff = HITBOX_SPRITE[boxid].xoff
2277 local yoff = HITBOX_SPRITE[boxid].yoff + Y_CAMERA_OFF
2278 local sprite_width = HITBOX_SPRITE[boxid].width
2279 local sprite_height = HITBOX_SPRITE[boxid].height
2280
2281 -- Sprite clipping vs objects
2282 local clip_obj = bit.band(u8(WRAM.sprite_1_tweaker + id), 0xf) -- type of hitbox for blocks
2283 local xpt_right = OBJ_CLIPPING_SPRITE[clip_obj].xright
2284 local ypt_right = OBJ_CLIPPING_SPRITE[clip_obj].yright
2285 local xpt_left = OBJ_CLIPPING_SPRITE[clip_obj].xleft
2286 local ypt_left = OBJ_CLIPPING_SPRITE[clip_obj].yleft
2287 local xpt_down = OBJ_CLIPPING_SPRITE[clip_obj].xdown
2288 local ypt_down = OBJ_CLIPPING_SPRITE[clip_obj].ydown
2289 local xpt_up = OBJ_CLIPPING_SPRITE[clip_obj].xup
2290 local ypt_up = OBJ_CLIPPING_SPRITE[clip_obj].yup
2291
2292 -- Process interaction with player every frame?
2293 -- Format: dpmksPiS. This 'm' bit seems odd, since it has false negatives
2294 local oscillation_flag = bit.test(u8(WRAM.sprite_4_tweaker + id), 5) or OSCILLATION_SPRITES[number]
2295
2296 -- calculates the correct color to use, according to id
2297 local info_color
2298 local color_background
2299 if number == 0x35 then
2300 info_color = COLOUR.yoshi
2301 color_background = COLOUR.yoshi_bg
2302 else
2303 info_color = COLOUR.sprites[id%(#COLOUR.sprites) + 1]
2304 color_background = COLOUR.sprites_bg
2305 end
2306
2307
2308 if (not oscillation_flag) and (Real_frame - id)%2 == 1 then color_background = 0 end -- due to sprite oscillation every other frame
2309 -- notice that some sprites interact with Mario every frame
2310 ;
2311
2312
2313 ---**********************************************
2314 -- Displays sprites hitboxes
2315 if OPTIONS.display_sprite_hitbox then
2316 -- That's the pixel that appears when the sprite vanishes in the pit
2317 if y_screen >= 224 or (OPTIONS.display_debug_info and OPTIONS.display_debug_sprite_extra) then
2318 draw_pixel(x_screen, y_screen, info_color)
2319 end
2320
2321 if Sprite_hitbox[id][number].block then
2322 draw_box(x_screen + xpt_left, y_screen + ypt_down, x_screen + xpt_right, y_screen + ypt_up,
2323 COLOUR.sprites_clipping_bg, Sprite_hitbox[id][number].sprite and 0 or COLOUR.sprites_clipping_bg)
2324 end
2325
2326 if Sprite_hitbox[id][number].sprite and not ABNORMAL_HITBOX_SPRITES[number] then -- show sprite/sprite clipping
2327 draw_rectangle(x_screen + xoff, y_screen + yoff, sprite_width, sprite_height, info_color, color_background)
2328 end
2329
2330 if Sprite_hitbox[id][number].block then -- show sprite/object clipping
2331 local size, color = 1, COLOUR.sprites_interaction_pts
2332 draw_line(x_screen + xpt_right, y_screen + ypt_right, x_screen + xpt_right - size, y_screen + ypt_right, 1, color) -- right
2333 draw_line(x_screen + xpt_left, y_screen + ypt_left, x_screen + xpt_left + size, y_screen + ypt_left, 1, color) -- left
2334 draw_line(x_screen + xpt_down, y_screen + ypt_down, x_screen + xpt_down, y_screen + ypt_down - size, 1, color) -- down
2335 draw_line(x_screen + xpt_up, y_screen + ypt_up, x_screen + xpt_up, y_screen + ypt_up + size, 1, color) -- up
2336 end
2337 end
2338
2339
2340 ---**********************************************
2341 -- Special sprites analysis:
2342
2343 --[[
2344 PROBLEMATIC ONES
2345 29 Koopa Kid
2346 54 Revolving door for climbing net, wrong hitbox area, not urgent
2347 5a Turn block bridge, horizontal, hitbox only applies to central block and wrongly
2348 86 Wiggler, the second part of the sprite, that hurts Mario even if he's on Yoshi, doesn't appear
2349 89 Layer 3 Smash, hitbox of generator outside
2350 9e Ball 'n' Chain, hitbox only applies to central block, rotating ball
2351 a3 Rotating gray platform, wrong hitbox, rotating plataforms
2352 ]]
2353
2354 if number == 0x5f then -- Swinging brown platform (fix it)
2355
2356 -- Powerup Incrementation helper
2357 local yoshi_right = 256*floor(x/256) - 58
2358 local yoshi_left = yoshi_right + 32
2359 local x_text, y_text, height = AR_x*(x_screen + xoff), AR_y*(y_screen + yoff), BIZHAWK_FONT_HEIGHT
2360
2361 if mouse_onregion(x_text, y_text, x_text + AR_x*sprite_width, y_text + AR_y*sprite_height) then
2362 local x_text, y_text = 0, height
2363 draw_text(x_text, y_text, "Powerup Incrementation help", info_color, COLOUR.background)
2364 draw_text(x_text, y_text + height, "Yoshi must have: id = #4;", info_color, COLOUR.background)
2365 draw_text(x_text, y_text + 2*height, ("Yoshi x pos: (%s %d) or (%s %d)")
2366 :format(LEFT_ARROW, yoshi_left, RIGHT_ARROW, yoshi_right), info_color, COLOUR.background)
2367 end
2368 --The status change happens when yoshi's id number is #4 and when (yoshi's x position) + Z mod 256 = 214,
2369 --where Z is 16 if yoshi is facing right, and -16 if facing left. More precisely, when (yoshi's x position + Z) mod 256 = 214,
2370 --the address 0x7E0015 + (yoshi's id number) will be added by 1.
2371 -- therefore: X_yoshi = 256*floor(x/256) + 32*yoshi_direction - 58
2372 end
2373
2374 if number == 0x35 then -- Yoshi
2375 if not Yoshi_riding_flag and OPTIONS.display_sprite_hitbox and Sprite_hitbox[id][number].sprite then
2376 draw_rectangle(x_screen + 4, y_screen + 20, 8, 8, COLOUR.yoshi)
2377 end
2378 end
2379
2380 if number == 0x62 or number == 0x63 then -- Brown line-guided platform & Brown/checkered line-guided platform
2381 xoff = xoff - 24
2382 yoff = yoff - 8
2383 -- for some reason, the actual base is 1 pixel below when Mario is small
2384 if OPTIONS.display_sprite_hitbox then
2385 draw_rectangle(x_screen + xoff, y_screen + yoff, sprite_width, sprite_height, info_color, color_background)
2386 end
2387 end
2388
2389 if number == 0x6b then -- Wall springboard (left wall)
2390 xoff = xoff - 8
2391 sprite_height = sprite_height + 1 -- for some reason, small Mario gets a bigger hitbox
2392
2393 if OPTIONS.display_sprite_hitbox then
2394 draw_rectangle(x_screen + xoff, y_screen + yoff, sprite_width, sprite_height, info_color, color_background)
2395 draw_line(x_screen + xoff, y_screen + yoff + 3, x_screen + xoff + sprite_width, y_screen + yoff + 3, 1, info_color)
2396 end
2397 end
2398
2399 if number == 0x6c then -- Wall springboard (right wall)
2400 xoff = xoff - 31
2401 sprite_height = sprite_height + 1
2402
2403 if OPTIONS.display_sprite_hitbox then
2404 draw_rectangle(x_screen + xoff, y_screen + yoff, sprite_width, sprite_height, info_color, color_background)
2405 draw_line(x_screen + xoff, y_screen + yoff + 3, x_screen + xoff + sprite_width, y_screen + yoff + 3, 1, info_color)
2406 end
2407 end
2408
2409 if number == 0x7b then -- Goal Tape
2410
2411 Text_opacity = 0.8
2412
2413 -- This draws the effective area of a goal tape
2414 local x_effective = 256*u8(WRAM.sprite_miscellaneous4 + id) + u8(0xc2 + id) -- unlisted WRAM
2415 local y_low = 256*u8(0x1534 + id) + u8(WRAM.sprite_miscellaneous5 + id) -- unlisted WRAM
2416 local _, y_high = screen_coordinates(0, 0, Camera_x, Camera_y)
2417 local x_s, y_s = screen_coordinates(x_effective, y_low, Camera_x, Camera_y)
2418
2419 if OPTIONS.display_sprite_hitbox then
2420 draw_box(x_s, y_high, x_s + 15, y_s, info_color, COLOUR.goal_tape_bg)
2421 end
2422 draw_text(AR_x*x_s, AR_y*y_screen, fmt("Touch=%4d.0->%4d.f", x_effective, x_effective + 15), info_color, false, false)
2423
2424 Text_opacity = 1.0
2425 Bg_opacity = 1.0
2426
2427 elseif number == 0xa9 then -- Reznor
2428
2429 local reznor
2430 local color
2431 for index = 0, SMW.sprite_max - 1 do
2432 reznor = u8(WRAM.sprite_miscellaneous4 + index)
2433 if index >= 4 and index <= 7 then
2434 color = COLOUR.warning
2435 else
2436 color = color_weak
2437 end
2438 draw_text(3*BIZHAWK_FONT_WIDTH*index, Buffer_height, fmt("%.2x", reznor), color, true, false, 0.0, 1.0)
2439 end
2440
2441 elseif number == 0xa0 then -- Bowser
2442
2443 local height = BIZHAWK_FONT_HEIGHT
2444 local y_text = Screen_height - 10*height
2445 local address = 0x14b0 -- unlisted WRAM
2446 for index = 0, 9 do
2447 local value = u8(address + index)
2448 draw_text(Buffer_width + Border_right, y_text + index*height, fmt("%2x = %3d", value, value), info_color, true)
2449 end
2450
2451 end
2452
2453
2454 ---**********************************************
2455 -- Prints those informations next to the sprite
2456 Text_opacity = 1.0
2457 Bg_opacity = 1.0
2458
2459 if x_offscreen ~= 0 or y_offscreen ~= 0 then
2460 Text_opacity = 0.6
2461 end
2462
2463 local contact_str = contact_mario == 0 and "" or " " .. contact_mario
2464
2465 local sprite_middle = x_screen + xoff + floor(sprite_width/2)
2466 local sprite_top = y_screen + math.min(yoff, ypt_up)
2467 draw_text(AR_x*sprite_middle, AR_y*sprite_top, fmt("#%.2d%s", id, contact_str), info_color, true, false, 0.5, 1.0)
2468 if Player_powerup == 2 then
2469 local contact_cape = u8(WRAM.sprite_disable_cape + id)
2470 if contact_cape ~= 0 then
2471 draw_text(AR_x*sprite_middle, AR_y*sprite_top - 2*BIZHAWK_FONT_HEIGHT, contact_cape, COLOUR.cape, true)
2472 end
2473 end
2474
2475
2476 ---**********************************************
2477 -- Sprite tweakers info
2478 if OPTIONS.display_debug_info and OPTIONS.display_debug_sprite_tweakers then
2479 Text_opacity = 0.8 -- BizHawk
2480 local height = BIZHAWK_FONT_HEIGHT
2481 local x_txt, y_txt = AR_x*sprite_middle - 4*BIZHAWK_FONT_WIDTH, AR_y*(y_screen + yoff) - 7*height
2482
2483 local tweaker_1 = u8(WRAM.sprite_1_tweaker + id)
2484 draw_over_text(x_txt, y_txt, tweaker_1, "sSjJcccc", COLOUR.weak, info_color)
2485 y_txt = y_txt + height
2486
2487 local tweaker_2 = u8(WRAM.sprite_2_tweaker + id)
2488 draw_over_text(x_txt, y_txt, tweaker_2, "dscccccc", COLOUR.weak, info_color)
2489 y_txt = y_txt + height
2490
2491 local tweaker_3 = u8(WRAM.sprite_3_tweaker + id)
2492 draw_over_text(x_txt, y_txt, tweaker_3, "lwcfpppg", COLOUR.weak, info_color)
2493 y_txt = y_txt + height
2494
2495 local tweaker_4 = u8(WRAM.sprite_4_tweaker + id)
2496 draw_over_text(x_txt, y_txt, tweaker_4, "dpmksPiS", COLOUR.weak, info_color)
2497 y_txt = y_txt + height
2498
2499 local tweaker_5 = u8(WRAM.sprite_5_tweaker + id)
2500 draw_over_text(x_txt, y_txt, tweaker_5, "dnctswye", COLOUR.weak, info_color)
2501 y_txt = y_txt + height
2502
2503 local tweaker_6 = u8(WRAM.sprite_6_tweaker + id)
2504 draw_over_text(x_txt, y_txt, tweaker_6, "wcdj5sDp", COLOUR.weak, info_color)
2505 Text_opacity = 1.0
2506 end
2507
2508
2509 ---**********************************************
2510 -- The sprite table:
2511 local x_speed_water = ""
2512 if underwater ~= 0 then -- if sprite is underwater
2513 local correction = floor(3*floor(x_speed/2)/2)
2514 x_speed_water = string.format("%+.2d=%+.2d", correction - x_speed, correction)
2515 end
2516 local sprite_str = fmt("#%02d %02x %s%d.%1x(%+.2d%s) %d.%1x(%+.2d)",
2517 id, number, special, x, floor(x_sub/16), x_speed, x_speed_water, y, floor(y_sub/16), y_speed)
2518
2519 Text_opacity = 1.0
2520 Bg_opacity = 1.0
2521 if x_offscreen ~= 0 or y_offscreen ~= 0 then
2522 Text_opacity = 0.6
2523 end
2524 draw_text(Buffer_width + Border_right, table_position + counter*BIZHAWK_FONT_HEIGHT, sprite_str, info_color, true)
2525
2526 -- Miscellaneous sprite table
2527 if OPTIONS.display_miscellaneous_sprite_table then
2528 local x_mis, y_mis = - Border_left, AR_y*144 + counter*BIZHAWK_FONT_HEIGHT
2529
2530 local t = OPTIONS.miscellaneous_sprite_table_number
2531 local misc, text = nil, fmt("#%.2d", id)
2532 for num = 1, 19 do
2533 misc = t[num] and u8(WRAM["sprite_miscellaneous" .. num] + id) or false
2534 text = misc and fmt("%s %3d", text, misc) or text
2535 end
2536
2537 draw_text(x_mis, y_mis, text, info_color)
2538 end
2539
2540 -- Exporting some values
2541 Sprites_info[id].number = number
2542 Sprites_info[id].x, Sprites_info[id].y = x, y
2543 Sprites_info[id].x_screen, Sprites_info[id].y_screen = x_screen, y_screen
2544 Sprites_info[id].boxid = boxid
2545 Sprites_info[id].xoff, Sprites_info[id].yoff = xoff, yoff
2546 Sprites_info[id].width, Sprites_info[id].height = sprite_width, sprite_height
2547
2548 return 1
2549end
2550
2551
2552local function sprites()
2553 if not OPTIONS.display_sprite_info then return end
2554
2555 local counter = 0
2556 local table_position = AR_y*48
2557 for id = 0, SMW.sprite_max - 1 do
2558 counter = counter + sprite_info(id, counter, table_position)
2559 end
2560
2561 -- Font
2562 Text_opacity = 0.6
2563
2564 local swap_slot = u8(0x1861) -- unlisted WRAM
2565 local smh = u8(WRAM.sprite_memory_header)
2566 draw_text(Buffer_width + Border_right, table_position - 2*BIZHAWK_FONT_HEIGHT, fmt("spr:%.2d", counter), COLOUR.weak, true)
2567 draw_text(Buffer_width + Border_right, table_position - BIZHAWK_FONT_HEIGHT, fmt("1st div: %d. Swap: %d",
2568 SPRITE_MEMORY_MAX[smh] or 0, swap_slot), COLOUR.weak, true)
2569 --
2570 -- Miscellaneous sprite table: index
2571 if OPTIONS.display_miscellaneous_sprite_table then
2572 local t = OPTIONS.miscellaneous_sprite_table_number
2573 local text = "Tab"
2574 for num = 1, 19 do
2575 text = t[num] and fmt("%s %3d", text, num) or text
2576 end
2577
2578 draw_text(- Border_left, AR_y*144 - BIZHAWK_FONT_HEIGHT, text, info_color)
2579 end
2580end
2581
2582
2583local function yoshi()
2584 if not OPTIONS.display_yoshi_info then
2585 return
2586 end
2587
2588 -- Font
2589 Text_opacity = 1.0
2590 Bg_opacity = 1.0
2591 local x_text = - Border_left
2592 local y_text = AR_y*88
2593
2594 local yoshi_id = Yoshi_id
2595 if yoshi_id ~= nil then
2596 local eat_id = u8(WRAM.sprite_miscellaneous16+ yoshi_id)
2597 local eat_type = u8(WRAM.sprite_number + eat_id)
2598 local tongue_len = u8(WRAM.sprite_miscellaneous4 + yoshi_id)
2599 local tongue_timer = u8(WRAM.sprite_miscellaneous9 + yoshi_id)
2600 local tongue_wait = u8(WRAM.sprite_tongue_wait)
2601 local tongue_height = u8(WRAM.yoshi_tile_pos)
2602 local tongue_out = u8(WRAM.sprite_miscellaneous13 + yoshi_id)
2603
2604 local eat_type_str = eat_id == SMW.null_sprite_id and "-" or string.format("%02x", eat_type)
2605 local eat_id_str = eat_id == SMW.null_sprite_id and "-" or string.format("#%02d", eat_id)
2606
2607 -- Yoshi's direction and turn around
2608 local turn_around = u8(WRAM.sprite_miscellaneous14 + yoshi_id)
2609 local yoshi_direction = u8(WRAM.sprite_miscellaneous12 + yoshi_id)
2610 local direction_symbol
2611 if yoshi_direction == 0 then direction_symbol = RIGHT_ARROW else direction_symbol = LEFT_ARROW end
2612
2613 draw_text(x_text, y_text, fmt("Yoshi %s %d", direction_symbol, turn_around), COLOUR.yoshi)
2614 local h = BIZHAWK_FONT_HEIGHT
2615
2616 if eat_id == SMW.null_sprite_id and tongue_len == 0 and tongue_timer == 0 and tongue_wait == 0 then
2617 Text_opacity = 0.2
2618 end
2619 draw_text(x_text, y_text + h, fmt("(%0s, %0s) %02d, %d, %d",
2620 eat_id_str, eat_type_str, tongue_len, tongue_wait, tongue_timer), COLOUR.yoshi)
2621 ;
2622
2623 -- more WRAM values
2624 local yoshi_x = 256*u8(WRAM.sprite_x_high + yoshi_id) + u8(WRAM.sprite_x_low + yoshi_id)
2625 local yoshi_y = 256*u8(WRAM.sprite_y_high + yoshi_id) + u8(WRAM.sprite_y_low + yoshi_id)
2626 local x_screen, y_screen = screen_coordinates(yoshi_x, yoshi_y, Camera_x, Camera_y)
2627
2628 -- invisibility timer
2629 local mount_invisibility = u8(WRAM.sprite_miscellaneous18 + yoshi_id)
2630 if mount_invisibility ~= 0 then
2631 Text_opacity = 0.5
2632 draw_text(AR_x*(x_screen + 4), AR_y*(y_screen - 12), mount_invisibility, COLOUR.yoshi)
2633 end
2634
2635 -- Tongue hitbox and timer
2636 if tongue_wait ~= 0 or tongue_out ~=0 or tongue_height == 0x89 then -- if tongue is out or appearing
2637 -- the position of the hitbox pixel
2638 local tongue_direction = yoshi_direction == 0 and 1 or -1
2639 local tongue_high = tongue_height ~= 0x89
2640 local x_tongue = x_screen + 24 - 40*yoshi_direction + tongue_len*tongue_direction
2641 x_tongue = not tongue_high and x_tongue or x_tongue - 5*tongue_direction
2642 local y_tongue = y_screen + 10 + 11*(tongue_high and 0 or 1)
2643
2644 -- the drawing
2645 local tongue_line
2646 if tongue_wait <= 9 then -- hitbox point vs berry tile
2647 draw_rectangle(x_tongue - 1, y_tongue - 1, 2, 2, COLOUR.tongue_bg, COLOUR.text)
2648 tongue_line = COLOUR.tongue_line
2649 else tongue_line = COLOUR.tongue_bg
2650 end
2651
2652 -- tongue out: time predictor
2653 local tinfo, tcolor
2654 if tongue_wait > 9 then tinfo = tongue_wait - 9; tcolor = COLOUR.tongue_line -- not ready yet
2655
2656 elseif tongue_out == 1 then tinfo = 17 + tongue_wait; tcolor = COLOUR.text -- tongue going out
2657
2658 elseif tongue_out == 2 then -- at the max or tongue going back
2659 tinfo = math.max(tongue_wait, tongue_timer) + floor((tongue_len + 7)/4) - (tongue_len ~= 0 and 1 or 0)
2660 tcolor = eat_id == SMW.null_sprite_id and COLOUR.text or COLOUR.warning
2661
2662 elseif tongue_out == 0 then tinfo = 0; tcolor = COLOUR.text -- tongue in
2663
2664 else tinfo = tongue_timer + 1; tcolor = COLOUR.tongue_line -- item was just spat out
2665 end
2666
2667 Text_opacity = 0.5
2668 draw_text(AR_x*(x_tongue + 4), AR_y*(y_tongue + 5), tinfo, tcolor, false, false, 0.5)
2669 Text_opacity = 1.0
2670 draw_rectangle(x_tongue, y_tongue + 1, 8, 4, tongue_line, COLOUR.tongue_bg)
2671 end
2672
2673 end
2674end
2675
2676
2677local function show_counters()
2678 if not OPTIONS.display_counters then
2679 return
2680 end
2681
2682 -- Font
2683 Text_opacity = 1.0
2684 Bg_opacity = 1.0
2685 local height = BIZHAWK_FONT_HEIGHT
2686 local text_counter = 0
2687
2688 local pipe_entrance_timer = u8(WRAM.pipe_entrance_timer)
2689 local multicoin_block_timer = u8(WRAM.multicoin_block_timer)
2690 local gray_pow_timer = u8(WRAM.gray_pow_timer)
2691 local blue_pow_timer = u8(WRAM.blue_pow_timer)
2692 local dircoin_timer = u8(WRAM.dircoin_timer)
2693 local pballoon_timer = u8(WRAM.pballoon_timer)
2694 local star_timer = u8(WRAM.star_timer)
2695 local invisibility_timer = u8(WRAM.invisibility_timer)
2696 local animation_timer = u8(WRAM.animation_timer)
2697 local fireflower_timer = u8(WRAM.fireflower_timer)
2698 local yoshi_timer = u8(WRAM.yoshi_timer)
2699 local swallow_timer = u8(WRAM.swallow_timer)
2700 local lakitu_timer = u8(WRAM.lakitu_timer)
2701 local score_incrementing = u8(WRAM.score_incrementing)
2702 local end_level_timer = u8(WRAM.end_level_timer)
2703
2704 local display_counter = function(label, value, default, mult, frame, color)
2705 if value == default then return end
2706 text_counter = text_counter + 1
2707 local color = color or COLOUR.text
2708
2709 draw_text(- Border_left, AR_y*102 + (text_counter * height), fmt("%s: %d", label, (value * mult) - frame), color)
2710 end
2711
2712 if Player_animation_trigger == 5 or Player_animation_trigger == 6 then
2713 display_counter("Pipe", pipe_entrance_timer, -1, 1, 0, COLOUR.counter_pipe)
2714 end
2715 display_counter("Multi Coin", multicoin_block_timer, 0, 1, 0, COLOUR.counter_multicoin)
2716 display_counter("Pow", gray_pow_timer, 0, 4, Effective_frame % 4, COLOUR.counter_gray_pow)
2717 display_counter("Pow", blue_pow_timer, 0, 4, Effective_frame % 4, COLOUR.counter_blue_pow)
2718 display_counter("Dir Coin", dircoin_timer, 0, 4, Real_frame % 4, COLOUR.counter_dircoin)
2719 display_counter("P-Balloon", pballoon_timer, 0, 4, Real_frame % 4, COLOUR.counter_pballoon)
2720 display_counter("Star", star_timer, 0, 4, (Effective_frame - 3) % 4, COLOUR.counter_star)
2721 display_counter("Invisibility", invisibility_timer, 0, 1, 0)
2722 display_counter("Fireflower", fireflower_timer, 0, 1, 0, COLOUR.counter_fireflower)
2723 display_counter("Yoshi", yoshi_timer, 0, 1, 0, COLOUR.yoshi)
2724 display_counter("Swallow", swallow_timer, 0, 4, (Effective_frame - 1) % 4, COLOUR.yoshi)
2725 display_counter("Lakitu", lakitu_timer, 0, 4, Effective_frame % 4)
2726 display_counter("End Level", end_level_timer, 0, 2, (Real_frame - 1) % 2)
2727 display_counter("Score Incrementing", score_incrementing, 0x50, 1, 0)
2728
2729 if Lock_animation_flag ~= 0 then display_counter("Animation", animation_timer, 0, 1, 0) end -- shows when player is getting hurt or dying
2730
2731end
2732
2733
2734-- Main function to run inside a level
2735local function level_mode()
2736 if Game_mode == SMW.game_mode_level then
2737
2738 -- Draws/Erases the tiles if user clicked
2739 draw_layer1_tiles(Camera_x, Camera_y)
2740
2741 draw_layer2_tiles()
2742
2743 sprites()
2744
2745 extended_sprites()
2746
2747 cluster_sprites()
2748
2749 minor_extended_sprites()
2750
2751 bounce_sprite_info()
2752
2753 level_info()
2754
2755 player()
2756
2757 yoshi()
2758
2759 show_counters()
2760
2761 -- Draws/Erases the hitbox for objects
2762 if User_input.mouse_inwindow then
2763 select_object(User_input.xmouse, User_input.ymouse, Camera_x, Camera_y)
2764 end
2765
2766 end
2767end
2768
2769
2770local function overworld_mode()
2771 if Game_mode ~= SMW.game_mode_overworld then return end
2772
2773 -- Font
2774 Text_opacity = 1.0
2775 Bg_opacity = 1.0
2776
2777 local height = BIZHAWK_FONT_HEIGHT
2778 local y_text = BIZHAWK_FONT_HEIGHT
2779
2780 -- Real frame modulo 8
2781 local real_frame_8 = Real_frame%8
2782 draw_text(Buffer_width + Border_right, y_text, fmt("Real Frame = %3d = %d(mod 8)", Real_frame, real_frame_8), true)
2783
2784 -- Star Road info
2785 local star_speed = u8(WRAM.star_road_speed)
2786 local star_timer = u8(WRAM.star_road_timer)
2787 y_text = y_text + height
2788 draw_text(Buffer_width + Border_right, y_text, fmt("Star Road(%x %x)", star_speed, star_timer), COLOUR.cape, true)
2789end
2790
2791
2792local function left_click()
2793 -- Call options menu if the form is closed
2794 if Options_form.is_form_closed and mouse_onregion(120*AR_x, 0, 120*AR_x + 4*BIZHAWK_FONT_WIDTH, BIZHAWK_FONT_HEIGHT) then -- bizhawk
2795 Options_form.create_window()
2796 return
2797 end
2798
2799 -- Drag and drop sprites
2800 if Cheat.allow_cheats then
2801 local id = select_object(User_input.xmouse, User_input.ymouse, Camera_x, Camera_y)
2802 if type(id) == "number" and id >= 0 and id < SMW.sprite_max then
2803 Cheat.dragging_sprite_id = id
2804 Cheat.is_dragging_sprite = true
2805 return
2806 end
2807 end
2808
2809 -- Layer 1 tiles
2810 local x_mouse, y_mouse = game_coordinates(User_input.xmouse, User_input.ymouse, Camera_x, Camera_y)
2811 x_mouse = 16*floor(x_mouse/16)
2812 y_mouse = 16*floor(y_mouse/16)
2813 if User_input.mouse_inwindow then
2814 select_tile(x_mouse, y_mouse, Layer1_tiles)
2815 end
2816end
2817
2818
2819-- This function runs at the end of paint callback
2820-- Specific for info that changes if the emulator is paused and idle callback is called
2821local function mouse_actions()
2822 -- Font
2823 Text_opacity = 1.0
2824
2825 if Cheat.allow_cheats then -- show cheat status anyway
2826 alert_text(-Border_left, Buffer_height + Border_bottom, "Cheats: allowed", COLOUR.warning, COLOUR.warning_bg,
2827 true, false, 0.0, 1.0)
2828 end
2829
2830 -- Drag and drop sprites with the mouse
2831 if Cheat.is_dragging_sprite then
2832 Cheat.drag_sprite(Cheat.dragging_sprite_id)
2833 Cheat.is_cheating = true
2834 end
2835
2836end
2837
2838
2839local function read_raw_input()
2840 -- User input data
2841 Previous.User_input = copytable(User_input)
2842 local tmp = input.get()
2843 for entry, value in pairs(User_input) do
2844 User_input[entry] = tmp[entry] or false
2845 end
2846 -- Mouse input
2847 tmp = input.getmouse()
2848 User_input.xmouse = tmp.X
2849 User_input.ymouse = tmp.Y
2850 User_input.leftclick = tmp.Left
2851 User_input.rightclick = tmp.Right
2852 -- BizHawk, custom field
2853 User_input.mouse_inwindow = mouse_onregion(-Border_left, -Border_top, Buffer_width + Border_right, Buffer_height + Border_bottom)
2854
2855 -- Detect if a key was just pressed or released
2856 for entry, value in pairs(User_input) do
2857 if (value ~= false) and (Previous.User_input[entry] == false) then Keys.pressed[entry] = true
2858 else Keys.pressed[entry] = false
2859 end
2860 if (value == false) and (Previous.User_input[entry] ~= false) then Keys.released[entry] = true
2861 else Keys.released[entry] = false
2862 end
2863 end
2864
2865 -- Key presses/releases execution:
2866 for entry, value in pairs(Keys.press) do
2867 if Keys.pressed[entry] then
2868 value()
2869 end
2870 end
2871 for entry, value in pairs(Keys.release) do
2872 if Keys.released[entry] then
2873 value()
2874 end
2875 end
2876
2877end
2878
2879
2880
2881--#############################################################################
2882-- CHEATS
2883
2884-- This signals that some cheat is activated, or was some short time ago
2885Cheat.allow_cheats = false
2886Cheat.is_cheating = false
2887function Cheat.is_cheat_active()
2888 if Cheat.is_cheating then
2889 Text_opacity = 1.0
2890 Bg_opacity = 1.0
2891 alert_text(Buffer_middle_x - 3*BIZHAWK_FONT_WIDTH, BIZHAWK_FONT_HEIGHT, " CHEAT ", COLOUR.warning, COLOUR.warning_bg)
2892 Previous.is_cheating = true
2893 else
2894 if Previous.is_cheating then
2895 gui.addmessage("Script applied cheat") -- BizHawk
2896 Previous.is_cheating = false
2897 end
2898 end
2899end
2900
2901
2902-- Called from Cheat.beat_level()
2903function Cheat.activate_next_level(secret_exit)
2904 if u8(WRAM.level_exit_type) == 0x80 and u8(WRAM.midway_point) == 1 then
2905 if secret_exit then
2906 w8(WRAM.level_exit_type, 0x2)
2907 else
2908 w8(WRAM.level_exit_type, 1)
2909 end
2910 end
2911
2912 Cheat.is_cheating = true
2913end
2914
2915
2916-- allows start + select + X to activate the normal exit
2917-- start + select + A to activate the secret exit
2918-- start + select + B to exit the level without activating any exits
2919function Cheat.beat_level()
2920 if Is_paused and Joypad["Select"] and (Joypad["X"] or Joypad["A"] or Joypad["B"]) then
2921 w8(WRAM.level_flag_table + Level_index, bit.bor(Level_flag, 0x80))
2922
2923 local secret_exit = Joypad["A"]
2924 if not Joypad["B"] then
2925 w8(WRAM.midway_point, 1)
2926 else
2927 w8(WRAM.midway_point, 0)
2928 end
2929
2930 Cheat.activate_next_level(secret_exit)
2931 end
2932end
2933
2934
2935-- This function makes Mario's position free
2936-- Press L+R+up to activate and L+R+down to turn it off.
2937-- While active, press directionals to fly free and Y or X to boost him up
2938Cheat.under_free_move = false
2939function Cheat.free_movement()
2940 if (Joypad["L"] and Joypad["R"] and Joypad["Up"]) then Cheat.under_free_move = true end
2941 if (Joypad["L"] and Joypad["R"] and Joypad["Down"]) then Cheat.under_free_move = false end
2942 if not Cheat.under_free_move then
2943 if Previous.under_free_move then w8(WRAM.frozen, 0) end
2944 return
2945 end
2946
2947 local x_pos, y_pos = u16(WRAM.x), u16(WRAM.y)
2948 local movement_mode = u8(WRAM.player_animation_trigger)
2949 local pixels = (Joypad["Y"] and 7) or (Joypad["X"] and 4) or 1 -- how many pixels per frame
2950
2951 if Joypad["Left"] then x_pos = x_pos - pixels end
2952 if Joypad["Right"] then x_pos = x_pos + pixels end
2953 if Joypad["Up"] then y_pos = y_pos - pixels end
2954 if Joypad["Down"] then y_pos = y_pos + pixels end
2955
2956 -- freeze player to avoid deaths
2957 if movement_mode == 0 then
2958 w8(WRAM.frozen, 1)
2959 w8(WRAM.x_speed, 0)
2960 w8(WRAM.y_speed, 0)
2961
2962 -- animate sprites by incrementing the effective frame
2963 w8(WRAM.effective_frame, (u8(WRAM.effective_frame) + 1) % 256)
2964 else
2965 w8(WRAM.frozen, 0)
2966 end
2967
2968 -- manipulate some values
2969 w16(WRAM.x, x_pos)
2970 w16(WRAM.y, y_pos)
2971 w8(WRAM.invisibility_timer, 127)
2972 w8(WRAM.vertical_scroll_flag_header, 1) -- free vertical scrolling
2973 w8(WRAM.vertical_scroll_enabled, 1)
2974
2975 Cheat.is_cheating = true
2976 Previous.under_free_move = true
2977end
2978
2979
2980-- Drag and drop sprites with the mouse, if the cheats are activated and mouse is over the sprite
2981-- Right clicking and holding: drags the sprite
2982-- Releasing: drops it over the latest spot
2983function Cheat.drag_sprite(id)
2984 if Game_mode ~= SMW.game_mode_level then Cheat.is_dragging_sprite = false ; return end
2985
2986 local xoff, yoff = Sprites_info[id].xoff, Sprites_info[id].yoff
2987 local xgame, ygame = game_coordinates(User_input.xmouse - xoff, User_input.ymouse - yoff, Camera_x, Camera_y)
2988
2989 local sprite_xhigh = floor(xgame/256)
2990 local sprite_xlow = xgame - 256*sprite_xhigh
2991 local sprite_yhigh = floor(ygame/256)
2992 local sprite_ylow = ygame - 256*sprite_yhigh
2993
2994 w8(WRAM.sprite_x_high + id, sprite_xhigh)
2995 w8(WRAM.sprite_x_low + id, sprite_xlow)
2996 w8(WRAM.sprite_y_high + id, sprite_yhigh)
2997 w8(WRAM.sprite_y_low + id, sprite_ylow)
2998end
2999
3000
3001function Cheat.score()
3002 if not Cheat.allow_cheats then
3003 print("Cheats not allowed.")
3004 return
3005 end
3006
3007 local num = forms.gettext(Options_form.score_number)
3008 local is_hex = num:sub(1,2):lower() == "0x"
3009 num = tonumber(num)
3010
3011 if not num or num%1 ~= 0 or num < 0
3012 or num > 9999990 or (not is_hex and num%10 ~= 0) then
3013 print("Enter a valid score: hexadecimal representation or decimal ending in 0.")
3014 return
3015 end
3016
3017 num = is_hex and num or num/10
3018 w24(WRAM.mario_score, num)
3019
3020 print(fmt("Cheat: score set to %d0.", num))
3021 Cheat.is_cheating = true
3022end
3023
3024
3025-- BizHawk: modifies address <address> value from <current> to <current + modification>
3026-- [size] is the optional size in bytes of the address
3027-- TODO: [is_signed] is untrue if the value is unsigned, true otherwise
3028function Cheat.change_address(address, value_form, size, criterion, error_message, success_message)
3029 if not Cheat.allow_cheats then
3030 print("Cheats not allowed.")
3031 return
3032 end
3033
3034 size = size or 1
3035 local max_value = 256^size - 1
3036 local value = Options_form[value_form] and forms.gettext(Options_form[value_form]) or value_form
3037 local default_criterion = function(value)
3038 value = tonumber(value)
3039 if not value or value%1 ~= 0 or value < 0 or value > max_value then
3040 return false
3041 else
3042 return value
3043 end
3044 end
3045
3046 local new = default_criterion(value)
3047 if criterion and new then
3048 new = criterion(new) and new or false
3049 end
3050 if not new then
3051 print(error_message or "Enter a valid value.")
3052 return
3053 end
3054
3055 local memoryf = (size == 1 and w8) or (size == 2 and w16) or (size == 3 and w24) or error"size is too big"
3056 memoryf(address, new)
3057 print(fmt("Cheat: %s set to %d.", success_message, new) or fmt("Cheat: set WRAM 0x%X to %d.", address, new))
3058 Cheat.is_cheating = true
3059end
3060
3061
3062--#############################################################################
3063-- MAIN --
3064
3065
3066-- Key presses:
3067Keys.registerkeypress("rightclick", right_click)
3068Keys.registerkeypress("leftclick", left_click)
3069
3070-- Key releases:
3071Keys.registerkeyrelease("mouse_inwindow", function() Cheat.is_dragging_sprite = false end)
3072Keys.registerkeyrelease("leftclick", function() Cheat.is_dragging_sprite = false end)
3073
3074-- Lateral gaps:
3075if not OLD_EMU_VERSION then
3076 client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
3077 client.SetClientExtraPadding(0, 0, 0, 0)
3078end
3079
3080function Options_form.create_window()
3081 Options_form.form = forms.newform(220, 554, "SMW Options")
3082 local xform, yform, delta_y = 2, 0, 20
3083
3084 -- Top label
3085 Options_form.label_cheats = forms.label(Options_form.form, "You can close this form at any time", xform, yform, 200, 20)
3086
3087 yform = yform + delta_y
3088 Options_form.allow_cheats = forms.checkbox(Options_form.form, "Allow cheats", xform, yform)
3089 forms.setproperty(Options_form.allow_cheats, "Checked", Cheat.allow_cheats)
3090
3091 xform = xform + 105
3092 forms.button(Options_form.form, "Powerup", function() Cheat.change_address(WRAM.powerup, "powerup_number", 1,
3093 nil, "Enter a valid integer (0-255).", "powerup")
3094 end, xform, yform, 58, 24)
3095
3096 yform = yform + 2
3097 xform = xform + 59
3098 Options_form.powerup_number = forms.textbox(Options_form.form, "", 24, 16, "UNSIGNED", xform, yform, false, false)
3099
3100 xform = 2
3101 yform = yform + 28
3102 forms.button(Options_form.form, "Score", Cheat.score, xform, yform, 43, 24)
3103
3104 yform = yform + 2
3105 xform = xform + 45
3106 Options_form.score_number = forms.textbox(Options_form.form, fmt("0x%X", u24(WRAM.mario_score)), 48, 16, nil, xform, yform, false, false)
3107
3108 xform = xform + 59
3109 forms.button(Options_form.form, "Coin", function() Cheat.change_address(WRAM.player_coin, "coin_number", 1,
3110 function(num) return num < 100 end, "Enter an integer between 0 and 99.", "coin")
3111 end, xform, yform, 43, 24)
3112
3113 yform = yform + 2
3114 xform = xform + 45
3115 Options_form.coin_number = forms.textbox(Options_form.form, "", 24, 16, "UNSIGNED", xform, yform, false, false)
3116
3117 xform = 2
3118 yform = yform + 28
3119 forms.button(Options_form.form, "Box", function() Cheat.change_address(0x0dc2, "item_box_number", 1, -- unlisted WRAM
3120 nil, "Enter a valid integer (0-255).", "Item box")
3121 end, xform, yform, 43, 24)
3122
3123 yform = yform + 2
3124 xform = xform + 45
3125 Options_form.item_box_number = forms.textbox(Options_form.form, "", 24, 16, "UNSIGNED", xform, yform, false, false)
3126
3127 -- SHOW/HIDE
3128 xform = 2
3129 yform = yform + 28
3130 Options_form.label1 = forms.label(Options_form.form, "Show/hide options:", xform, yform)
3131
3132 yform = yform + delta_y
3133 local y_begin_showhide = yform -- 1st column
3134 Options_form.debug_info = forms.checkbox(Options_form.form, "Debug info", xform, yform)
3135 forms.setproperty(Options_form.debug_info, "Checked", OPTIONS.display_debug_info)
3136
3137 yform = yform + delta_y
3138 Options_form.movie_info = forms.checkbox(Options_form.form, "Movie info", xform, yform)
3139 forms.setproperty(Options_form.movie_info, "Checked", OPTIONS.display_movie_info)
3140
3141 yform = yform + delta_y
3142 Options_form.misc_info = forms.checkbox(Options_form.form, "Miscellaneous", xform, yform)
3143 forms.setproperty(Options_form.misc_info, "Checked", OPTIONS.display_misc_info)
3144
3145 yform = yform + delta_y
3146 Options_form.player_info = forms.checkbox(Options_form.form, "Player info", xform, yform)
3147 forms.setproperty(Options_form.player_info, "Checked", OPTIONS.display_player_info)
3148
3149 yform = yform + delta_y
3150 Options_form.sprite_info = forms.checkbox(Options_form.form, "Sprite info", xform, yform)
3151 forms.setproperty(Options_form.sprite_info, "Checked", OPTIONS.display_sprite_info)
3152
3153 yform = yform + delta_y
3154 Options_form.sprite_hitbox = forms.checkbox(Options_form.form, "Sprite hitbox", xform, yform)
3155 forms.setproperty(Options_form.sprite_hitbox, "Checked", OPTIONS.display_sprite_hitbox)
3156
3157 yform = yform + delta_y
3158 Options_form.sprite_tables = forms.checkbox(Options_form.form, "Sprite tables", xform, yform)
3159 forms.setproperty(Options_form.sprite_tables, "Checked", OPTIONS.display_miscellaneous_sprite_table)
3160
3161 yform = yform + delta_y
3162 Options_form.extended_sprite_info = forms.checkbox(Options_form.form, "Extended sprites", xform, yform)
3163 forms.setproperty(Options_form.extended_sprite_info, "Checked", OPTIONS.display_extended_sprite_info)
3164
3165 xform = xform + 105 -- 2nd column
3166 yform = y_begin_showhide
3167 Options_form.cluster_sprite_info = forms.checkbox(Options_form.form, "Cluster sprites", xform, yform)
3168 forms.setproperty(Options_form.cluster_sprite_info, "Checked", OPTIONS.display_cluster_sprite_info)
3169
3170 yform = yform + delta_y
3171 Options_form.minor_extended_sprite_info = forms.checkbox(Options_form.form, "Minor ext. spr.", xform, yform)
3172 forms.setproperty(Options_form.minor_extended_sprite_info, "Checked", OPTIONS.display_minor_extended_sprite_info)
3173
3174 yform = yform + delta_y
3175 Options_form.bounce_sprite_info = forms.checkbox(Options_form.form, "Bounce sprites", xform, yform)
3176 forms.setproperty(Options_form.bounce_sprite_info, "Checked", OPTIONS.display_bounce_sprite_info)
3177
3178 yform = yform + delta_y
3179 Options_form.level_info = forms.checkbox(Options_form.form, "Level info", xform, yform)
3180 forms.setproperty(Options_form.level_info, "Checked", OPTIONS.display_level_info)
3181
3182 yform = yform + delta_y
3183 Options_form.yoshi_info = forms.checkbox(Options_form.form, "Yoshi info", xform, yform)
3184 forms.setproperty(Options_form.yoshi_info, "Checked", OPTIONS.display_yoshi_info)
3185
3186 yform = yform + delta_y
3187 Options_form.counters_info = forms.checkbox(Options_form.form, "Counters info", xform, yform)
3188 forms.setproperty(Options_form.counters_info, "Checked", OPTIONS.display_counters)
3189
3190 yform = yform + delta_y
3191 Options_form.static_camera_region = forms.checkbox(Options_form.form, "Static camera", xform, yform)
3192 forms.setproperty(Options_form.static_camera_region, "Checked", OPTIONS.display_static_camera_region)
3193 yform = yform + delta_y -- if odd number of show/hide checkboxes
3194
3195 xform, yform = 2, yform + 30
3196 forms.label(Options_form.form, "Player hitbox:", xform, yform + 2, 70, 25)
3197 xform = xform + 70
3198 Options_form.player_hitbox = forms.dropdown(Options_form.form, {"Hitbox", "Interaction points", "Both", "None"}, xform, yform)
3199 xform, yform = 2, yform + 30
3200
3201 -- DEBUG/EXTRA
3202 forms.label(Options_form.form, "Debug info:", xform, yform, 62, 22)
3203 yform = yform + delta_y
3204
3205 local y_begin_debug = yform -- 1st column
3206 Options_form.debug_player_extra = forms.checkbox(Options_form.form, "Player extra", xform, yform)
3207 forms.setproperty(Options_form.debug_player_extra, "Checked", OPTIONS.display_debug_player_extra)
3208 yform = yform + delta_y
3209
3210 Options_form.debug_sprite_extra = forms.checkbox(Options_form.form, "Sprite extra", xform, yform)
3211 forms.setproperty(Options_form.debug_sprite_extra, "Checked", OPTIONS.display_debug_sprite_extra)
3212 yform = yform + delta_y
3213
3214 Options_form.debug_sprite_tweakers = forms.checkbox(Options_form.form, "Sprite tweakers", xform, yform)
3215 forms.setproperty(Options_form.debug_sprite_tweakers, "Checked", OPTIONS.display_debug_sprite_tweakers)
3216 yform = yform + delta_y
3217
3218 Options_form.debug_extended_sprite = forms.checkbox(Options_form.form, "Extended sprites", xform, yform)
3219 forms.setproperty(Options_form.debug_extended_sprite, "Checked", OPTIONS.display_debug_extended_sprite)
3220 yform = yform + delta_y
3221
3222 xform, yform = xform + 105, y_begin_debug
3223 Options_form.debug_cluster_sprite = forms.checkbox(Options_form.form, "Cluster sprites", xform, yform)
3224 forms.setproperty(Options_form.debug_cluster_sprite, "Checked", OPTIONS.display_debug_cluster_sprite)
3225 yform = yform + delta_y
3226
3227 Options_form.debug_minor_extended_sprite = forms.checkbox(Options_form.form, "Minor ext. spr.", xform, yform)
3228 forms.setproperty(Options_form.debug_minor_extended_sprite, "Checked", OPTIONS.display_debug_minor_extended_sprite)
3229 yform = yform + delta_y
3230
3231 Options_form.debug_bounce_sprite = forms.checkbox(Options_form.form, "Bounce sprites", xform, yform)
3232 forms.setproperty(Options_form.debug_bounce_sprite, "Checked", OPTIONS.display_debug_bounce_sprite)
3233 yform = yform + delta_y
3234
3235 Options_form.debug_controller_data = forms.checkbox(Options_form.form, "Controller data", xform, yform)
3236 forms.setproperty(Options_form.debug_controller_data, "Checked", OPTIONS.display_debug_controller_data)
3237 --yform = yform + delta_y
3238
3239 -- HELP:
3240 xform, yform = 4, yform + 30
3241 forms.label(Options_form.form, "Miscellaneous:", xform, yform, 78, 22)
3242 xform, yform = xform + 78, yform - 2
3243
3244 Options_form.draw_tiles_with_click = forms.checkbox(Options_form.form, "Draw/erase tiles", xform, yform)
3245 forms.setproperty(Options_form.draw_tiles_with_click, "Checked", OPTIONS.draw_tiles_with_click)
3246 xform, yform = 4, yform + 30
3247
3248 -- OPACITY
3249 Options_form.text_opacity = forms.label(Options_form.form, ("Text opacity: (%.0f%%, %.0f%%)"):
3250 format(100*Text_max_opacity, 100*Background_max_opacity), xform, yform, 135, 22)
3251 ;
3252 xform, yform = xform + 135, yform - 4
3253 forms.button(Options_form.form, "-", function() decrease_opacity()
3254 forms.settext(Options_form.text_opacity, ("Text opacity: (%.0f%%, %.0f%%)"):format(100*Text_max_opacity, 100*Background_max_opacity))
3255 end, xform, yform, 14, 24)
3256 xform = xform + 14
3257 forms.button(Options_form.form, "+", function() increase_opacity()
3258 forms.settext(Options_form.text_opacity, ("Text opacity: (%.0f%%, %.0f%%)"):format(100*Text_max_opacity, 100*Background_max_opacity))
3259 end, xform, yform, 14, 24)
3260 xform, yform = 4, yform + 25
3261
3262 -- HELP
3263 Options_form.erase_tiles = forms.button(Options_form.form, "Erase tiles", function() Layer1_tiles = {}; Layer2_tiles = {} end, xform, yform)
3264 xform = xform + 105
3265 Options_form.write_help_handle = forms.button(Options_form.form, "Help", Options_form.write_help, xform, yform)
3266end
3267
3268
3269function Options_form.evaluate_form()
3270 -- Option form's buttons
3271 Cheat.allow_cheats = forms.ischecked(Options_form.allow_cheats) or false
3272 OPTIONS.display_debug_info = forms.ischecked(Options_form.debug_info) or false
3273 -- Show/hide
3274 OPTIONS.display_movie_info = forms.ischecked(Options_form.movie_info) or false
3275 OPTIONS.display_misc_info = forms.ischecked(Options_form.misc_info) or false
3276 OPTIONS.display_player_info = forms.ischecked(Options_form.player_info) or false
3277 OPTIONS.display_sprite_info = forms.ischecked(Options_form.sprite_info) or false
3278 OPTIONS.display_sprite_hitbox = forms.ischecked(Options_form.sprite_hitbox) or false
3279 OPTIONS.display_miscellaneous_sprite_table = forms.ischecked(Options_form.sprite_tables) or false
3280 OPTIONS.display_extended_sprite_info = forms.ischecked(Options_form.extended_sprite_info) or false
3281 OPTIONS.display_cluster_sprite_info = forms.ischecked(Options_form.cluster_sprite_info) or false
3282 OPTIONS.display_minor_extended_sprite_info = forms.ischecked(Options_form.minor_extended_sprite_info) or false
3283 OPTIONS.display_bounce_sprite_info = forms.ischecked(Options_form.bounce_sprite_info) or false
3284 OPTIONS.display_level_info = forms.ischecked(Options_form.level_info) or false
3285 OPTIONS.display_yoshi_info = forms.ischecked(Options_form.yoshi_info) or false
3286 OPTIONS.display_counters = forms.ischecked(Options_form.counters_info) or false
3287 OPTIONS.display_static_camera_region = forms.ischecked(Options_form.static_camera_region) or false
3288 -- Debug/Extra
3289 OPTIONS.display_debug_player_extra = forms.ischecked(Options_form.debug_player_extra) or false
3290 OPTIONS.display_debug_sprite_extra = forms.ischecked(Options_form.debug_sprite_extra) or false
3291 OPTIONS.display_debug_sprite_tweakers = forms.ischecked(Options_form.debug_sprite_tweakers) or false
3292 OPTIONS.display_debug_extended_sprite = forms.ischecked(Options_form.debug_extended_sprite) or false
3293 OPTIONS.display_debug_cluster_sprite = forms.ischecked(Options_form.debug_cluster_sprite) or false
3294 OPTIONS.display_debug_minor_extended_sprite = forms.ischecked(Options_form.debug_minor_extended_sprite) or false
3295 OPTIONS.display_debug_bounce_sprite = forms.ischecked(Options_form.debug_bounce_sprite) or false
3296 OPTIONS.display_debug_controller_data = forms.ischecked(Options_form.debug_controller_data) or false
3297 -- Other buttons
3298 OPTIONS.draw_tiles_with_click = forms.ischecked(Options_form.draw_tiles_with_click) or false
3299 local button_text = forms.gettext(Options_form.player_hitbox)
3300 OPTIONS.display_player_hitbox = button_text == "Both" or button_text == "Hitbox"
3301 OPTIONS.display_interaction_points = button_text == "Both" or button_text == "Interaction points"
3302
3303 -- Save the configurations
3304 if Bizhawk_loop_counter == 0 then INI.save_options() end
3305end
3306
3307
3308function Options_form.write_help()
3309 print(" - - - TIPS - - - ")
3310 print("MOUSE:")
3311 print("Use the left click to draw blocks and to see the Map16 properties.")
3312 print("Use the right click to toogle the hitbox mode of Mario and sprites.")
3313 print("\n")
3314
3315 print("CHEATS(better turn off while recording a movie):")
3316 print("L+R+up: stop gravity for Mario fly / L+R+down to cancel")
3317 print("Use the mouse to drag and drop sprites")
3318 print("While paused: B+select to get out of the level")
3319 print(" X+select to beat the level (main exit)")
3320 print(" A+select to get the secret exit (don't use it if there isn't one)")
3321
3322 print("\n")
3323 print("OTHERS:")
3324 print("If performance suffers, disable some options that are not needed at the moment.")
3325 print(" - - - end of tips - - - ")
3326end
3327Options_form.create_window()
3328Options_form.is_form_closed = false
3329
3330
3331event.unregisterbyname("smw-tas-bizhawk-onexit")
3332event.onexit(function()
3333 local destroyed = forms.destroy(Options_form.form)
3334 if not OLD_EMU_VERSION then
3335 client.SetGameExtraPadding(0, 0, 0, 0)
3336 client.SetClientExtraPadding(0, 0, 0, 0)
3337 end
3338
3339 print("Finishing smw-bizhawk script.")
3340 client.paint()
3341end, "smw-tas-bizhawk-onexit")
3342
3343
3344while true do
3345 if emu.getsystemid() ~= "SNES" then
3346 gui.text(0, 0, "WRONG CORE: " .. emu.getsystemid(), "black", "red", "bottomright")
3347
3348 else
3349
3350 Options_form.is_form_closed = forms.gettext(Options_form.player_hitbox) == ""
3351 if not Options_form.is_form_closed then Options_form.evaluate_form() end
3352
3353 bizhawk_status()
3354 bizhawk_screen_info()
3355 read_raw_input()
3356
3357 -- Drawings are allowed now
3358 scan_smw()
3359 level_mode()
3360 overworld_mode()
3361 show_movie_info()
3362 if Is_lagged then -- BizHawk: outside show_movie_info
3363 alert_text(Buffer_middle_x - 3*BIZHAWK_FONT_WIDTH, 2*BIZHAWK_FONT_HEIGHT, " LAG ", COLOUR.warning, COLOUR.warning_bg)
3364 end
3365 show_misc_info()
3366 show_controller_data()
3367
3368 Cheat.is_cheat_active()
3369
3370 mouse_actions()
3371
3372 -- Checks if options form exits and create a button in case it doesn't
3373 if Options_form.is_form_closed then
3374 if User_input.mouse_inwindow then
3375 draw_rectangle(120 - 1, 0, 4*BIZHAWK_FONT_WIDTH/AR_x + 1, BIZHAWK_FONT_HEIGHT/AR_y + 1, 0xff000000, 0xff808080) -- bizhawk
3376 gui.text(120*AR_x + Border_left, 0 + Border_top, "Menu") -- unlisted color
3377 end
3378 end
3379
3380 -- INPUT MANIPULATION
3381 Joypad = joypad.get(1)
3382 if Cheat.allow_cheats then
3383 Cheat.is_cheating = false
3384
3385 Cheat.beat_level()
3386 Cheat.free_movement()
3387 else
3388 -- Cancel any continuous cheat
3389 Cheat.under_free_move = false
3390
3391 Cheat.is_cheating = false
3392 end
3393 end
3394
3395 -- Frame advance: hack for performance
3396 Bizhawk_loop_counter = (Bizhawk_loop_counter + 1)%300 -- save options each 5 seconds
3397 if client.ispaused() then
3398 emu.yield()
3399 gui.clearGraphics()
3400 gui.cleartext()
3401 else
3402 emu.frameadvance()
3403 end
3404
3405end