Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alexbevi
GitHub Repository: alexbevi/BizHawk
Path: blob/master/Assets/Lua/SNES/Super Mario World.lua
2 views
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
12
local INI_CONFIG_FILENAME = "config.ini" -- relative to the folder of the script
13
local OLD_EMU_VERSION
14
15
local 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
60
local 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
131
local BIZHAWK_FONT_HEIGHT = 14
132
local BIZHAWK_FONT_WIDTH = 10
133
134
-- Symbols
135
local LEFT_ARROW = "<-"
136
local RIGHT_ARROW = "->"
137
138
-- Others
139
local Y_CAMERA_OFF = 1 -- small adjustment to display the tiles according to their actual graphics
140
141
-- Input key names
142
local 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
175
local gui, input, joypad, emu, movie, memory, mainmemory, bit = gui, input, joypad, emu, movie, memory, mainmemory, bit
176
local unpack = unpack or table.unpack
177
local 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
180
if tastudio == nil then
181
gui.text(0, 0, "This script works with BizHawk emulator.")
182
error("This script works with BizHawk emulator.")
183
elseif 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.")
188
else
189
OLD_EMU_VERSION = client.SetGameExtraPadding == nil
190
end
191
192
print("\nStarting smw-bizhawk script.")
193
194
-- TEST: INI library for handling an ini configuration file
195
function file_exists(name)
196
local f = io.open(name, "r")
197
if f ~= nil then io.close(f) return true else return false end
198
end
199
200
function 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
213
end
214
215
function 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
228
end
229
230
-- Creates a set from a list
231
local function make_set(list)
232
local set = {}
233
for _, l in ipairs(list) do set[l] = true end
234
return set
235
end
236
237
local INI = {}
238
239
function 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
257
end
258
259
-- creates the string for ini
260
function 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)
276
end
277
278
function 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
310
end
311
312
function 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
348
end
349
350
function 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
363
end
364
365
function 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()
371
end
372
373
function 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)
385
end
386
387
local 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
393
end
394
395
local OPTIONS = file_exists(INI_CONFIG_FILENAME) and
396
INI.retrieve(INI_CONFIG_FILENAME, {["BIZHAWK OPTIONS"] = DEFAULT_OPTIONS})["BIZHAWK OPTIONS"] or DEFAULT_OPTIONS
397
local COLOUR = file_exists(INI_CONFIG_FILENAME) and
398
INI.retrieve(INI_CONFIG_FILENAME, {["BIZHAWK COLOURS"] = DEFAULT_COLOUR})["BIZHAWK COLOURS"] or DEFAULT_COLOUR
399
INI.save(INI_CONFIG_FILENAME, {["BIZHAWK COLOURS"] = COLOUR})
400
INI.save(INI_CONFIG_FILENAME, {["BIZHAWK OPTIONS"] = OPTIONS})
401
402
function 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
410
end
411
interpret_color(COLOUR)
412
413
function INI.save_options()
414
INI.save(INI_CONFIG_FILENAME, {["BIZHAWK OPTIONS"] = OPTIONS})
415
end
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
421
local Text_max_opacity = COLOUR.default_text_opacity
422
local Background_max_opacity = COLOUR.default_bg_opacity
423
local Text_opacity = 1
424
local Bg_opacity = 1
425
426
local fmt = string.format
427
local floor = math.floor
428
429
-- unsigned to signed (based in <bits> bits)
430
local function signed(num, bits)
431
local maxval = 2^(bits - 1)
432
if num < maxval then return num else return num - 2*maxval end
433
end
434
435
-- Compatibility of the memory read/write functions
436
local u8 = mainmemory.read_u8
437
local s8 = mainmemory.read_s8
438
local w8 = mainmemory.write_u8
439
local u16 = mainmemory.read_u16_le
440
local s16 = mainmemory.read_s16_le
441
local w16 = mainmemory.write_u16_le
442
local u24 = mainmemory.read_u24_le
443
local s24 = mainmemory.read_s24_le
444
local w24 = mainmemory.write_u32_le
445
446
447
--#############################################################################
448
-- GAME AND SNES SPECIFIC MACROS:
449
450
451
local NTSC_FRAMERATE = 60.0988138974405
452
local PAL_FRAMERATE = 50.0069789081886
453
454
local 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
471
WRAM = {
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
}
666
local WRAM = WRAM
667
668
local X_INTERACTION_POINTS = {center = 0x8, left_side = 0x2 + 1, left_foot = 0x5, right_side = 0xe - 1, right_foot = 0xb}
669
670
local 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
677
local 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
744
local 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
763
local 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
788
local 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
801
local 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
804
local function make_set(list)
805
local set = {}
806
for _, l in ipairs(list) do set[l] = true end
807
return set
808
end
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)
812
local 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
815
local ABNORMAL_HITBOX_SPRITES = make_set{0x62, 0x63, 0x6b, 0x6c}
816
817
-- Sprites whose clipping interaction points usually matter
818
local GOOD_SPRITES_CLIPPING = make_set{
819
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xf, 0x10, 0x11, 0x13, 0x14, 0x18,
820
0x1b, 0x1d, 0x1f, 0x20, 0x26, 0x27, 0x29, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31,
821
0x32, 0x34, 0x35, 0x3d, 0x3e, 0x3f, 0x40, 0x46, 0x47, 0x48, 0x4d, 0x4e,
822
0x51, 0x53, 0x6e, 0x6f, 0x70, 0x80, 0x81, 0x86,
823
0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa1, 0xa2, 0xa5, 0xa6, 0xa7, 0xab, 0xb2,
824
0xb4, 0xbb, 0xbc, 0xbd, 0xbf, 0xc3, 0xda, 0xdb, 0xdc, 0xdd, 0xdf
825
}
826
827
-- Extended sprites that don't interact with the player
828
local 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
836
local Cheat = {} -- family of cheat functions and variables
837
local Previous = {}
838
local User_input = INPUT_KEYNAMES -- BizHawk
839
local Joypad = {}
840
local Layer1_tiles = {}
841
local Layer2_tiles = {}
842
local Is_lagged = nil
843
local Mario_boost_indicator = nil
844
local Show_player_point_position = false
845
local Sprites_info = {} -- keeps track of useful sprite info that might be used outside the main sprite function
846
local Sprite_hitbox = {} -- keeps track of what sprite slots must display the hitbox
847
local Options_form = {} -- BizHawk
848
local Bizhawk_loop_counter = 1 -- BizHawk specific, a hack for saving the ini regularly
849
850
-- Initialization of some tables
851
for i = 0, SMW.sprite_max -1 do
852
Sprites_info[i] = {}
853
end
854
for 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
859
end
860
861
862
local 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
874
end
875
876
877
-- Sum of the digits of a integer
878
local 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
886
end
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
891
local 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
921
end
922
923
924
bit.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
929
local Keys = {}
930
Keys.press = {}
931
Keys.release = {}
932
Keys.down, Keys.up, Keys.pressed, Keys.released = {}, {}, {}, {}
933
function Keys.registerkeypress(key, fn)
934
Keys.press[key] = fn
935
end
936
function Keys.registerkeyrelease(key, fn)
937
Keys.release[key] = fn
938
end
939
940
941
-- A cross sign with pos and size
942
gui.crosshair = gui.drawAxis
943
944
945
local Movie_active, Readonly, Framecount, Lagcount, Rerecords, Game_region
946
local Lastframe_emulated, Starting_subframe_last_frame, Size_last_frame, Final_subframe_last_frame
947
local Nextframe, Starting_subframe_next_frame, Starting_subframe_next_frame, Final_subframe_next_frame
948
local 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
962
end
963
964
965
-- Get screen values of the game and emulator areas
966
local Left_gap, Right_gap, Top_gap, Bottom_gap
967
local Border_left, Border_right, Border_top, Border_bottom
968
local Buffer_width, Buffer_height, Buffer_middle_x, Buffer_middle_y
969
local Screen_width, Screen_height, AR_x, AR_y
970
local 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
1005
end
1006
1007
1008
local 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
1026
end
1027
1028
1029
-- draw a pixel given (x,y) with SNES' pixel sizes
1030
local 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)
1034
local 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)
1045
end
1046
1047
1048
-- draws a box given (x,y) and (x',y') with SNES' pixel sizes
1049
local 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
1053
local 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).
1057
local 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
1073
end
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
1084
local 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
1120
end
1121
1122
1123
-- Complex function for drawing, that uses text_position
1124
local 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
1161
end
1162
1163
1164
local 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)
1177
end
1178
1179
1180
local 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
1190
end
1191
1192
1193
-- Returns frames-time conversion
1194
local 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
1206
end
1207
1208
1209
-- Background opacity functions
1210
local 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
1215
end
1216
1217
1218
local 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
1223
end
1224
1225
1226
1227
--#############################################################################
1228
-- SMW FUNCTIONS:
1229
1230
1231
-- Returns the id of Yoshi; if more than one, the lowest sprite slot
1232
local 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
1240
end
1241
1242
1243
local Real_frame, Previous_real_frame, Effective_frame, Game_mode
1244
local Level_index, Room_index, Level_flag, Current_level
1245
local Is_paused, Lock_animation_flag, Player_powerup, Player_animation_trigger
1246
local Camera_x, Camera_y
1247
local 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()
1265
end
1266
1267
1268
-- Converts the in-game (x, y) to SNES-screen coordinates
1269
local 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
1278
end
1279
1280
1281
-- Converts BizHawk/emu-screen coordinates to in-game (x, y)
1282
local 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
1291
end
1292
1293
1294
-- Returns the extreme values that Mario needs to have in order to NOT touch a rectangular object
1295
local 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
1339
end
1340
1341
1342
local 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
1362
end
1363
1364
1365
local 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
1397
end
1398
1399
1400
local 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
1445
end
1446
1447
1448
local 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
1455
end
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?]}
1462
local 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
1492
end
1493
1494
1495
-- uses the mouse to select an object
1496
local 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
1535
end
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.
1540
local 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)
1584
end
1585
1586
1587
local 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
1630
end
1631
1632
1633
local 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
1661
end
1662
1663
1664
-- Shows the controller input as the RAM and SNES registers store it
1665
local 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)
1678
end
1679
1680
1681
local 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
;
1714
end
1715
1716
1717
function 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
1757
end
1758
1759
1760
-- displays player's hitbox
1761
local 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
1830
end
1831
1832
1833
-- displays the hitbox of the cape while spinning
1834
local 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
1859
end
1860
1861
1862
local 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
1979
end
1980
1981
1982
local 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
2064
end
2065
2066
2067
local 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
2147
end
2148
2149
2150
local 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
2196
end
2197
2198
2199
local 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
2237
end
2238
2239
2240
local 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
2549
end
2550
2551
2552
local 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
2580
end
2581
2582
2583
local 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
2674
end
2675
2676
2677
local 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
2731
end
2732
2733
2734
-- Main function to run inside a level
2735
local 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
2767
end
2768
2769
2770
local 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)
2789
end
2790
2791
2792
local 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
2816
end
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
2821
local 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
2836
end
2837
2838
2839
local 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
2877
end
2878
2879
2880
2881
--#############################################################################
2882
-- CHEATS
2883
2884
-- This signals that some cheat is activated, or was some short time ago
2885
Cheat.allow_cheats = false
2886
Cheat.is_cheating = false
2887
function 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
2899
end
2900
2901
2902
-- Called from Cheat.beat_level()
2903
function 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
2913
end
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
2919
function 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
2932
end
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
2938
Cheat.under_free_move = false
2939
function 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
2977
end
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
2983
function 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)
2998
end
2999
3000
3001
function 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
3022
end
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
3028
function 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
3059
end
3060
3061
3062
--#############################################################################
3063
-- MAIN --
3064
3065
3066
-- Key presses:
3067
Keys.registerkeypress("rightclick", right_click)
3068
Keys.registerkeypress("leftclick", left_click)
3069
3070
-- Key releases:
3071
Keys.registerkeyrelease("mouse_inwindow", function() Cheat.is_dragging_sprite = false end)
3072
Keys.registerkeyrelease("leftclick", function() Cheat.is_dragging_sprite = false end)
3073
3074
-- Lateral gaps:
3075
if 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)
3078
end
3079
3080
function 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)
3266
end
3267
3268
3269
function 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
3305
end
3306
3307
3308
function 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 - - - ")
3326
end
3327
Options_form.create_window()
3328
Options_form.is_form_closed = false
3329
3330
3331
event.unregisterbyname("smw-tas-bizhawk-onexit")
3332
event.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()
3341
end, "smw-tas-bizhawk-onexit")
3342
3343
3344
while 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
3405
end
3406