A somewhat complete lua script for The Bouncing Ball, a simple homebrew game for GB. This should be used in BizHawk only!
--[[
$0000 (4 bytes) = Clock, minutes 2nd digit (y position, x position, value in graphics, ?)
$0004 (4 bytes) = Clock, minutes 1st digit (y position, x position, value in graphics, ?)
$0008 (4 bytes) = Clock, seconds 2nd digit (y position, x position, value in graphics, ?)
$000C (4 bytes) = Clock, seconds 1st digit (y position, x position, value in graphics, ?)
$0010 (4 bytes) = Clock, tenths of a second (y position, x position, value in graphics, ?)
$003C (32 bytes) = OAM - Moving platforms first tile info: y position, x position, tile ID, ??. Each platform uses 4 bytes, and it can have 8 platforms
$005C (32 bytes) = OAM - Moving platforms second tile info: y position, x position, tile ID, ??. Each platform uses 4 bytes, and it can have 8 platforms
$007C (32 bytes) = OAM - Moving platforms third tile info: y position, x position, tile ID, ??. Each platform uses 4 bytes, and it can have 8 platforms
$009C (4 bytes) = OAM - Ball: y position, x position, tile ID, ??
$00A1 = Current menu option
$00A3 = In game: last block type updated. In menu: Current letter selected in password (graphically)
$00A8 = Button input, format: Ssba-dulr
$00A9 = Button input previous frame, format: Ssba-dulr
$00AE = Menu scrolling background animation frame
$00B0 = Moving platform currently processed x position?
$00B5 = Current level
$00C1 (2 bytes) = X position
$00C3 (2 bytes) = Y position
$00C5 (2 bytes) = X speed
$00C7 (2 bytes) = Y speed
$00C8 = Ball ascending (0xff) or descending (0x00)
$00C9 = Level mode. 0x00 = fading in/out; 0x01 = controlling ball; 0x02 = flying via cannon ;0x04 = dying; 0x05 = winning; 0x06 = level load/password screen
$00CA = X position in blocks
$00CB = Y position in blocks
$00CC (2 bytes) = Block type the ball is touching (top)
$00CE (2 bytes) = Block type the ball is touching (bottom)
$00D0 (2 bytes) = Block type the ball is touching (left)
$00D2 (2 bytes) = Block type the ball is touching (right)
$00D6 = Hearts remaining to get in the level.
$00D7 (16 bytes) = Moving platforms x position table, 2 bytes per platform
$00E7 (16 bytes) = Moving platforms y position table, 2 bytes per platform
$00F7 (8 bytes) = Moving platforms type table, 1 byte per platform
$00FF (8 bytes) = Moving platforms direction table, 1 byte per platform
$0107 (3 bytes) = Clock, in frames (tenths of a second, seconds, minutes)
$0157 (8 bytes) = Password letters (A = 0x00, B = 0x01, ... , P = 0x0F)
$0166 (2 bytes) = Frame counter (global)
]]--
--- ###########################################################################################################################
--- INITIAL STATEMENTS
local OPTIONS = {
left_gap = 80,
top_gap = 32,
right_gap = 40,
bottom_gap = 040,
display_grid = false,
display_out_of_bounds_blocks = false,
display_movie_info = true,
}
local COLOUR = {
GB_pal_1 = 0xffA4C505,
GB_pal_2 = 0xff88A905,
GB_pal_3 = 0xff1D551D,
GB_pal_4 = 0xff052505,
GB_pal_inv_1 = 0xff2605c5,
GB_pal_inv_2 = 0xff2605a9,
GB_pal_inv_3 = 0xff551d55,
GB_pal_inv_4 = 0xff250525,
GB_pal_tri_1_1 = 0xffA4C505,
GB_pal_tri_1_2 = 0xff05A4C5,
GB_pal_tri_1_3 = 0xffC505A4,
GB_pal_tri_2_1 = 0xff88A905,
GB_pal_tri_2_2 = 0xff0588A9,
GB_pal_tri_2_3 = 0xffA90588,
GB_pal_tri_3_1 = 0xff1D551D,
GB_pal_tri_3_2 = 0xff1D1D55,
GB_pal_tri_3_3 = 0xff551D1D,
GB_pal_tri_4_1 = 0xff052505,
GB_pal_tri_4_2 = 0xff050525,
GB_pal_tri_4_3 = 0xff250505,
positive = 0xff00FF00,
warning = 0xffFF0000,
weak = 0x80808080,
}
local WRAM = {
last_block_updated = 0x00A3,
controller_input = 0x00A8, -- format: Ssba-dulr
controller_input_prev = 0x00A9,
current_level = 0x00B5,
x_pos = 0x00C1, -- 2 bytes
y_pos = 0x00C3, -- 2 bytes
x_speed = 0x00C5, -- 2 bytes
y_speed = 0x00C7, -- 2 bytes
vertical_direction = 0x00C8, -- ascending = 0xff, descending = 0x00
level_mode = 0x00C9, -- 0x00 = fading in/out; 0x01 = controlling ball; 0x02 = flying via cannon ;0x04 = dying; 0x05 = winning; 0x06 = level load/password screen
x_pos_block = 0x00CA,
y_pos_block = 0x00CB,
block_type_top = 0x00CC, -- 2 bytes
block_type_bottom = 0x00CE, -- 2 bytes
block_type_left = 0x00D0, -- 2 bytes
block_type_right = 0x00D2, -- 2 bytes
hearts_remaining = 0x00D6,
platform_x_pos = 0x00D7, -- 16 bytes, 2 bytes/platform
platform_y_pos = 0x00E7, -- 16 bytes, 2 bytes/platform
platform_type = 0x00F7, -- 8 bytes, 1 byte/platform
platform_direction = 0x00FF, -- 8 bytes, 1 byte/platform
on_level = 0x011A,
frame_counter = 0x0166, -- 2 bytes
}
local GB_FRAMERATE = 59.727500569606
--- ###########################################################################################################################
--- SCRIPT UTILS
-- Basic functions renaming
local fmt = string.format
local floor = math.floor
local ceil = math.ceil
local sqrt = math.sqrt
local sin = math.sin
local cos = math.cos
local pi = math.pi
function math.round(num, decimal_places)
local mult = 10^(decimal_places or 0)
if num >= 0 then return math.floor(num * mult + 0.5) / mult
else return math.ceil(num * mult - 0.5) / mult 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_u24_le
local u32 = mainmemory.read_u32_le
local s32 = mainmemory.read_s32_le
local w32 = mainmemory.write_u32_le
memory.usememorydomain("VRAM")
local u8_vram = memory.read_u8
local s8_vram = memory.read_s8
local w8_vram = memory.write_u8
local u16_vram = memory.read_u16_le
local s16_vram = memory.read_s16_le
local w16_vram = memory.write_u16_le
local u24_vram = memory.read_u24_le
local s24_vram = memory.read_s24_le
local w24_vram = memory.write_u24_le
-- Drawing constants and operations
local draw = {}
draw.BIZHAWK_FONT_WIDTH = 10
draw.BIZHAWK_FONT_HEIGHT = 14
draw.text = gui.text
draw.line = gui.drawLine
draw.cross = gui.drawAxis
draw.pixel = gui.drawPixel
draw.rectangle = gui.drawRectangle
draw.box = gui.drawBox
draw.ellipse = gui.drawEllipse
draw.image = gui.drawImage
draw.image_region = gui.drawImageRegion
draw.polygon = gui.drawPolygon
draw.pixel_text = gui.pixelText
-- Draw an arrow given (x1, y1) and (x2, y2)
function draw.arrow(x1, y1, x2, y2, head, colour)
local angle = math.atan((y2-y1)/(x2-x1)) -- in radians
-- Arrow head
local head_size = head or 10
local angle1, angle2 = angle + pi/4, angle - pi/4 --0.785398163398, angle - 0.785398163398 -- 45° in radians
--local delta_x1, delta_y1 = floor(head_size*cos(angle1)), floor(head_size*sin(angle1))
local delta_x1, delta_y1 = head_size*cos(angle1), head_size*sin(angle1)
--local delta_x2, delta_y2 = floor(head_size*cos(angle2)), floor(head_size*sin(angle2))
local delta_x2, delta_y2 = head_size*cos(angle2), head_size*sin(angle2)
local head1_x1, head1_y1 = x2, y2
local head1_x2, head1_y2
local head2_x1, head2_y1 = x2, y2
local head2_x2, head2_y2
if x1 < x2 then -- 1st and 4th quadrant
head1_x2, head1_y2 = head1_x1 - delta_x1, head1_y1 - delta_y1
head2_x2, head2_y2 = head2_x1 - delta_x2, head2_y1 - delta_y2
elseif x1 == x2 then -- vertical arrow
head1_x2, head1_y2 = head1_x1 - delta_x1, head1_y1 - delta_y1
head2_x2, head2_y2 = head2_x1 - delta_x2, head2_y1 - delta_y2
else
head1_x2, head1_y2 = head1_x1 + delta_x1, head1_y1 + delta_y1
head2_x2, head2_y2 = head2_x1 + delta_x2, head2_y1 + delta_y2
end
-- Draw
if colour == nil then colour = "white" end
draw.line(x1, y1, x2, y2, colour)
draw.line(head1_x1, head1_y1, head1_x2, head1_y2, colour)
draw.line(head2_x1, head2_y1, head2_x2, head2_y2, colour)
end
-- General emulation info
local Movie_active, Readonly, Framecount, Lagcount, Rerecords, Is_lagged, Lastframe_emulated, Nextframe
local function bizhawk_status()
Movie_active = movie.isloaded()
Readonly = movie.getreadonly()
Framecount = movie.length()
Lagcount = emu.lagcount()
Rerecords = movie.getrerecordcount()
Is_lagged = emu.islagged()
Lastframe_emulated = emu.framecount()
Nextframe = Lastframe_emulated + 1
end
-- Get screen values of the game and emulator areas
local function bizhawk_screen_info()
-- Gaps
draw.Left_gap = OPTIONS.left_gap
draw.Top_gap = OPTIONS.top_gap
draw.Right_gap = OPTIONS.right_gap
draw.Bottom_gap = OPTIONS.bottom_gap
-- Scale
if client.borderwidth() == 0 then -- to avoid division by zero bug when borders are not yet ready when loading the script
draw.Scale_x = 2
draw.Scale_y = 2
else
draw.Scale_x = math.min(client.borderwidth()/OPTIONS.left_gap, client.borderheight()/OPTIONS.top_gap) -- Pixel scale
draw.Scale_y = draw.Scale_x -- assumming square pixels only
end
-- Main dimensions
draw.Screen_width = client.screenwidth()/draw.Scale_x -- Emu screen width CONVERTED to game pixels
draw.Screen_height = client.screenheight()/draw.Scale_y -- Emu screen height CONVERTED to game pixels
draw.Buffer_width = client.bufferwidth() -- Game area width, in game pixels
draw.Buffer_height = client.bufferheight() -- Game area height, in game pixels
-- Derived dimensions
draw.Buffer_middle_x = (OPTIONS.left_gap + draw.Buffer_width/2)*draw.Scale_x -- Game area middle x relative to emu window, in game pixels
draw.Buffer_middle_y = (OPTIONS.top_gap + draw.Buffer_height/2)*draw.Scale_y -- Game area middle y relative to emu window, in game pixels
draw.Border_right_start = (OPTIONS.left_gap + draw.Buffer_width)*draw.Scale_x
draw.Border_bottom_start = (OPTIONS.top_gap + draw.Buffer_height)*draw.Scale_y
draw.BizHawk_font_width = 10/draw.Scale_x -- to make compatible to the scale
draw.BizHawk_font_height = 18/draw.Scale_y
--[[ DEBUG
local y_tmp = 20
draw.text(0, y_tmp, fmt("draw.Scale_x = %d", draw.Scale_x)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.Scale_y = %d", draw.Scale_y)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.Screen_width = %d", draw.Screen_width)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.Screen_height = %d", draw.Screen_height)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.Buffer_width = %d", draw.Buffer_width)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.Buffer_height = %d", draw.Buffer_height)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.Buffer_middle_x = %d", draw.Buffer_middle_x)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.Buffer_middle_y = %d", draw.Buffer_middle_y)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.Border_right_start = %d", draw.Border_right_start)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.Border_bottom_start = %d", draw.Border_bottom_start)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.BizHawk_font_width = %d", draw.BizHawk_font_width)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT
draw.text(0, y_tmp, fmt("draw.BizHawk_font_height = %d", draw.BizHawk_font_height)) ; y_tmp = y_tmp + draw.BIZHAWK_FONT_HEIGHT]]
end
-- Transform unsigned byte to signed in hex string
local function signed8hex(num, signal)
local maxval = 128
if signal == nil then signal = true end
if num < maxval then -- positive
return fmt("%s%02X", signal and "+" or "", num)
else -- negative
return fmt("%s%02X", signal and "-" or "", 2*maxval - num)
end
end
-- Transform unsigned word to signed in hex string
local function signed16hex(num, signal)
local maxval = 32768
if signal == nil then signal = true end
if num < maxval then -- positive
return fmt("%s%04X", signal and "+" or "", num)
else -- negative
return fmt("%s%04X", signal and "-" or "", 2*maxval - num)
end
end
-- Verify if a point is inside a rectangle
local function inside_rectangle(xpoint, ypoint, x1, y1, x2, y2)
-- From top-left to bottom-right
if x2 < x1 then
x1, x2 = x2, x1
end
if y2 < y1 then
y1, y2 = y2, y1
end
-- Verification
if xpoint >= x1 and xpoint <= x2 and ypoint >= y1 and ypoint <= y2 then
return true
else
return false
end
end
-- Returns frames-time conversion
local function frame_time(frame)
local total_seconds = frame/GB_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%02d:%02d.%03.0f", hours, minutes, seconds, miliseconds)
return str
end
--- ###########################################################################################################################
--- INFO FUNCTIONS
local function general_info()
local x_pos = draw.Left_gap*draw.Scale_x
local y_pos = 2*draw.BIZHAWK_FONT_HEIGHT
local frame_counter = u16(WRAM.frame_counter)
local frame_counter_str = fmt("Frame: %04X", frame_counter)
draw.text(x_pos, y_pos, frame_counter_str)
x_pos = x_pos + (string.len(frame_counter_str)+2)*draw.BIZHAWK_FONT_WIDTH
local level_mode = u8(WRAM.level_mode)
local level_mode_str = fmt("Mode: %02X", level_mode)
draw.text(x_pos, y_pos, level_mode_str)
x_pos = x_pos + (string.len(level_mode_str)+2)*draw.BIZHAWK_FONT_WIDTH
local hearts_remaining = u8(WRAM.hearts_remaining)
local hearts_remaining_str = fmt("Hearts: %d", hearts_remaining)
draw.text(x_pos, y_pos, hearts_remaining_str)
x_pos = x_pos + (string.len(hearts_remaining_str)+2)*draw.BIZHAWK_FONT_WIDTH
end
local function level_info()
local current_level = u8(WRAM.current_level)
local current_level_str = fmt("Level: %02d(%02X)", current_level, current_level)
draw.text(draw.Buffer_middle_x - string.len(current_level_str)*draw.BIZHAWK_FONT_WIDTH/2, 4*draw.BIZHAWK_FONT_HEIGHT, current_level_str)
end
local function blocks_info()
local colour
-- Grid
if OPTIONS.display_grid then
for j = 0x00, 0x12-1 do -- 160 x 144
for i = 0x00, 0x14-1 do
if (i+j%2)%2 == 1 then colour = COLOUR.GB_pal_tri_2_3 - 0x80000000 else colour = COLOUR.GB_pal_tri_2_3 - 0xC0000000 end
draw.rectangle(i*8 + draw.Left_gap, j*8 + draw.Top_gap, 7, 7, colour)--0x80FFFFFF)
end
end
end
-- Out-of-bounds blocks
if OPTIONS.display_out_of_bounds_blocks then
local tilemap_file = "The Bouncing Ball Tilemap.png"
local level_vram_addr = 0x1800
local block_id
local x_level, y_level = draw.Left_gap, draw.Top_gap
local x, y = x_level, y_level
local tilemap_x, tilemap_y
gui.clearImageCache()
for i = 0, 0x2000 - 1 do -- GPU is 0x2000 bytes, half backgroung half window
block_id = u8_vram(level_vram_addr + i)
tilemap_x = bit.band(block_id, 0xF)*8
tilemap_y = floor(block_id/0x10)*8
if block_id ~= 0x00 then -- to minimize lag, don't need to draw void
if not inside_rectangle(x, y, x_level, y_level, x_level + draw.Buffer_width - 1, y_level + draw.Buffer_height - 1) then
draw.image_region(tilemap_file, tilemap_x, tilemap_y, 8, 8, x, y)
if i%0x20 == 0x1f and i <= 0x023F then
draw.image_region(tilemap_file, tilemap_x, tilemap_y, 8, 8, x - 0x100, y - 7*8)
end
end
--draw.pixel_text(x, y, fmt("%02X", block_id), COLOUR.weak, 0) -- don't need actually
end
x = x + 8
if i%0x20 == 0x1f then x = x_level ; y = y + 8 end
end
end
-- gui.drawImage(string path, int x, int y, [int? width = null], [int? height = null], [bool cache = True])
-- gui.drawImageRegion(string path, int source_x, int source_y, int source_width, int source_height, int dest_x, int dest_y, [int? dest_width = null], [int? dest_height = null])
end
local function platforms_info()
local table_x, table_y = 0, 80*draw.Scale_y
local platform_x_pos, platform_y_pos, platform_type, platform_type
local colour
for slot = 0, 8 - 1 do
-- RAM reading
platform_x_pos = u16(WRAM.platform_x_pos + 2*slot) -- 16 bytes, 2 bytes/platform
platform_y_pos = u16(WRAM.platform_y_pos + 2*slot) -- 16 bytes, 2 bytes/platform
platform_type = u8(WRAM.platform_type + slot) -- 8 bytes, 1 byte/platform
platform_direction = u8(WRAM.platform_direction + slot) -- 8 bytes, 1 byte/platform
-- Position interpretation
local x_pos_int = floor(platform_x_pos/0x10)
local y_pos_int = floor(platform_y_pos/0x10)
local x_pos_frac = platform_x_pos - x_pos_int*0x10
local y_pos_frac = platform_y_pos - y_pos_int*0x10
-- Drawings
colour = platform_direction ~= 0 and "white" or COLOUR.weak
draw.text(table_x, table_y + slot*draw.BIZHAWK_FONT_HEIGHT, fmt("#%02d %02X (%02X.%x, %02X.%x) %s",
slot, platform_type, x_pos_int, x_pos_frac, y_pos_int, y_pos_frac, platform_direction == 1 and "<-" or "->"), colour)
end
--[[
platform_x_pos = 0x00D7, -- 16 bytes, 2 bytes/platform
platform_y_pos = 0x00E7, -- 16 bytes, 2 bytes/platform
platform_type = 0x00F7, -- 8 bytes, 1 byte/platform
platform_direction = 0x00FF, -- 8 bytes, 1 byte/platform
]]
end
local function player_info()
-- Settings
local i = 0
local delta_x = draw.BIZHAWK_FONT_WIDTH
local delta_y = draw.BIZHAWK_FONT_HEIGHT
local table_x = 0
local table_y = draw.Top_gap*draw.Scale_y
local x_temp = table_x
local colour
-- RAM reading
local x_pos = s16(WRAM.x_pos)
local y_pos = s16(WRAM.y_pos)
local x_speed = s16(WRAM.x_speed)
local x_speed_u = u8(WRAM.x_speed)
local y_speed = s16(WRAM.y_speed)
local y_speed_u = u8(WRAM.y_speed)
local x_pos_block = u8(WRAM.x_pos_block)
local y_pos_block = u8(WRAM.y_pos_block)
local vertical_direction = u8(WRAM.vertical_direction)
local block_type_top = u16(WRAM.block_type_top)
local block_type_bottom = u16(WRAM.block_type_bottom)
local block_type_left = u16(WRAM.block_type_left)
local block_type_right = u16(WRAM.block_type_right)
local last_block_updated = u8(WRAM.last_block_updated)
--- Display info
local x_pos_int = floor(x_pos/0x10)
local y_pos_int = floor(y_pos/0x10)
local x_pos_frac = x_pos - x_pos_int*0x10
local y_pos_frac = y_pos - y_pos_int*0x10
-- Position data
draw.text(table_x, table_y + i*delta_y, fmt("Pos (%03X.%x, %03X.%x) (%02X, %02X)", x_pos_int, x_pos_frac, y_pos_int, y_pos_frac, x_pos_block, y_pos_block))
-- Position pixel (nominal and collision)
draw.cross(x_pos_int + draw.Left_gap, y_pos_int + draw.Top_gap, 2, COLOUR.GB_pal_tri_2_2)
draw.cross(x_pos_int + 2 + draw.Left_gap, y_pos_int + 2 + draw.Top_gap, 2, COLOUR.GB_pal_tri_1_2)
-- Position mod 256 pixel (nominal and collision)
draw.cross(x_pos_int%0x100 + draw.Left_gap, y_pos_int%0x100 + draw.Top_gap, 2, COLOUR.GB_pal_tri_2_2 - 0x80000000)
draw.cross(x_pos_int%0x100 + 2 + draw.Left_gap, y_pos_int%0x100 + 2 + draw.Top_gap, 2, COLOUR.GB_pal_tri_1_2 - 0x80000000)
i = i + 1
-- Speed
if math.abs(x_speed) >= 0x10 then colour = COLOUR.positive -- lateral springblock speed
elseif math.abs(x_speed) >= 0x08 then colour = "yellow" -- max running speed
else colour = "white" end
draw.text(table_x, table_y + i*delta_y, fmt("Speed ( , %s)", signed8hex(y_speed_u, true)))
draw.text(table_x, table_y + i*delta_y, fmt(" %s", signed8hex(x_speed_u, true)), colour)
-- Vertical direction
x_temp = table_x + 18*draw.BizHawk_font_width
draw.line(x_temp, (table_y + i*delta_y)/draw.Scale_y, x_temp, (table_y + (i+1)*delta_y)/draw.Scale_y)
if vertical_direction == 0 then -- ball ascending (0xff) or descending (0x00)
draw.line(x_temp, (table_y + (i+1)*delta_y)/draw.Scale_y, x_temp - 2, (table_y + (i+1)*delta_y)/draw.Scale_y - 2)
draw.line(x_temp, (table_y + (i+1)*delta_y)/draw.Scale_y, x_temp + 2, (table_y + (i+1)*delta_y)/draw.Scale_y - 2)
else
draw.line(x_temp, (table_y + i*delta_y)/draw.Scale_y, x_temp - 2, (table_y + i*delta_y)/draw.Scale_y + 2)
draw.line(x_temp, (table_y + i*delta_y)/draw.Scale_y, x_temp + 2, (table_y + i*delta_y)/draw.Scale_y + 2)
end
i = i + 1
-- Blocked status
local ball_file = "The Bouncing Ball Ball.png"
local tilemap_file = "The Bouncing Ball Tilemap.png"
local tilemap_x, tilemap_y
-- ball
draw.image(ball_file, table_x + 12, (table_y + i*delta_y)/draw.Scale_y + 4 + 11)
-- top
tilemap_x, tilemap_y = bit.band(block_type_top, 0xF)*8, floor(block_type_top/0x10)*8
draw.image_region(tilemap_file, tilemap_x, tilemap_y, 8, 8, table_x + 10, (table_y + i*delta_y)/draw.Scale_y + 4)
draw.rectangle(table_x + 9, (table_y + i*delta_y)/draw.Scale_y + 3, 9, 9)
-- bottom
tilemap_x, tilemap_y = bit.band(block_type_bottom, 0xF)*8, floor(block_type_bottom/0x10)*8
draw.image_region(tilemap_file, tilemap_x, tilemap_y, 8, 8, table_x + 10, (table_y + i*delta_y)/draw.Scale_y + 3 + 17 + 2)
draw.rectangle(table_x + 9, (table_y + i*delta_y)/draw.Scale_y + 4 + 17, 9, 9)
-- left
tilemap_x, tilemap_y = bit.band(block_type_left, 0xF)*8, floor(block_type_left/0x10)*8
draw.image_region(tilemap_file, tilemap_x, tilemap_y, 8, 8, table_x + 1, (table_y + i*delta_y)/draw.Scale_y + 3 + 8 + 2)
draw.rectangle(table_x + 0, (table_y + i*delta_y)/draw.Scale_y + 4 + 8, 9, 9)
-- right
tilemap_x, tilemap_y = bit.band(block_type_right, 0xF)*8, floor(block_type_right/0x10)*8
draw.image_region(tilemap_file, tilemap_x, tilemap_y, 8, 8, table_x + 19, (table_y + i*delta_y)/draw.Scale_y + 3 + 8 + 2)
draw.rectangle(table_x + 18, (table_y + i*delta_y)/draw.Scale_y + 4 + 8, 9, 9)
end
-- Movie info
local function movie_info()
if not OPTIONS.display_movie_info then return end
-- Font
local x_text, y_text = 0, 0
local width = draw.BIZHAWK_FONT_WIDTH
local rec_color = (Readonly or not Movie_active) and "white" or "red"
-- Read-only or read-write?
local movie_type = (not Movie_active and "No movie ") or (Readonly and "Movie " or "REC")
draw.text(x_text, y_text, movie_type, rec_color)
x_text = x_text + width*(string.len(movie_type) + 1)
-- Frame count
local movie_info
if Readonly and Movie_active then
movie_info = fmt("%d/%d", Lastframe_emulated, Framecount)
else
movie_info = fmt("%d", Lastframe_emulated)
end
draw.text(x_text, y_text, movie_info) -- Shows the latest frame emulated, not the frame being run now
x_text = x_text + width*string.len(movie_info)
if Movie_active then
-- Rerecord count
local rr_info = fmt(" %d ", Rerecords)
draw.text(x_text, y_text, rr_info, COLOUR.weak)
x_text = x_text + width*string.len(rr_info)
-- Lag count
draw.text(x_text, y_text, Lagcount, COLOUR.warning)
x_text = x_text + width*string.len(Lagcount)
end
-- Time
local time_str = frame_time(Lastframe_emulated) -- Shows the latest frame emulated, not the frame being run now
draw.text(x_text, y_text, fmt(" (%s)", time_str))
-- Lag warning
x_text, y_text = 0, y_text + draw.BIZHAWK_FONT_HEIGHT
if Is_lagged then
draw.text(x_text, y_text, "LAG", COLOUR.warning)
end
end
local function draw_info()
general_info()
level_info()
blocks_info()
platforms_info()
player_info()
movie_info()
-- DEBUG
--[[
draw.rectangle(30, 0*8+1, 8, 8, COLOUR.GB_pal_1, COLOUR.GB_pal_1) ; draw.pixel_text(40, 0*8+1 , "GB_pal_1")
draw.rectangle(30, 1*8+1, 8, 8, COLOUR.GB_pal_2, COLOUR.GB_pal_2) ; draw.pixel_text(40, 1*8+1 , "GB_pal_2")
draw.rectangle(30, 2*8+1, 8, 8, COLOUR.GB_pal_3, COLOUR.GB_pal_3) ; draw.pixel_text(40, 2*8+1 , "GB_pal_3")
draw.rectangle(30, 3*8+1, 8, 8, COLOUR.GB_pal_4, COLOUR.GB_pal_4) ; draw.pixel_text(40, 3*8+1 , "GB_pal_4")
draw.rectangle(30, 4*8+1, 8, 8, COLOUR.GB_pal_inv_1, COLOUR.GB_pal_inv_1) ; draw.pixel_text(40, 4*8+1 , "GB_pal_inv_1")
draw.rectangle(30, 5*8+1, 8, 8, COLOUR.GB_pal_inv_2, COLOUR.GB_pal_inv_2) ; draw.pixel_text(40, 5*8+1 , "GB_pal_inv_2")
draw.rectangle(30, 6*8+1, 8, 8, COLOUR.GB_pal_inv_3, COLOUR.GB_pal_inv_3) ; draw.pixel_text(40, 6*8+1 , "GB_pal_inv_3")
draw.rectangle(30, 7*8+1, 8, 8, COLOUR.GB_pal_inv_4, COLOUR.GB_pal_inv_4) ; draw.pixel_text(40, 7*8+1 , "GB_pal_inv_4")
draw.rectangle(30, 8*8+1, 8, 8, COLOUR.GB_pal_tri_1_1, COLOUR.GB_pal_tri_1_1) ; draw.pixel_text(40, 8*8+1 , "GB_pal_tri_1_1")
draw.rectangle(30, 9*8+1, 8, 8, COLOUR.GB_pal_tri_1_2, COLOUR.GB_pal_tri_1_2) ; draw.pixel_text(40, 9*8+1 , "GB_pal_tri_1_2")
draw.rectangle(30, 10*8+1, 8, 8, COLOUR.GB_pal_tri_1_3, COLOUR.GB_pal_tri_1_3) ; draw.pixel_text(40, 10*8+1 , "GB_pal_tri_1_3")
draw.rectangle(30, 11*8+1, 8, 8, COLOUR.GB_pal_tri_2_1, COLOUR.GB_pal_tri_2_1) ; draw.pixel_text(40, 11*8+1 , "GB_pal_tri_2_1")
draw.rectangle(30, 12*8+1, 8, 8, COLOUR.GB_pal_tri_2_2, COLOUR.GB_pal_tri_2_2) ; draw.pixel_text(40, 12*8+1 , "GB_pal_tri_2_2")
draw.rectangle(30, 13*8+1, 8, 8, COLOUR.GB_pal_tri_2_3, COLOUR.GB_pal_tri_2_3) ; draw.pixel_text(40, 13*8+1 , "GB_pal_tri_2_3")
draw.rectangle(30, 14*8+1, 8, 8, COLOUR.GB_pal_tri_3_1, COLOUR.GB_pal_tri_3_1) ; draw.pixel_text(40, 14*8+1 , "GB_pal_tri_3_1")
draw.rectangle(30, 15*8+1, 8, 8, COLOUR.GB_pal_tri_3_2, COLOUR.GB_pal_tri_3_2) ; draw.pixel_text(40, 15*8+1 , "GB_pal_tri_3_2")
draw.rectangle(30, 16*8+1, 8, 8, COLOUR.GB_pal_tri_3_3, COLOUR.GB_pal_tri_3_3) ; draw.pixel_text(40, 16*8+1 , "GB_pal_tri_3_3")
draw.rectangle(30, 17*8+1, 8, 8, COLOUR.GB_pal_tri_4_1, COLOUR.GB_pal_tri_4_1) ; draw.pixel_text(40, 17*8+1 , "GB_pal_tri_4_1")
draw.rectangle(30, 18*8+1, 8, 8, COLOUR.GB_pal_tri_4_2, COLOUR.GB_pal_tri_4_2) ; draw.pixel_text(40, 18*8+1 , "GB_pal_tri_4_2")
draw.rectangle(30, 19*8+1, 8, 8, COLOUR.GB_pal_tri_4_3, COLOUR.GB_pal_tri_4_3) ; draw.pixel_text(40, 19*8+1 , "GB_pal_tri_4_3")
draw.rectangle(30, 20*8+1, 8, 8, COLOUR.positive, COLOUR.positive) ; draw.pixel_text(40, 20*8+1 , "positive")
draw.rectangle(30, 21*8+1, 8, 8, COLOUR.warning, COLOUR.warning) ; draw.pixel_text(40, 21*8+1 , "warning")
draw.rectangle(30, 22*8+1, 8, 8, COLOUR.weak, COLOUR.weak) ; draw.pixel_text(40, 22*8+1 , "weak")
]]
end
--###########################################################################################
-- OPTIONS MENU
-- Declare the group of functions and variables used in the options menu
local Options_form = {}
-- Options menu forms
function Options_form.create_window()
-- Create form
local form_width, form_height = 256, 256
Options_form.form = forms.newform(form_width, form_height, "OPTIONS")
-- Set form location based on the emu window
local emu_window_x, emu_window_y = client.xpos(), client.ypos()
forms.setlocation(Options_form.form, emu_window_x, emu_window_y + client.screenheight() + 77)
local xform, yform, delta_x, delta_y = 4, 4, 120, 20
--- Display options ---
Options_form.display_out_of_bounds_blocks = forms.checkbox(Options_form.form, "Out of bounds blocks", xform, yform)
forms.setproperty(Options_form.display_out_of_bounds_blocks, "Checked", OPTIONS.display_out_of_bounds_blocks)
yform = yform + delta_y
Options_form.display_grid = forms.checkbox(Options_form.form, "Tile grid", xform, yform)
forms.setproperty(Options_form.display_grid, "Checked", OPTIONS.display_grid)
yform = yform + delta_y
Options_form.display_movie_info = forms.checkbox(Options_form.form, "Movie info", xform, yform)
forms.setproperty(Options_form.display_movie_info, "Checked", OPTIONS.display_movie_info)
yform = yform + delta_y
--- Emu gaps (lateral gaps) ---
xform = xform + 140
-- top gap
forms.button(Options_form.form, "-", function()
if OPTIONS.top_gap - 10 >= draw.BIZHAWK_FONT_HEIGHT then OPTIONS.top_gap = OPTIONS.top_gap - 10 end
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
end, xform, yform, 24, 24)
xform = xform + 24
forms.button(Options_form.form, "+", function()
OPTIONS.top_gap = OPTIONS.top_gap + 10
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
end, xform, yform, 24, 24)
-- left gap
xform, yform = xform - 3*24, yform + 24
forms.button(Options_form.form, "-", function()
if OPTIONS.left_gap - 10 >= draw.BIZHAWK_FONT_HEIGHT then OPTIONS.left_gap = OPTIONS.left_gap - 10 end
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
end, xform, yform, 24, 24)
xform = xform + 24
forms.button(Options_form.form, "+", function()
OPTIONS.left_gap = OPTIONS.left_gap + 10
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
end, xform, yform, 24, 24)
-- right gap
xform = xform + 3*24
forms.button(Options_form.form, "-", function()
if OPTIONS.right_gap - 10 >= draw.BIZHAWK_FONT_HEIGHT then OPTIONS.right_gap = OPTIONS.right_gap - 10 end
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
end, xform, yform, 24, 24)
xform = xform + 24
forms.button(Options_form.form, "+", function()
OPTIONS.right_gap = OPTIONS.right_gap + 10
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
end, xform, yform, 24, 24)
-- bottom gap
xform, yform = xform - 3*24, yform + 24
forms.button(Options_form.form, "-", function()
if OPTIONS.bottom_gap - 10 >= draw.BIZHAWK_FONT_HEIGHT then OPTIONS.bottom_gap = OPTIONS.bottom_gap - 10 end
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
end, xform, yform, 24, 24)
xform = xform + 24
forms.button(Options_form.form, "+", function()
OPTIONS.bottom_gap = OPTIONS.bottom_gap + 10
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
end, xform, yform, 24, 24)
xform, yform = xform - 26, yform - 20
forms.label(Options_form.form, "Emu gaps", xform, yform, 70, 20)
end
-- Update the options based on the state of the controls in the options menu
function Options_form.evaluate_form()
OPTIONS.display_out_of_bounds_blocks = forms.ischecked(Options_form.display_out_of_bounds_blocks) or false
OPTIONS.display_grid = forms.ischecked(Options_form.display_grid) or false
OPTIONS.display_movie_info = forms.ischecked(Options_form.display_movie_info) or false
end
--- ###########################################################################################################################
--- MAIN
-- Create emu extra gaps
client.SetGameExtraPadding(OPTIONS.left_gap, OPTIONS.top_gap, OPTIONS.right_gap, OPTIONS.bottom_gap)
client.SetClientExtraPadding(0, 0, 0, 0)
-- Create the option menu forms
forms.destroyall() -- to prevent more than one forms (usually happens when script has an error)
Options_form.create_window()
Options_form.is_form_closed = false
-- Actions to do when script is closed
event.onexit(function()
forms.destroy(Options_form.form)
gui.clearImageCache()
client.SetGameExtraPadding(0, 0, 0, 0)
print("Finishing The Boucing Ball script.")
end)
print(fmt("\nThe Bouncing Ball script loaded successfully (%s)\n", os.date("%H:%M:%S %d/%m/%Y")))
-- Script main loop
while true do
Options_form.is_form_closed = forms.gettext(Options_form.form) == ""
if not Options_form.is_form_closed then Options_form.evaluate_form() end
bizhawk_status()
bizhawk_screen_info()
draw_info()
emu.frameadvance()
end