3405 lines
138 KiB
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 |