print("CPS-2 hitbox display") print("August 15, 2010") print() local DRAW_DELAY = 2 local SCREEN_WIDTH = 384 local SCREEN_HEIGHT = 224 local MAX_GAME_PROJECTILES = 32 local AXIS_COLOUR = 0xFFFFFFFF local MINI_AXIS_COLOUR = 0xFFFF00FF local AXIS_SIZE = 25 local MINI_AXIS_SIZE = 2 local HITBOX_VULNERABILITY = 0 local HITBOX_ATTACK = 1 local HITBOX_PUSH = 2 local HITBOX_VULNERABILITY_COLOUR = 0x00e4FF40 local HITBOX_ATTACK_COLOUR = 0xFF000040 local HITBOX_PUSH_COLOUR = 0x00FF0040 local GAME_PHASE_NOT_PLAYING= 0 if fba then DRAW_DELAY = DRAW_DELAY + 1 end local profile = { { games = {"sfa3"}, address = { player1 = 0x00FF8400, player2 = 0x00FF8800, projectile = 0x00FF8D00, left_screen_edge = 0x00FF8026, top_screen_edge = 0x00FF8028, game_phase = 0x00FF812D, }, offset = { v_hb_addr_table = 0x90, v_hb_curr_id = 0xC8, a_hb_addr_table = 0xA0, a_hb_curr_id = 0x09, p_hb_addr_table = 0x9c, p_hb_curr_id = 0xCB, }, }, { games = {"vsav","vhunt2"}, address = { player1 = 0x00FF8400, player2 = 0x00FF8800, projectile = 0x00FF9400, left_screen_edge = 0x00FF8026, top_screen_edge = 0x00FF8028, game_phase = 0x00FF812D, }, offset = { v_hb_addr_table = 0x80, v_hb_curr_id = 0x94, a_hb_addr_table = 0x8c, a_hb_curr_id = 0x0A, p_hb_addr_table = 0x90, p_hb_curr_id = 0x97, }, }, } local address, offset local globals = { game_phase = 0, left_screen_edge = 0, top_screen_edge = 0, num_projectiles = 0 } local player1 = {} local player2 = {} local projectiles = {} local frame_buffer_array = {} for f = 1, DRAW_DELAY + 1 do frame_buffer_array[f] = {} frame_buffer_array[f][projectiles] = {} end function update_globals() globals.left_screen_edge = memory.readword(address.left_screen_edge) globals.top_screen_edge = memory.readword(address.top_screen_edge) globals.game_phase = memory.readword(address.game_phase) end function hitbox_load(obj, i, type, facing_dir, offset_x, offset_y, addr) local hval = memory.readwordsigned(addr + 0) local vval = memory.readwordsigned(addr + 2) local hrad = memory.readwordsigned(addr + 4) local vrad = memory.readwordsigned(addr + 6) if facing_dir == 1 then hval = -hval end local left = offset_x + hval - hrad local top = offset_y + vval - vrad local right = offset_x + hval + hrad local bottom = offset_y + vval + vrad if type == HITBOX_VULNERABILITY then obj[HITBOX_VULNERABILITY][i] = { left = left, right = right, bottom = bottom, top = top, hval = offset_x+hval, vval = offset_y+vval, type = type } else obj[type] = { left = left, right = right, bottom = bottom, top = top, hval = offset_x+hval, vval = offset_y+vval, type = type } end end function update_game_object(obj, base) obj.facing_dir = memory.readbyte(base + 0xB) obj.pos_x = memory.readword(base + 0x10) obj.pos_y = memory.readword(base + 0x14) obj.opponent_dir = memory.readbyte(base + 0x5D) -- Load the vulnerability hitboxes obj[HITBOX_VULNERABILITY] = {} for i = 0, 2 do local v_hb_addr_table = memory.readdword(base + offset.v_hb_addr_table + (i*4)) local v_hb_curr_id = memory.readbyte(base + offset.v_hb_curr_id + i) hitbox_load(obj, i, HITBOX_VULNERABILITY, obj.facing_dir, obj.pos_x, obj.pos_y, v_hb_addr_table+(v_hb_curr_id*8)) end -- Load the attack hitbox local a_hb_addr_table = memory.readdword(base + offset.a_hb_addr_table) local animation_ptr = memory.readdword(base + 0x1C) local a_hb_curr_id = memory.readbyte(animation_ptr + offset.a_hb_curr_id) hitbox_load(obj, 0, HITBOX_ATTACK, obj.facing_dir, obj.pos_x, obj.pos_y, a_hb_addr_table+(a_hb_curr_id*0x20)) -- Load the push hitbox local p_hb_addr_table = memory.readdword(base + offset.p_hb_addr_table) local p_hb_curr_id = memory.readbyte(base + offset.p_hb_curr_id) hitbox_load(obj, 0, HITBOX_PUSH, obj.facing_dir, obj.pos_x, obj.pos_y, p_hb_addr_table+(p_hb_curr_id*8)) end function read_projectiles() globals.num_projectiles = 0 for i = 0,MAX_GAME_PROJECTILES do local base = address.projectile + (i*0x100) if memory.readbyte(base) ~= 0 then projectiles[globals.num_projectiles] = {} update_game_object(projectiles[globals.num_projectiles], base) globals.num_projectiles = globals.num_projectiles+1 end end end function game_x_to_mame(x) return (x - globals.left_screen_edge) end function game_y_to_mame(y) -- Why subtract 17? No idea, the game driver does the same thing. return (SCREEN_HEIGHT - (y - 17) + globals.top_screen_edge) end function draw_hitbox(hb) local left = game_x_to_mame(hb.left) local bottom = game_y_to_mame(hb.bottom) local right = game_x_to_mame(hb.right) local top = game_y_to_mame(hb.top) local hval = game_x_to_mame(hb.hval) local vval = game_y_to_mame(hb.vval) if hb.type == HITBOX_VULNERABILITY then colour = HITBOX_VULNERABILITY_COLOUR elseif hb.type == HITBOX_ATTACK then colour = HITBOX_ATTACK_COLOUR elseif hb.type == HITBOX_PUSH then colour = HITBOX_PUSH_COLOUR end -- draw mini axis -- gui.drawline(hval, vval-MINI_AXIS_SIZE, hval, vval+MINI_AXIS_SIZE, MINI_AXIS_COLOUR) -- gui.drawline(hval-MINI_AXIS_SIZE, vval, hval+MINI_AXIS_SIZE, vval, MINI_AXIS_COLOUR) gui.box(left, top, right, bottom, colour) end function draw_game_object(obj) if not obj then return end local x = game_x_to_mame(obj.pos_x) local y = game_y_to_mame(obj.pos_y) for i = 0, 2 do draw_hitbox(obj[HITBOX_VULNERABILITY][i]) end draw_hitbox(obj[HITBOX_ATTACK]) draw_hitbox(obj[HITBOX_PUSH]) gui.drawline(x, y-AXIS_SIZE, x, y+AXIS_SIZE, AXIS_COLOUR) gui.drawline(x-AXIS_SIZE, y, x+AXIS_SIZE, y, AXIS_COLOUR) end local function whatgame() address, offset = nil, nil for n, module in ipairs(profile) do for m, shortname in ipairs(module.games) do if emu.romname() == shortname or emu.parentname() == shortname then print("drawing " .. shortname .. " hitboxes") address = module.address offset = module.offset return end end end print("not prepared for " .. emu.romname() .. " hitboxes") end local function update_cps2_hitboxes() if not address then return end update_globals() if globals.game_phase == GAME_PHASE_NOT_PLAYING then return end update_game_object(player1, address.player1) update_game_object(player2, address.player2) read_projectiles() for f = 1, DRAW_DELAY do frame_buffer_array[f][player1] = copytable(frame_buffer_array[f+1][player1]) frame_buffer_array[f][player2] = copytable(frame_buffer_array[f+1][player2]) for i = 0, globals.num_projectiles-1 do frame_buffer_array[f][projectiles][i] = copytable(frame_buffer_array[f+1][projectiles][i]) end end frame_buffer_array[DRAW_DELAY+1][player1] = copytable(player1) frame_buffer_array[DRAW_DELAY+1][player2] = copytable(player2) for i = 0, globals.num_projectiles-1 do frame_buffer_array[DRAW_DELAY+1][projectiles][i] = copytable(projectiles[i]) end end function render_cps2_hitboxes() if not address or globals.game_phase == GAME_PHASE_NOT_PLAYING then gui.clearuncommitted() return end draw_game_object(frame_buffer_array[1][player1]) draw_game_object(frame_buffer_array[1][player2]) for i = 0, globals.num_projectiles-1 do draw_game_object(frame_buffer_array[1][projectiles][i]) end end emu.registerstart( function() whatgame() end) emu.registerafter( function() update_cps2_hitboxes() end) gui.register( function() render_cps2_hitboxes() end)