[wof] warriors of fate hitbox viewer

This forum is for posting M.A.M.E. arcade cheats. Requests will be fulfilled here....but please keep the requests to the requests forum.
Post Reply
rabbyzero
Posts: 24
Joined: Thu Nov 16, 2017 3:20 am

[wof] warriors of fate hitbox viewer

Post by rabbyzero »

Code: Select all

[{
    "space":{
      "cpup":{
        "type":"program",
        "tag":":maincpu"
      }
    },
    "script":{
      "on":"


-- mem = manager:machine().devices[\":maincpu\"].spaces[\"program\"]

local memory = {
	readbyte        = function(addr) return cpup:read_u8(addr) end,
	readbytesigned  = function(addr) return cpup:read_i8(addr) end,
	
	readword        = function(addr) return cpup:read_u16(addr) end,
	readwordsigned  = function(addr) return cpup:read_i16(addr) end,

	readdword       = function(addr) return cpup:read_u32(addr) end,
	readdwordsigned = function(addr) return cpup:read_i32(addr) end,
}

-- local s = manager:machine().screens[\":screen\"]
-- local screenwidth = s:width()
-- local screenheight = s:height()

local olddata = {}
for i=1,0xF0,1 do
	olddata[i] = 0
end


local boxes = {
	      [\"vulnerability\"] = {color = 0x7777FF, fill = 0x40, outline = 0xFF},
	             [\"attack\"] = {color = 0xFF0000, fill = 0x40, outline = 0xFF},
	               [\"hold\"] = {color = 0xFFff00, fill = 0x40, outline = 0xFF},
	             [\"beheld\"] = {color = 0xFF00ff, fill = 0x20, outline = 0xFF},
	[\"proj. vulnerability\"] = {color = 0x00FFFF, fill = 0x40, outline = 0xFF},
	       [\"proj. attack\"] = {color = 0xFF66FF, fill = 0x40, outline = 0xFF},
}

local globals = {
	axis_color     = 0xFFFFFFFF,
	blank_color    = 0xFFFFFFFF,
	axis_size      = 4,
	mini_axis_size = 2,
	blank_screen   = false,
	draw_axis      = true,
	draw_mini_axis = false,
	no_alpha       = false, --fill = 0x00, outline = 0xFF for all box types

	text_color = {
		life = 0xCC00FF00,
		life_low = 0xCCffFF00,
		life_critical = 0xCCff0000,
		life_high = 0xCCff00ff,
		life_dead = 0x80808080,
		remain_possibility = 0xCC00FF00,
		remain_possibility_low = 0xCCffFF00,
		remain_possibility_none = 0xCCff0000,
	}
}

local count = 0

local profile = 
{	game = \"wof\",
	address = {
		screen_left = {0xFF7a8c}, --0xFF6346,0xFF6348,0xFF7a8c,0xff7c26
		game_phase  = 0xFF0000,
	},
	offset = {
		flip_x = 0x16,
		pos_x  = 0x04,
		pos_y  = 0x08,
		pos_z  = 0x0C,
		pos_z2 = 0x10, -- ground height
	},
	objects = {
		{address = 0xFFbe1c, number = 0x03, space = 0xe0, invulnerable = 0xB9, 
		 hp = 0x82, hp_previous = 0x84, 
		 status = 0x31, command = 0xb0, command_countdown = 0xb1, command_grab = 0xc1, 
		 command_grab_countdown = {0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9},
		 weapon = 0x8c,
		 }, --players
		{addr_list = 0xff837e, number_addr = 0xff8318, status = 0x31, hp = 0x82, hp_previous = 0x84,
		temp1 = 0x2A, temp2 = 0x2c, temp3 =0x2e}, -- enemies and corpses
		{addr_list = 0xff841e, number_addr = 0xff831a, status = 0x31}, -- projectiles
		{addr_list = 0xff84be, number_addr = 0xff831c, status = 0x31}, -- projectiles
		{addr_list = 0xff850e, number_addr = 0xff831e, style = \"noheight\"}, -- items on ground
		{addr_list = 0xff855e, number_addr = 0xff8320, style = \"noheight\", remain = \"0xAA\"}, -- weapons on ground
		{addr_list = 0xff85ae, number_addr = 0xff8322, hp=0x82, hp_previous = 0x84}, -- chests
	},
	box = {
		radius_read = memory.readwordsigned,
		val_x = 0x0, val_y = 0x4, rad_x = 0x2, rad_y = 0x6,
		radscale = 2,
	},
	box_list = {
		{id_ptr = 0x6e, type = \"vulnerability\"},
		{id_ptr = 0x6c, type = \"beheld\"},
		{id_ptr = 0x70, status_code = 0x4, type = \"attack\"},
		{id_ptr = 0x70, status_code = 0xc, type = \"attack\"},
		{id_ptr = 0x70, status_code = 0x8, type = \"hold\"},
		{id_ptr = 0x70, status_code = 0x1C, type = \"hold\"},
		{id_ptr = 0x70, status_code = 0x1C, type = \"attack\"},
		{id_ptr = 0x70, status_code = 0x14, type = \"attack\"},
		{id_ptr = 0x70, status_code = 0x24, type = \"attack\"},
		{id_ptr = 0x70, status_code = 0x18, type = \"attack\"},
		{id_ptr = 0x32, status_code = 0x18, type = \"attack\", style = \"noheight\"},
		{id = function(base_addr) return memory.readword(memory.readword(base_addr+0x8c) + 0xFF0000 + 0x70) end,
		 status_code = 0x20, type = \"weapon\"}, -- find weapon
		{id = function(base_addr) return 0x16c8 end, character = 0x1, type = \"hold\"} -- command grab
	},
	id_read = memory.readword,
	box_address = function(obj, box, box_entry)
		local address = 0xe3e4a
		return address + box.id
	end,
}


--------------------------------------------------------------------------------
-- post-process modules


local g = profile
g.box.offset_read = g.box.radius_read == memory.readbytesigned and memory.readbytesigned or memory.readwordsigned
g.offset.pos_y = g.offset.pos_x + 0x4
g.exist_val = g.exist_val or 0x0100
-- for entry in ipairs(g.objects) do
-- 	if type(g.objects[entry].active) == \"number\" then
-- 		g.objects[entry].active = {g.objects[entry].active}
-- 	end
-- end
-- if type(g.address.screen_left) ~= \"table\" then
-- 	g.address.screen_left = {g.address.screen_left}
-- end



for _, box in pairs(boxes) do
	box.fill    = (globals.no_alpha and 0x00 or box.fill)    * 0x1000000 + box.color
	box.outline = (globals.no_alpha and 0x00 or box.outline) * 0x1000000 + box.color
end

local game, framebuffer
local DRAW_DELAY = 1
-- if fba then
-- 	DRAW_DELAY = DRAW_DELAY + 1
-- end


--------------------------------------------------------------------------------
-- prepare the hitboxes

local set_box_center = {
	function(obj, box)
		return
			obj.pos_x + box.val_x * (obj.flip_x > 0 and -1 or 1),
			obj.pos_y - box.val_y
	end,

	function(obj, box)
		return
			obj.pos_x + (box.val_x + box.rad_x) * (obj.flip_x > 0 and -1 or 1),
			obj.pos_y - (box.val_y + box.rad_y)
	end,
}

local function object_pos(f,obj) 
	obj.flip_x = memory.readbyte(obj.base + game.offset.flip_x)
	obj.pos_z  = game.offset.pos_z and memory.readwordsigned(obj.base + game.offset.pos_z) or 0
	obj.pos_z  = obj.pos_z + (game.offset.pos_z2 and memory.readwordsigned(obj.base + game.offset.pos_z2) or 0)
	obj.pos_x  = memory.readwordsigned(obj.base + game.offset.pos_x) - f.screen_left
	obj.pos_y  = memory.readwordsigned(obj.base + game.offset.pos_y) + obj.pos_z
	obj.pos_y  = screen:height() - (obj.pos_y - 0x0F) + f.screen_top
	return obj
end


local function define_box(f,obj, box_entry)
	local box = {type = box_entry.type, style = box_entry.style}
	local base_id = obj.base

	if box_entry.status_code then 
		if not obj.status or memory.readbyte(obj.base+obj.status) ~= box_entry.status_code then
			return nil
		end
		if box_entry.status_code == 0x18 and box_entry.id_ptr == 0x70 then return nil end -- no horse
		if box_entry.status_code == 0x00 and memory.readbyte(base_id+0x63) == 1 then return nil end -- on horse
	end

	if box_entry.character then
		if memory.readbyte(base_id + 0x21) ~= box_entry.character then return nil end
		if memory.readbyte(base_id + 0xc1) ~= 0xff then return nil end
	end
	
	if box_entry.id_ptr then 
		if box_entry.id_ptr == 0x32 and memory.readbyte(base_id+0x63) == 1 then return nil end -- on horse
		box.id = game.id_read(base_id + box_entry.id_ptr)
	elseif box_entry.id then
		box.id = box_entry.id(obj.base)
	else
		return nil
	end


	if base_id == 0 or box.id == 0 or (obj.invulnerable and box.type == \"vulnerability\") then 
		return nil
	end

	box.address = game.box_address(obj, box, box_entry)
	if not box.address then
		return nil
	end

	if obj.style and not box.style then box.style = obj.style end

	box.rad_x = game.box.radius_read(box.address + game.box.rad_x)/game.box.radscale
	box.rad_y = game.box.radius_read(box.address + game.box.rad_y)/game.box.radscale
	box.val_x = game.box.offset_read(box.address + game.box.val_x)
	box.val_y = game.box.offset_read(box.address + game.box.val_y)

	if box.type == \"beheld\" then
		box.val_y = box.rad_x
		box.rad_x = 0
		box.rad_y = 0
	end
	if box.type == \"weapon\" then 
		local newobj = object_pos(f,{base=0xff0000+memory.readword(obj.base+0x8c),})
		box.val_x, box.val_y = set_box_center[game.box.radscale](newobj, box)
		box.type = \"attack\"
	else
		box.val_x, box.val_y = set_box_center[game.box.radscale](obj, box)
	end
	box.left   = box.val_x - box.rad_x
	box.right  = box.val_x + box.rad_x
	box.top    = box.val_y - box.rad_y
	box.bottom = box.val_y + box.rad_y

	return box
end


local function update_object(f, obj)
	object_pos(f,obj)

	for entry in ipairs(game.box_list) do
		table.insert(obj, define_box(f,obj, game.box_list[entry]))
	end
	return obj
end

local function inactive(base, active)
	for _, offset in ipairs(active) do
		if memory.readword(base + offset) > 0 then
			return false
		end
	end
	return true
end

local function copytable(orig)
	local copy = {}
	for orig_key, orig_value in pairs(orig) do
		copy[orig_key] = orig_value
	end
	return copy
end

-- function deepcopy(orig)
--     local orig_type = type(orig)
--     local copy
--     if orig_type == 'table' then
--         copy = {}
--         for orig_key, orig_value in next, orig, nil do
--             copy[deepcopy(orig_key)] = deepcopy(orig_value)
--         end
--         setmetatable(copy, deepcopy(getmetatable(orig)))
--     else -- number, string, boolean, etc
--         copy = orig
--     end
--     return copy
-- end

function update_hitboxes()
	if not game then
		return
	end
	for f = 1, DRAW_DELAY do
		framebuffer[f] = copytable(framebuffer[f+1])
	end

	-- framebuffer[DRAW_DELAY+1] = {game_active = memory.readbyte(game.address.game_phase) > 0}
	framebuffer[DRAW_DELAY+1] = {game_active = true}
	local f = framebuffer[DRAW_DELAY+1]
	if not f.game_active then
		return
	end

	local camera_mode = game.address.camera_mode and memory.readbyte(game.address.camera_mode) or 1
	if not game.address.screen_left[camera_mode] then
		camera_mode = 1
	end
	f.screen_left = memory.readwordsigned(game.address.screen_left[camera_mode])
	f.screen_top  = memory.readwordsigned(game.address.screen_left[camera_mode] + 0x4)

	for _, set in ipairs(game.objects) do
		local obj_count = set.number or set.number_addr and memory.readword(set.number_addr)
		
		for n = 1, obj_count do
			local obj = {base = set.address and set.address + (n-1) * set.space or 0xff0000 + memory.readword(set.addr_list-(n-1)*2)}
			if memory.readword(obj.base) >= game.exist_val then
				obj.projectile = set.projectile
				obj.invulnerable = (set.hp and memory.readwordsigned(obj.base + set.hp) < 0) or
					(set.alive and memory.readword(obj.base + set.alive) == 0) or
					(set.invulnerable and memory.readword(obj.base + set.invulnerable) > 0)
				obj.harmless = set.harmless or (set.active and inactive(obj.base, set.active))
				if set.hp then
					obj.life = memory.readwordsigned(obj.base+set.hp)
					obj.life_previous = memory.readwordsigned(obj.base+set.hp_previous)
				end
				if set.command then
					obj.command = memory.readbyte(obj.base+set.command)
					obj.command_countdown = memory.readbyte(obj.base+set.command_countdown)
				end
				
				if memory.readbyte(obj.base + 0x63) == 1 then -- on horse
					local d1 = memory.readbyte(obj.base + 0xa0)
					local d2 = memory.readbyte(obj.base + 0xa1)
					if d1 ~= 0 and d1 == d2 then
						if d1 == 2 or d1 == 6 or d1 == 0xa then
							if memory.readbyte(obj.base + 0x16) == 0x00 then
								obj.turnback_countdown = memory.readbyte(obj.base+0xb7)
							end
						elseif d1 == 1 or d1 == 5 or d1 == 9 then
							if memory.readbyte(obj.base + 0x16) == 0xff then
								obj.turnback_countdown = memory.readbyte(obj.base+0xb7)
							end
						end
					end
				elseif set.command_grab and memory.readbyte(obj.base+0x21) == 0x1 then -- not no horse and kassar/zhang fei
					obj.command_grab = memory.readbyte(obj.base+set.command_grab)
					obj.command_grab_countdown = {}
					for i,v in ipairs(set.command_grab_countdown) do
						table.insert(obj.command_grab_countdown,memory.readbyte(obj.base+v))
					end
				end

				if set.weapon and memory.readword(obj.base+set.weapon) ~= 0 then
					local weapon_obj = object_pos(f,{base = 0xff0000+memory.readword(obj.base+set.weapon)})
					weapon_obj.remain = memory.readdword(weapon_obj.base + 0xaa)
					weapon_obj.remain_possibility = memory.readbytesigned(weapon_obj.remain)
					if weapon_obj.remain_possibility < 0 then
						weapon_obj.remain_possibility = - weapon_obj.remain_possibility
					end
					table.insert(f,weapon_obj)
				end

				if set.remain then
					local remain = memory.readdword(obj.base + set.remain)
					obj.remain_possibility = memory.readbytesigned(remain)
					if obj.remain_possibility < 0 then
						obj.remain_possibility = - obj.remain_possibility
					end
				end

				if set.temp1 then
					obj.temp1 = memory.readword(obj.base+set.temp1)
					obj.temp2 = memory.readword(obj.base+set.temp2)
					obj.temp3 = memory.readword(obj.base+set.temp3)
				end
				obj.addr_table = set.addr_table
				obj.status = set.status
				obj.style = set.style
				table.insert(f, update_object(f, obj))
				if obj.box_count then
					f.box_count = obj.box_count
				end
			end
		end
	end
end


-- emu.registerafter( function()
-- 	update_hitboxes()
-- end)


--------------------------------------------------------------------------------
-- draw the hitboxes

local function draw_hitbox(hb)
	if not hb then
		return
	end

	if globals.draw_mini_axis then
		draw_line(screen,hb.val_x, hb.val_y+globals.mini_axis_size-1, hb.val_x, hb.val_y-globals.mini_axis_size+1, boxes[hb.type].outline)
		draw_line(screen,hb.val_x-globals.mini_axis_size, hb.val_y, hb.val_x+globals.mini_axis_size, hb.val_y, boxes[hb.type].outline)
	end

	if hb.type == \"beheld\" then
		draw_line(screen,hb.val_x, hb.val_y-globals.mini_axis_size, hb.val_x, hb.val_y+globals.mini_axis_size, boxes[hb.type].outline)
		draw_line(screen,hb.val_x-globals.mini_axis_size, hb.val_y, hb.val_x+globals.mini_axis_size, hb.val_y, boxes[hb.type].outline)
		return
	end

	if hb.style == \"noheight\" then
		draw_box(screen,hb.left, hb.top, hb.right, hb.bottom, boxes[hb.type].fill)
	else
		draw_box(screen,hb.left, hb.top, hb.right, hb.bottom, boxes[hb.type].fill, boxes[hb.type].outline)
	end
end

local function draw_axis(obj)
	draw_line(screen,obj.pos_x, obj.pos_y+globals.axis_size-1, obj.pos_x, obj.pos_y-globals.axis_size+1, 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)
end

local function display_bar(obj)
	if obj.life then
		local life_color = globals.text_color.life
		if obj.life < 0 then
			life_color = globals.text_color.life_dead
		elseif obj.life < 26 then
			life_color = globals.text_color.life_critical
		elseif obj.life < 52 then
			life_color = globals.text_color.life_low
		elseif obj.life > 104 then
			life_color = globals.text_color.life_high
		end
		if obj.life_previous and obj.life ~= obj.life_previous then
			-- draw_text(screen,obj.pos_x-26, obj.pos_y-8, string.format(\"%d (%d)\",obj.life,obj.life-obj.life_previous),life_color)
			-- draw_text(screen,obj.pos_x-26, obj.pos_y-8,\"18\",life_color)
		else
			-- draw_text(screen,obj.pos_x-26, obj.pos_y-8, string.format(\"%d\",obj.life),life_color)
		end
		-- bar_count = math.floor(obj.life/104)
		if obj.life%104 == 0 then 
			bar_count = obj.life/104
		else
			bar_count = (obj.life - obj.life%104)/104
		end
		for i=0,bar_count do
			if i < bar_count then
				draw_box(screen,obj.pos_x-26, obj.pos_y+i*4, obj.pos_x-26+104/2, obj.pos_y+i*4+3, life_color-0x40000000)
			else
				draw_box(screen,obj.pos_x-26, obj.pos_y+i*4, obj.pos_x-26+(obj.life%104)/2, obj.pos_y+i*4+3, life_color-0x40000000)
			end
		end
	end

	if obj.remain_possibility then
		local color = globals.text_color.remain_possibility
		if obj.remain_possibility >= 0x18 then
			color = globals.text_color.remain_possibility_low
		elseif obj.remain_possibility >= 0x20 then
			obj.remain_possibility = 0x20
			color = globals.text_color.remain_possibility_none
		end
		-- draw_text(screen,obj.pos_x-4, obj.pos_y-8, (0x20 - obj.remain_possibility)/32,color)
	end

	if obj.command and obj.command ~= 0 then
		local color = obj.command == 2 and 0xa0cccc00 or 0xa0cc0000
		draw_box(screen,obj.pos_x-26, obj.pos_y-12, obj.pos_x-26+obj.command_countdown, obj.pos_y-12+3, color)
	end

	if obj.turnback_countdown then
		local color = 0xa0cc0000
		draw_box(screen,obj.pos_x-26, obj.pos_y-16, obj.pos_x-26+obj.turnback_countdown, obj.pos_y-16+3, color)
	elseif obj.command_grab and obj.command_grab ~= 0 then
		local color = obj.command_grab == 0xff and 0xa0cc0000 or 0xa0cccc00
		for i,v in ipairs(obj.command_grab_countdown) do
			draw_box(screen,obj.pos_x-26+(i-1)*2, obj.pos_y-13-v, obj.pos_x-26+i*2, obj.pos_y-13, color)
		end
	end
end


function render_hitboxes()
	local f = framebuffer[1]
	if not f then return end
	if not f.game_active then
		return
	end

	-- if globals.blank_screen then
	-- 	s:draw_box(0, 0, screenwidth, screenheight, globals.blank_color)
	-- end

	for entry = 1, f.box_count or #game.box_list 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

	for _, obj in ipairs(f) do
		display_bar(obj)
	end

end



-- initialize on game startup

local function initialize_fb()
	framebuffer = {}
	for f = 1, DRAW_DELAY + 1 do
		framebuffer[f] = {}
	end
end


local function whatgame()
	-- print()
	game = profile
	initialize_fb()
end


initialize_fb()
whatgame()

",
      "run":"update_hitboxes() render_hitboxes()"
    },
    "screen":{
      "screen":":screen"
    },
    "desc":"Hitbox viewer"
}]
hitbox viewer for wof (warriors of fate)
save as wof.json and put it under cheat folder. You need plugin cheat to enable this.
rabbyzero
Posts: 24
Joined: Thu Nov 16, 2017 3:20 am

Re: [wof] warriors of fate hitbox viewer

Post by rabbyzero »

Code: Select all

mem = manager:machine().devices[":maincpu"].spaces["program"]

local memory = {
	readbyte        = function(addr) return mem:read_u8(addr) end,
	readbytesigned  = function(addr) return mem:read_i8(addr) end,
	
	readword        = function(addr) return mem:read_u16(addr) end,
	readwordsigned  = function(addr) return mem:read_i16(addr) end,

	readdword       = function(addr) return mem:read_u32(addr) end,
	readdwordsigned = function(addr) return mem:read_i32(addr) end,
}

local s = manager:machine().screens[":screen"]
local screenwidth = s:width()
local screenheight = s:height()

local olddata = {}
for i=1,0xF0,1 do
	olddata[i] = 0
end


local boxes = {
	      ["vulnerability"] = {color = 0x7777FF, fill = 0x40, outline = 0xFF},
	             ["attack"] = {color = 0xFF0000, fill = 0x40, outline = 0xFF},
	               ["hold"] = {color = 0xFFff00, fill = 0x40, outline = 0xFF},
	             ["beheld"] = {color = 0xFF00ff, fill = 0x20, outline = 0xFF},
	["proj. vulnerability"] = {color = 0x00FFFF, fill = 0x40, outline = 0xFF},
	       ["proj. attack"] = {color = 0xFF66FF, fill = 0x40, outline = 0xFF},
}

local globals = {
	axis_color     = 0xFFFFFFFF,
	blank_color    = 0xFFFFFFFF,
	axis_size      = 4,
	mini_axis_size = 2,
	blank_screen   = false,
	draw_axis      = true,
	draw_mini_axis = false,
	no_alpha       = false, --fill = 0x00, outline = 0xFF for all box types

	text_color = {
		life = 0xCC00FF00,
		life_low = 0xCCffFF00,
		life_critical = 0xCCff0000,
		life_high = 0xCCff00ff,
		life_dead = 0x80808080,
		remain_possibility = 0xCC00FF00,
		remain_possibility_low = 0xCCffFF00,
		remain_possibility_none = 0xCCff0000,
	}
}

local count = 0

local profile = {
{	game = "wof",
	address = {
		screen_left = 0xFF7a8c, --0xFF6346,0xFF6348,0xFF7a8c,0xff7c26
		game_phase  = 0xFF0000,
	},
	offset = {
		flip_x = 0x16,
		pos_x  = 0x04,
		pos_y  = 0x08,
		pos_z  = 0x0C,
		pos_z2 = 0x10, -- ground height
	},
	objects = {
		{address = 0xFFbe1c, number = 0x03, space = 0xe0, invulnerable = 0xB9, 
		 hp = 0x82, hp_previous = 0x84, 
		 status = 0x31, command = 0xb0, command_countdown = 0xb1, command_grab = 0xc1, 
		 command_grab_countdown = {0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9},
		 weapon = 0x8c,
		 }, --players
		{addr_list = 0xff837e, number_addr = 0xff8318, status = 0x31, hp = 0x82, hp_previous = 0x84,
		temp1 = 0x2A, temp2 = 0x2c, temp3 =0x2e}, -- enemies and corpses
		{addr_list = 0xff841e, number_addr = 0xff831a, status = 0x31}, -- projectiles
		{addr_list = 0xff84be, number_addr = 0xff831c, status = 0x31}, -- projectiles
		{addr_list = 0xff850e, number_addr = 0xff831e, style = "noheight"}, -- items on ground
		{addr_list = 0xff855e, number_addr = 0xff8320, style = "noheight", remain = "0xAA"}, -- weapons on ground
		{addr_list = 0xff85ae, number_addr = 0xff8322, hp=0x82, hp_previous = 0x84}, -- chests
	},
	box = {
		radius_read = memory.readwordsigned,
		val_x = 0x0, val_y = 0x4, rad_x = 0x2, rad_y = 0x6,
		radscale = 2,
	},
	box_list = {
		{id_ptr = 0x6e, type = "vulnerability"},
		{id_ptr = 0x6c, type = "beheld"},
		{id_ptr = 0x70, status_code = 0x4, type = "attack"},
		{id_ptr = 0x70, status_code = 0xc, type = "attack"},
		{id_ptr = 0x70, status_code = 0x8, type = "hold"},
		{id_ptr = 0x70, status_code = 0x1C, type = "hold"},
		{id_ptr = 0x70, status_code = 0x1C, type = "attack"},
		{id_ptr = 0x70, status_code = 0x14, type = "attack"},
		{id_ptr = 0x70, status_code = 0x24, type = "attack"},
		{id_ptr = 0x70, status_code = 0x18, type = "attack"},
		{id_ptr = 0x32, status_code = 0x18, type = "attack", style = "noheight"},
		{id = function(base_addr) return memory.readword(memory.readword(base_addr+0x8c) + 0xFF0000 + 0x70) end,
		 status_code = 0x20, type = "weapon"}, -- find weapon
		{id = function(base_addr) return 0x16c8 end, character = 0x1, type = "hold"} -- command grab
	},
	id_read = memory.readword,
	box_address = function(obj, box, box_entry)
		local address = 0xe3e4a
		return address + box.id
	end,
},
}

--------------------------------------------------------------------------------
-- post-process modules

for game in ipairs(profile) do
	local g = profile[game]
	g.box.offset_read = g.box.radius_read == memory.readbytesigned and memory.readbytesigned or memory.readwordsigned
	g.offset.pos_y = g.offset.pos_x + 0x4
	g.exist_val = g.exist_val or 0x0100
	for entry in ipairs(g.objects) do
		if type(g.objects[entry].active) == "number" then
			g.objects[entry].active = {g.objects[entry].active}
		end
	end
	if type(g.address.screen_left) ~= "table" then
		g.address.screen_left = {g.address.screen_left}
	end
end

for _, box in pairs(boxes) do
	box.fill    = (globals.no_alpha and 0x00 or box.fill)    * 0x1000000 + box.color
	box.outline = (globals.no_alpha and 0x00 or box.outline) * 0x1000000 + box.color
end

local game, framebuffer
local DRAW_DELAY = 1
-- if fba then
-- 	DRAW_DELAY = DRAW_DELAY + 1
-- end


--------------------------------------------------------------------------------
-- prepare the hitboxes

local set_box_center = {
	function(obj, box)
		return
			obj.pos_x + box.val_x * (obj.flip_x > 0 and -1 or 1),
			obj.pos_y - box.val_y
	end,

	function(obj, box)
		return
			obj.pos_x + (box.val_x + box.rad_x) * (obj.flip_x > 0 and -1 or 1),
			obj.pos_y - (box.val_y + box.rad_y)
	end,
}

local function object_pos(f,obj) 
	obj.flip_x = memory.readbyte(obj.base + game.offset.flip_x)
	obj.pos_z  = game.offset.pos_z and memory.readwordsigned(obj.base + game.offset.pos_z) or 0
	obj.pos_z  = obj.pos_z + (game.offset.pos_z2 and memory.readwordsigned(obj.base + game.offset.pos_z2) or 0)
	obj.pos_x  = memory.readwordsigned(obj.base + game.offset.pos_x) - f.screen_left
	obj.pos_y  = memory.readwordsigned(obj.base + game.offset.pos_y) + obj.pos_z
	obj.pos_y  = screenheight - (obj.pos_y - 0x0F) + f.screen_top
	return obj
end


local function define_box(f,obj, box_entry)
	local box = {type = box_entry.type, style = box_entry.style}
	local base_id = obj.base

	if box_entry.status_code then 
		if not obj.status or memory.readbyte(obj.base+obj.status) ~= box_entry.status_code then
			return nil
		end
		if box_entry.status_code == 0x18 and box_entry.id_ptr == 0x70 then return nil end -- no horse
		if box_entry.status_code == 0x00 and memory.readbyte(base_id+0x63) == 1 then return nil end -- on horse
	end

	if box_entry.character then
		if memory.readbyte(base_id + 0x21) ~= box_entry.character then return nil end
		if memory.readbyte(base_id + 0xc1) ~= 0xff then return nil end
	end
	
	if box_entry.id_ptr then 
		if box_entry.id_ptr == 0x32 and memory.readbyte(base_id+0x63) == 1 then return nil end -- on horse
		box.id = game.id_read(base_id + box_entry.id_ptr)
	elseif box_entry.id then
		box.id = box_entry.id(obj.base)
	else
		return nil
	end


	if base_id == 0 or box.id == 0 or (obj.invulnerable and box.type == "vulnerability") then 
		return nil
	end

	box.address = game.box_address(obj, box, box_entry)
	if not box.address then
		return nil
	end

	if obj.style and not box.style then box.style = obj.style end

	box.rad_x = game.box.radius_read(box.address + game.box.rad_x)/game.box.radscale
	box.rad_y = game.box.radius_read(box.address + game.box.rad_y)/game.box.radscale
	box.val_x = game.box.offset_read(box.address + game.box.val_x)
	box.val_y = game.box.offset_read(box.address + game.box.val_y)

	if box.type == "beheld" then
		box.val_y = box.rad_x
		box.rad_x = 0
		box.rad_y = 0
	end
	if box.type == "weapon" then 
		local newobj = object_pos(f,{base=0xff0000+memory.readword(obj.base+0x8c),})
		box.val_x, box.val_y = set_box_center[game.box.radscale](newobj, box)
		box.type = "attack"
	else
		box.val_x, box.val_y = set_box_center[game.box.radscale](obj, box)
	end
	box.left   = box.val_x - box.rad_x
	box.right  = box.val_x + box.rad_x
	box.top    = box.val_y - box.rad_y
	box.bottom = box.val_y + box.rad_y

	return box
end


local function update_object(f, obj)
	object_pos(f,obj)

	for entry in ipairs(game.box_list) do
		table.insert(obj, define_box(f,obj, game.box_list[entry]))
	end
	return obj
end

local function inactive(base, active)
	for _, offset in ipairs(active) do
		if memory.readword(base + offset) > 0 then
			return false
		end
	end
	return true
end

function deepcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[deepcopy(orig_key)] = deepcopy(orig_value)
        end
        setmetatable(copy, deepcopy(getmetatable(orig)))
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

local function update_hitboxes()
	if not game then
		return
	end
	for f = 1, DRAW_DELAY do
		framebuffer[f] = deepcopy(framebuffer[f+1])
	end

	-- framebuffer[DRAW_DELAY+1] = {game_active = memory.readbyte(game.address.game_phase) > 0}
	framebuffer[DRAW_DELAY+1] = {game_active = true}
	local f = framebuffer[DRAW_DELAY+1]
	if not f.game_active then
		return
	end

	local camera_mode = game.address.camera_mode and memory.readbyte(game.address.camera_mode) or 1
	if not game.address.screen_left[camera_mode] then
		camera_mode = 1
	end
	f.screen_left = memory.readwordsigned(game.address.screen_left[camera_mode])
	f.screen_top  = memory.readwordsigned(game.address.screen_left[camera_mode] + 0x4)

	for _, set in ipairs(game.objects) do
		local obj_count = set.number or set.number_addr and memory.readword(set.number_addr)
		
		for n = 1, obj_count do
			local obj = {base = set.address and set.address + (n-1) * set.space or 0xff0000 + memory.readword(set.addr_list-(n-1)*2)}
			if memory.readword(obj.base) >= game.exist_val then
				obj.projectile = set.projectile
				obj.invulnerable = (set.hp and memory.readwordsigned(obj.base + set.hp) < 0) or
					(set.alive and memory.readword(obj.base + set.alive) == 0) or
					(set.invulnerable and memory.readword(obj.base + set.invulnerable) > 0)
				obj.harmless = set.harmless or (set.active and inactive(obj.base, set.active))
				if set.hp then
					obj.life = memory.readwordsigned(obj.base+set.hp)
					obj.life_previous = memory.readwordsigned(obj.base+set.hp_previous)
				end
				if set.command then
					obj.command = memory.readbyte(obj.base+set.command)
					obj.command_countdown = memory.readbyte(obj.base+set.command_countdown)
				end
				
				if memory.readbyte(obj.base + 0x63) == 1 then -- on horse
					local d1 = memory.readbyte(obj.base + 0xa0)
					local d2 = memory.readbyte(obj.base + 0xa1)
					if d1 ~= 0 and d1 == d2 then
						if d1 == 2 or d1 == 6 or d1 == 0xa then
							if memory.readbyte(obj.base + 0x16) == 0x00 then
								obj.turnback_countdown = memory.readbyte(obj.base+0xb7)
							end
						elseif d1 == 1 or d1 == 5 or d1 == 9 then
							if memory.readbyte(obj.base + 0x16) == 0xff then
								obj.turnback_countdown = memory.readbyte(obj.base+0xb7)
							end
						end
					end
				elseif set.command_grab and memory.readbyte(obj.base+0x21) == 0x1 then -- not no horse and kassar/zhang fei
					obj.command_grab = memory.readbyte(obj.base+set.command_grab)
					obj.command_grab_countdown = {}
					for i,v in ipairs(set.command_grab_countdown) do
						table.insert(obj.command_grab_countdown,memory.readbyte(obj.base+v))
					end
				end

				if set.weapon and memory.readword(obj.base+set.weapon) ~= 0 then
					local weapon_obj = object_pos(f,{base = 0xff0000+memory.readword(obj.base+set.weapon)})
					weapon_obj.remain = memory.readdword(weapon_obj.base + 0xaa)
					weapon_obj.remain_possibility = memory.readbytesigned(weapon_obj.remain)
					if weapon_obj.remain_possibility < 0 then
						weapon_obj.remain_possibility = - weapon_obj.remain_possibility
					end
					table.insert(f,weapon_obj)
				end

				if set.remain then
					local remain = memory.readdword(obj.base + set.remain)
					obj.remain_possibility = memory.readbytesigned(remain)
					if obj.remain_possibility < 0 then
						obj.remain_possibility = - obj.remain_possibility
					end
				end

				if set.temp1 then
					obj.temp1 = memory.readword(obj.base+set.temp1)
					obj.temp2 = memory.readword(obj.base+set.temp2)
					obj.temp3 = memory.readword(obj.base+set.temp3)
				end
				obj.addr_table = set.addr_table
				obj.status = set.status
				obj.style = set.style
				table.insert(f, update_object(f, obj))
				if obj.box_count then
					f.box_count = obj.box_count
				end
			end
		end
	end
end


-- emu.registerafter( function()
-- 	update_hitboxes()
-- end)


--------------------------------------------------------------------------------
-- draw the hitboxes

local function draw_hitbox(hb)
	if not hb then
		return
	end

	if globals.draw_mini_axis then
		s:draw_line(hb.val_x, hb.val_y+globals.mini_axis_size-1, hb.val_x, hb.val_y-globals.mini_axis_size+1, boxes[hb.type].outline)
		s:draw_line(hb.val_x-globals.mini_axis_size, hb.val_y, hb.val_x+globals.mini_axis_size, hb.val_y, boxes[hb.type].outline)
	end

	if hb.type == "beheld" then
		s:draw_line(hb.val_x, hb.val_y-globals.mini_axis_size, hb.val_x, hb.val_y+globals.mini_axis_size, boxes[hb.type].outline)
		s:draw_line(hb.val_x-globals.mini_axis_size, hb.val_y, hb.val_x+globals.mini_axis_size, hb.val_y, boxes[hb.type].outline)
		return
	end

	if hb.style == "noheight" then
		s:draw_box(hb.left, hb.top, hb.right, hb.bottom, boxes[hb.type].fill)
	else
		s:draw_box(hb.left, hb.top, hb.right, hb.bottom, boxes[hb.type].fill, boxes[hb.type].outline)
	end
end

local function draw_axis(obj)
	s:draw_line(obj.pos_x, obj.pos_y+globals.axis_size-1, obj.pos_x, obj.pos_y-globals.axis_size+1, globals.axis_color)
	s:draw_line(obj.pos_x-globals.axis_size, obj.pos_y, obj.pos_x+globals.axis_size, obj.pos_y, globals.axis_color)
end

local function display_bar(obj)
	if obj.life then
		local life_color = globals.text_color.life
		if obj.life < 0 then
			life_color = globals.text_color.life_dead
		elseif obj.life < 26 then
			life_color = globals.text_color.life_critical
		elseif obj.life < 52 then
			life_color = globals.text_color.life_low
		elseif obj.life > 104 then
			life_color = globals.text_color.life_high
		end
		if obj.life_previous and obj.life ~= obj.life_previous then
			s:draw_text(obj.pos_x-26, obj.pos_y-8, string.format("%d (%d)",obj.life,obj.life-obj.life_previous),life_color)
		else
			s:draw_text(obj.pos_x-26, obj.pos_y-8, string.format("%d",obj.life),life_color)
		end
		bar_count = math.floor(obj.life/104)
		for i=0,bar_count do
			if i < bar_count then
				s:draw_box(obj.pos_x-26, obj.pos_y+i*4, obj.pos_x-26+104/2, obj.pos_y+i*4+3, life_color-0x40000000)
			else
				s:draw_box(obj.pos_x-26, obj.pos_y+i*4, obj.pos_x-26+(obj.life%104)/2, obj.pos_y+i*4+3, life_color-0x40000000)
			end
		end
	end

	if obj.remain_possibility then
		local color = globals.text_color.remain_possibility
		if obj.remain_possibility >= 0x18 then
			color = globals.text_color.remain_possibility_low
		elseif obj.remain_possibility >= 0x20 then
			obj.remain_possibility = 0x20
			color = globals.text_color.remain_possibility_none
		end
		s:draw_text(obj.pos_x-4, obj.pos_y-8, string.format("%d/32",0x20 - obj.remain_possibility),color)
	end

	if obj.command and obj.command ~= 0 then
		local color = obj.command == 2 and 0xa0cccc00 or 0xa0cc0000
		s:draw_box(obj.pos_x-26, obj.pos_y-12, obj.pos_x-26+obj.command_countdown, obj.pos_y-12+3, color)
	end

	if obj.turnback_countdown then
		local color = 0xa0cc0000
		s:draw_box(obj.pos_x-26, obj.pos_y-16, obj.pos_x-26+obj.turnback_countdown, obj.pos_y-16+3, color)
	elseif obj.command_grab and obj.command_grab ~= 0 then
		local color = obj.command_grab == 0xff and 0xa0cc0000 or 0xa0cccc00
		for i,v in ipairs(obj.command_grab_countdown) do
			s:draw_box(obj.pos_x-26+(i-1)*2, obj.pos_y-13-v, obj.pos_x-26+i*2, obj.pos_y-13, color)
		end
	end
end


local function render_hitboxes()
	local f = framebuffer[1]
	if not f then return end
	if not f.game_active then
		return
	end

	if globals.blank_screen then
		s:draw_box(0, 0, screenwidth, screenheight, globals.blank_color)
	end

	for entry = 1, f.box_count or #game.box_list 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

	for _, obj in ipairs(f) do
		display_bar(obj)
	end

end



emu.register_frame_done(function()
	update_hitboxes()
	render_hitboxes()
-- local watch_list = {
-- 		0x4,0x5,0x8,0x9,0xc,0xd,
-- 		0x84,0x85,0x82,0x83,0x16,0x70,0x10,0x11,
-- 		0x16,0x31,0xb9,0x78,0xe0,0x30,0x6e,0x6f,
-- 		0x7c,0x7d,0x7e,0x7f,0x79,0x72,0x73,0x16,
-- 		0x96,0x6a,0x6b,0x82,0x83,0x3a,0x3b,0x6a,0x6b,
-- 		0xa9
-- 	}
	
	
-- 	local offset = 0xffbe10
-- 	local base_setting = memory.readword(0xff8510)
-- 	if base_setting ~= 0 then
-- 		offset=0xff0000+base_setting
-- 	end

-- 	-- local offset = 0xffcf90
-- 	for i = 0,0xE,1 do
-- 		s:draw_box(1,2+i*8,92,10+i*8,0x40000000)
-- 		for ii=0,0xF,1 do
-- 			local addr = offset+i*0x10+ii
-- 			local data = memory.readbyte(addr)
			
-- 			local text_color = (data == olddata[i*0x10+ii+1] and 0x80FFFFFF) or 0xA0FF0000
-- 			local watch_color = (data == olddata[i*0x10+ii+1] and 0xFFffFF00) or 0xFF00FF00

-- 			for _,j in ipairs(watch_list) do 
-- 				if addr - offset == j + 0xC then 
-- 					text_color = watch_color
-- 					break
-- 				end
-- 			end

-- 			s:draw_text(1+ii*12,2+i*8,string.format("%02X",data),text_color)
-- 			olddata[i*0x10+ii+1]=data
-- 		end
-- 	end
	
end)

-- initialize on game startup

local function initialize_fb()
	framebuffer = {}
	for f = 1, DRAW_DELAY + 1 do
		framebuffer[f] = {}
	end
end


local function whatgame()
	print()
	game = nil
	initialize_fb()
	for _, module in ipairs(profile) do
			if emu.romname() == module.game then
			print("drawing hitboxes for " .. emu.gamename())
			game = module
			return
		end
	end
	print("not prepared for " .. emu.gamename())
end


initialize_fb()
whatgame()
This is lua version. save it as a lua file(wof-hitbox.lua for example) and load with -autoboot_script wof-hitbox.lua options to use this.
There are more features. It adds hp and damage display, next time weapon remain probability.
User avatar
Pugsy
Posts: 3638
Joined: Fri Aug 17, 2001 12:59 am
Location: North Wales, UK.
Has thanked: 1 time
Been thanked: 12 times
Contact:

Re: [wof] warriors of fate hitbox viewer

Post by Pugsy »

Thanks, I've not added it as yet. Fixed the height call (MAME changes) but I enabled a Invincibility cheat and the hitbox around the main character disappeared completely.
Pugsy

Servicing your cheating needs since 1985 8)

Grab the latest cheat collection:
MAME 0.259 XML cheat collection (6 OCTOBER 2023) from http://www.mamecheat.co.uk or direct from:-
https://mega.nz/file/q4dHGZ6K#i-EUiqIjH ... KMz7hnbTfw (ZIP Archive 3.76MB)
rabbyzero
Posts: 24
Joined: Thu Nov 16, 2017 3:20 am

Re: [wof] warriors of fate hitbox viewer

Post by rabbyzero »

Invincibility will certainly remove the main character hitbox. This is designed behavior of invincibility.
I do not understand what is "height call".
User avatar
Pugsy
Posts: 3638
Joined: Fri Aug 17, 2001 12:59 am
Location: North Wales, UK.
Has thanked: 1 time
Been thanked: 12 times
Contact:

Re: [wof] warriors of fate hitbox viewer

Post by Pugsy »

rabbyzero wrote: Fri Feb 23, 2024 5:04 pm Invincibility will certainly remove the main character hitbox. This is designed behavior of invincibility.
I do not understand what is "height call".
Looks like I never added this oops. I think I must have waited for a reply and forgot about it...

The height change was related to screen.height() becoming screen.height

https://www.mamecheat.co.uk/forums/view ... 371#p26371
Pugsy

Servicing your cheating needs since 1985 8)

Grab the latest cheat collection:
MAME 0.259 XML cheat collection (6 OCTOBER 2023) from http://www.mamecheat.co.uk or direct from:-
https://mega.nz/file/q4dHGZ6K#i-EUiqIjH ... KMz7hnbTfw (ZIP Archive 3.76MB)
Post Reply