User File #28335593014878764

Upload All User Files

#28335593014878764 - Donkey Kong Country 2 - Hitbox of sprites (Lsnes emulator)

DKC2-hitbox-lsnes.lua
1091 downloads
Uploaded 1/20/2016 2:00 AM by Amaraticando (see all 21)
I tested the american and japanese versions of the game, v.1.0 and v.1.1. Probably works on PAL too.
This is only a piece of the utility script that I'm creating (which is a mess yet). Support for BizHawk and maybe Snes9x-rr is coming.
You can view offscreen sprites by increasing the lateral paddings of the emulator: Configure > Settings > Advanced > UI paddings
You can draw only one tile area with left click.
---------------------------------------------------------------------------
--  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()