---------------------------------------------------------------------------
-- Donkey Kong Country 2: Hitbox Script for Lsnes - rr2 version
-- http://tasvideos.org/Lsnes.html
--
-- Author: Rodrigo A. do Amaral (Amaraticando)
-- Git repository: not public yet -> a complete utility/debug script is in progress
---------------------------------------------------------------------------
-- MACROS & DEFINITIONS
local User_input = {}
local Camera_x, Camera_y = nil, nil
local Tile_x, Tile_y = nil, nil
-- Those 'Keys functions' register presses and releases. Pretty much a copy from the script of player Fat Rat Knight (FRK)
-- http://tasvideos.org/userfiles/info/5481697172299767
Keys = {}
Keys.KeyPress= {}
Keys.KeyRelease= {}
function Keys.registerkeypress(key,fn)
-- key - string. Which key do you wish to bind?
-- fn - function. To execute on key press. False or nil removes it.
-- Return value: The old function previously assigned to the key.
local OldFn= Keys.KeyPress[key]
Keys.KeyPress[key]= fn
--Keys.KeyPress[key]= Keys.KeyPress[key] or {}
--table.insert(Keys.KeyPress[key], fn)
input.keyhook(key,type(fn or Keys.KeyRelease[key]) == "function")
return OldFn
end
function Keys.registerkeyrelease(key,fn)
-- key - string. Which key do you wish to bind?
-- fn - function. To execute on key release. False or nil removes it.
-- Return value: The old function previously assigned to the key.
local OldFn= Keys.KeyRelease[key]
Keys.KeyRelease[key]= fn
input.keyhook(key,type(fn or Keys.KeyPress[key]) == "function")
return OldFn
end
function Keys.altkeyhook(s,t)
-- s,t - input expected is identical to on_keyhook input. Also passed along.
-- You may set by this line: on_keyhook = Keys.altkeyhook
-- Only handles keyboard input. If you need to handle other inputs, you may
-- need to have your own on_keyhook function to handle that, but you can still
-- call this when generic keyboard handling is desired.
if Keys.KeyPress[s] and (t.value == 1) then
Keys.KeyPress[s](s,t)
elseif Keys.KeyRelease[s] and (t.value == 0) then
Keys.KeyRelease[s](s,t)
end
end
local function draw_pixel(x, y, color, color_around)
gui.rectangle(2*x - 2, 2*y - 2, 6, 6, 2, color_around, color)
end
-- draws a line given (x,y) and (x',y') with SNES' pixel thickness
local function draw_line(x1, y1, x2, y2, 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 = 2*x1, 2*y1, 2*x2, 2*y2
if x1 == x2 then
gui.line(x1, y1, x2, y2 + 1, color)
gui.line(x1 + 1, y1, x2 + 1, y2 + 1, color)
elseif y1 == y2 then
gui.line(x1, y1, x2 + 1, y2, color)
gui.line(x1, y1 + 1, x2 + 1, y2 + 1, color)
else
gui.line(x1, y1, x2, y2, color)
gui.line(x1 + 1, y1, x2 + 1, y2, color)
gui.line(x1, y1 + 1, x2, y2 + 1, color)
gui.line(x1 + 1, y1 + 1, x2 + 1, y2 + 1, color)
end
end
local function draw_rectangle(x, y, w, h, color_line, color_bg)
x, y, w, h = 2*x, 2*y, 2*w, 2*h
if w < 0 then w = - w; x = x - w end
if h < 0 then h = - h; y = y - h end
gui.rectangle(x, y, w, h, 2, color_line, color_bg)
end
-- draws a box given (x,y) and (x',y') with SNES' pixel sizes
function draw_box(x1, y1, x2, y2, color_line, color_bg)
-- 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
gui.rectangle(2*x1, 2*y1, 2*(x2 - x1 + 1), 2*(y2 - y1 + 1), 2, color_line, color_bg)
end
local SPRITES_COLOR = {
[0] = "magenta", -- Diddy
"red", -- Dixie
"chartreuse",
"yellow",
"blueviolet",
"burlywood",
"cadet",
"chocolate",
"coral",
"cornflowerblue",
"blue",
"cyan",
"darkblue",
"darkcyan",
"darkgoldenrod",
"darkgray",
"darkgreen",
"darkgrey",
"darkkhaki",
"darkmagenta",
"darkolivegreen",
"darkorange",
"gold",
"white"
}
-- Compatibility of the memory read/write functions
local u8 = memory.readbyte
local s8 = memory.readsbyte
local w8 = memory.writebyte
local u16 = memory.readword
local s16 = memory.readsword
local w16 = memory.writeword
local u24 = memory.readhword
local s24 = memory.readshword
local w24 = memory.writehword
-- Stores the raw input in a table for later use. Should be called at the start of paint and timer callbacks
local function read_raw_input()
for key, inner in pairs(input.raw()) do
User_input[key] = inner.value
end
User_input.mouse_x = math.floor(User_input.mouse_x)
User_input.mouse_y = math.floor(User_input.mouse_y)
end
local function left_click()
-- Tiles
read_raw_input()
local x_mouse, y_mouse = math.floor(User_input.mouse_x/2) + Camera_x - 256, math.floor(User_input.mouse_y/2) + Camera_y - 256
x_mouse = math.floor(x_mouse/32)
y_mouse = math.floor(y_mouse/32)
if Tile_x == x_mouse and Tile_y == y_mouse then
Tile_x, Tile_y = nil, nil
else
Tile_x, Tile_y = x_mouse, y_mouse
end
end
--#############################################################################
-- DKC2 SPECIFIC FUNCTIONS --
local function scan_dkc2()
Camera_x = s16("WRAM", 0x17BA)
Camera_y = s16("WRAM", 0x17C0)
end
local function sprites()
local sprites_count = 0
local second_kong_id = u8("WRAM", 0x08A4) == 0 and 1 or 0
local second_kong_alive_flag = bit.test(u16("WRAM", 0x08c3), 6)
for id = 0, 23 do
if id ~= second_kong_id or second_kong_alive_flag then
local base = 0x0de2 + id*94
local sprite_number = u16("WRAM", base)
if sprite_number ~= 0 then
local xpos = u16("WRAM", base + 0x06)
local ypos = u16("WRAM", base + 0x0a)
local tweaker = u8("WRAM", base + 0x13)
local facing_left = bit.test(tweaker, 6)
local upside_down = bit.test(tweaker, 7)
-- Hitbox dimensions and offsets
local boxid = u16("WRAM", base + 0x1a)
local offset = u16("BUS", 0xbcb600 + math.floor(boxid/2))
local xoff = s8("BUS", 0xbc0000 + offset)
local yoff = s8("BUS", 0xbc0002 + offset)
local width = s8("BUS", 0xbc0004 + offset)
local height = s8("BUS", 0xbc0006 + offset)
-- Translation
if facing_left then xoff = - xoff - width end
if upside_down then yoff = - yoff - height end
local x_screen, y_screen = xpos-Camera_x + xoff, ypos-Camera_y + yoff
-- Next to sprite
local color = SPRITES_COLOR[id]
gui.text(2*(xpos-Camera_x - 2), 2*(ypos-Camera_y), id, color, nil, 0)
draw_rectangle(x_screen, y_screen, width + 1, height + 1, color, 0xd00000ff) -- hitbox
draw_pixel(xpos-Camera_x, ypos-Camera_y - 1, 0xffffff, 0x60000000) -- interaction with tiles
sprites_count = sprites_count + 1
end
end
end
end
local function display_grid(xorigin, yorigin)
local level_height = 8*u8("WRAM", 0x0aff)
local address = 0x10000 + level_height*xorigin + 2*yorigin
xorigin = xorigin*32
yorigin = yorigin*32
local x1 = xorigin - Camera_x + 256
local y1 = yorigin - Camera_y + 256
if address >= 0x10000 and address < 0x1b232 then
gui.rectangle(2*x1, 2*y1, 2*16, 2*16, 2, "darkblue", 0xc000ffff)
gui.rectangle(2*(x1 + 16), 2*(y1 + 16), 2*16, 2*16, 2, "darkblue", 0xc000ffff)
gui.rectangle(2*(x1 + 16), 2*y1, 2*16, 2*16, 2, "darkblue", 0xc000ffff)
gui.rectangle(2*x1, 2*(y1 + 16), 2*16, 2*16, 2, "darkblue", 0xc000ffff)
end
end
--#############################################################################
-- CALLBACKS --
function on_paint()
read_raw_input()
scan_dkc2()
-- Display tile
if Tile_x and Tile_y then
display_grid(Tile_x, Tile_y)
end
-- Display hitboxex and slots
sprites()
end
on_video = on_paint -- remove or comment out this line if you don't want Lua drawings on video
-- KEYHOOK callback
on_keyhook = Keys.altkeyhook
--#############################################################################
-- ON START --
-- Key presses:
Keys.registerkeypress("mouse_inwindow", gui.repaint)
Keys.registerkeypress("mouse_left", function() left_click(); gui.repaint() end)
print"Click with the left to draw the area of a tile"
gui.repaint()