Tested on sfiii3nr1. I believe it also works in other sfiii3 variants.
Just save the code as sfiii3nr1.json or [sfiii3 game].json and put in cheat folder or add to cheat.7z.
Hotkey function in original mame-rr was removed.
mini_axis and blank screen layer are disabled and throw box is enabled by default.
Code: Select all
[{
"space":{
"cpup":{
"type":"program",
"tag":":maincpu"
}
},
"script":{
"on":"
--[[--Street Fighter III 3rd Strike hitbox viewer]]
--[[--October 27, 2011]]
--[[--http://code.google.com/p/mame-rr/wiki/Hitboxes]]
boxes = {
[\"vulnerability\"] = {color = 0x7777FF, fill = 0x40, outline = 0xFF},
[\"ext. vulnerability\"] = {color = 0x0000FF, fill = 0x40, outline = 0xFF},
--extended limbs during attacks
[\"attack\"] = {color = 0xFF0000, fill = 0x40, outline = 0xFF},
[\"proj. vulnerability\"] = {color = 0x00FFFF, fill = 0x40, outline = 0xFF},
[\"proj. attack\"] = {color = 0xFF66FF, fill = 0x40, outline = 0xFF},
[\"push\"] = {color = 0x00FF00, fill = 0x20, outline = 0xFF},
[\"throw\"] = {color = 0xFFFF00, fill = 0x40, outline = 0xFF},
[\"throwable\"] = {color = 0xF0F0F0, fill = 0x20, outline = 0xFF},
}
globals = {
axis_color = 0xFFFFFFFF,
blank_color = 0xFFFFFFFF,
axis_size = 12,
mini_axis_size = 2,
blank_screen = false,
draw_axis = true,
draw_mini_axis = false,
draw_pushboxes = true,
draw_throwable_boxes = true,
no_alpha = false, --fill = 0x00, outline = 0xFF for all box types
}
--------------------------------------------------------------------------------
-- game-specific modules
DRAW_DELAY = 1
GROUND_OFFSET = -23
MAX_OBJECTS = 30
projectile_type = {
[\"attack\"] = \"proj. attack\",
[\"vulnerability\"] = \"proj. vulnerability\",
[\"ext. vulnerability\"] = \"proj. vulnerability\",
}
function any_true(condition)
for n = 1, #condition do
if condition[n] == true then return true end
end
end
profile =
{\tgames = {\"sfiii3\"},
player = {0x02068C6C, 0x2069104}, --0x498
object = {initial = 0x02028990, index = 0x02068A96},
screen = {x = 0x02026CB0, y = 0x02026CB4},
match = 0x020154A6,
scale = 0x0200DCBA,
char_id = 0x3C0,
ptr = {
valid_object = 0x2A0,
{offset = 0x2D4, type = \"push\"},
{offset = 0x2C0, type = \"throwable\"},
{offset = 0x2A0, type = \"vulnerability\", number = 4},
{offset = 0x2A8, type = \"ext. vulnerability\", number = 4},
{offset = 0x2C8, type = \"attack\", number = 4},
{offset = 0x2B8, type = \"throw\"},
},
}
for _, box in ipairs(profile.ptr) do
box.initial = box.initial or 1
box.number = box.number or 1
end
profile.match_active = function()
return cpup:read_u32(profile.match) > 0x00010000 and cpup:read_u16(profile.match) ~= 0x0009
end
profile.get_screen = function(f)
f.screen_x = cpup:read_i16(profile.screen.x)
f.screen_y = cpup:read_i16(profile.screen.y)
end
profile.define_box = function(f, obj, ptr, type)
if obj.friends > 1 then
--Yang SA3
if type ~= \"attack\" then
return
end
elseif obj.projectile then
type = projectile_type[type] or type
end
local box = {
left = f.scale * -cpup:read_i16(ptr + 0x0),
right = f.scale * -cpup:read_i16(ptr + 0x2),
bottom = f.scale * -cpup:read_i16(ptr + 0x4),
top = f.scale * -cpup:read_i16(ptr + 0x6),
type = type,
}
if box.left == 0 and box.right == 0 and box.top == 0 and box.bottom == 0 then
return
elseif obj.flip_x == 0 then
box.left = -box.left
box.right = -box.right
end
box.left = box.left + obj.pos_x
box.right = box.right + box.left
box.bottom = box.bottom + obj.pos_y
box.top = box.top + box.bottom
box.hval = (box.left + box.right)/2
box.vval = (box.bottom + box.top)/2
table.insert(obj, box)
end
profile.update_game_object = function(f, obj)
if cpup:read_u32(obj.base + profile.ptr.valid_object) == 0 then
--invalid objects
return
end
obj.friends = cpup:read_u8(obj.base + 0x1)
obj.flip_x = cpup:read_i8(obj.base + 0x0A)
obj.pos_x = cpup:read_i16(obj.base + 0x64)
obj.pos_y = cpup:read_i16(obj.base + 0x68)
obj.pos_x = obj.pos_x - f.screen_x + screen:width()/2
obj.pos_y = -obj.pos_y + f.screen_y + screen:height() + GROUND_OFFSET
obj.char_id = cpup:read_u16(obj.base + profile.char_id)
for _, box in ipairs(profile.ptr) do
for i = box.initial, box.number do
profile.define_box(f, obj, cpup:read_u32(obj.base + box.offset) + (i-1)*8, box.type)
end
end
table.insert(f, obj)
end
profile.read_misc_objects = function(f)
-- This function reads all game objects other than the two player characters.
-- This includes all projectiles and even Yang's Seiei-Enbu shadows.
-- The game uses the same structure all over the place and groups them
-- into lists with each element containing an index to the next element
-- in that list. An index of -1 signals the end of the list.
-- I believe there are at least 7 lists (0-6) but it seems everything we need
-- (and lots we don't) is in list 3.
local list = 3
local obj_index = cpup:read_u16(profile.object.index + (list * 2))
local obj_slot = 1
while obj_slot <= MAX_OBJECTS and obj_index ~= -1 do
--local obj = {base = profile.object.initial + bit.lshift(obj_index, 11), projectile = obj_slot}
local obj = {base = profile.object.initial + (obj_index << 11), projectile = obj_slot}
profile.update_game_object(f, obj)
-- Get the index to the next object in this list.
obj_index = cpup:read_i16(obj.base + 0x1C)
obj_slot = obj_slot + 1
end
end
profile.read_throws = function(f) end
for _,box in pairs(boxes) do
box.fill = box.color | ((globals.no_alpha and 0x00 or box.fill) << 24)
box.outline = box.color | ((globals.no_alpha and 0xFF or box.outline) << 24)
end
frame_buffer = {}
function copytable(orig)
local copy = {}
for orig_key, orig_value in pairs(orig) do
copy[orig_key] = orig_value
end
return copy
end
function max(a, b)
if a > b then
return a
end
return b
end
--------------------------------------------------------------------------------
-- prepare the hitboxes
function update_hitboxes()
for f = 1, DRAW_DELAY do
frame_buffer[f] = copytable(frame_buffer[f+1])
end
frame_buffer[DRAW_DELAY+1] = {match_active = profile and profile.match_active()}
local f = frame_buffer[DRAW_DELAY+1]
if not f.match_active then
return
end
profile.get_screen(f)
f.scale = cpup:read_i16(0x040C006E)
f.scale = 0x40/(f.scale > 0 and f.scale or 1)
for p = 1, #profile.player do
local player = {base = profile.player[p]}
profile.update_game_object(f, player)
end
profile.read_misc_objects(f)
f = frame_buffer[DRAW_DELAY]
profile.read_throws(f)
f.max_boxes = 0
for _, obj in ipairs(f) do
f.max_boxes = max(f.max_boxes, #obj)
end
end
--------------------------------------------------------------------------------
-- draw the hitboxes
function draw_hitbox(hb)
if not hb or any_true({
not globals.draw_pushboxes and hb.type == \"push\",
not globals.draw_throwable_boxes and hb.type == \"throwable\",
}) then return
end
if globals.draw_mini_axis then
draw_line(screen,hb.hval, hb.vval-globals.mini_axis_size, hb.hval, hb.vval+globals.mini_axis_size, boxes[hb.type].outline)
draw_line(screen,hb.hval-globals.mini_axis_size, hb.vval, hb.hval+globals.mini_axis_size, hb.vval, boxes[hb.type].outline)
end
draw_box(screen,hb.left, hb.top, hb.right, hb.bottom, boxes[hb.type].fill, boxes[hb.type].outline)
end
function draw_axis(obj)
draw_line(screen,obj.pos_x, obj.pos_y-globals.axis_size, obj.pos_x, obj.pos_y+globals.axis_size, globals.axis_color)
draw_line(screen,obj.pos_x-globals.axis_size, obj.pos_y, obj.pos_x+globals.axis_size, obj.pos_y, globals.axis_color)
--draw_text(screen,obj.pos_x+4, obj.pos_y-0x08, string.format(\"%08X\", obj.base))
end
function render_hitboxes()
local f = frame_buffer[1]
if not f.match_active then
return
end
if globals.blank_screen then
draw_box(screen,0, 0, screen:width(), screen:height(), globals.blank_color)
end
for entry = 1, f.max_boxes or 0 do
for _, obj in ipairs(f) do
draw_hitbox(obj[entry])
end
end
if globals.draw_axis then
for _, obj in ipairs(f) do
draw_axis(obj)
end
end
end
function initialize_fb()
for f = 1, DRAW_DELAY+1 do
frame_buffer[f] = {}
end
end
initialize_fb()
game = profile
",
"run":"update_hitboxes() render_hitboxes()"
},
"screen":{
"screen":":screen"
},
"desc":"Hitbox viewer"
}]