BizHawk/Assets/Lua/SNES/Super Mario World.lua

3405 lines
138 KiB
Lua

---------------------------------------------------------------------------
-- Super Mario World (U) Utility Script for BizHawk
-- http://tasvideos.org/Bizhawk.html
--
-- Author: Rodrigo A. do Amaral (Amaraticando)
-- Git repository: https://github.com/rodamaral/smw-tas
---------------------------------------------------------------------------
--#############################################################################
-- CONFIG:
local INI_CONFIG_FILENAME = "config.ini" -- relative to the folder of the script
local OLD_EMU_VERSION
local DEFAULT_OPTIONS = {
-- Display
display_movie_info = false, -- BizHawk: has good built-in movie info with custom positioning
display_misc_info = true,
display_player_info = true,
display_player_hitbox = true, -- can be changed by right-clicking on player
display_interaction_points = true, -- can be changed by right-clicking on player
display_sprite_info = true,
display_sprite_hitbox = true, -- you still have to select the sprite with the mouse
display_extended_sprite_info = false,
display_cluster_sprite_info = true,
display_minor_extended_sprite_info = true,
display_bounce_sprite_info = true,
display_level_info = false,
display_yoshi_info = true,
display_counters = true,
display_static_camera_region = false, -- shows the region in which the camera won't scroll horizontally
draw_tiles_with_click = false,
-- Some extra/debug info
display_debug_info = false, -- shows useful info while investigating the game, but not very useful while TASing
display_debug_player_extra = true,
display_debug_sprite_extra = true,
display_debug_sprite_tweakers = true,
display_debug_extended_sprite = true,
display_debug_cluster_sprite = true,
display_debug_minor_extended_sprite = true,
display_debug_bounce_sprite = true,
display_debug_controller_data = true,
display_miscellaneous_sprite_table = false,
miscellaneous_sprite_table_number = {[1] = true, [2] = true, [3] = true, [4] = true, [5] = true, [6] = true, [7] = true, [8] = true, [9] = true,
[10] = true, [11] = true, [12] = true, [13] = true, [14] = true, [15] = true, [16] = true, [17] = true, [18] = true, [19] = true
},
-- Script settings
max_tiles_drawn = 20, -- the max number of tiles to be drawn/registered by the script
-- Lateral gaps (initial values) / bizhawk specific
left_gap = 100,
right_gap = 100,
top_gap = 20,
bottom_gap = 8,
}
-- Colour settings
local DEFAULT_COLOUR = {
-- Text
default_text_opacity = 1.0,
default_bg_opacity = 0.4,
text = "#ffffffff",
background = "#000000ff",
outline = "#000040ff",
warning = "#ff0000ff",
warning_bg = "#0000ffff",
warning2 = "#ff00ffff",
weak = "#a9a9a9ff",
very_weak = "#ffffff60",
joystick_input = "#ffff00ff",
joystick_input_bg = "#ffffff30",
button_text = "#300030ff",
mainmenu_outline = "#ffffffc0",
mainmenu_bg = "#000000c0",
-- Counters
counter_pipe = "#00ff00ff",
counter_multicoin = "#ffff00ff",
counter_gray_pow = "#a5a5a5ff",
counter_blue_pow = "#4242deff",
counter_dircoin = "#8c5a19ff",
counter_pballoon = "#f8d870ff",
counter_star = "#ffd773ff",
counter_fireflower = "#ff8c00ff",
-- hitbox and related text
mario = "#ff0000ff",
mario_bg = "#00000000",
mario_mounted_bg = "#00000000",
interaction = "#ffffffff",
interaction_bg = "#00000020",
interaction_nohitbox = "#000000a0",
interaction_nohitbox_bg = "#00000070",
sprites = {"#00ff00ff", "#0000ffff", "#ffff00ff", "#ff00ffff", "#b00040ff"},
sprites_interaction_pts = "#ffffffff",
sprites_bg = "#0000b050",
sprites_clipping_bg = "#000000a0",
extended_sprites = "#ff8000ff",
extended_sprites_bg = "#00ff0050",
special_extended_sprite_bg = "#00ff0060",
goal_tape_bg = "#ffff0050",
fireball = "#b0d0ffff",
baseball = "#0040a0ff",
cluster_sprites = "#ff80a0ff",
sumo_brother_flame = "#0040a0ff",
minor_extended_sprites = "#ff90b0ff",
awkward_hitbox = "#204060ff",
awkward_hitbox_bg = "#ff800060",
yoshi = "#00ffffff",
yoshi_bg = "#00ffff40",
yoshi_mounted_bg = "#00000000",
tongue_line = "#ffa000ff",
tongue_bg = "#00000060",
cape = "#ffd700ff",
cape_bg = "#ffd70060",
block = "#00008bff",
blank_tile = "#ffffff70",
block_bg = "#22cc88a0",
layer2_line = "#ff2060ff",
layer2_bg = "#ff206040",
static_camera_region = "#40002040",
}
-- Font settings
local BIZHAWK_FONT_HEIGHT = 14
local BIZHAWK_FONT_WIDTH = 10
-- Symbols
local LEFT_ARROW = "<-"
local RIGHT_ARROW = "->"
-- Others
local Y_CAMERA_OFF = 1 -- small adjustment to display the tiles according to their actual graphics
-- Input key names
local INPUT_KEYNAMES = { -- BizHawk
A=false, Add=false, Alt=false, Apps=false, Attn=false, B=false, Back=false, BrowserBack=false, BrowserFavorites=false,
BrowserForward=false, BrowserHome=false, BrowserRefresh=false, BrowserSearch=false, BrowserStop=false, C=false,
Cancel=false, Capital=false, CapsLock=false, Clear=false, Control=false, ControlKey=false, Crsel=false, D=false, D0=false,
D1=false, D2=false, D3=false, D4=false, D5=false, D6=false, D7=false, D8=false, D9=false, Decimal=false, Delete=false,
Divide=false, Down=false, E=false, End=false, Enter=false, EraseEof=false, Escape=false, Execute=false, Exsel=false,
F=false, F1=false, F10=false, F11=false, F12=false, F13=false, F14=false, F15=false, F16=false, F17=false, F18=false,
F19=false, F2=false, F20=false, F21=false, F22=false, F23=false, F24=false, F3=false, F4=false, F5=false, F6=false,
F7=false, F8=false, F9=false, FinalMode=false, G=false, H=false, HanguelMode=false, HangulMode=false, HanjaMode=false,
Help=false, Home=false, I=false, IMEAccept=false, IMEAceept=false, IMEConvert=false, IMEModeChange=false,
IMENonconvert=false, Insert=false, J=false, JunjaMode=false, K=false, KanaMode=false, KanjiMode=false, KeyCode=false,
L=false, LaunchApplication1=false, LaunchApplication2=false, LaunchMail=false, LButton=false, LControlKey=false,
Left=false, LineFeed=false, LMenu=false, LShiftKey=false, LWin=false, M=false, MButton=false, MediaNextTrack=false,
MediaPlayPause=false, MediaPreviousTrack=false, MediaStop=false, Menu=false, Modifiers=false, Multiply=false, N=false,
Next=false, NoName=false, None=false, NumLock=false, NumPad0=false, NumPad1=false, NumPad2=false, NumPad3=false,
NumPad4=false, NumPad5=false, NumPad6=false, NumPad7=false, NumPad8=false, NumPad9=false, O=false, Oem1=false,
Oem102=false, Oem2=false, Oem3=false, Oem4=false, Oem5=false, Oem6=false, Oem7=false, Oem8=false, OemBackslash=false,
OemClear=false, OemCloseBrackets=false, Oemcomma=false, OemMinus=false, OemOpenBrackets=false, OemPeriod=false,
OemPipe=false, Oemplus=false, OemQuestion=false, OemQuotes=false, OemSemicolon=false, Oemtilde=false, P=false, Pa1=false,
Packet=false, PageDown=false, PageUp=false, Pause=false, Play=false, Print=false, PrintScreen=false, Prior=false,
ProcessKey=false, Q=false, R=false, RButton=false, RControlKey=false, Return=false, Right=false, RMenu=false, RShiftKey=false,
RWin=false, S=false, Scroll=false, Select=false, SelectMedia=false, Separator=false, Shift=false, ShiftKey=false,
Sleep=false, Snapshot=false, Space=false, Subtract=false, T=false, Tab=false, U=false, Up=false, V=false, VolumeDown=false,
VolumeMute=false, VolumeUp=false, W=false, X=false, XButton1=false, XButton2=false, Y=false, Z=false, Zoom=false
}
-- END OF CONFIG < < < < < < <
--#############################################################################
-- INITIAL STATEMENTS:
-- Load environment
local gui, input, joypad, emu, movie, memory, mainmemory, bit = gui, input, joypad, emu, movie, memory, mainmemory, bit
local unpack = unpack or table.unpack
local string, math, table, next, ipairs, pairs, io, os, type = string, math, table, next, ipairs, pairs, io, os, type
-- Script tries to verify whether the emulator is indeed BizHawk
if tastudio == nil then
gui.text(0, 0, "This script works with BizHawk emulator.")
error("This script works with BizHawk emulator.")
elseif gui.drawAxis == nil then
gui.text(0, 0, "This script works with BizHawk 1.11.0 or superior.")
gui.text(0, 16, "Your version seems to be older.")
gui.text(0, 32, "Visit http://tasvideos.org/Bizhawk.html to download the latest version.")
error("This script works with BizHawk 1.11.0 or superior.")
else
OLD_EMU_VERSION = client.SetGameExtraPadding == nil
end
print("\nStarting smw-bizhawk script.")
-- TEST: INI library for handling an ini configuration file
function file_exists(name)
local f = io.open(name, "r")
if f ~= nil then io.close(f) return true else return false end
end
function copytable(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[copytable(orig_key)] = copytable(orig_value) -- possible stack overflow
end
setmetatable(copy, copytable(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
function mergetable(source, t2)
for key, value in pairs(t2) do
if type(value) == "table" then
if type(source[key] or false) == "table" then
mergetable(source[key] or {}, t2[key] or {}) -- possible stack overflow
else
source[key] = value
end
else
source[key] = value
end
end
return source
end
-- Creates a set from a list
local function make_set(list)
local set = {}
for _, l in ipairs(list) do set[l] = true end
return set
end
local INI = {}
function INI.arg_to_string(value)
local str
if type(value) == "string" then
str = "\"" .. value .. "\""
elseif type(value) == "number" or type(value) == "boolean" or value == nil then
str = tostring(value)
elseif type(value) == "table" then
local tmp = {"{"} -- only arrays
for a, b in ipairs(value) do
table.insert(tmp, ("%s%s"):format(INI.arg_to_string(b), a ~= #value and ", " or "")) -- possible stack overflow
end
table.insert(tmp, "}")
str = table.concat(tmp)
else
str = "#BAD_VALUE"
end
return str
end
-- creates the string for ini
function INI.data_to_string(data)
local sections = {}
for section, prop in pairs(data) do
local properties = {}
for key, value in pairs(prop) do
table.insert(properties, ("%s = %s\n"):format(key, INI.arg_to_string(value))) -- properties
end
table.sort(properties)
table.insert(sections, ("[%s]\n"):format(section) .. table.concat(properties) .. "\n")
end
table.sort(sections)
return table.concat(sections)
end
function INI.string_to_data(value)
local data
if tonumber(value) then
data = tonumber(value)
elseif value == "true" then
data = true
elseif value == "false" then
data = false
elseif value == "nil" then
data = nil
else
local quote1, text, quote2 = value:match("(['\"{])(.+)(['\"}])") -- value is surrounded by "", '' or {}?
if quote1 and quote2 and text then
if (quote1 == '"' or quote1 == "'") and quote1 == quote2 then
data = text
elseif quote1 == "{" and quote2 == "}" then
local tmp = {} -- test
for words in text:gmatch("[^,%s]+") do
tmp[#tmp + 1] = INI.string_to_data(words) -- possible stack overflow
end
data = tmp
else
data = value
end
else
data = value
end
end
return data
end
function INI.load(filename)
local file = io.open(filename, "r")
if not file then return false end
local data, section = {}, nil
for line in file:lines() do
local new_section = line:match("^%[([^%[%]]+)%]$")
if new_section then
section = INI.string_to_data(new_section) and INI.string_to_data(new_section) or new_section
if data[section] then print("Duplicated section") end
data[section] = data[section] or {}
else
local prop, value = line:match("^([%w_%-%.]+)%s*=%s*(.+)%s*$") -- prop = value
if prop and value then
value = INI.string_to_data(value)
prop = INI.string_to_data(prop) and INI.string_to_data(prop) or prop
if data[section] == nil then print(prop, value) ; error("Property outside section") end
data[section][prop] = value
else
local ignore = line:match("^;") or line == ""
if not ignore then
print("BAD LINE:", line, prop, value)
end
end
end
end
file:close()
return data
end
function INI.retrieve(filename, data)
if type(data) ~= "table" then error"data must be a table" end
local data, previous_data = copytable(data), nil
-- Verifies if file already exists
if file_exists(filename) then
ini_data = INI.load(filename)
else return data
end
-- Adds previous values to the new ini
local union_data = mergetable(data, ini_data)
return union_data
end
function INI.overwrite(filename, data)
local file, err = assert(io.open(filename, "w"), "Error loading file :" .. filename)
if not file then print(err) ; return end
file:write(INI.data_to_string(data))
file:close()
end
function INI.save(filename, data)
if type(data) ~= "table" then error"data must be a table" end
local tmp, previous_data
if file_exists(filename) then
previous_data = INI.load(filename)
tmp = mergetable(previous_data, data)
else
tmp = data
end
INI.overwrite(filename, tmp)
end
local function color_number(str)
local r, g, b, a = str:match("^#(%x+%x+)(%x+%x+)(%x+%x+)(%x+%x+)$")
if not a then print(str) return gui.color(str) end -- lsnes specific
r, g, b, a = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), tonumber(a, 16)
return 0x1000000*a + 0x10000*r + 0x100*g + b -- BizHawk specific
end
local OPTIONS = file_exists(INI_CONFIG_FILENAME) and
INI.retrieve(INI_CONFIG_FILENAME, {["BIZHAWK OPTIONS"] = DEFAULT_OPTIONS})["BIZHAWK OPTIONS"] or DEFAULT_OPTIONS
local COLOUR = file_exists(INI_CONFIG_FILENAME) and
INI.retrieve(INI_CONFIG_FILENAME, {["BIZHAWK COLOURS"] = DEFAULT_COLOUR})["BIZHAWK COLOURS"] or DEFAULT_COLOUR
INI.save(INI_CONFIG_FILENAME, {["BIZHAWK COLOURS"] = COLOUR})
INI.save(INI_CONFIG_FILENAME, {["BIZHAWK OPTIONS"] = OPTIONS})
function interpret_color(data)
for k, v in pairs(data) do
if type(v) == "string" then
data[k] = type(v) == "string" and color_number(v) or v
elseif type(v) == "table" then
interpret_color(data[k]) -- possible stack overflow
end
end
end
interpret_color(COLOUR)
function INI.save_options()
INI.save(INI_CONFIG_FILENAME, {["BIZHAWK OPTIONS"] = OPTIONS})
end
--######################## -- end of test
-- Text/Background_max_opacity is only changed by the player using the hotkeys
-- Text/Bg_opacity must be used locally inside the functions
local Text_max_opacity = COLOUR.default_text_opacity
local Background_max_opacity = COLOUR.default_bg_opacity
local Text_opacity = 1
local Bg_opacity = 1
local fmt = string.format
local floor = math.floor
-- unsigned to signed (based in <bits> bits)
local function signed(num, bits)
local maxval = 2^(bits - 1)
if num < maxval then return num else return num - 2*maxval end
end
-- Compatibility of the memory read/write functions
local u8 = mainmemory.read_u8
local s8 = mainmemory.read_s8
local w8 = mainmemory.write_u8
local u16 = mainmemory.read_u16_le
local s16 = mainmemory.read_s16_le
local w16 = mainmemory.write_u16_le
local u24 = mainmemory.read_u24_le
local s24 = mainmemory.read_s24_le
local w24 = mainmemory.write_u32_le
--#############################################################################
-- GAME AND SNES SPECIFIC MACROS:
local NTSC_FRAMERATE = 60.0988138974405
local PAL_FRAMERATE = 50.0069789081886
local SMW = {
-- Game Modes
game_mode_overworld = 0x0e,
game_mode_level = 0x14,
-- Sprites
sprite_max = 12,
extended_sprite_max = 10,
cluster_sprite_max = 20,
minor_extended_sprite_max = 12,
bounce_sprite_max = 4,
null_sprite_id = 0xff,
-- Blocks
blank_tile_map16 = 0x25,
}
WRAM = {
-- I/O
ctrl_1_1 = 0x0015,
ctrl_1_2 = 0x0017,
firstctrl_1_1 = 0x0016,
firstctrl_1_2 = 0x0018,
-- General
game_mode = 0x0100,
real_frame = 0x0013,
effective_frame = 0x0014,
lag_indicator = 0x01fe,
timer_frame_counter = 0x0f30,
RNG = 0x148d,
current_level = 0x00fe, -- plus 1
sprite_memory_header = 0x1692,
lock_animation_flag = 0x009d, -- Most codes will still run if this is set, but almost nothing will move or animate.
level_mode_settings = 0x1925,
star_road_speed = 0x1df7,
star_road_timer = 0x1df8,
-- Cheats
frozen = 0x13fb,
level_paused = 0x13d4,
level_index = 0x13bf,
room_index = 0x00ce,
level_flag_table = 0x1ea2,
level_exit_type = 0x0dd5,
midway_point = 0x13ce,
-- Camera
camera_x = 0x1462,
camera_y = 0x1464,
screens_number = 0x005d,
hscreen_number = 0x005e,
vscreen_number = 0x005f,
vertical_scroll_flag_header = 0x1412, -- #$00 = Disable; #$01 = Enable; #$02 = Enable if flying/climbing/etc.
vertical_scroll_enabled = 0x13f1,
camera_scroll_timer = 0x1401,
-- Sprites
sprite_status = 0x14c8,
sprite_number = 0x009e,
sprite_x_high = 0x14e0,
sprite_x_low = 0x00e4,
sprite_y_high = 0x14d4,
sprite_y_low = 0x00d8,
sprite_x_sub = 0x14f8,
sprite_y_sub = 0x14ec,
sprite_x_speed = 0x00b6,
sprite_y_speed = 0x00aa,
sprite_x_offscreen = 0x15a0,
sprite_y_offscreen = 0x186c,
sprite_miscellaneous1 = 0x00c2,
sprite_miscellaneous2 = 0x1504,
sprite_miscellaneous3 = 0x1510,
sprite_miscellaneous4 = 0x151c,
sprite_miscellaneous5 = 0x1528,
sprite_miscellaneous6 = 0x1534,
sprite_miscellaneous7 = 0x1540,
sprite_miscellaneous8 = 0x154c,
sprite_miscellaneous9 = 0x1558,
sprite_miscellaneous10 = 0x1564,
sprite_miscellaneous11 = 0x1570,
sprite_miscellaneous12 = 0x157c,
sprite_miscellaneous13 = 0x1594,
sprite_miscellaneous14 = 0x15ac,
sprite_miscellaneous15 = 0x1602,
sprite_miscellaneous16 = 0x160e,
sprite_miscellaneous17 = 0x1626,
sprite_miscellaneous18 = 0x163e,
sprite_miscellaneous19 = 0x187b,
sprite_underwater = 0x164a,
sprite_disable_cape = 0x1fe2,
sprite_1_tweaker = 0x1656,
sprite_2_tweaker = 0x1662,
sprite_3_tweaker = 0x166e,
sprite_4_tweaker = 0x167a,
sprite_5_tweaker = 0x1686,
sprite_6_tweaker = 0x190f,
sprite_tongue_wait = 0x14a3,
sprite_yoshi_squatting = 0x18af,
sprite_buoyancy = 0x190e,
-- Extended sprites
extspr_number = 0x170b,
extspr_x_high = 0x1733,
extspr_x_low = 0x171f,
extspr_y_high = 0x1729,
extspr_y_low = 0x1715,
extspr_x_speed = 0x1747,
extspr_y_speed = 0x173d,
extspr_suby = 0x1751,
extspr_subx = 0x175b,
extspr_table = 0x1765,
extspr_table2 = 0x176f,
-- Cluster sprites
cluspr_flag = 0x18b8,
cluspr_number = 0x1892,
cluspr_x_high = 0x1e3e,
cluspr_x_low = 0x1e16,
cluspr_y_high = 0x1e2a,
cluspr_y_low = 0x1e02,
cluspr_timer = 0x0f9a,
cluspr_table_1 = 0x0f4a,
cluspr_table_2 = 0x0f72,
cluspr_table_3 = 0x0f86,
reappearing_boo_counter = 0x190a,
-- Minor extended sprites
minorspr_number = 0x17f0,
minorspr_x_high = 0x18ea,
minorspr_x_low = 0x1808,
minorspr_y_high = 0x1814,
minorspr_y_low = 0x17fc,
minorspr_xspeed = 0x182c,
minorspr_yspeed = 0x1820,
minorspr_x_sub = 0x1844,
minorspr_y_sub = 0x1838,
minorspr_timer = 0x1850,
-- Bounce sprites
bouncespr_number = 0x1699,
bouncespr_x_high = 0x16ad,
bouncespr_x_low = 0x16a5,
bouncespr_y_high = 0x16a9,
bouncespr_y_low = 0x16a1,
bouncespr_timer = 0x16c5,
bouncespr_last_id = 0x18cd,
turn_block_timer = 0x18ce,
-- Player
x = 0x0094,
y = 0x0096,
previous_x = 0x00d1,
previous_y = 0x00d3,
x_sub = 0x13da,
y_sub = 0x13dc,
x_speed = 0x007b,
x_subspeed = 0x007a,
y_speed = 0x007d,
direction = 0x0076,
is_ducking = 0x0073,
p_meter = 0x13e4,
take_off = 0x149f,
powerup = 0x0019,
cape_spin = 0x14a6,
cape_fall = 0x14a5,
cape_interaction = 0x13e8,
flight_animation = 0x1407,
diving_status = 0x1409,
player_animation_trigger = 0x0071,
climbing_status = 0x0074,
spinjump_flag = 0x140d,
player_blocked_status = 0x0077,
player_item = 0x0dc2, --hex
cape_x = 0x13e9,
cape_y = 0x13eb,
on_ground = 0x13ef,
on_ground_delay = 0x008d,
on_air = 0x0072,
can_jump_from_water = 0x13fa,
carrying_item = 0x148f,
mario_score = 0x0f34,
player_coin = 0x0dbf,
player_looking_up = 0x13de,
-- Yoshi
yoshi_riding_flag = 0x187a, -- #$00 = No, #$01 = Yes, #$02 = Yes, and turning around.
yoshi_tile_pos = 0x0d8c,
-- Timer
--keep_mode_active = 0x0db1,
pipe_entrance_timer = 0x0088,
score_incrementing = 0x13d6,
end_level_timer = 0x1493,
multicoin_block_timer = 0x186b,
gray_pow_timer = 0x14ae,
blue_pow_timer = 0x14ad,
dircoin_timer = 0x190c,
pballoon_timer = 0x1891,
star_timer = 0x1490,
animation_timer = 0x1496,--
invisibility_timer = 0x1497,
fireflower_timer = 0x149b,
yoshi_timer = 0x18e8,
swallow_timer = 0x18ac,
lakitu_timer = 0x18e0,
spinjump_fireball_timer = 0x13e2,
-- Layers
layer2_x_nextframe = 0x1466,
layer2_y_nextframe = 0x1468,
}
local WRAM = WRAM
local X_INTERACTION_POINTS = {center = 0x8, left_side = 0x2 + 1, left_foot = 0x5, right_side = 0xe - 1, right_foot = 0xb}
local Y_INTERACTION_POINTS = {
{head = 0x10, center = 0x18, shoulder = 0x16, side = 0x1a, foot = 0x20, sprite = 0x15},
{head = 0x08, center = 0x12, shoulder = 0x0f, side = 0x1a, foot = 0x20, sprite = 0x07},
{head = 0x13, center = 0x1d, shoulder = 0x19, side = 0x28, foot = 0x30, sprite = 0x19},
{head = 0x10, center = 0x1a, shoulder = 0x16, side = 0x28, foot = 0x30, sprite = 0x11}
}
local HITBOX_SPRITE = { -- sprites' hitbox against player and other sprites
[0x00] = { xoff = 2, yoff = 3, width = 12, height = 10, oscillation = true },
[0x01] = { xoff = 2, yoff = 3, width = 12, height = 21, oscillation = true },
[0x02] = { xoff = 16, yoff = -2, width = 16, height = 18, oscillation = true },
[0x03] = { xoff = 20, yoff = 8, width = 8, height = 8, oscillation = true },
[0x04] = { xoff = 0, yoff = -2, width = 48, height = 14, oscillation = true },
[0x05] = { xoff = 0, yoff = -2, width = 80, height = 14, oscillation = true },
[0x06] = { xoff = 1, yoff = 2, width = 14, height = 24, oscillation = true },
[0x07] = { xoff = 8, yoff = 8, width = 40, height = 48, oscillation = true },
[0x08] = { xoff = -8, yoff = -2, width = 32, height = 16, oscillation = true },
[0x09] = { xoff = -2, yoff = 8, width = 20, height = 30, oscillation = true },
[0x0a] = { xoff = 3, yoff = 7, width = 1, height = 2, oscillation = true },
[0x0b] = { xoff = 6, yoff = 6, width = 3, height = 3, oscillation = true },
[0x0c] = { xoff = 1, yoff = -2, width = 13, height = 22, oscillation = true },
[0x0d] = { xoff = 0, yoff = -4, width = 15, height = 16, oscillation = true },
[0x0e] = { xoff = 6, yoff = 6, width = 20, height = 20, oscillation = true },
[0x0f] = { xoff = 2, yoff = -2, width = 36, height = 18, oscillation = true },
[0x10] = { xoff = 0, yoff = -2, width = 15, height = 32, oscillation = true },
[0x11] = { xoff = -24, yoff = -24, width = 64, height = 64, oscillation = true },
[0x12] = { xoff = -4, yoff = 16, width = 8, height = 52, oscillation = true },
[0x13] = { xoff = -4, yoff = 16, width = 8, height = 116, oscillation = true },
[0x14] = { xoff = 4, yoff = 2, width = 24, height = 12, oscillation = true },
[0x15] = { xoff = 0, yoff = -2, width = 15, height = 14, oscillation = true },
[0x16] = { xoff = -4, yoff = -12, width = 24, height = 24, oscillation = true },
[0x17] = { xoff = 2, yoff = 8, width = 12, height = 69, oscillation = true },
[0x18] = { xoff = 2, yoff = 19, width = 12, height = 58, oscillation = true },
[0x19] = { xoff = 2, yoff = 35, width = 12, height = 42, oscillation = true },
[0x1a] = { xoff = 2, yoff = 51, width = 12, height = 26, oscillation = true },
[0x1b] = { xoff = 2, yoff = 67, width = 12, height = 10, oscillation = true },
[0x1c] = { xoff = 0, yoff = 10, width = 10, height = 48, oscillation = true },
[0x1d] = { xoff = 2, yoff = -3, width = 28, height = 27, oscillation = true },
[0x1e] = { xoff = 6, yoff = -8, width = 3, height = 32, oscillation = true }, -- default: { xoff = -32, yoff = -8, width = 48, height = 32, oscillation = true },
[0x1f] = { xoff = -16, yoff = -4, width = 48, height = 18, oscillation = true },
[0x20] = { xoff = -4, yoff = -24, width = 8, height = 24, oscillation = true },
[0x21] = { xoff = -4, yoff = 16, width = 8, height = 24, oscillation = true },
[0x22] = { xoff = 0, yoff = 0, width = 16, height = 16, oscillation = true },
[0x23] = { xoff = -8, yoff = -24, width = 32, height = 32, oscillation = true },
[0x24] = { xoff = -12, yoff = 32, width = 56, height = 56, oscillation = true },
[0x25] = { xoff = -14, yoff = 4, width = 60, height = 20, oscillation = true },
[0x26] = { xoff = 0, yoff = 88, width = 32, height = 8, oscillation = true },
[0x27] = { xoff = -4, yoff = -4, width = 24, height = 24, oscillation = true },
[0x28] = { xoff = -14, yoff = -24, width = 28, height = 40, oscillation = true },
[0x29] = { xoff = -16, yoff = -4, width = 32, height = 27, oscillation = true },
[0x2a] = { xoff = 2, yoff = -8, width = 12, height = 19, oscillation = true },
[0x2b] = { xoff = 0, yoff = 2, width = 16, height = 76, oscillation = true },
[0x2c] = { xoff = -8, yoff = -8, width = 16, height = 16, oscillation = true },
[0x2d] = { xoff = 4, yoff = 4, width = 8, height = 4, oscillation = true },
[0x2e] = { xoff = 2, yoff = -2, width = 28, height = 34, oscillation = true },
[0x2f] = { xoff = 2, yoff = -2, width = 28, height = 32, oscillation = true },
[0x30] = { xoff = 8, yoff = -14, width = 16, height = 28, oscillation = true },
[0x31] = { xoff = 0, yoff = -2, width = 48, height = 18, oscillation = true },
[0x32] = { xoff = 0, yoff = -2, width = 48, height = 18, oscillation = true },
[0x33] = { xoff = 0, yoff = -2, width = 64, height = 18, oscillation = true },
[0x34] = { xoff = -4, yoff = -4, width = 8, height = 8, oscillation = true },
[0x35] = { xoff = 3, yoff = 0, width = 18, height = 32, oscillation = true },
[0x36] = { xoff = 8, yoff = 8, width = 52, height = 46, oscillation = true },
[0x37] = { xoff = 0, yoff = -8, width = 15, height = 20, oscillation = true },
[0x38] = { xoff = 8, yoff = 16, width = 32, height = 40, oscillation = true },
[0x39] = { xoff = 4, yoff = 3, width = 8, height = 10, oscillation = true },
[0x3a] = { xoff = -8, yoff = 16, width = 32, height = 16, oscillation = true },
[0x3b] = { xoff = 0, yoff = 0, width = 16, height = 13, oscillation = true },
[0x3c] = { xoff = 12, yoff = 10, width = 3, height = 6, oscillation = true },
[0x3d] = { xoff = 12, yoff = 21, width = 3, height = 20, oscillation = true },
[0x3e] = { xoff = 16, yoff = 18, width = 254, height = 16, oscillation = true },
[0x3f] = { xoff = 8, yoff = 8, width = 8, height = 24, oscillation = true }
}
local OBJ_CLIPPING_SPRITE = { -- sprites' interaction points against objects
[0x0] = {xright = 14, xleft = 2, xdown = 8, xup = 8, yright = 8, yleft = 8, ydown = 16, yup = 2},
[0x1] = {xright = 14, xleft = 2, xdown = 7, xup = 7, yright = 18, yleft = 18, ydown = 32, yup = 2},
[0x2] = {xright = 7, xleft = 7, xdown = 7, xup = 7, yright = 7, yleft = 7, ydown = 7, yup = 7},
[0x3] = {xright = 14, xleft = 2, xdown = 8, xup = 8, yright = 16, yleft = 16, ydown = 32, yup = 11},
[0x4] = {xright = 16, xleft = 0, xdown = 8, xup = 8, yright = 18, yleft = 18, ydown = 32, yup = 2},
[0x5] = {xright = 13, xleft = 2, xdown = 8, xup = 8, yright = 24, yleft = 24, ydown = 32, yup = 16},
[0x6] = {xright = 7, xleft = 0, xdown = 4, xup = 4, yright = 4, yleft = 4, ydown = 8, yup = 0},
[0x7] = {xright = 31, xleft = 1, xdown = 16, xup = 16, yright = 16, yleft = 16, ydown = 31, yup = 1},
[0x8] = {xright = 15, xleft = 0, xdown = 8, xup = 8, yright = 8, yleft = 8, ydown = 15, yup = 0},
[0x9] = {xright = 16, xleft = 0, xdown = 8, xup = 8, yright = 8, yleft = 8, ydown = 16, yup = 0},
[0xa] = {xright = 13, xleft = 2, xdown = 8, xup = 8, yright = 72, yleft = 72, ydown = 80, yup = 66},
[0xb] = {xright = 14, xleft = 2, xdown = 8, xup = 8, yright = 4, yleft = 4, ydown = 8, yup = 0},
[0xc] = {xright = 13, xleft = 2, xdown = 8, xup = 8, yright = 0, yleft = 0, ydown = 0, yup = 0},
[0xd] = {xright = 16, xleft = 0, xdown = 8, xup = 8, yright = 8, yleft = 8, ydown = 16, yup = 0},
[0xe] = {xright = 31, xleft = 0, xdown = 16, xup = 16, yright = 8, yleft = 8, ydown = 16, yup = 0},
[0xf] = {xright = 8, xleft = 8, xdown = 8, xup = 16, yright = 4, yleft = 1, ydown = 2, yup = 4}
}
local HITBOX_EXTENDED_SPRITE = { -- extended sprites' hitbox
-- To fill the slots...
--[0] ={ xoff = 3, yoff = 3, width = 64, height = 64}, -- Free slot
[0x01] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Puff of smoke with various objects
[0x0e] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Wiggler's flower
[0x0f] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Trail of smoke -- TODO: add experimental hitbox
[0x10] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Spinjump stars
[0x12] ={ xoff = 3, yoff = 3, width = 0, height = 0}, -- Water bubble
-- extracted from ROM:
[0x02] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball }, -- Reznor fireball
[0x03] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball}, -- Flame left by hopping flame
[0x04] = { xoff = 4, yoff = 4, width = 8, height = 8}, -- Hammer
[0x05] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball }, -- Player fireball
[0x06] = { xoff = 4, yoff = 4, width = 8, height = 8}, -- Bone from Dry Bones
[0x07] = { xoff = 0, yoff = 0, width = 0, height = 0}, -- Lava splash
[0x08] = { xoff = 0, yoff = 0, width = 0, height = 0}, -- Torpedo Ted shooter's arm
[0x09] = { xoff = 0, yoff = 0, width = 15, height = 15}, -- Unknown flickering object
[0x0a] = { xoff = 4, yoff = 2, width = 8, height = 12}, -- Coin from coin cloud game
[0x0b] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball }, -- Piranha Plant fireball
[0x0c] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.fireball }, -- Lava Lotus's fiery objects
[0x0d] = { xoff = 3, yoff = 3, width = 1, height = 1, color_line = COLOUR.baseball }, -- Baseball
-- got experimentally:
[0x11] = { xoff = -0x1, yoff = -0x4, width = 11, height = 19}, -- Yoshi fireballs
}
local HITBOX_CLUSTER_SPRITE = { -- got experimentally
--[0] -- Free slot
[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)
[0x02] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Unused
[0x03] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Boo from Boo Ceiling
[0x04] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Boo from Boo Ring
[0x05] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Castle candle flame (meaningless hitbox)
[0x06] = { xoff = 2, yoff = 2, width = 12, height = 20, oscillation = 4, color = COLOUR.sumo_brother_flame}, -- Sumo Brother lightning flames
[0x07] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Reappearing Boo
[0x08] = { xoff = 4, yoff = 7, width = 7, height = 7, oscillation = 4}, -- Swooper bat from Swooper Death Bat Ceiling (untested)
}
; -- 0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12
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
-- Creates a set from a list
local function make_set(list)
local set = {}
for _, l in ipairs(list) do set[l] = true end
return set
end
-- from sprite number, returns oscillation flag
-- 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)
local OSCILLATION_SPRITES = make_set{0x0e, 0x21, 0x29, 0x35, 0x54, 0x74, 0x75, 0x76, 0x77, 0x78, 0x81, 0x83, 0x87}
-- Sprites that have a custom hitbox drawing
local ABNORMAL_HITBOX_SPRITES = make_set{0x62, 0x63, 0x6b, 0x6c}
-- Sprites whose clipping interaction points usually matter
local GOOD_SPRITES_CLIPPING = make_set{
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xf, 0x10, 0x11, 0x13, 0x14, 0x18,
0x1b, 0x1d, 0x1f, 0x20, 0x26, 0x27, 0x29, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31,
0x32, 0x34, 0x35, 0x3d, 0x3e, 0x3f, 0x40, 0x46, 0x47, 0x48, 0x4d, 0x4e,
0x51, 0x53, 0x6e, 0x6f, 0x70, 0x80, 0x81, 0x86,
0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa1, 0xa2, 0xa5, 0xa6, 0xa7, 0xab, 0xb2,
0xb4, 0xbb, 0xbc, 0xbd, 0xbf, 0xc3, 0xda, 0xdb, 0xdc, 0xdd, 0xdf
}
-- Extended sprites that don't interact with the player
local UNINTERESTING_EXTENDED_SPRITES = make_set{1, 7, 8, 0x0e, 0x10, 0x12}
--#############################################################################
-- SCRIPT UTILITIES:
-- Variables used in various functions
local Cheat = {} -- family of cheat functions and variables
local Previous = {}
local User_input = INPUT_KEYNAMES -- BizHawk
local Joypad = {}
local Layer1_tiles = {}
local Layer2_tiles = {}
local Is_lagged = nil
local Mario_boost_indicator = nil
local Show_player_point_position = false
local Sprites_info = {} -- keeps track of useful sprite info that might be used outside the main sprite function
local Sprite_hitbox = {} -- keeps track of what sprite slots must display the hitbox
local Options_form = {} -- BizHawk
local Bizhawk_loop_counter = 1 -- BizHawk specific, a hack for saving the ini regularly
-- Initialization of some tables
for i = 0, SMW.sprite_max -1 do
Sprites_info[i] = {}
end
for key = 0, SMW.sprite_max - 1 do
Sprite_hitbox[key] = {}
for number = 0, 0xff do
Sprite_hitbox[key][number] = {["sprite"] = true, ["block"] = GOOD_SPRITES_CLIPPING[number]}
end
end
local function copytable(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in pairs(orig) do
copy[orig_key] = orig_value
end
else -- number, string, boolean, etc
copy = orig
end
return copy
end
-- Sum of the digits of a integer
local function sum_digits(number)
local sum = 0
while number > 0 do
sum = sum + number%10
number = floor(number*0.1)
end
return sum
end
-- Transform the binary representation of base into a string
-- For instance, if each bit of a number represents a char of base, then this function verifies what chars are on
local function decode_bits(data, base)
local i = 1
local size = base:len()
local direct_concatenation = size <= 45 -- Performance: I found out that the .. operator is faster for 45 operations or less
local result
if direct_concatenation then
result = ""
for ch in base:gmatch(".") do
if bit.test(data, size - i) then
result = result .. ch
else
result = result .. " "
end
i = i + 1
end
else
result = {}
for ch in base:gmatch(".") do
if bit.test(data, size-i) then
result[i] = ch
else
result[i] = " "
end
i = i + 1
end
result = table.concat(result)
end
return result
end
bit.test = bit.check -- BizHawk
-- Register a function to be executed on key press or release
-- execution happens in the main loop
local Keys = {}
Keys.press = {}
Keys.release = {}
Keys.down, Keys.up, Keys.pressed, Keys.released = {}, {}, {}, {}
function Keys.registerkeypress(key, fn)
Keys.press[key] = fn
end
function Keys.registerkeyrelease(key, fn)
Keys.release[key] = fn
end
-- A cross sign with pos and size
gui.crosshair = gui.drawAxis
local Movie_active, Readonly, Framecount, Lagcount, Rerecords, Game_region
local Lastframe_emulated, Starting_subframe_last_frame, Size_last_frame, Final_subframe_last_frame
local Nextframe, Starting_subframe_next_frame, Starting_subframe_next_frame, Final_subframe_next_frame
local function bizhawk_status()
Movie_active = movie.isloaded() -- BizHawk
Readonly = movie.getreadonly() -- BizHawk
Framecount = movie.length() -- BizHawk
Lagcount = emu.lagcount() -- BizHawk
Rerecords = movie.getrerecordcount() -- BizHawk
Is_lagged = emu.islagged() -- BizHawk
Game_region = emu.getdisplaytype() -- BizHawk
-- Last frame info
Lastframe_emulated = emu.framecount()
-- Next frame info (only relevant in readonly mode)
Nextframe = Lastframe_emulated + 1
end
-- Get screen values of the game and emulator areas
local Left_gap, Right_gap, Top_gap, Bottom_gap
local Border_left, Border_right, Border_top, Border_bottom
local Buffer_width, Buffer_height, Buffer_middle_x, Buffer_middle_y
local Screen_width, Screen_height, AR_x, AR_y
local function bizhawk_screen_info()
-- zero gaps in old versions
if OLD_EMU_VERSION then
Left_gap = 0
Top_gap = 0
Right_gap = 0
Bottom_gap = 0
else
Left_gap = OPTIONS.left_gap
Top_gap = OPTIONS.top_gap
Right_gap = OPTIONS.right_gap
Bottom_gap = OPTIONS.bottom_gap
end
Screen_width = client.screenwidth() -- Screen area
Screen_height = client.screenheight()
Buffer_width = client.bufferwidth() -- Game area
Buffer_height = client.bufferheight()
Border_left = client.borderwidth() -- Borders' dimensions
Border_top = client.borderheight()
-- BizHawk bug: buffer dimensions go crazy when emu is minimized
if Buffer_width == 0 then
Buffer_width, Screen_width = 256, 256
Buffer_height, Screen_height = 224, 224
Border_left, Border_top = 0, 0
end
-- Derived dimensions
Buffer_middle_x = floor(Buffer_width/2)
Buffer_middle_y = floor(Buffer_height/2)
Border_right = Screen_width - Buffer_width - Border_left
Border_bottom = Screen_height - Buffer_height - Border_top
AR_x = Buffer_width/256
AR_y = Buffer_height/224
end
local function mouse_onregion(x1, y1, x2, y2)
-- Reads external mouse coordinates
local mouse_x = User_input.xmouse*AR_x
local mouse_y = User_input.ymouse*AR_y
-- From top-left to bottom-right
if x2 < x1 then
x1, x2 = x2, x1
end
if y2 < y1 then
y1, y2 = y2, y1
end
if mouse_x >= x1 and mouse_x <= x2 and mouse_y >= y1 and mouse_y <= y2 then
return true
else
return false
end
end
-- draw a pixel given (x,y) with SNES' pixel sizes
local draw_pixel = function(x, y, color) gui.drawPixel(x + Left_gap, y + Top_gap, color) end
-- draws a line given (x,y) and (x',y') with given scale and SNES' pixel thickness (whose scale is 2)
local function draw_line(x1, y1, x2, y2, scale, color)
-- Draw from top-left to bottom-right
if x2 < x1 then
x1, x2 = x2, x1
end
if y2 < y1 then
y1, y2 = y2, y1
end
x1, y1, x2, y2 = scale*x1, scale*y1, scale*x2, scale*y2
gui.drawLine(x1 + Left_gap, y1 + Top_gap, x2 + Left_gap, y2 + Top_gap, color)
end
-- draws a box given (x,y) and (x',y') with SNES' pixel sizes
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
-- draws a rectangle given (x,y) and dimensions, with SNES' pixel sizes
local draw_rectangle = function(x, y, w, h, line, bg) gui.drawRectangle(x + Left_gap, y + Top_gap, w, h, line, bg) end
-- Changes transparency of a color: result is opaque original * transparency level (0.0 to 1.0).
local function change_transparency(color, transparency)
-- Sane transparency
if transparency >= 1 then return color end -- no transparency
if transparency <= 0 then return 0 end -- total transparency
-- Sane colour
if color == 0 then return 0 end
if type(color) ~= "number" then
print(color)
error"Wrong color"
end
local a = floor(color/0x1000000)
local rgb = color - a*0x1000000
local new_a = floor(a*transparency)
return new_a*0x1000000 + rgb
end
-- returns the (x, y) position to start the text and its length:
-- number, number, number text_position(x, y, text, font_width, font_height[[[[, always_on_client], always_on_game], ref_x], ref_y])
-- x, y: the coordinates that the refereed point of the text must have
-- text: a string, don't make it bigger than the buffer area width and don't include escape characters
-- font_width, font_height: the sizes of the font
-- always_on_client, always_on_game: boolean
-- ref_x and ref_y: refer to the relative point of the text that must occupy the origin (x,y), from 0% to 100%
-- for instance, if you want to display the middle of the text in (x, y), then use 0.5, 0.5
local function text_position(x, y, text, font_width, font_height, always_on_client, always_on_game, ref_x, ref_y)
-- Reads external variables
local border_left = Border_left
local border_right = Border_right
local border_top = Border_top
local border_bottom = Border_bottom
local buffer_width = Buffer_width
local buffer_height = Buffer_height
-- text processing
local text_length = text and string.len(text)*font_width or font_width -- considering another objects, like bitmaps
-- actual position, relative to game area origin
x = (not ref_x and x) or (ref_x == 0 and x) or x - floor(text_length*ref_x)
y = (not ref_y and y) or (ref_y == 0 and y) or y - floor(font_height*ref_y)
-- adjustment needed if text is supposed to be on screen area
local x_end = x + text_length
local y_end = y + font_height
if always_on_game then
if x < 0 then x = 0 end
if y < 0 then y = 0 end
if x_end > buffer_width then x = buffer_width - text_length end
if y_end > buffer_height then y = buffer_height - font_height end
elseif always_on_client then
if x < -border_left then x = -border_left end
if y < -border_top then y = -border_top end
if x_end > buffer_width + border_right then x = buffer_width + border_right - text_length end
if y_end > buffer_height + border_bottom then y = buffer_height + border_bottom - font_height end
end
return x, y, text_length
end
-- Complex function for drawing, that uses text_position
local function draw_text(x, y, text, ...)
-- Reads external variables
local font_name = Font or false
local font_width = BIZHAWK_FONT_WIDTH
local font_height = BIZHAWK_FONT_HEIGHT
local bg_default_color = font_name and COLOUR.outline or COLOUR.background
local text_color, bg_color, always_on_client, always_on_game, ref_x, ref_y
local arg1, arg2, arg3, arg4, arg5, arg6 = ...
if not arg1 or arg1 == true then
text_color = COLOUR.text
bg_color = bg_default_color
always_on_client, always_on_game, ref_x, ref_y = arg1, arg2, arg3, arg4
elseif not arg2 or arg2 == true then
text_color = arg1
bg_color = bg_default_color
always_on_client, always_on_game, ref_x, ref_y = arg2, arg3, arg4, arg5
else
text_color, bg_color = arg1, arg2
always_on_client, always_on_game, ref_x, ref_y = arg3, arg4, arg5, arg6
end
local x_pos, y_pos, length = text_position(x, y, text, font_width, font_height,
always_on_client, always_on_game, ref_x, ref_y)
;
text_color = change_transparency(text_color, Text_max_opacity * Text_opacity)
bg_color = change_transparency(bg_color, Text_max_opacity * Text_opacity)
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)
return x_pos + length, y_pos + font_height, length
end
local function alert_text(x, y, text, text_color, bg_color, always_on_game, ref_x, ref_y)
-- Reads external variables
local font_width = BIZHAWK_FONT_WIDTH
local font_height = BIZHAWK_FONT_HEIGHT
local x_pos, y_pos, text_length = text_position(x, y, text, font_width, font_height, false, always_on_game, ref_x, ref_y)
if not bg_color then bg_color = BACKGROUND_COLOR end
text_color = change_transparency(text_color, Text_max_opacity * Text_opacity)
bg_color = change_transparency(bg_color, Background_max_opacity * Bg_opacity)
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)
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)
end
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)
value = decode_bits(value, base)
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)
change_transparency(color_value or COLOUR.text, Text_max_opacity * Text_opacity)
gui.text(x_end + Border_left - length, y_end + Border_top - BIZHAWK_FONT_HEIGHT, value,
OLD_EMU_VERSION and 0 or color_value, OLD_EMU_VERSION and color_value or 0) -- BizHawk
;
return x_end, y_end, length
end
-- Returns frames-time conversion
local function frame_time(frame)
local total_seconds = frame/(Game_region == "NTSC" and NTSC_FRAMERATE or PAL_FRAMERATE)
local hours = floor(total_seconds/3600)
local tmp = total_seconds - 3600*hours
local minutes = floor(tmp/60)
tmp = tmp - 60*minutes
local seconds = floor(tmp)
local miliseconds = 1000* (total_seconds%1)
if hours == 0 then hours = "" else hours = string.format("%d:", hours) end
local str = string.format("%s%.2d:%.2d.%03.0f", hours, minutes, seconds, miliseconds)
return str
end
-- Background opacity functions
local function increase_opacity()
if Text_max_opacity <= 0.9 then Text_max_opacity = Text_max_opacity + 0.1
else
if Background_max_opacity <= 0.9 then Background_max_opacity = Background_max_opacity + 0.1 end
end
end
local function decrease_opacity()
if Background_max_opacity >= 0.1 then Background_max_opacity = Background_max_opacity - 0.1
else
if Text_max_opacity >= 0.1 then Text_max_opacity = Text_max_opacity - 0.1 end
end
end
--#############################################################################
-- SMW FUNCTIONS:
-- Returns the id of Yoshi; if more than one, the lowest sprite slot
local function get_yoshi_id()
for i = 0, SMW.sprite_max - 1 do
local id = u8(WRAM.sprite_number + i)
local status = u8(WRAM.sprite_status + i)
if id == 0x35 and status ~= 0 then return i end
end
return nil
end
local Real_frame, Previous_real_frame, Effective_frame, Game_mode
local Level_index, Room_index, Level_flag, Current_level
local Is_paused, Lock_animation_flag, Player_powerup, Player_animation_trigger
local Camera_x, Camera_y
local function scan_smw()
Previous_real_frame = Real_frame or u8(WRAM.real_frame)
Real_frame = u8(WRAM.real_frame)
Effective_frame = u8(WRAM.effective_frame)
Game_mode = u8(WRAM.game_mode)
Level_index = u8(WRAM.level_index)
Level_flag = u8(WRAM.level_flag_table + Level_index)
Is_paused = u8(WRAM.level_paused) == 1
Lock_animation_flag = u8(WRAM.lock_animation_flag)
Room_index = u24(WRAM.room_index)
-- In level frequently used info
Player_animation_trigger = u8(WRAM.player_animation_trigger)
Player_powerup = u8(WRAM.powerup)
Camera_x = s16(WRAM.camera_x)
Camera_y = s16(WRAM.camera_y)
Yoshi_riding_flag = u8(WRAM.yoshi_riding_flag) ~= 0
Yoshi_id = get_yoshi_id()
end
-- Converts the in-game (x, y) to SNES-screen coordinates
local function screen_coordinates(x, y, camera_x, camera_y)
-- Sane values
camera_x = camera_x or Camera_x or u8(WRAM.camera_x)
camera_y = camera_y or Camera_y or u8(WRAM.camera_y)
local x_screen = (x - camera_x)
local y_screen = (y - camera_y) - Y_CAMERA_OFF
return x_screen, y_screen
end
-- Converts BizHawk/emu-screen coordinates to in-game (x, y)
local function game_coordinates(x_emu, y_emu, camera_x, camera_y)
-- Sane values
camera_x = camera_x or Camera_x or u8(WRAM.camera_x)
camera_y = camera_y or Camera_y or u8(WRAM.camera_y)
local x_game = x_emu + camera_x
local y_game = y_emu + Y_CAMERA_OFF + camera_y
return x_game, y_game
end
-- Returns the extreme values that Mario needs to have in order to NOT touch a rectangular object
local function display_boundaries(x_game, y_game, width, height, camera_x, camera_y)
-- Font
Text_opacity = 0.6
Bg_opacity = 0.4
-- Coordinates around the rectangle
local left = width*floor(x_game/width)
local top = height*floor(y_game/height)
left, top = screen_coordinates(left, top, camera_x, camera_y)
local right = left + width - 1
local bottom = top + height - 1
-- Reads WRAM values of the player
local is_ducking = u8(WRAM.is_ducking)
local powerup = Player_powerup
local is_small = is_ducking ~= 0 or powerup == 0
-- Left
local left_text = string.format("%4d.0", width*floor(x_game/width) - 13)
draw_text(AR_x*left, AR_y*(top+bottom)/2, left_text, false, false, 1.0, 0.5)
-- Right
local right_text = string.format("%d.f", width*floor(x_game/width) + 12)
draw_text(AR_x*right, AR_y*(top+bottom)/2, right_text, false, false, 0.0, 0.5)
-- Top
local value = (Yoshi_riding_flag and y_game - 16) or y_game
local top_text = fmt("%d.0", width*floor(value/width) - 32)
draw_text(AR_x*(left+right)/2, AR_y*top, top_text, false, false, 0.5, 1.0)
-- Bottom
value = height*floor(y_game/height)
if not is_small and not Yoshi_riding_flag then
value = value + 0x07
elseif is_small and Yoshi_riding_flag then
value = value - 4
else
value = value - 1 -- the 2 remaining cases are equal
end
local bottom_text = fmt("%d.f", value)
draw_text(AR_x*(left+right)/2, AR_y*bottom, bottom_text, false, false, 0.5, 0.0)
return left, top
end
local function read_screens()
local screens_number = u8(WRAM.screens_number)
local vscreen_number = u8(WRAM.vscreen_number)
local hscreen_number = u8(WRAM.hscreen_number) - 1
local vscreen_current = s8(WRAM.y + 1)
local hscreen_current = s8(WRAM.x + 1)
local level_mode_settings = u8(WRAM.level_mode_settings)
--local b1, b2, b3, b4, b5, b6, b7, b8 = bit.multidiv(level_mode_settings, 128, 64, 32, 16, 8, 4, 2)
--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)
local level_type
if (level_mode_settings ~= 0) and (level_mode_settings == 0x3 or level_mode_settings == 0x4 or level_mode_settings == 0x7
or level_mode_settings == 0x8 or level_mode_settings == 0xa or level_mode_settings == 0xd) then
level_type = "Vertical"
;
else
level_type = "Horizontal"
end
return level_type, screens_number, hscreen_current, hscreen_number, vscreen_current, vscreen_number
end
local function get_map16_value(x_game, y_game)
local num_x = floor(x_game/16)
local num_y = floor(y_game/16)
if num_x < 0 or num_y < 0 then return end -- 1st breakpoint
local level_type, screens, _, hscreen_number, _, vscreen_number = read_screens()
local max_x, max_y
if level_type == "Horizontal" then
max_x = 16*(hscreen_number + 1)
max_y = 27
else
max_x = 32
max_y = 16*(vscreen_number + 1)
end
if num_x > max_x or num_y > max_y then return end -- 2nd breakpoint
local num_id, kind, address
if level_type == "Horizontal" then
num_id = 16*27*floor(num_x/16) + 16*num_y + num_x%16
else
local nx = floor(num_x/16)
local ny = floor(num_y/16)
local n = 2*ny + nx
num_id = 16*16*n + 16*(num_y%16) + num_x%16
end
if (num_id >= 0 and num_id <= 0x37ff) then
address = fmt(" $%4.x", 0xc800 + num_id)
kind = 256*u8(0x1c800 + num_id) + u8(0xc800 + num_id)
end
if kind then return num_x, num_y, kind, address end
end
local function draw_layer1_tiles(camera_x, camera_y)
local x_origin, y_origin = screen_coordinates(0, 0, camera_x, camera_y)
local x_mouse, y_mouse = game_coordinates(User_input.xmouse, User_input.ymouse, camera_x, camera_y)
x_mouse = 16*floor(x_mouse/16)
y_mouse = 16*floor(y_mouse/16)
local push_direction = Real_frame%2 == 0 and 0 or 7 -- block pushes sprites to left or right?
for number, positions in ipairs(Layer1_tiles) do
-- Calculate the Lsnes coordinates
local left = positions[1] + x_origin
local top = positions[2] + y_origin
local right = left + 15
local bottom = top + 15
local x_game, y_game = game_coordinates(left, top, camera_x, camera_y)
-- Returns if block is way too outside the screen
if left > - Border_left - 32 and top > - Border_top - 32 and
right < Screen_width + Border_right + 32 and bottom < Screen_height + Border_bottom + 32 then
-- Drawings
Text_opacity = 1.0
local num_x, num_y, kind, address = get_map16_value(x_game, y_game)
if kind then
if kind >= 0x111 and kind <= 0x16d or kind == 0x2b then
-- default solid blocks, don't know how to include custom blocks
draw_rectangle(left + push_direction, top, 8, 15, 0, COLOUR.block_bg)
end
draw_rectangle(left, top, 15, 15, kind == SMW.blank_tile_map16 and COLOUR.blank_tile or COLOUR.block, 0)
if Layer1_tiles[number][3] then
display_boundaries(x_game, y_game, 16, 16, camera_x, camera_y) -- the text around it
end
-- Draw Map16 id
Text_opacity = 1.0
if kind and x_mouse == positions[1] and y_mouse == positions[2] then
draw_text(AR_x*(left + 4), AR_y*top - BIZHAWK_FONT_HEIGHT, fmt("Map16 (%d, %d), %x%s", num_x, num_y, kind, address),
false, false, 0.5, 1.0)
end
end
end
end
end
local function draw_layer2_tiles()
local layer2x = s16(WRAM.layer2_x_nextframe)
local layer2y = s16(WRAM.layer2_y_nextframe)
for number, positions in ipairs(Layer2_tiles) do
draw_rectangle(-layer2x + positions[1], -layer2y + positions[2], 15, 15, COLOUR.layer2_line, COLOUR.layer2_bg)
end
end
-- if the user clicks in a tile, it will be be drawn
-- if click is onto drawn region, it'll be erased
-- there's a max of possible tiles
-- layer_table[n] is an array {x, y, [draw info?]}
local function select_tile(x, y, layer_table)
if not OPTIONS.draw_tiles_with_click then return end
if Game_mode ~= SMW.game_mode_level then return end
for number, positions in ipairs(layer_table) do -- if mouse points a drawn tile, erase it
if x == positions[1] and y == positions[2] then
-- Layer 1
if layer_table == Layer1_tiles then
if layer_table[number][3] == false then
layer_table[number][3] = true
else
table.remove(layer_table, number)
end
-- Layer 2
elseif layer_table == Layer2_tiles then
table.remove(layer_table, number)
end
return
end
end
-- otherwise, draw a new tile
if #layer_table == OPTIONS.max_tiles_drawn then
table.remove(layer_table, 1)
layer_table[OPTIONS.max_tiles_drawn] = {x, y, false}
else
table.insert(layer_table, {x, y, false})
end
end
-- uses the mouse to select an object
local function select_object(mouse_x, mouse_y, camera_x, camera_y)
-- Font
Text_opacity = 1.0
Bg_opacity = 0.5
local x_game, y_game = game_coordinates(mouse_x, mouse_y, camera_x, camera_y)
local obj_id
-- Checks if the mouse is over Mario
local x_player = s16(WRAM.x)
local y_player = s16(WRAM.y)
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
obj_id = "Mario"
end
if not obj_id and OPTIONS.display_sprite_info then
for id = 0, SMW.sprite_max - 1 do
local sprite_status = u8(WRAM.sprite_status + id)
if sprite_status ~= 0 and Sprites_info[id].x then -- TODO: see why the script gets here without exporting Sprites_info
-- Import some values
local x_sprite, y_sprite = Sprites_info[id].x, Sprites_info[id].y
local x_screen, y_screen = Sprites_info[id].x_screen, Sprites_info[id].y_screen
local boxid = Sprites_info[id].boxid
local xoff, yoff = Sprites_info[id].xoff, Sprites_info[id].yoff
local width, height = Sprites_info[id].width, Sprites_info[id].height
if x_sprite + xoff + width >= x_game and x_sprite + xoff <= x_game and
y_sprite + yoff + height >= y_game and y_sprite + yoff <= y_game then
obj_id = id
break
end
end
end
end
if not obj_id then return end
draw_text(AR_x*User_input.xmouse, AR_y*(User_input.ymouse - 8), obj_id, true, false, 0.5, 1.0)
return obj_id, x_game, y_game
end
-- This function sees if the mouse if over some object, to change its hitbox mode
-- The order is: 1) player, 2) sprite.
local function right_click()
local id = select_object(User_input.xmouse, User_input.ymouse, Camera_x, Camera_y)
if tostring(id) == "Mario" then
if OPTIONS.display_player_hitbox and OPTIONS.display_interaction_points then
OPTIONS.display_interaction_points = false
OPTIONS.display_player_hitbox = false
elseif OPTIONS.display_player_hitbox then
OPTIONS.display_interaction_points = true
OPTIONS.display_player_hitbox = false
elseif OPTIONS.display_interaction_points then
OPTIONS.display_player_hitbox = true
else
OPTIONS.display_player_hitbox = true
end
end
if id then return end
local spr_id = tonumber(id)
if spr_id and spr_id >= 0 and spr_id <= SMW.sprite_max - 1 then
local number = Sprites_info[spr_id].number
if Sprite_hitbox[spr_id][number].sprite and Sprite_hitbox[spr_id][number].block then
Sprite_hitbox[spr_id][number].sprite = false
Sprite_hitbox[spr_id][number].block = false
elseif Sprite_hitbox[spr_id][number].sprite then
Sprite_hitbox[spr_id][number].block = true
Sprite_hitbox[spr_id][number].sprite = false
elseif Sprite_hitbox[spr_id][number].block then
Sprite_hitbox[spr_id][number].sprite = true
else
Sprite_hitbox[spr_id][number].sprite = true
end
end
if id then return end
-- Select layer 2 tiles
local layer2x = s16(WRAM.layer2_x_nextframe)
local layer2y = s16(WRAM.layer2_y_nextframe)
local x_mouse, y_mouse = User_input.xmouse + layer2x, User_input.ymouse + layer2y
select_tile(16*floor(x_mouse/16), 16*floor(y_mouse/16) - Y_CAMERA_OFF, Layer2_tiles)
end
local function show_movie_info()
if not OPTIONS.display_movie_info then
return
end
-- Font
Text_opacity = 1.0
Bg_opacity = 1.0
local y_text = - Border_top
local x_text = 0
local width = BIZHAWK_FONT_WIDTH
local rec_color = (Readonly or not Movie_active) and COLOUR.text or COLOUR.warning
local recording_bg = (Readonly or not Movie_active) and COLOUR.background or COLOUR.warning_bg
-- Read-only or read-write?
local movie_type = (not Movie_active and "No movie ") or (Readonly and "Movie " or "REC ")
alert_text(x_text, y_text, movie_type, rec_color, recording_bg)
if Movie_active then
-- Frame count
x_text = x_text + width*string.len(movie_type)
local movie_info
if Readonly then
movie_info = string.format("%d/%d", Lastframe_emulated, Framecount)
else
movie_info = string.format("%d", Lastframe_emulated)
end
draw_text(x_text, y_text, movie_info) -- Shows the latest frame emulated, not the frame being run now
-- Rerecord count
x_text = x_text + width*string.len(movie_info)
local rr_info = string.format(" %d ", Rerecords)
draw_text(x_text, y_text, rr_info, COLOUR.weak)
-- Lag count
x_text = x_text + width*string.len(rr_info)
draw_text(x_text, y_text, Lagcount, COLOUR.warning)
end
local str = frame_time(Lastframe_emulated) -- Shows the latest frame emulated, not the frame being run now
alert_text(Buffer_width, Buffer_height, str, COLOUR.text, recording_bg, false, 1.0, 1.0)
end
local function show_misc_info()
if not OPTIONS.display_misc_info then
return
end
-- Font
Text_opacity = 1.0
Bg_opacity = 1.0
-- Display
local RNG = u16(WRAM.RNG)
local main_info = string.format("Frame(%02x, %02x) RNG(%04x) Mode(%02x)",
Real_frame, Effective_frame, RNG, Game_mode)
;
draw_text(Buffer_width + Border_right, -Border_top, main_info, true, false)
if Game_mode == SMW.game_mode_level then
-- Time frame counter of the clock
Text_opacity = 1.0
local timer_frame_counter = u8(WRAM.timer_frame_counter)
draw_text(AR_x*161, AR_y*15, fmt("%.2d", timer_frame_counter))
-- Score: sum of digits, useful for avoiding lag
Text_opacity = 0.5
local score = u24(WRAM.mario_score)
draw_text(AR_x*240, AR_y*24, fmt("=%d", sum_digits(score)), COLOUR.weak)
end
end
-- Shows the controller input as the RAM and SNES registers store it
local function show_controller_data()
if not (OPTIONS.display_debug_info and OPTIONS.display_debug_controller_data) then return end
-- Font
Text_opacity = 0.9
local height = BIZHAWK_FONT_HEIGHT
local x_pos, y_pos, x, y, _ = 0, 0, 0, BIZHAWK_FONT_HEIGHT
x = draw_over_text(x_pos, y_pos, 256*u8(WRAM.ctrl_1_1) + u8(WRAM.ctrl_1_2), "BYsS^v<>AXLR0123", COLOUR.weak)
_, y = draw_text(x, y_pos, " (RAM data)", COLOUR.weak, false, true)
x = x_pos
draw_over_text(x, y, 256*u8(WRAM.firstctrl_1_1) + u8(WRAM.firstctrl_1_2), "BYsS^v<>AXLR0123", 0, 0xff0000ff, 0)
end
local function level_info()
if not OPTIONS.display_level_info then
return
end
-- Font
local x_pos = Buffer_width + Border_right
local y_pos = - Border_top + BIZHAWK_FONT_HEIGHT
local color = COLOUR.text
Text_opacity = 1.0
Bg_opacity = 1.0
local sprite_buoyancy = floor(u8(WRAM.sprite_buoyancy)/64)
if sprite_buoyancy == 0 then sprite_buoyancy = "" else
sprite_buoyancy = fmt(" %.2x", sprite_buoyancy)
color = COLOUR.warning
end
-- converts the level number to the Lunar Magic number; should not be used outside here
local lm_level_number = Level_index
if Level_index > 0x24 then lm_level_number = Level_index + 0xdc end
-- Number of screens within the level
local level_type, screens_number, hscreen_current, hscreen_number, vscreen_current, vscreen_number = read_screens()
draw_text(x_pos, y_pos, fmt("%.1sLevel(%.2x)%s", level_type, lm_level_number, sprite_buoyancy),
color, true, false)
;
draw_text(x_pos, y_pos + BIZHAWK_FONT_HEIGHT, fmt("Screens(%d):", screens_number), true)
draw_text(x_pos, y_pos + 2*BIZHAWK_FONT_HEIGHT, fmt("(%d/%d, %d/%d)", hscreen_current, hscreen_number,
vscreen_current, vscreen_number), true)
;
end
function draw_blocked_status(x_text, y_text, player_blocked_status, x_speed, y_speed)
local block_width = 9 -- BizHawk
local block_height = 9 -- BizHawk
local block_str = "Block:"
local str_len = string.len(block_str)
local xoffset = (x_text + str_len*BIZHAWK_FONT_WIDTH)/AR_x
local yoffset = y_text/AR_y
local color_line = COLOUR.warning
gui.drawRectangle(xoffset + Left_gap, yoffset + Top_gap, block_width - 1, block_height - 1, 0x40000000, 0x40ff0000)
local blocked_status = {}
local was_boosted = false
if bit.test(player_blocked_status, 0) then -- Right
draw_line(xoffset + block_width - 1, yoffset, xoffset + block_width - 1, yoffset + block_height - 1, 1, color_line)
if x_speed < 0 then was_boosted = true end
end
if bit.test(player_blocked_status, 1) then -- Left
draw_line(xoffset, yoffset, xoffset, yoffset + block_height - 1, 1, color_line)
if x_speed > 0 then was_boosted = true end
end
if bit.test(player_blocked_status, 2) then -- Down
draw_line(xoffset, yoffset + block_height - 1, xoffset + block_width - 1, yoffset + block_height - 1, 1, color_line)
end
if bit.test(player_blocked_status, 3) then -- Up
draw_line(xoffset, yoffset, xoffset + block_width - 1, yoffset, 1, color_line)
if y_speed > 6 then was_boosted = true end
end
if bit.test(player_blocked_status, 4) then -- Middle
gui.crosshair(xoffset + floor(block_width/2) + Left_gap, yoffset + floor(block_height/2) + Top_gap, -- BizHawk
math.min(block_width/3, block_height/3), color_line)
end
draw_text(x_text, y_text, block_str, COLOUR.text, was_boosted and COLOUR.warning_bg or nil)
end
-- displays player's hitbox
local function player_hitbox(x, y, is_ducking, powerup, transparency_level)
local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
local is_small = is_ducking ~= 0 or powerup == 0
-- Colors BizHawk
local is_transparent = transparency_level == 1
local interaction_bg = is_transparent and COLOUR.interaction_bg or 0
local mario_bg = is_transparent and COLOUR.mario_bg or 0
local mario_mounted_bg = is_transparent and COLOUR.mario_mounted_bg or 0
local mario = is_transparent and COLOUR.mario or change_transparency(COLOUR.mario, transparency_level)
local interaction_nohitbox = is_transparent and COLOUR.interaction_nohitbox or change_transparency(COLOUR.interaction_nohitbox, transparency_level)
local interaction_nohitbox_bg = is_transparent and COLOUR.interaction_nohitbox_bg or 0
local interaction = is_transparent and COLOUR.interaction or change_transparency(COLOUR.interaction, transparency_level)
local x_points = X_INTERACTION_POINTS
local y_points
if is_small and not Yoshi_riding_flag then
y_points = Y_INTERACTION_POINTS[1]
elseif not is_small and not Yoshi_riding_flag then
y_points = Y_INTERACTION_POINTS[2]
elseif is_small and Yoshi_riding_flag then
y_points = Y_INTERACTION_POINTS[3]
else
y_points = Y_INTERACTION_POINTS[4]
end
draw_box(x_screen + x_points.left_side, y_screen + y_points.head, x_screen + x_points.right_side, y_screen + y_points.foot,
interaction_bg, interaction_bg) -- background for block interaction
;
if OPTIONS.display_player_hitbox then
-- Collision with sprites
local mario_bg = (not Yoshi_riding_flag and mario_bg) or mario_mounted_bg
draw_box(x_screen + x_points.left_side - 1, y_screen + y_points.sprite,
x_screen + x_points.right_side + 1, y_screen + y_points.foot + 1, mario, mario_bg)
;
end
-- interaction points (collision with blocks)
if OPTIONS.display_interaction_points then
if not SHOW_PLAYER_HITBOX then
draw_box(x_screen + x_points.left_side , y_screen + y_points.head,
x_screen + x_points.right_side, y_screen + y_points.foot, interaction_nohitbox, interaction_nohitbox_bg)
end
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
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
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
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
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
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
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
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
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
end
-- That's the pixel that appears when Mario dies in the pit
Show_player_point_position = Show_player_point_position or y_screen >= 200 or
(OPTIONS.display_debug_info and OPTIONS.display_debug_player_extra)
if Show_player_point_position then
draw_rectangle(x_screen - 1, y_screen - 1, 2, 2, interaction_bg, interaction)
Show_player_point_position = false
end
return x_points, y_points
end
-- displays the hitbox of the cape while spinning
local function cape_hitbox(spin_direction)
local cape_interaction = u8(WRAM.cape_interaction)
if cape_interaction == 0 then return end
local cape_x = u16(WRAM.cape_x)
local cape_y = u16(WRAM.cape_y)
local cape_x_screen, cape_y_screen = screen_coordinates(cape_x, cape_y, Camera_x, Camera_y)
local cape_left = -2
local cape_right = 0x12
local cape_up = 0x01
local cape_down = 0x11
local cape_middle = 0x08
local block_interaction_cape = (spin_direction < 0 and cape_left + 4) or cape_right - 4
local active_frame_sprites = Real_frame%2 == 1 -- active iff the cape can hit a sprite
local active_frame_blocks = Real_frame%2 == (spin_direction < 0 and 0 or 1) -- active iff the cape can hit a block
if active_frame_sprites then bg_color = COLOUR.cape_bg else bg_color = 0 end
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)
if active_frame_blocks then
draw_pixel(cape_x_screen + block_interaction_cape, cape_y_screen + cape_middle, COLOUR.warning)
else
draw_pixel(cape_x_screen + block_interaction_cape, cape_y_screen + cape_middle, COLOUR.text)
end
end
local function player()
if not OPTIONS.display_player_info then
return
end
-- Font
Text_opacity = 1.0
-- Reads WRAM
local x = s16(WRAM.x)
local y = s16(WRAM.y)
local previous_x = s16(WRAM.previous_x)
local previous_y = s16(WRAM.previous_y)
local x_sub = u8(WRAM.x_sub)
local y_sub = u8(WRAM.y_sub)
local x_speed = s8(WRAM.x_speed)
local x_subspeed = u8(WRAM.x_subspeed)
local y_speed = s8(WRAM.y_speed)
local p_meter = u8(WRAM.p_meter)
local take_off = u8(WRAM.take_off)
local powerup = Player_powerup
local direction = u8(WRAM.direction)
local cape_spin = u8(WRAM.cape_spin)
local cape_fall = u8(WRAM.cape_fall)
local flight_animation = u8(WRAM.flight_animation)
local diving_status = s8(WRAM.diving_status)
local player_blocked_status = u8(WRAM.player_blocked_status)
local player_item = u8(WRAM.player_item)
local is_ducking = u8(WRAM.is_ducking)
local on_ground = u8(WRAM.on_ground)
local spinjump_flag = u8(WRAM.spinjump_flag)
local can_jump_from_water = u8(WRAM.can_jump_from_water)
local carrying_item = u8(WRAM.carrying_item)
local scroll_timer = u8(WRAM.camera_scroll_timer)
local vertical_scroll_flag_header = u8(WRAM.vertical_scroll_flag_header)
local vertical_scroll_enabled = u8(WRAM.vertical_scroll_enabled)
-- Transformations
if direction == 0 then direction = LEFT_ARROW else direction = RIGHT_ARROW end
local x_sub_simple, y_sub_simple-- = x_sub, y_sub
if x_sub%0x10 == 0 then x_sub_simple = fmt("%x", x_sub/0x10) else x_sub_simple = fmt("%.2x", x_sub) end
if y_sub%0x10 == 0 then y_sub_simple = fmt("%x", y_sub/0x10) else y_sub_simple = fmt("%.2x", y_sub) end
local x_speed_int, x_speed_frac = math.modf(x_speed + x_subspeed/0x100)
x_speed_frac = math.abs(x_speed_frac*100)
local spin_direction = (Effective_frame)%8
if spin_direction < 4 then
spin_direction = spin_direction + 1
else
spin_direction = 3 - spin_direction
end
local is_caped = powerup == 0x2
local is_spinning = cape_spin ~= 0 or spinjump_flag ~= 0
-- Display info
local i = 0
local delta_x = BIZHAWK_FONT_WIDTH
local delta_y = BIZHAWK_FONT_HEIGHT
local table_x = - Border_left
local table_y = AR_y*32
draw_text(table_x, table_y + i*delta_y, fmt("Meter (%03d, %02d) %s", p_meter, take_off, direction))
draw_text(table_x + 18*delta_x, table_y + i*delta_y, fmt(" %+d", spin_direction),
(is_spinning and COLOUR.text) or COLOUR.weak)
i = i + 1
draw_text(table_x, table_y + i*delta_y, fmt("Pos (%+d.%s, %+d.%s)", x, x_sub_simple, y, y_sub_simple))
i = i + 1
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))
i = i + 1
if is_caped then
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)
i = i + 1
end
local x_txt = draw_text(table_x, table_y + i*delta_y, fmt("Camera (%d, %d)", Camera_x, Camera_y))
if scroll_timer ~= 0 then x_txt = draw_text(x_txt, table_y + i*delta_y, 16 - scroll_timer, COLOUR.warning) end
if vertical_scroll_flag_header ~=0 and vertical_scroll_enabled ~= 0 then
draw_text(x_txt, table_y + i*delta_y, vertical_scroll_enabled, COLOUR.warning2)
end
i = i + 1
if OPTIONS.display_static_camera_region then
Show_player_point_position = true
local left_cam, right_cam = u16(0x142c), u16(0x142e) -- unlisted WRAM
draw_box(left_cam, 0, right_cam, 224, COLOUR.static_camera_region, COLOUR.static_camera_region)
end
draw_blocked_status(table_x, table_y + i*delta_y, player_blocked_status, x_speed, y_speed)
-- Mario boost indicator (experimental)
-- This looks for differences between the expected x position and the actual x position, after a frame advance
-- Fails during a loadstate and has false positives if the game is paused or lagged
Previous.player_x = 256*x + x_sub -- the total amount of 256-based subpixels
Previous.x_speed = 16*x_speed -- the speed in 256-based subpixels
if Mario_boost_indicator and not Cheat.under_free_move then
local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
draw_text(AR_x*(x_screen + 4), AR_y*(y_screen + 60), Mario_boost_indicator, COLOUR.warning, 0x20000000) -- unlisted color
end
-- shows hitbox and interaction points for player
if not (OPTIONS.display_player_hitbox or OPTIONS.display_interaction_points) then return end
cape_hitbox(spin_direction)
player_hitbox(x, y, is_ducking, powerup, 1.0)
-- Shows where Mario is expected to be in the next frame, if he's not boosted or stopped (DEBUG)
if OPTIONS.display_debug_info and OPTIONS.display_debug_player_extra then
player_hitbox( floor((256*x + x_sub + 16*x_speed)/256),
floor((256*y + y_sub + 16*y_speed)/256), is_ducking, powerup, 0.3) -- BizHawk
end
end
local function extended_sprites()
if not OPTIONS.display_extended_sprite_info then
return
end
-- Font
Text_opacity = 1.0
local height = BIZHAWK_FONT_HEIGHT
local y_pos = AR_y*144
local counter = 0
for id = 0, SMW.extended_sprite_max - 1 do
local extspr_number = u8(WRAM.extspr_number + id)
if extspr_number ~= 0 then
-- Reads WRAM addresses
local x = 256*u8(WRAM.extspr_x_high + id) + u8(WRAM.extspr_x_low + id)
local y = 256*u8(WRAM.extspr_y_high + id) + u8(WRAM.extspr_y_low + id)
local sub_x = bit.rshift(u8(WRAM.extspr_subx + id), 4)
local sub_y = bit.rshift(u8(WRAM.extspr_suby + id), 4)
local x_speed = s8(WRAM.extspr_x_speed + id)
local y_speed = s8(WRAM.extspr_y_speed + id)
local extspr_table = u8(WRAM.extspr_table + id)
local extspr_table2 = u8(WRAM.extspr_table2 + id)
-- Reduction of useless info
local special_info = ""
if OPTIONS.display_debug_info and OPTIONS.display_debug_extended_sprite and (extspr_table ~= 0 or extspr_table2 ~= 0) then
special_info = fmt("(%x, %x) ", extspr_table, extspr_table2)
end
-- x speed for Fireballs
if extspr_number == 5 then x_speed = 16*x_speed end
if OPTIONS.display_extended_sprite_info then
draw_text(Buffer_width + Border_right, y_pos + counter*height, fmt("#%.2d %.2x %s(%d.%x(%+.2d), %d.%x(%+.2d))",
id, extspr_number, special_info, x, sub_x, x_speed, y, sub_y, y_speed),
COLOUR.extended_sprites, true, false)
end
if (OPTIONS.display_debug_info and OPTIONS.display_debug_extended_sprite) or not UNINTERESTING_EXTENDED_SPRITES[extspr_number]
or (extspr_number == 1 and extspr_table2 == 0xf)
then
local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
local t = HITBOX_EXTENDED_SPRITE[extspr_number] or
{xoff = 0, yoff = 0, width = 16, height = 16, color_line = COLOUR.awkward_hitbox, color_bg = COLOUR.awkward_hitbox_bg}
local xoff = t.xoff
local yoff = t.yoff + Y_CAMERA_OFF
local xrad = t.width
local yrad = t.height
local color_line = t.color_line or COLOUR.extended_sprites
local color_bg = t.color_bg or COLOUR.extended_sprites_bg
if extspr_number == 0x5 or extspr_number == 0x11 then
color_bg = (Real_frame - id)%4 == 0 and COLOUR.special_extended_sprite_bg or 0
end
draw_rectangle(x_screen+xoff, y_screen+yoff, xrad, yrad, color_line, color_bg) -- regular hitbox
-- Experimental: attempt to show Mario's fireball vs sprites
-- this is likely wrong in some situation, but I can't solve this yet
if extspr_number == 5 or extspr_number == 1 then
local xoff_spr = x_speed >= 0 and -5 or 1
local yoff_spr = - floor(y_speed/16) - 4 + (y_speed >= -40 and 1 or 0)
local yrad_spr = y_speed >= -40 and 19 or 20
draw_rectangle(x_screen + xoff_spr, y_screen + yoff_spr, 12, yrad_spr, color_line, color_bg)
end
end
counter = counter + 1
end
end
Text_opacity = 0.5
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)
if u8(WRAM.spinjump_flag) ~= 0 and u8(WRAM.powerup) == 3 then
local fireball_timer = u8(WRAM.spinjump_fireball_timer)
draw_text(x_pos - length - BIZHAWK_FONT_WIDTH, y_pos, fmt("%d %s",
fireball_timer%16, bit.test(fireball_timer, 4) and RIGHT_ARROW or LEFT_ARROW), COLOUR.extended_sprites, true, false, 1.0, 1.0)
end
end
local function cluster_sprites()
if not OPTIONS.display_cluster_sprite_info or u8(WRAM.cluspr_flag) == 0 then return end
-- Font
Text_opacity = 1.0
local height = BIZHAWK_FONT_HEIGHT
local x_pos, y_pos = AR_x*90, AR_y*77 -- BizHawk
local counter = 0
if OPTIONS.display_debug_info and OPTIONS.display_debug_cluster_sprite then
draw_text(x_pos, y_pos, "Cluster Spr.", COLOUR.weak)
counter = counter + 1
end
local reappearing_boo_counter
for id = 0, SMW.cluster_sprite_max - 1 do
local clusterspr_number = u8(WRAM.cluspr_number + id)
if clusterspr_number ~= 0 then
if not HITBOX_CLUSTER_SPRITE[clusterspr_number] then
print("Warning: wrong cluster sprite number:", clusterspr_number) -- should not happen without cheats
return
end
-- Reads WRAM addresses
local x = signed(256*u8(WRAM.cluspr_x_high + id) + u8(WRAM.cluspr_x_low + id), 16)
local y = signed(256*u8(WRAM.cluspr_y_high + id) + u8(WRAM.cluspr_y_low + id), 16)
local clusterspr_timer, special_info, table_1, table_2, table_3
-- Reads cluster's table
local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
local t = HITBOX_CLUSTER_SPRITE[clusterspr_number] or
{xoff = 0, yoff = 0, width = 16, height = 16, color_line = COLOUR.awkward_hitbox, color_bg = COLOUR.awkward_hitbox_bg, oscillation = 1}
local xoff = t.xoff
local yoff = t.yoff + Y_CAMERA_OFF
local xrad = t.width
local yrad = t.height
local phase = t.phase or 0
local oscillation = (Real_frame - id)%t.oscillation == phase
local color = t.color or COLOUR.cluster_sprites
local color_bg = t.bg or COLOUR.sprites_bg
local invencibility_hitbox = nil
if OPTIONS.display_debug_info and OPTIONS.display_debug_cluster_sprite then
table_1 = u8(WRAM.cluspr_table_1 + id)
table_2 = u8(WRAM.cluspr_table_2 + id)
table_3 = u8(WRAM.cluspr_table_3 + id)
draw_text(x_pos, y_pos + counter*height, ("#%d(%d): (%d, %d) %d, %d, %d")
:format(id, clusterspr_number, x, y, table_1, table_2, table_3), color)
counter = counter + 1
end
-- Case analysis
if clusterspr_number == 3 or clusterspr_number == 8 then
clusterspr_timer = u8(WRAM.cluspr_timer + id)
if clusterspr_timer ~= 0 then special_info = " " .. clusterspr_timer end
elseif clusterspr_number == 6 then
table_1 = table_1 or u8(WRAM.cluspr_table_1 + id)
if table_1 >= 111 or (table_1 < 31 and table_1 >= 16) then
yoff = yoff + 17
elseif table_1 >= 103 or table_1 < 16 then
invencibility_hitbox = true
elseif table_1 >= 95 or (table_1 < 47 and table_1 >= 31) then
yoff = yoff + 16
end
elseif clusterspr_number == 7 then
reappearing_boo_counter = reappearing_boo_counter or u8(WRAM.reappearing_boo_counter)
invencibility_hitbox = (reappearing_boo_counter > 0xde) or (reappearing_boo_counter < 0x3f)
special_info = " " .. reappearing_boo_counter
end
-- Hitbox and sprite id
color = invencibility_hitbox and COLOUR.weak or color
color_bg = (invencibility_hitbox and 0) or (oscillation and color_bg) or 0
draw_rectangle(x_screen + xoff, y_screen + yoff, xrad, yrad, color, color_bg)
draw_text(AR_x*(x_screen + xoff) + xrad, AR_y*(y_screen + yoff), special_info and id .. special_info or id,
color, false, false, 0.5, 1.0)
end
end
end
local function minor_extended_sprites()
if not OPTIONS.display_minor_extended_sprite_info then return end
-- Font
Text_opacity = 1.0
local height = BIZHAWK_FONT_HEIGHT
local x_pos, y_pos = 0, Buffer_height - height*SMW.minor_extended_sprite_max
local counter = 0
for id = 0, SMW.minor_extended_sprite_max - 1 do
local minorspr_number = u8(WRAM.minorspr_number + id)
if minorspr_number ~= 0 then
-- Reads WRAM addresses
local x = signed(256*u8(WRAM.minorspr_x_high + id) + u8(WRAM.minorspr_x_low + id), 16)
local y = signed(256*u8(WRAM.minorspr_y_high + id) + u8(WRAM.minorspr_y_low + id), 16)
local xspeed, yspeed = s8(WRAM.minorspr_xspeed + id), s8(WRAM.minorspr_yspeed + id)
local x_sub, y_sub = u8(WRAM.minorspr_x_sub + id), u8(WRAM.minorspr_y_sub + id)
local timer = u8(WRAM.minorspr_timer + id)
-- Only sprites 1 and 10 use the higher byte
local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
if minorspr_number ~= 1 and minorspr_number ~= 10 then -- Boo stream and Piece of brick block
x_screen = x_screen%0x100
y_screen = y_screen%0x100
end
-- Draw next to the sprite
local text = "#" .. id .. (timer ~= 0 and (" " .. timer) or "")
draw_text(AR_x*(x_screen + 8), AR_y*(y_screen + 4), text, COLOUR.minor_extended_sprites, false, false, 0.5, 1.0)
if minorspr_number == 10 then -- Boo stream
draw_rectangle(x_screen + 4, y_screen + 4 + Y_CAMERA_OFF, 8, 8, COLOUR.minor_extended_sprites, COLOUR.sprites_bg)
end
-- Draw in the table
if OPTIONS.display_debug_info and OPTIONS.display_debug_minor_extended_sprite then
draw_text(x_pos, y_pos + counter*height, ("#%d(%d): %d.%x(%d), %d.%x(%d)")
:format(id, minorspr_number, x, floor(x_sub/16), xspeed, y, floor(y_sub/16), yspeed), COLOUR.minor_extended_sprites)
end
counter = counter + 1
end
end
if OPTIONS.display_debug_info and OPTIONS.display_debug_minor_extended_sprite then
draw_text(x_pos, y_pos - height, "Minor Ext Spr:" .. counter, COLOUR.weak)
end
end
local function bounce_sprite_info()
if not OPTIONS.display_bounce_sprite_info then return end
-- Debug info
local x_txt, y_txt = AR_x*90, AR_y*37
if OPTIONS.display_debug_info and OPTIONS.display_debug_bounce_sprite then
Text_opacity = 0.5
draw_text(x_txt, y_txt, "Bounce Spr.", COLOUR.weak)
end
-- Font
Text_opacity = 1.0
local height = BIZHAWK_FONT_HEIGHT
local stop_id = (u8(WRAM.bouncespr_last_id) - 1)%SMW.bounce_sprite_max
for id = 0, SMW.bounce_sprite_max - 1 do
local bounce_sprite_number = u8(WRAM.bouncespr_number + id)
if bounce_sprite_number ~= 0 then
local x = 256*u8(WRAM.bouncespr_x_high + id) + u8(WRAM.bouncespr_x_low + id)
local y = 256*u8(WRAM.bouncespr_y_high + id) + u8(WRAM.bouncespr_y_low + id)
local bounce_timer = u8(WRAM.bouncespr_timer + id)
if OPTIONS.display_debug_info and OPTIONS.display_debug_bounce_sprite then
draw_text(x_txt, y_txt + height*(id + 1), fmt("#%d:%d (%d, %d)", id, bounce_sprite_number, x, y))
end
local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
x_screen, y_screen = AR_x*(x_screen + 8), AR_y*y_screen
local color = id == stop_id and COLOUR.warning or COLOUR.text
draw_text(x_screen , y_screen, fmt("#%d:%d", id, bounce_timer), color, false, false, 0.5) -- timer
-- Turn blocks
if bounce_sprite_number == 7 then
turn_block_timer = u8(WRAM.turn_block_timer + id)
draw_text(x_screen, y_screen + height, turn_block_timer, color, false, false, 0.5)
end
end
end
end
local function sprite_info(id, counter, table_position)
Text_opacity = 1.0
local sprite_status = u8(WRAM.sprite_status + id)
if sprite_status == 0 then return 0 end -- returns if the slot is empty
local x = 256*u8(WRAM.sprite_x_high + id) + u8(WRAM.sprite_x_low + id)
local y = 256*u8(WRAM.sprite_y_high + id) + u8(WRAM.sprite_y_low + id)
local x_sub = u8(WRAM.sprite_x_sub + id)
local y_sub = u8(WRAM.sprite_y_sub + id)
local number = u8(WRAM.sprite_number + id)
local stun = u8(WRAM.sprite_miscellaneous7 + id)
local x_speed = s8(WRAM.sprite_x_speed + id)
local y_speed = s8(WRAM.sprite_y_speed + id)
local contact_mario = u8(WRAM.sprite_miscellaneous8 + id)
local underwater = u8(WRAM.sprite_underwater + id)
local x_offscreen = s8(WRAM.sprite_x_offscreen + id)
local y_offscreen = s8(WRAM.sprite_y_offscreen + id)
local special = ""
if (OPTIONS.display_debug_info and OPTIONS.display_debug_sprite_extra) or
((sprite_status ~= 0x8 and sprite_status ~= 0x9 and sprite_status ~= 0xa and sprite_status ~= 0xb) or stun ~= 0) then
special = string.format("(%d %d) ", sprite_status, stun)
end
-- Let x and y be 16-bit signed
x = signed(x, 16)
y = signed(y, 16)
---**********************************************
-- Calculates the sprites dimensions and screen positions
local x_screen, y_screen = screen_coordinates(x, y, Camera_x, Camera_y)
-- Sprite clipping vs mario and sprites
local boxid = bit.band(u8(WRAM.sprite_2_tweaker + id), 0x3f) -- This is the type of box of the sprite
local xoff = HITBOX_SPRITE[boxid].xoff
local yoff = HITBOX_SPRITE[boxid].yoff + Y_CAMERA_OFF
local sprite_width = HITBOX_SPRITE[boxid].width
local sprite_height = HITBOX_SPRITE[boxid].height
-- Sprite clipping vs objects
local clip_obj = bit.band(u8(WRAM.sprite_1_tweaker + id), 0xf) -- type of hitbox for blocks
local xpt_right = OBJ_CLIPPING_SPRITE[clip_obj].xright
local ypt_right = OBJ_CLIPPING_SPRITE[clip_obj].yright
local xpt_left = OBJ_CLIPPING_SPRITE[clip_obj].xleft
local ypt_left = OBJ_CLIPPING_SPRITE[clip_obj].yleft
local xpt_down = OBJ_CLIPPING_SPRITE[clip_obj].xdown
local ypt_down = OBJ_CLIPPING_SPRITE[clip_obj].ydown
local xpt_up = OBJ_CLIPPING_SPRITE[clip_obj].xup
local ypt_up = OBJ_CLIPPING_SPRITE[clip_obj].yup
-- Process interaction with player every frame?
-- Format: dpmksPiS. This 'm' bit seems odd, since it has false negatives
local oscillation_flag = bit.test(u8(WRAM.sprite_4_tweaker + id), 5) or OSCILLATION_SPRITES[number]
-- calculates the correct color to use, according to id
local info_color
local color_background
if number == 0x35 then
info_color = COLOUR.yoshi
color_background = COLOUR.yoshi_bg
else
info_color = COLOUR.sprites[id%(#COLOUR.sprites) + 1]
color_background = COLOUR.sprites_bg
end
if (not oscillation_flag) and (Real_frame - id)%2 == 1 then color_background = 0 end -- due to sprite oscillation every other frame
-- notice that some sprites interact with Mario every frame
;
---**********************************************
-- Displays sprites hitboxes
if OPTIONS.display_sprite_hitbox then
-- That's the pixel that appears when the sprite vanishes in the pit
if y_screen >= 224 or (OPTIONS.display_debug_info and OPTIONS.display_debug_sprite_extra) then
draw_pixel(x_screen, y_screen, info_color)
end
if Sprite_hitbox[id][number].block then
draw_box(x_screen + xpt_left, y_screen + ypt_down, x_screen + xpt_right, y_screen + ypt_up,
COLOUR.sprites_clipping_bg, Sprite_hitbox[id][number].sprite and 0 or COLOUR.sprites_clipping_bg)
end
if Sprite_hitbox[id][number].sprite and not ABNORMAL_HITBOX_SPRITES[number] then -- show sprite/sprite clipping
draw_rectangle(x_screen + xoff, y_screen + yoff, sprite_width, sprite_height, info_color, color_background)
end
if Sprite_hitbox[id][number].block then -- show sprite/object clipping
local size, color = 1, COLOUR.sprites_interaction_pts
draw_line(x_screen + xpt_right, y_screen + ypt_right, x_screen + xpt_right - size, y_screen + ypt_right, 1, color) -- right
draw_line(x_screen + xpt_left, y_screen + ypt_left, x_screen + xpt_left + size, y_screen + ypt_left, 1, color) -- left
draw_line(x_screen + xpt_down, y_screen + ypt_down, x_screen + xpt_down, y_screen + ypt_down - size, 1, color) -- down
draw_line(x_screen + xpt_up, y_screen + ypt_up, x_screen + xpt_up, y_screen + ypt_up + size, 1, color) -- up
end
end
---**********************************************
-- Special sprites analysis:
--[[
PROBLEMATIC ONES
29 Koopa Kid
54 Revolving door for climbing net, wrong hitbox area, not urgent
5a Turn block bridge, horizontal, hitbox only applies to central block and wrongly
86 Wiggler, the second part of the sprite, that hurts Mario even if he's on Yoshi, doesn't appear
89 Layer 3 Smash, hitbox of generator outside
9e Ball 'n' Chain, hitbox only applies to central block, rotating ball
a3 Rotating gray platform, wrong hitbox, rotating plataforms
]]
if number == 0x5f then -- Swinging brown platform (fix it)
-- Powerup Incrementation helper
local yoshi_right = 256*floor(x/256) - 58
local yoshi_left = yoshi_right + 32
local x_text, y_text, height = AR_x*(x_screen + xoff), AR_y*(y_screen + yoff), BIZHAWK_FONT_HEIGHT
if mouse_onregion(x_text, y_text, x_text + AR_x*sprite_width, y_text + AR_y*sprite_height) then
local x_text, y_text = 0, height
draw_text(x_text, y_text, "Powerup Incrementation help", info_color, COLOUR.background)
draw_text(x_text, y_text + height, "Yoshi must have: id = #4;", info_color, COLOUR.background)
draw_text(x_text, y_text + 2*height, ("Yoshi x pos: (%s %d) or (%s %d)")
:format(LEFT_ARROW, yoshi_left, RIGHT_ARROW, yoshi_right), info_color, COLOUR.background)
end
--The status change happens when yoshi's id number is #4 and when (yoshi's x position) + Z mod 256 = 214,
--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,
--the address 0x7E0015 + (yoshi's id number) will be added by 1.
-- therefore: X_yoshi = 256*floor(x/256) + 32*yoshi_direction - 58
end
if number == 0x35 then -- Yoshi
if not Yoshi_riding_flag and OPTIONS.display_sprite_hitbox and Sprite_hitbox[id][number].sprite then
draw_rectangle(x_screen + 4, y_screen + 20, 8, 8, COLOUR.yoshi)
end
end
if number == 0x62 or number == 0x63 then -- Brown line-guided platform & Brown/checkered line-guided platform
xoff = xoff - 24
yoff = yoff - 8
-- for some reason, the actual base is 1 pixel below when Mario is small
if OPTIONS.display_sprite_hitbox then
draw_rectangle(x_screen + xoff, y_screen + yoff, sprite_width, sprite_height, info_color, color_background)
end
end
if number == 0x6b then -- Wall springboard (left wall)
xoff = xoff - 8
sprite_height = sprite_height + 1 -- for some reason, small Mario gets a bigger hitbox
if OPTIONS.display_sprite_hitbox then
draw_rectangle(x_screen + xoff, y_screen + yoff, sprite_width, sprite_height, info_color, color_background)
draw_line(x_screen + xoff, y_screen + yoff + 3, x_screen + xoff + sprite_width, y_screen + yoff + 3, 1, info_color)
end
end
if number == 0x6c then -- Wall springboard (right wall)
xoff = xoff - 31
sprite_height = sprite_height + 1
if OPTIONS.display_sprite_hitbox then
draw_rectangle(x_screen + xoff, y_screen + yoff, sprite_width, sprite_height, info_color, color_background)
draw_line(x_screen + xoff, y_screen + yoff + 3, x_screen + xoff + sprite_width, y_screen + yoff + 3, 1, info_color)
end
end
if number == 0x7b then -- Goal Tape
Text_opacity = 0.8
-- This draws the effective area of a goal tape
local x_effective = 256*u8(WRAM.sprite_miscellaneous4 + id) + u8(0xc2 + id) -- unlisted WRAM
local y_low = 256*u8(0x1534 + id) + u8(WRAM.sprite_miscellaneous5 + id) -- unlisted WRAM
local _, y_high = screen_coordinates(0, 0, Camera_x, Camera_y)
local x_s, y_s = screen_coordinates(x_effective, y_low, Camera_x, Camera_y)
if OPTIONS.display_sprite_hitbox then
draw_box(x_s, y_high, x_s + 15, y_s, info_color, COLOUR.goal_tape_bg)
end
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)
Text_opacity = 1.0
Bg_opacity = 1.0
elseif number == 0xa9 then -- Reznor
local reznor
local color
for index = 0, SMW.sprite_max - 1 do
reznor = u8(WRAM.sprite_miscellaneous4 + index)
if index >= 4 and index <= 7 then
color = COLOUR.warning
else
color = color_weak
end
draw_text(3*BIZHAWK_FONT_WIDTH*index, Buffer_height, fmt("%.2x", reznor), color, true, false, 0.0, 1.0)
end
elseif number == 0xa0 then -- Bowser
local height = BIZHAWK_FONT_HEIGHT
local y_text = Screen_height - 10*height
local address = 0x14b0 -- unlisted WRAM
for index = 0, 9 do
local value = u8(address + index)
draw_text(Buffer_width + Border_right, y_text + index*height, fmt("%2x = %3d", value, value), info_color, true)
end
end
---**********************************************
-- Prints those informations next to the sprite
Text_opacity = 1.0
Bg_opacity = 1.0
if x_offscreen ~= 0 or y_offscreen ~= 0 then
Text_opacity = 0.6
end
local contact_str = contact_mario == 0 and "" or " " .. contact_mario
local sprite_middle = x_screen + xoff + floor(sprite_width/2)
local sprite_top = y_screen + math.min(yoff, ypt_up)
draw_text(AR_x*sprite_middle, AR_y*sprite_top, fmt("#%.2d%s", id, contact_str), info_color, true, false, 0.5, 1.0)
if Player_powerup == 2 then
local contact_cape = u8(WRAM.sprite_disable_cape + id)
if contact_cape ~= 0 then
draw_text(AR_x*sprite_middle, AR_y*sprite_top - 2*BIZHAWK_FONT_HEIGHT, contact_cape, COLOUR.cape, true)
end
end
---**********************************************
-- Sprite tweakers info
if OPTIONS.display_debug_info and OPTIONS.display_debug_sprite_tweakers then
Text_opacity = 0.8 -- BizHawk
local height = BIZHAWK_FONT_HEIGHT
local x_txt, y_txt = AR_x*sprite_middle - 4*BIZHAWK_FONT_WIDTH, AR_y*(y_screen + yoff) - 7*height
local tweaker_1 = u8(WRAM.sprite_1_tweaker + id)
draw_over_text(x_txt, y_txt, tweaker_1, "sSjJcccc", COLOUR.weak, info_color)
y_txt = y_txt + height
local tweaker_2 = u8(WRAM.sprite_2_tweaker + id)
draw_over_text(x_txt, y_txt, tweaker_2, "dscccccc", COLOUR.weak, info_color)
y_txt = y_txt + height
local tweaker_3 = u8(WRAM.sprite_3_tweaker + id)
draw_over_text(x_txt, y_txt, tweaker_3, "lwcfpppg", COLOUR.weak, info_color)
y_txt = y_txt + height
local tweaker_4 = u8(WRAM.sprite_4_tweaker + id)
draw_over_text(x_txt, y_txt, tweaker_4, "dpmksPiS", COLOUR.weak, info_color)
y_txt = y_txt + height
local tweaker_5 = u8(WRAM.sprite_5_tweaker + id)
draw_over_text(x_txt, y_txt, tweaker_5, "dnctswye", COLOUR.weak, info_color)
y_txt = y_txt + height
local tweaker_6 = u8(WRAM.sprite_6_tweaker + id)
draw_over_text(x_txt, y_txt, tweaker_6, "wcdj5sDp", COLOUR.weak, info_color)
Text_opacity = 1.0
end
---**********************************************
-- The sprite table:
local x_speed_water = ""
if underwater ~= 0 then -- if sprite is underwater
local correction = floor(3*floor(x_speed/2)/2)
x_speed_water = string.format("%+.2d=%+.2d", correction - x_speed, correction)
end
local sprite_str = fmt("#%02d %02x %s%d.%1x(%+.2d%s) %d.%1x(%+.2d)",
id, number, special, x, floor(x_sub/16), x_speed, x_speed_water, y, floor(y_sub/16), y_speed)
Text_opacity = 1.0
Bg_opacity = 1.0
if x_offscreen ~= 0 or y_offscreen ~= 0 then
Text_opacity = 0.6
end
draw_text(Buffer_width + Border_right, table_position + counter*BIZHAWK_FONT_HEIGHT, sprite_str, info_color, true)
-- Miscellaneous sprite table
if OPTIONS.display_miscellaneous_sprite_table then
local x_mis, y_mis = - Border_left, AR_y*144 + counter*BIZHAWK_FONT_HEIGHT
local t = OPTIONS.miscellaneous_sprite_table_number
local misc, text = nil, fmt("#%.2d", id)
for num = 1, 19 do
misc = t[num] and u8(WRAM["sprite_miscellaneous" .. num] + id) or false
text = misc and fmt("%s %3d", text, misc) or text
end
draw_text(x_mis, y_mis, text, info_color)
end
-- Exporting some values
Sprites_info[id].number = number
Sprites_info[id].x, Sprites_info[id].y = x, y
Sprites_info[id].x_screen, Sprites_info[id].y_screen = x_screen, y_screen
Sprites_info[id].boxid = boxid
Sprites_info[id].xoff, Sprites_info[id].yoff = xoff, yoff
Sprites_info[id].width, Sprites_info[id].height = sprite_width, sprite_height
return 1
end
local function sprites()
if not OPTIONS.display_sprite_info then return end
local counter = 0
local table_position = AR_y*48
for id = 0, SMW.sprite_max - 1 do
counter = counter + sprite_info(id, counter, table_position)
end
-- Font
Text_opacity = 0.6
local swap_slot = u8(0x1861) -- unlisted WRAM
local smh = u8(WRAM.sprite_memory_header)
draw_text(Buffer_width + Border_right, table_position - 2*BIZHAWK_FONT_HEIGHT, fmt("spr:%.2d", counter), COLOUR.weak, true)
draw_text(Buffer_width + Border_right, table_position - BIZHAWK_FONT_HEIGHT, fmt("1st div: %d. Swap: %d",
SPRITE_MEMORY_MAX[smh] or 0, swap_slot), COLOUR.weak, true)
--
-- Miscellaneous sprite table: index
if OPTIONS.display_miscellaneous_sprite_table then
local t = OPTIONS.miscellaneous_sprite_table_number
local text = "Tab"
for num = 1, 19 do
text = t[num] and fmt("%s %3d", text, num) or text
end
draw_text(- Border_left, AR_y*144 - BIZHAWK_FONT_HEIGHT, text, info_color)
end
end
local function yoshi()
if not OPTIONS.display_yoshi_info then
return
end
-- Font
Text_opacity = 1.0
Bg_opacity = 1.0
local x_text = - Border_left
local y_text = AR_y*88
local yoshi_id = Yoshi_id
if yoshi_id ~= nil then
local eat_id = u8(WRAM.sprite_miscellaneous16+ yoshi_id)
local eat_type = u8(WRAM.sprite_number + eat_id)
local tongue_len = u8(WRAM.sprite_miscellaneous4 + yoshi_id)
local tongue_timer = u8(WRAM.sprite_miscellaneous9 + yoshi_id)
local tongue_wait = u8(WRAM.sprite_tongue_wait)
local tongue_height = u8(WRAM.yoshi_tile_pos)
local tongue_out = u8(WRAM.sprite_miscellaneous13 + yoshi_id)
local eat_type_str = eat_id == SMW.null_sprite_id and "-" or string.format("%02x", eat_type)
local eat_id_str = eat_id == SMW.null_sprite_id and "-" or string.format("#%02d", eat_id)
-- Yoshi's direction and turn around
local turn_around = u8(WRAM.sprite_miscellaneous14 + yoshi_id)
local yoshi_direction = u8(WRAM.sprite_miscellaneous12 + yoshi_id)
local direction_symbol
if yoshi_direction == 0 then direction_symbol = RIGHT_ARROW else direction_symbol = LEFT_ARROW end
draw_text(x_text, y_text, fmt("Yoshi %s %d", direction_symbol, turn_around), COLOUR.yoshi)
local h = BIZHAWK_FONT_HEIGHT
if eat_id == SMW.null_sprite_id and tongue_len == 0 and tongue_timer == 0 and tongue_wait == 0 then
Text_opacity = 0.2
end
draw_text(x_text, y_text + h, fmt("(%0s, %0s) %02d, %d, %d",
eat_id_str, eat_type_str, tongue_len, tongue_wait, tongue_timer), COLOUR.yoshi)
;
-- more WRAM values
local yoshi_x = 256*u8(WRAM.sprite_x_high + yoshi_id) + u8(WRAM.sprite_x_low + yoshi_id)
local yoshi_y = 256*u8(WRAM.sprite_y_high + yoshi_id) + u8(WRAM.sprite_y_low + yoshi_id)
local x_screen, y_screen = screen_coordinates(yoshi_x, yoshi_y, Camera_x, Camera_y)
-- invisibility timer
local mount_invisibility = u8(WRAM.sprite_miscellaneous18 + yoshi_id)
if mount_invisibility ~= 0 then
Text_opacity = 0.5
draw_text(AR_x*(x_screen + 4), AR_y*(y_screen - 12), mount_invisibility, COLOUR.yoshi)
end
-- Tongue hitbox and timer
if tongue_wait ~= 0 or tongue_out ~=0 or tongue_height == 0x89 then -- if tongue is out or appearing
-- the position of the hitbox pixel
local tongue_direction = yoshi_direction == 0 and 1 or -1
local tongue_high = tongue_height ~= 0x89
local x_tongue = x_screen + 24 - 40*yoshi_direction + tongue_len*tongue_direction
x_tongue = not tongue_high and x_tongue or x_tongue - 5*tongue_direction
local y_tongue = y_screen + 10 + 11*(tongue_high and 0 or 1)
-- the drawing
local tongue_line
if tongue_wait <= 9 then -- hitbox point vs berry tile
draw_rectangle(x_tongue - 1, y_tongue - 1, 2, 2, COLOUR.tongue_bg, COLOUR.text)
tongue_line = COLOUR.tongue_line
else tongue_line = COLOUR.tongue_bg
end
-- tongue out: time predictor
local tinfo, tcolor
if tongue_wait > 9 then tinfo = tongue_wait - 9; tcolor = COLOUR.tongue_line -- not ready yet
elseif tongue_out == 1 then tinfo = 17 + tongue_wait; tcolor = COLOUR.text -- tongue going out
elseif tongue_out == 2 then -- at the max or tongue going back
tinfo = math.max(tongue_wait, tongue_timer) + floor((tongue_len + 7)/4) - (tongue_len ~= 0 and 1 or 0)
tcolor = eat_id == SMW.null_sprite_id and COLOUR.text or COLOUR.warning
elseif tongue_out == 0 then tinfo = 0; tcolor = COLOUR.text -- tongue in
else tinfo = tongue_timer + 1; tcolor = COLOUR.tongue_line -- item was just spat out
end
Text_opacity = 0.5
draw_text(AR_x*(x_tongue + 4), AR_y*(y_tongue + 5), tinfo, tcolor, false, false, 0.5)
Text_opacity = 1.0
draw_rectangle(x_tongue, y_tongue + 1, 8, 4, tongue_line, COLOUR.tongue_bg)
end
end
end
local function show_counters()
if not OPTIONS.display_counters then
return
end
-- Font
Text_opacity = 1.0
Bg_opacity = 1.0
local height = BIZHAWK_FONT_HEIGHT
local text_counter = 0
local pipe_entrance_timer = u8(WRAM.pipe_entrance_timer)
local multicoin_block_timer = u8(WRAM.multicoin_block_timer)
local gray_pow_timer = u8(WRAM.gray_pow_timer)
local blue_pow_timer = u8(WRAM.blue_pow_timer)
local dircoin_timer = u8(WRAM.dircoin_timer)
local pballoon_timer = u8(WRAM.pballoon_timer)
local star_timer = u8(WRAM.star_timer)
local invisibility_timer = u8(WRAM.invisibility_timer)
local animation_timer = u8(WRAM.animation_timer)
local fireflower_timer = u8(WRAM.fireflower_timer)
local yoshi_timer = u8(WRAM.yoshi_timer)
local swallow_timer = u8(WRAM.swallow_timer)
local lakitu_timer = u8(WRAM.lakitu_timer)
local score_incrementing = u8(WRAM.score_incrementing)
local end_level_timer = u8(WRAM.end_level_timer)
local display_counter = function(label, value, default, mult, frame, color)
if value == default then return end
text_counter = text_counter + 1
local color = color or COLOUR.text
draw_text(- Border_left, AR_y*102 + (text_counter * height), fmt("%s: %d", label, (value * mult) - frame), color)
end
if Player_animation_trigger == 5 or Player_animation_trigger == 6 then
display_counter("Pipe", pipe_entrance_timer, -1, 1, 0, COLOUR.counter_pipe)
end
display_counter("Multi Coin", multicoin_block_timer, 0, 1, 0, COLOUR.counter_multicoin)
display_counter("Pow", gray_pow_timer, 0, 4, Effective_frame % 4, COLOUR.counter_gray_pow)
display_counter("Pow", blue_pow_timer, 0, 4, Effective_frame % 4, COLOUR.counter_blue_pow)
display_counter("Dir Coin", dircoin_timer, 0, 4, Real_frame % 4, COLOUR.counter_dircoin)
display_counter("P-Balloon", pballoon_timer, 0, 4, Real_frame % 4, COLOUR.counter_pballoon)
display_counter("Star", star_timer, 0, 4, (Effective_frame - 3) % 4, COLOUR.counter_star)
display_counter("Invisibility", invisibility_timer, 0, 1, 0)
display_counter("Fireflower", fireflower_timer, 0, 1, 0, COLOUR.counter_fireflower)
display_counter("Yoshi", yoshi_timer, 0, 1, 0, COLOUR.yoshi)
display_counter("Swallow", swallow_timer, 0, 4, (Effective_frame - 1) % 4, COLOUR.yoshi)
display_counter("Lakitu", lakitu_timer, 0, 4, Effective_frame % 4)
display_counter("End Level", end_level_timer, 0, 2, (Real_frame - 1) % 2)
display_counter("Score Incrementing", score_incrementing, 0x50, 1, 0)
if Lock_animation_flag ~= 0 then display_counter("Animation", animation_timer, 0, 1, 0) end -- shows when player is getting hurt or dying
end
-- Main function to run inside a level
local function level_mode()
if Game_mode == SMW.game_mode_level then
-- Draws/Erases the tiles if user clicked
draw_layer1_tiles(Camera_x, Camera_y)
draw_layer2_tiles()
sprites()
extended_sprites()
cluster_sprites()
minor_extended_sprites()
bounce_sprite_info()
level_info()
player()
yoshi()
show_counters()
-- Draws/Erases the hitbox for objects
if User_input.mouse_inwindow then
select_object(User_input.xmouse, User_input.ymouse, Camera_x, Camera_y)
end
end
end
local function overworld_mode()
if Game_mode ~= SMW.game_mode_overworld then return end
-- Font
Text_opacity = 1.0
Bg_opacity = 1.0
local height = BIZHAWK_FONT_HEIGHT
local y_text = BIZHAWK_FONT_HEIGHT
-- Real frame modulo 8
local real_frame_8 = Real_frame%8
draw_text(Buffer_width + Border_right, y_text, fmt("Real Frame = %3d = %d(mod 8)", Real_frame, real_frame_8), true)
-- Star Road info
local star_speed = u8(WRAM.star_road_speed)
local star_timer = u8(WRAM.star_road_timer)
y_text = y_text + height
draw_text(Buffer_width + Border_right, y_text, fmt("Star Road(%x %x)", star_speed, star_timer), COLOUR.cape, true)
end
local function left_click()
-- Call options menu if the form is closed
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
Options_form.create_window()
return
end
-- Drag and drop sprites
if Cheat.allow_cheats then
local id = select_object(User_input.xmouse, User_input.ymouse, Camera_x, Camera_y)
if type(id) == "number" and id >= 0 and id < SMW.sprite_max then
Cheat.dragging_sprite_id = id
Cheat.is_dragging_sprite = true
return
end
end
-- Layer 1 tiles
local x_mouse, y_mouse = game_coordinates(User_input.xmouse, User_input.ymouse, Camera_x, Camera_y)
x_mouse = 16*floor(x_mouse/16)
y_mouse = 16*floor(y_mouse/16)
if User_input.mouse_inwindow then
select_tile(x_mouse, y_mouse, Layer1_tiles)
end
end
-- This function runs at the end of paint callback
-- Specific for info that changes if the emulator is paused and idle callback is called
local function mouse_actions()
-- Font
Text_opacity = 1.0
if Cheat.allow_cheats then -- show cheat status anyway
alert_text(-Border_left, Buffer_height + Border_bottom, "Cheats: allowed", COLOUR.warning, COLOUR.warning_bg,
true, false, 0.0, 1.0)
end
-- Drag and drop sprites with the mouse
if Cheat.is_dragging_sprite then
Cheat.drag_sprite(Cheat.dragging_sprite_id)
Cheat.is_cheating = true
end
end
local function read_raw_input()
-- User input data
Previous.User_input = copytable(User_input)
local tmp = input.get()
for entry, value in pairs(User_input) do
User_input[entry] = tmp[entry] or false
end
-- Mouse input
tmp = input.getmouse()
User_input.xmouse = tmp.X
User_input.ymouse = tmp.Y
User_input.leftclick = tmp.Left
User_input.rightclick = tmp.Right
-- BizHawk, custom field
User_input.mouse_inwindow = mouse_onregion(-Border_left, -Border_top, Buffer_width + Border_right, Buffer_height + Border_bottom)
-- Detect if a key was just pressed or released
for entry, value in pairs(User_input) do
if (value ~= false) and (Previous.User_input[entry] == false) then Keys.pressed[entry] = true
else Keys.pressed[entry] = false
end
if (value == false) and (Previous.User_input[entry] ~= false) then Keys.released[entry] = true
else Keys.released[entry] = false
end
end
-- Key presses/releases execution:
for entry, value in pairs(Keys.press) do
if Keys.pressed[entry] then
value()
end
end
for entry, value in pairs(Keys.release) do
if Keys.released[entry] then
value()
end
end
end
--#############################################################################
-- CHEATS
-- This signals that some cheat is activated, or was some short time ago
Cheat.allow_cheats = false
Cheat.is_cheating = false
function Cheat.is_cheat_active()
if Cheat.is_cheating then
Text_opacity = 1.0
Bg_opacity = 1.0
alert_text(Buffer_middle_x - 3*BIZHAWK_FONT_WIDTH, BIZHAWK_FONT_HEIGHT, " CHEAT ", COLOUR.warning, COLOUR.warning_bg)
Previous.is_cheating = true
else
if Previous.is_cheating then
gui.addmessage("Script applied cheat") -- BizHawk
Previous.is_cheating = false
end
end
end
-- Called from Cheat.beat_level()
function Cheat.activate_next_level(secret_exit)
if u8(WRAM.level_exit_type) == 0x80 and u8(WRAM.midway_point) == 1 then
if secret_exit then
w8(WRAM.level_exit_type, 0x2)
else
w8(WRAM.level_exit_type, 1)
end
end
Cheat.is_cheating = true
end
-- allows start + select + X to activate the normal exit
-- start + select + A to activate the secret exit
-- start + select + B to exit the level without activating any exits
function Cheat.beat_level()
if Is_paused and Joypad["Select"] and (Joypad["X"] or Joypad["A"] or Joypad["B"]) then
w8(WRAM.level_flag_table + Level_index, bit.bor(Level_flag, 0x80))
local secret_exit = Joypad["A"]
if not Joypad["B"] then
w8(WRAM.midway_point, 1)
else
w8(WRAM.midway_point, 0)
end
Cheat.activate_next_level(secret_exit)
end
end
-- This function makes Mario's position free
-- Press L+R+up to activate and L+R+down to turn it off.
-- While active, press directionals to fly free and Y or X to boost him up
Cheat.under_free_move = false
function Cheat.free_movement()
if (Joypad["L"] and Joypad["R"] and Joypad["Up"]) then Cheat.under_free_move = true end
if (Joypad["L"] and Joypad["R"] and Joypad["Down"]) then Cheat.under_free_move = false end
if not Cheat.under_free_move then
if Previous.under_free_move then w8(WRAM.frozen, 0) end
return
end
local x_pos, y_pos = u16(WRAM.x), u16(WRAM.y)
local movement_mode = u8(WRAM.player_animation_trigger)
local pixels = (Joypad["Y"] and 7) or (Joypad["X"] and 4) or 1 -- how many pixels per frame
if Joypad["Left"] then x_pos = x_pos - pixels end
if Joypad["Right"] then x_pos = x_pos + pixels end
if Joypad["Up"] then y_pos = y_pos - pixels end
if Joypad["Down"] then y_pos = y_pos + pixels end
-- freeze player to avoid deaths
if movement_mode == 0 then
w8(WRAM.frozen, 1)
w8(WRAM.x_speed, 0)
w8(WRAM.y_speed, 0)
-- animate sprites by incrementing the effective frame
w8(WRAM.effective_frame, (u8(WRAM.effective_frame) + 1) % 256)
else
w8(WRAM.frozen, 0)
end
-- manipulate some values
w16(WRAM.x, x_pos)
w16(WRAM.y, y_pos)
w8(WRAM.invisibility_timer, 127)
w8(WRAM.vertical_scroll_flag_header, 1) -- free vertical scrolling
w8(WRAM.vertical_scroll_enabled, 1)
Cheat.is_cheating = true
Previous.under_free_move = true
end
-- Drag and drop sprites with the mouse, if the cheats are activated and mouse is over the sprite
-- Right clicking and holding: drags the sprite
-- Releasing: drops it over the latest spot
function Cheat.drag_sprite(id)
if Game_mode ~= SMW.game_mode_level then Cheat.is_dragging_sprite = false ; return end
local xoff, yoff = Sprites_info[id].xoff, Sprites_info[id].yoff
local xgame, ygame = game_coordinates(User_input.xmouse - xoff, User_input.ymouse - yoff, Camera_x, Camera_y)
local sprite_xhigh = floor(xgame/256)
local sprite_xlow = xgame - 256*sprite_xhigh
local sprite_yhigh = floor(ygame/256)
local sprite_ylow = ygame - 256*sprite_yhigh
w8(WRAM.sprite_x_high + id, sprite_xhigh)
w8(WRAM.sprite_x_low + id, sprite_xlow)
w8(WRAM.sprite_y_high + id, sprite_yhigh)
w8(WRAM.sprite_y_low + id, sprite_ylow)
end
function Cheat.score()
if not Cheat.allow_cheats then
print("Cheats not allowed.")
return
end
local num = forms.gettext(Options_form.score_number)
local is_hex = num:sub(1,2):lower() == "0x"
num = tonumber(num)
if not num or num%1 ~= 0 or num < 0
or num > 9999990 or (not is_hex and num%10 ~= 0) then
print("Enter a valid score: hexadecimal representation or decimal ending in 0.")
return
end
num = is_hex and num or num/10
w24(WRAM.mario_score, num)
print(fmt("Cheat: score set to %d0.", num))
Cheat.is_cheating = true
end
-- BizHawk: modifies address <address> value from <current> to <current + modification>
-- [size] is the optional size in bytes of the address
-- TODO: [is_signed] is untrue if the value is unsigned, true otherwise
function Cheat.change_address(address, value_form, size, criterion, error_message, success_message)
if not Cheat.allow_cheats then
print("Cheats not allowed.")
return
end
size = size or 1
local max_value = 256^size - 1
local value = Options_form[value_form] and forms.gettext(Options_form[value_form]) or value_form
local default_criterion = function(value)
value = tonumber(value)
if not value or value%1 ~= 0 or value < 0 or value > max_value then
return false
else
return value
end
end
local new = default_criterion(value)
if criterion and new then
new = criterion(new) and new or false
end
if not new then
print(error_message or "Enter a valid value.")
return
end
local memoryf = (size == 1 and w8) or (size == 2 and w16) or (size == 3 and w24) or error"size is too big"
memoryf(address, new)
print(fmt("Cheat: %s set to %d.", success_message, new) or fmt("Cheat: set WRAM 0x%X to %d.", address, new))
Cheat.is_cheating = true
end
--#############################################################################
-- MAIN --
-- Key presses:
Keys.registerkeypress("rightclick", right_click)
Keys.registerkeypress("leftclick", left_click)
-- Key releases:
Keys.registerkeyrelease("mouse_inwindow", function() Cheat.is_dragging_sprite = false end)
Keys.registerkeyrelease("leftclick", function() Cheat.is_dragging_sprite = false end)
-- Lateral gaps:
if not OLD_EMU_VERSION then
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
client.SetClientExtraPadding(0, 0, 0, 0)
end
function Options_form.create_window()
Options_form.form = forms.newform(220, 554, "SMW Options")
local xform, yform, delta_y = 2, 0, 20
-- Top label
Options_form.label_cheats = forms.label(Options_form.form, "You can close this form at any time", xform, yform, 200, 20)
yform = yform + delta_y
Options_form.allow_cheats = forms.checkbox(Options_form.form, "Allow cheats", xform, yform)
forms.setproperty(Options_form.allow_cheats, "Checked", Cheat.allow_cheats)
xform = xform + 105
forms.button(Options_form.form, "Powerup", function() Cheat.change_address(WRAM.powerup, "powerup_number", 1,
nil, "Enter a valid integer (0-255).", "powerup")
end, xform, yform, 58, 24)
yform = yform + 2
xform = xform + 59
Options_form.powerup_number = forms.textbox(Options_form.form, "", 24, 16, "UNSIGNED", xform, yform, false, false)
xform = 2
yform = yform + 28
forms.button(Options_form.form, "Score", Cheat.score, xform, yform, 43, 24)
yform = yform + 2
xform = xform + 45
Options_form.score_number = forms.textbox(Options_form.form, fmt("0x%X", u24(WRAM.mario_score)), 48, 16, nil, xform, yform, false, false)
xform = xform + 59
forms.button(Options_form.form, "Coin", function() Cheat.change_address(WRAM.player_coin, "coin_number", 1,
function(num) return num < 100 end, "Enter an integer between 0 and 99.", "coin")
end, xform, yform, 43, 24)
yform = yform + 2
xform = xform + 45
Options_form.coin_number = forms.textbox(Options_form.form, "", 24, 16, "UNSIGNED", xform, yform, false, false)
xform = 2
yform = yform + 28
forms.button(Options_form.form, "Box", function() Cheat.change_address(0x0dc2, "item_box_number", 1, -- unlisted WRAM
nil, "Enter a valid integer (0-255).", "Item box")
end, xform, yform, 43, 24)
yform = yform + 2
xform = xform + 45
Options_form.item_box_number = forms.textbox(Options_form.form, "", 24, 16, "UNSIGNED", xform, yform, false, false)
-- SHOW/HIDE
xform = 2
yform = yform + 28
Options_form.label1 = forms.label(Options_form.form, "Show/hide options:", xform, yform)
yform = yform + delta_y
local y_begin_showhide = yform -- 1st column
Options_form.debug_info = forms.checkbox(Options_form.form, "Debug info", xform, yform)
forms.setproperty(Options_form.debug_info, "Checked", OPTIONS.display_debug_info)
yform = yform + delta_y
Options_form.movie_info = forms.checkbox(Options_form.form, "Movie info", xform, yform)
forms.setproperty(Options_form.movie_info, "Checked", OPTIONS.display_movie_info)
yform = yform + delta_y
Options_form.misc_info = forms.checkbox(Options_form.form, "Miscellaneous", xform, yform)
forms.setproperty(Options_form.misc_info, "Checked", OPTIONS.display_misc_info)
yform = yform + delta_y
Options_form.player_info = forms.checkbox(Options_form.form, "Player info", xform, yform)
forms.setproperty(Options_form.player_info, "Checked", OPTIONS.display_player_info)
yform = yform + delta_y
Options_form.sprite_info = forms.checkbox(Options_form.form, "Sprite info", xform, yform)
forms.setproperty(Options_form.sprite_info, "Checked", OPTIONS.display_sprite_info)
yform = yform + delta_y
Options_form.sprite_hitbox = forms.checkbox(Options_form.form, "Sprite hitbox", xform, yform)
forms.setproperty(Options_form.sprite_hitbox, "Checked", OPTIONS.display_sprite_hitbox)
yform = yform + delta_y
Options_form.sprite_tables = forms.checkbox(Options_form.form, "Sprite tables", xform, yform)
forms.setproperty(Options_form.sprite_tables, "Checked", OPTIONS.display_miscellaneous_sprite_table)
yform = yform + delta_y
Options_form.extended_sprite_info = forms.checkbox(Options_form.form, "Extended sprites", xform, yform)
forms.setproperty(Options_form.extended_sprite_info, "Checked", OPTIONS.display_extended_sprite_info)
xform = xform + 105 -- 2nd column
yform = y_begin_showhide
Options_form.cluster_sprite_info = forms.checkbox(Options_form.form, "Cluster sprites", xform, yform)
forms.setproperty(Options_form.cluster_sprite_info, "Checked", OPTIONS.display_cluster_sprite_info)
yform = yform + delta_y
Options_form.minor_extended_sprite_info = forms.checkbox(Options_form.form, "Minor ext. spr.", xform, yform)
forms.setproperty(Options_form.minor_extended_sprite_info, "Checked", OPTIONS.display_minor_extended_sprite_info)
yform = yform + delta_y
Options_form.bounce_sprite_info = forms.checkbox(Options_form.form, "Bounce sprites", xform, yform)
forms.setproperty(Options_form.bounce_sprite_info, "Checked", OPTIONS.display_bounce_sprite_info)
yform = yform + delta_y
Options_form.level_info = forms.checkbox(Options_form.form, "Level info", xform, yform)
forms.setproperty(Options_form.level_info, "Checked", OPTIONS.display_level_info)
yform = yform + delta_y
Options_form.yoshi_info = forms.checkbox(Options_form.form, "Yoshi info", xform, yform)
forms.setproperty(Options_form.yoshi_info, "Checked", OPTIONS.display_yoshi_info)
yform = yform + delta_y
Options_form.counters_info = forms.checkbox(Options_form.form, "Counters info", xform, yform)
forms.setproperty(Options_form.counters_info, "Checked", OPTIONS.display_counters)
yform = yform + delta_y
Options_form.static_camera_region = forms.checkbox(Options_form.form, "Static camera", xform, yform)
forms.setproperty(Options_form.static_camera_region, "Checked", OPTIONS.display_static_camera_region)
yform = yform + delta_y -- if odd number of show/hide checkboxes
xform, yform = 2, yform + 30
forms.label(Options_form.form, "Player hitbox:", xform, yform + 2, 70, 25)
xform = xform + 70
Options_form.player_hitbox = forms.dropdown(Options_form.form, {"Hitbox", "Interaction points", "Both", "None"}, xform, yform)
xform, yform = 2, yform + 30
-- DEBUG/EXTRA
forms.label(Options_form.form, "Debug info:", xform, yform, 62, 22)
yform = yform + delta_y
local y_begin_debug = yform -- 1st column
Options_form.debug_player_extra = forms.checkbox(Options_form.form, "Player extra", xform, yform)
forms.setproperty(Options_form.debug_player_extra, "Checked", OPTIONS.display_debug_player_extra)
yform = yform + delta_y
Options_form.debug_sprite_extra = forms.checkbox(Options_form.form, "Sprite extra", xform, yform)
forms.setproperty(Options_form.debug_sprite_extra, "Checked", OPTIONS.display_debug_sprite_extra)
yform = yform + delta_y
Options_form.debug_sprite_tweakers = forms.checkbox(Options_form.form, "Sprite tweakers", xform, yform)
forms.setproperty(Options_form.debug_sprite_tweakers, "Checked", OPTIONS.display_debug_sprite_tweakers)
yform = yform + delta_y
Options_form.debug_extended_sprite = forms.checkbox(Options_form.form, "Extended sprites", xform, yform)
forms.setproperty(Options_form.debug_extended_sprite, "Checked", OPTIONS.display_debug_extended_sprite)
yform = yform + delta_y
xform, yform = xform + 105, y_begin_debug
Options_form.debug_cluster_sprite = forms.checkbox(Options_form.form, "Cluster sprites", xform, yform)
forms.setproperty(Options_form.debug_cluster_sprite, "Checked", OPTIONS.display_debug_cluster_sprite)
yform = yform + delta_y
Options_form.debug_minor_extended_sprite = forms.checkbox(Options_form.form, "Minor ext. spr.", xform, yform)
forms.setproperty(Options_form.debug_minor_extended_sprite, "Checked", OPTIONS.display_debug_minor_extended_sprite)
yform = yform + delta_y
Options_form.debug_bounce_sprite = forms.checkbox(Options_form.form, "Bounce sprites", xform, yform)
forms.setproperty(Options_form.debug_bounce_sprite, "Checked", OPTIONS.display_debug_bounce_sprite)
yform = yform + delta_y
Options_form.debug_controller_data = forms.checkbox(Options_form.form, "Controller data", xform, yform)
forms.setproperty(Options_form.debug_controller_data, "Checked", OPTIONS.display_debug_controller_data)
--yform = yform + delta_y
-- HELP:
xform, yform = 4, yform + 30
forms.label(Options_form.form, "Miscellaneous:", xform, yform, 78, 22)
xform, yform = xform + 78, yform - 2
Options_form.draw_tiles_with_click = forms.checkbox(Options_form.form, "Draw/erase tiles", xform, yform)
forms.setproperty(Options_form.draw_tiles_with_click, "Checked", OPTIONS.draw_tiles_with_click)
xform, yform = 4, yform + 30
-- OPACITY
Options_form.text_opacity = forms.label(Options_form.form, ("Text opacity: (%.0f%%, %.0f%%)"):
format(100*Text_max_opacity, 100*Background_max_opacity), xform, yform, 135, 22)
;
xform, yform = xform + 135, yform - 4
forms.button(Options_form.form, "-", function() decrease_opacity()
forms.settext(Options_form.text_opacity, ("Text opacity: (%.0f%%, %.0f%%)"):format(100*Text_max_opacity, 100*Background_max_opacity))
end, xform, yform, 14, 24)
xform = xform + 14
forms.button(Options_form.form, "+", function() increase_opacity()
forms.settext(Options_form.text_opacity, ("Text opacity: (%.0f%%, %.0f%%)"):format(100*Text_max_opacity, 100*Background_max_opacity))
end, xform, yform, 14, 24)
xform, yform = 4, yform + 25
-- HELP
Options_form.erase_tiles = forms.button(Options_form.form, "Erase tiles", function() Layer1_tiles = {}; Layer2_tiles = {} end, xform, yform)
xform = xform + 105
Options_form.write_help_handle = forms.button(Options_form.form, "Help", Options_form.write_help, xform, yform)
end
function Options_form.evaluate_form()
-- Option form's buttons
Cheat.allow_cheats = forms.ischecked(Options_form.allow_cheats) or false
OPTIONS.display_debug_info = forms.ischecked(Options_form.debug_info) or false
-- Show/hide
OPTIONS.display_movie_info = forms.ischecked(Options_form.movie_info) or false
OPTIONS.display_misc_info = forms.ischecked(Options_form.misc_info) or false
OPTIONS.display_player_info = forms.ischecked(Options_form.player_info) or false
OPTIONS.display_sprite_info = forms.ischecked(Options_form.sprite_info) or false
OPTIONS.display_sprite_hitbox = forms.ischecked(Options_form.sprite_hitbox) or false
OPTIONS.display_miscellaneous_sprite_table = forms.ischecked(Options_form.sprite_tables) or false
OPTIONS.display_extended_sprite_info = forms.ischecked(Options_form.extended_sprite_info) or false
OPTIONS.display_cluster_sprite_info = forms.ischecked(Options_form.cluster_sprite_info) or false
OPTIONS.display_minor_extended_sprite_info = forms.ischecked(Options_form.minor_extended_sprite_info) or false
OPTIONS.display_bounce_sprite_info = forms.ischecked(Options_form.bounce_sprite_info) or false
OPTIONS.display_level_info = forms.ischecked(Options_form.level_info) or false
OPTIONS.display_yoshi_info = forms.ischecked(Options_form.yoshi_info) or false
OPTIONS.display_counters = forms.ischecked(Options_form.counters_info) or false
OPTIONS.display_static_camera_region = forms.ischecked(Options_form.static_camera_region) or false
-- Debug/Extra
OPTIONS.display_debug_player_extra = forms.ischecked(Options_form.debug_player_extra) or false
OPTIONS.display_debug_sprite_extra = forms.ischecked(Options_form.debug_sprite_extra) or false
OPTIONS.display_debug_sprite_tweakers = forms.ischecked(Options_form.debug_sprite_tweakers) or false
OPTIONS.display_debug_extended_sprite = forms.ischecked(Options_form.debug_extended_sprite) or false
OPTIONS.display_debug_cluster_sprite = forms.ischecked(Options_form.debug_cluster_sprite) or false
OPTIONS.display_debug_minor_extended_sprite = forms.ischecked(Options_form.debug_minor_extended_sprite) or false
OPTIONS.display_debug_bounce_sprite = forms.ischecked(Options_form.debug_bounce_sprite) or false
OPTIONS.display_debug_controller_data = forms.ischecked(Options_form.debug_controller_data) or false
-- Other buttons
OPTIONS.draw_tiles_with_click = forms.ischecked(Options_form.draw_tiles_with_click) or false
local button_text = forms.gettext(Options_form.player_hitbox)
OPTIONS.display_player_hitbox = button_text == "Both" or button_text == "Hitbox"
OPTIONS.display_interaction_points = button_text == "Both" or button_text == "Interaction points"
-- Save the configurations
if Bizhawk_loop_counter == 0 then INI.save_options() end
end
function Options_form.write_help()
print(" - - - TIPS - - - ")
print("MOUSE:")
print("Use the left click to draw blocks and to see the Map16 properties.")
print("Use the right click to toogle the hitbox mode of Mario and sprites.")
print("\n")
print("CHEATS(better turn off while recording a movie):")
print("L+R+up: stop gravity for Mario fly / L+R+down to cancel")
print("Use the mouse to drag and drop sprites")
print("While paused: B+select to get out of the level")
print(" X+select to beat the level (main exit)")
print(" A+select to get the secret exit (don't use it if there isn't one)")
print("\n")
print("OTHERS:")
print("If performance suffers, disable some options that are not needed at the moment.")
print(" - - - end of tips - - - ")
end
Options_form.create_window()
Options_form.is_form_closed = false
event.unregisterbyname("smw-tas-bizhawk-onexit")
event.onexit(function()
local destroyed = forms.destroy(Options_form.form)
if not OLD_EMU_VERSION then
client.SetGameExtraPadding(0, 0, 0, 0)
client.SetClientExtraPadding(0, 0, 0, 0)
end
print("Finishing smw-bizhawk script.")
client.paint()
end, "smw-tas-bizhawk-onexit")
while true do
if emu.getsystemid() ~= "SNES" then
gui.text(0, 0, "WRONG CORE: " .. emu.getsystemid(), "black", "red", "bottomright")
else
Options_form.is_form_closed = forms.gettext(Options_form.player_hitbox) == ""
if not Options_form.is_form_closed then Options_form.evaluate_form() end
bizhawk_status()
bizhawk_screen_info()
read_raw_input()
-- Drawings are allowed now
scan_smw()
level_mode()
overworld_mode()
show_movie_info()
if Is_lagged then -- BizHawk: outside show_movie_info
alert_text(Buffer_middle_x - 3*BIZHAWK_FONT_WIDTH, 2*BIZHAWK_FONT_HEIGHT, " LAG ", COLOUR.warning, COLOUR.warning_bg)
end
show_misc_info()
show_controller_data()
Cheat.is_cheat_active()
mouse_actions()
-- Checks if options form exits and create a button in case it doesn't
if Options_form.is_form_closed then
if User_input.mouse_inwindow then
draw_rectangle(120 - 1, 0, 4*BIZHAWK_FONT_WIDTH/AR_x + 1, BIZHAWK_FONT_HEIGHT/AR_y + 1, 0xff000000, 0xff808080) -- bizhawk
gui.text(120*AR_x + Border_left, 0 + Border_top, "Menu") -- unlisted color
end
end
-- INPUT MANIPULATION
Joypad = joypad.get(1)
if Cheat.allow_cheats then
Cheat.is_cheating = false
Cheat.beat_level()
Cheat.free_movement()
else
-- Cancel any continuous cheat
Cheat.under_free_move = false
Cheat.is_cheating = false
end
end
-- Frame advance: hack for performance
Bizhawk_loop_counter = (Bizhawk_loop_counter + 1)%300 -- save options each 5 seconds
if client.ispaused() then
emu.yield()
gui.clearGraphics()
gui.cleartext()
else
emu.frameadvance()
end
end