Advertisement
nodthenbow

CPS2 hitbox veiwer from mame-rr

Feb 20th, 2018
346
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 44.69 KB | None | 0 0
  1. print("CPS-2 fighting game hitbox viewer")
  2. print("February 20, 2012")
  3. print("http://code.google.com/p/mame-rr/wiki/Hitboxes")
  4. print("Lua hotkey 1: toggle blank screen")
  5. print("Lua hotkey 2: toggle object axis")
  6. print("Lua hotkey 3: toggle hitbox axis")
  7. print("Lua hotkey 4: toggle pushboxes")
  8. print("Lua hotkey 5: toggle throwable boxes")
  9.  
  10. local boxes = {
  11.           ["vulnerability"] = {color = 0x7777FF, fill = 0x40, outline = 0xFF},
  12.                  ["attack"] = {color = 0xFF0000, fill = 0x40, outline = 0xFF},
  13.     ["proj. vulnerability"] = {color = 0x00FFFF, fill = 0x40, outline = 0xFF},
  14.            ["proj. attack"] = {color = 0xFF66FF, fill = 0x40, outline = 0xFF},
  15.                    ["push"] = {color = 0x00FF00, fill = 0x20, outline = 0xFF},
  16.                ["tripwire"] = {color = 0xFF66FF, fill = 0x40, outline = 0xFF}, --sfa3
  17.                  ["negate"] = {color = 0xFFFF00, fill = 0x40, outline = 0xFF}, --dstlk, nwarr
  18.                   ["throw"] = {color = 0xFFFF00, fill = 0x40, outline = 0xFF},
  19.              ["axis throw"] = {color = 0xFFAA00, fill = 0x40, outline = 0xFF}, --sfa, sfa2, nwarr
  20.               ["throwable"] = {color = 0xF0F0F0, fill = 0x20, outline = 0xFF},
  21. }
  22.  
  23. local globals = {
  24.     axis_color           = 0xFFFFFFFF,
  25.     blank_color          = 0xFFFFFFFF,
  26.     axis_size            = 12,
  27.     mini_axis_size       = 2,
  28.     blank_screen         = false,
  29.     draw_axis            = true,
  30.     draw_mini_axis       = false,
  31.     draw_pushboxes       = true,
  32.     draw_throwable_boxes = false,
  33.     no_alpha             = false, --fill = 0x00, outline = 0xFF for all box types
  34.     ground_throw_height  = 0x50, --default for sfa & sfa2 if pushbox unavailable
  35. }
  36.  
  37. --------------------------------------------------------------------------------
  38. -- game-specific modules
  39.  
  40. local rb, rbs, rw, rws, rd = memory.readbyte, memory.readbytesigned, memory.readword, memory.readwordsigned, memory.readdword
  41. local any_true, get_thrower, insert_throw, signed_register, define_box, get_x, get_y
  42. local game, frame_buffer, throw_buffer
  43.  
  44. local profile = {
  45. {   games = {"sfa"},
  46.     number = {players = 3, projectiles = 8},
  47.     address = {
  48.         player      = 0xFF8400,
  49.         projectile  = 0xFF9000,
  50.         screen_left = 0xFF8290,
  51.     },
  52.     offset = {
  53.         object_space = 0x80,
  54.         flip_x       = 0x0B,
  55.         hitbox_ptr   = {player = 0x50, projectile = 0x50},
  56.     },
  57.     friends = {0x0D},
  58.     box = {
  59.         radius_read = rb,
  60.         offset_read = rbs,
  61.         val_x = 0x0, val_y = 0x1, rad_x = 0x2, rad_y = 0x3,
  62.     },
  63.     box_list = {
  64.         {anim_ptr = 0x20, addr_table_ptr = 0x08, p_addr_table_ptr = 0x4, id_ptr = 0x0C, id_shift = 0x2, type = "push"},
  65.         {anim_ptr = 0x20, addr_table_ptr = 0x00, p_addr_table_ptr = 0x0, id_ptr = 0x08, id_shift = 0x2, type = "vulnerability"},
  66.         {anim_ptr = 0x20, addr_table_ptr = 0x02, p_addr_table_ptr = 0x0, id_ptr = 0x09, id_shift = 0x2, type = "vulnerability"},
  67.         {anim_ptr = 0x20, addr_table_ptr = 0x04, p_addr_table_ptr = 0x0, id_ptr = 0x0A, id_shift = 0x2, type = "vulnerability"},
  68.         {anim_ptr = 0x20, addr_table_ptr = 0x08, p_addr_table_ptr = 0x4, id_ptr = 0x0C, id_shift = 0x2, type = "throwable"},
  69.         {anim_ptr = 0x20, addr_table_ptr = 0x06, p_addr_table_ptr = 0x2, id_ptr = 0x0B, id_shift = 0x4, type = "attack"},
  70.     },
  71.     breakpoints = {
  72.         {["sfa"] = 0x020F14, func = function() --ground throws
  73.             insert_throw({
  74.                 val_x = signed_register("d0"),
  75.                 rad_x = signed_register("d1"),
  76.                 type = "throw",
  77.             })
  78.         end},
  79.         {["sfa"] = 0x020FF2, func = function() --air throws
  80.             insert_throw({
  81.                 val_x = signed_register("d0"),
  82.                 rad_x = signed_register("d1"),
  83.                 val_y = signed_register("d2"),
  84.                 rad_y = signed_register("d3"),
  85.                 type = "axis throw",
  86.             })
  87.         end},
  88.     },
  89.     clones = {
  90.         ["sfar3"] = -0xB4, ["sfar2"] = -0x64, ["sfar1"] = -0x18, ["sfad"] = 0, ["sfau"] = -0x64,
  91.         ["sfza"] = -0x64, ["sfzbr1"] = 0, ["sfzb"] = 0x5B4, ["sfzhr1"] = -0x64, ["sfzh"] = -0x18,
  92.         ["sfzjr2"] = -0xB4, ["sfzjr1"] = -0x64, ["sfzj"] = 0,
  93.     },
  94.     process_throw = function(obj, box)
  95.         box.val_y = box.val_y or obj.val_y or globals.ground_throw_height/2
  96.         box.rad_y = box.rad_y or obj.rad_y or globals.ground_throw_height/2
  97.  
  98.         box.val_x  = obj.pos_x + box.val_x * obj.flip_x
  99.         box.val_y  = obj.pos_y - box.val_y
  100.         box.left   = box.val_x - box.rad_x
  101.         box.right  = box.val_x + box.rad_x
  102.         box.top    = box.val_y - box.rad_y
  103.         box.bottom = box.val_y + box.rad_y
  104.  
  105.         return box
  106.     end,
  107.     active = function() return any_true({
  108.         (rd(0xFF8004) == 0x40000 and rd(0xFF8008) == 0x40000),
  109.         (rw(0xFF8008) == 0x2 and rw(0xFF800A) > 0),
  110.     }) end,
  111.     invulnerable = function(obj, box) return any_true({
  112.         rb(obj.base + 0x13B) > 0,
  113.     }) end,
  114.     unthrowable = function(obj, box) return any_true({
  115.         rb(obj.base + 0x241) > 0,
  116.         rw(obj.base + 0x004) ~= 0x200,
  117.         rb(obj.base + 0x02F) > 0,
  118.         bit.band(rd(rd(obj.base + 0x020) + 0x8), 0xFFFFFF00) == 0,
  119.     }) end,
  120. },
  121. {   games = {"sfa2", "sfz2al"},
  122.     number = {players = 3, projectiles = 26},
  123.     address = {
  124.         player      = 0xFF8400,
  125.         projectile  = 0xFF9400,
  126.         screen_left = 0xFF8290,
  127.     },
  128.     offset = {
  129.         object_space = 0x80,
  130.         flip_x       = 0x0B,
  131.         hitbox_ptr   = {player = nil, projectile = 0x60},
  132.     },
  133.     friends = {0x17},
  134.     box_list = {
  135.         {anim_ptr = 0x1C, addr_table_ptr = 0x120, p_addr_table_ptr = 0x4, id_ptr = 0x0C, id_shift = 0x3, type = "push"},
  136.         {anim_ptr = 0x1C, addr_table_ptr = 0x110, p_addr_table_ptr = 0x0, id_ptr = 0x08, id_shift = 0x3, type = "vulnerability"},
  137.         {anim_ptr = 0x1C, addr_table_ptr = 0x114, p_addr_table_ptr = 0x0, id_ptr = 0x09, id_shift = 0x3, type = "vulnerability"},
  138.         {anim_ptr = 0x1C, addr_table_ptr = 0x118, p_addr_table_ptr = 0x0, id_ptr = 0x0A, id_shift = 0x3, type = "vulnerability"},
  139.         {anim_ptr = 0x1C, addr_table_ptr = 0x120, p_addr_table_ptr = 0x4, id_ptr = 0x0C, id_shift = 0x3, type = "throwable"},
  140.         {anim_ptr = 0x1C, addr_table_ptr = 0x11C, p_addr_table_ptr = 0x2, id_ptr = 0x0B, id_shift = 0x5, type = "attack"},
  141.     },
  142.     breakpoints = {
  143.         {["sfa2"] = 0x025516, ["sfz2al"] = 0x025C8A, func = function() --ground throws
  144.             insert_throw({
  145.                 val_x = signed_register("d0"),
  146.                 rad_x = signed_register("d1"),
  147.                 type = "throw",
  148.             })
  149.         end},
  150.         {["sfa2"] = 0x02564A, ["sfz2al"] = 0x025DD6, func = function() --tripwire
  151.             insert_throw({
  152.                 val_x = signed_register("d0"),
  153.                 rad_x = signed_register("d1"),
  154.                 type = "throw",
  155.             })
  156.         end},
  157.         {["sfa2"] = 0x025786, ["sfz2al"] = 0x025F12, func = function() --air throws
  158.             insert_throw({
  159.                 val_x = signed_register("d0"),
  160.                 rad_x = signed_register("d1"),
  161.                 val_y = signed_register("d2"),
  162.                 rad_y = signed_register("d3"),
  163.                 type = "axis throw",
  164.             })
  165.         end},
  166.     },
  167.     clones = {
  168.         ["sfa2u"] = 0xBD2, ["sfa2ur1"] = 0xBC2, ["sfz2ad"] = 0xC0A, ["sfz2a"] = 0xC0A,
  169.         ["sfz2br1"] = 0x48, ["sfz2b"] = 0x42, ["sfz2h"] = 0x48, ["sfz2jd"] = 0xC0A, ["sfz2j"] = 0xC0A, ["sfz2n"] = 0,
  170.         ["sfz2al"] = 0, ["sfz2ald"] = 0, ["sfz2alb"] = 0, ["sfz2alh"] = 0, ["sfz2alj"] = -0x310,
  171.     },
  172.     process_throw = function(obj, box)
  173.         box.val_y = box.val_y or obj.val_y or globals.ground_throw_height/2
  174.         box.rad_y = box.rad_y or obj.rad_y or globals.ground_throw_height/2
  175.  
  176.         box.val_x  = obj.pos_x + box.val_x * obj.flip_x
  177.         box.val_y  = obj.pos_y - box.val_y
  178.         box.left   = box.val_x - box.rad_x
  179.         box.right  = box.val_x + box.rad_x
  180.         box.top    = box.val_y - box.rad_y
  181.         box.bottom = box.val_y + box.rad_y
  182.  
  183.         return box
  184.     end,
  185.     active = function() return any_true({
  186.         (rd(0xFF8004) == 0x40000 and
  187.         (rd(0xFF8008) == 0x40000 or rd(0xFF8008) == 0xA0000)),
  188.         rw(0xFF8008) == 0x2 and rw(0xFF800A) > 0,
  189.     }) end,
  190.     invulnerable = function(obj, box) return any_true({
  191.         rb(obj.base + 0x25B) > 0,
  192.         rb(obj.base + 0x273) > 0,
  193.         rb(obj.base + 0x13B) > 0,
  194.     }) end,
  195.     unthrowable = function(obj, box)
  196.         if any_true({
  197.                 rb(0xFF810E) > 0,
  198.                 rb(obj.base + 0x273) > 0,
  199.                 bit.band(rd(rd(obj.base + 0x01C) + 0x8), 0xFFFFFF00) == 0,
  200.             }) then
  201.             return true
  202.         elseif rb(0xFF0000 + rw(obj.base + 0x38) + 0x142) > 0 then --opponent in CC
  203.             return any_true({
  204.                 rb(rd(obj.base + 0x01C) + 0xD) > 0,
  205.             })
  206.         else --not in CC
  207.             return any_true({
  208.                 rb(obj.base + 0x241) > 0,
  209.                 rw(obj.base + 0x004) ~= 0x200,
  210.                 rb(obj.base + 0x031) > 0,
  211.             })
  212.         end
  213.     end,
  214. },
  215. {   games = {"sfa3"},
  216.     number = {players = 4, projectiles = 24},
  217.     address = {
  218.         player      = 0xFF8400,
  219.         projectile  = 0xFF9400,
  220.         screen_left = 0xFF8290,
  221.     },
  222.     offset = {
  223.         object_space = 0x100,
  224.         flip_x       = 0x0B,
  225.         hitbox_ptr   = nil,
  226.     },
  227.     friends = {0x17, 0x22},
  228.     box_list = {
  229.         {anim_ptr =  nil, addr_table_ptr = 0x9C, id_ptr =  0xCB, id_shift = 0x3, type = "push"},
  230.         {anim_ptr =  nil, addr_table_ptr = 0x90, id_ptr =  0xC8, id_shift = 0x3, type = "vulnerability"},
  231.         {anim_ptr =  nil, addr_table_ptr = 0x94, id_ptr =  0xC9, id_shift = 0x3, type = "vulnerability"},
  232.         {anim_ptr =  nil, addr_table_ptr = 0x98, id_ptr =  0xCA, id_shift = 0x3, type = "vulnerability"},
  233.         {anim_ptr =  nil, addr_table_ptr = 0x9C, id_ptr =  0xCB, id_shift = 0x3, type = "throwable"}, --identical to pushbox
  234.         {anim_ptr = 0x1C, addr_table_ptr = 0xA0, id_ptr =   0x9, id_shift = 0x5, type = "attack"},
  235.     },
  236.     throw_box_list = {
  237.         {anim_ptr =  nil, addr_table_ptr = 0xA0, id_ptr = 0x32F, id_shift = 0x5, type = "throw", clear = true},
  238.         {anim_ptr =  nil, addr_table_ptr = 0xA0, id_ptr =  0x82, id_shift = 0x5, type = "tripwire", clear = true},
  239.     },
  240.     watchpoints = {
  241.         {offset = 0x32F, size = 1, func = function() insert_throw({
  242.             id = bit.band(memory.getregister("m68000.d0"), 0xFF),
  243.             anim_ptr = nil, addr_table_ptr = 0xA0, type = "throw", id_shift = 0x5,
  244.         }) end},
  245.         {offset = 0x1E4, size = 2, func = function() insert_throw({
  246.             pos_x = bit.band(memory.getregister("m68000.d0"), 0xFFFF),
  247.             anim_ptr = nil, addr_table_ptr = 0xA0, type = "tripwire", id_ptr = 0x82, id_shift = 0x5,
  248.         }) end},
  249.     },
  250.     process_throw = function(obj, box)
  251.         box.pos_x = box.pos_x and rws(obj.base + 0x1E4) + box.pos_x
  252.         return define_box[game.box_type](obj, box)
  253.     end,
  254.     active = function() return any_true({
  255.         (rd(0xFF8004) == 0x40000 and rd(0xFF8008) == 0x60000),
  256.         (rw(0xFF8008) == 0x2 and rw(0xFF800A) > 0),
  257.     }) end,
  258.     invulnerable = function(obj, box) return any_true({
  259.         rb(obj.base + 0x067) > 0,
  260.         rb(obj.base + 0x25D) > 0,
  261.         rb(obj.base + 0x0D6) > 0,
  262.         rb(obj.base + 0x2CE) > 0,
  263.     }) end,
  264.     unpushable = function(obj, box) return any_true({
  265.         rb(obj.base + 0x67) > 0,
  266.     }) end,
  267.     unthrowable = function(obj, box)
  268.         if any_true({
  269.                 rb(obj.base + 0x25D) > 0,
  270.                 rb(obj.base + 0x23F) > 0,
  271.                 rb(obj.base + 0x2CE) > 0,
  272.                 bit.band(rd(obj.base + 0x0C8), 0xFFFFFF00) == 0,
  273.                 rb(obj.base + 0x067) > 0,
  274.             }) then
  275.             return true
  276.         end
  277.         local opp = { base = 0xFF0000 + rw(obj.base + 0x38)}
  278.         opp.air = rb(opp.base + 0x31) > 0
  279.         opp.VC  = rb(opp.base + 0xB9) > 0
  280.         local status = rw(obj.base + 0x4)
  281.         if opp.VC and rb(obj.base + 0x24E) == 0 then --VC: 02E37C
  282.             return
  283.         elseif not opp.air then --ground: 02E3FE
  284.             return any_true({ --02E422
  285.                 status ~= 0x204 and status ~= 0x200 and rb(obj.base + 0x24E) == 0 and
  286.                 (status ~= 0x202 or rb(obj.base + 0x54) ~= 0xC),
  287.                 rb(obj.base + 0x031) > 0,
  288.             })
  289.         else --air: 02E636
  290.             return any_true({ --02E66E
  291.                 rb(obj.base + 0x031) == 0,
  292.                 rb(obj.base + 0x0D6) > 0,
  293.                 status ~= 0x204 and status ~= 0x200 and status ~= 0x202,
  294.             })
  295.         end
  296.     end,
  297. },
  298. {   games = {"dstlk"},
  299.     number = {players = 2, projectiles = 4},
  300.     address = {
  301.         player      = 0xFF8388,
  302.         projectile  = 0xFFAA2E,
  303.         screen_left = 0xFF9518,
  304.     },
  305.     offset = {
  306.         object_space = 0xC0,
  307.         flip_x       = 0x09,
  308.         hitbox_ptr   = {player = 0x5C, projectile = 0x5C},
  309.         character    = 0x3A1,
  310.     },
  311.     box = {val_x = 0x0, val_y = 0x4, rad_x = 0x2, rad_y = 0x6},
  312.     box_list = {
  313.         {anim_ptr = 0x1C, addr_table_ptr = 0x0A, id_ptr = 0x15, id_shift = 0x3, type = "push", no_projectile = true},
  314.         {anim_ptr = 0x1C, addr_table_ptr = 0x00, id_ptr = 0x10, id_shift = 0x3, type = "vulnerability"},
  315.         {anim_ptr = 0x1C, addr_table_ptr = 0x02, id_ptr = 0x11, id_shift = 0x3, type = "vulnerability", no_projectile = true},
  316.         {anim_ptr = 0x1C, addr_table_ptr = 0x04, id_ptr = 0x12, id_shift = 0x3, type = "vulnerability", no_projectile = true},
  317.         {anim_ptr = 0x1C, addr_table_ptr = 0x06, id_ptr = 0x13, id_shift = 0x3, type = "negate"},
  318.         {anim_ptr = 0x1C, addr_table_ptr = 0x08, id_ptr = 0x14, id_shift = 0x4, type = "attack"},
  319.     },
  320.     throw_box_list = {
  321.         {method = "range given", base_x = 0x010, range_x = 0x1E8, range_y = 0x1E9, air_state = 0x4C, type = "throwable"},
  322.     },
  323.     breakpoints = {
  324.         {["dstlk"] = 0x033CBE, func = function() --attempt ground throws from any range
  325.             memory.setregister("m68000.d3", 0)
  326.         end},
  327.         {["dstlk"] = 0x033D22, func = function() --ground throws
  328.             local base = memory.getregister("m68000.a6")
  329.             local range = rws(rd(base + 0x1EA) + rb(base + 0x27))
  330.             insert_throw({range_x = range})
  331.         end},
  332.         {["dstlk"] = 0x033BEC, func = function() --air throws
  333.             local base = memory.getregister("m68000.a6")
  334.             local curr = rw(base + game.offset.player_space - 0x7A)
  335.             local prev = rw(base + game.offset.player_space - 0x78)
  336.             local range = bit.band(memory.getregister("m68000.d0"), 0xFF)
  337.             range = rws(rd(base + 0x1EA) + range)
  338.             if any_true({
  339.                 bit.band(curr, 0x07) == 0,
  340.                 bit.band(bit.band(bit.bnot(prev), curr), 0x60) == 0,
  341.                 range == 0,
  342.             }) then
  343.                 return --input check @ 0451D6
  344.             end
  345.             insert_throw({range_x = range, height = 0xC}) --vertical range @ 033BE0
  346.         end},
  347.         {["dstlk"] = 0x03B8F4, ["dstlku"] = 0x03BBCE, ["dstlkh"] = 0x03BBCE,
  348.             ["vampjr1"] = 0x03DDA4, ["vampj"] = 0x03DDB8, ["vampja"] = 0x03DDB8,
  349.             func = function() --midnight pleasure
  350.                 insert_throw({range_x = 0x24}) --hard range @ 03B938
  351.             end,
  352.         },
  353.     },
  354.     clones = {
  355.         ["dstlka"] = 0, ["dstlkh"] = 0x2D6, ["dstlku1d"] = 0, ["dstlkur1"] = 0, ["dstlku"] = 0x2D6,
  356.         ["vampjr1"] = 0x24A2, ["vampj"] = 0x24B6, ["vampja"] = 0x24B6,
  357.     },
  358.     breakables = {start = 0xFFAD2E, space = 0x80, number = 8},
  359.     process_throw = function(obj, box)
  360.         box.type   = "throw"
  361.         box.right  = obj.pos_x
  362.         box.left   = obj.pos_x + box.range_x * obj.flip_x
  363.         if rb(obj.base + game.offset.character) == 0x06 then --Anakaris
  364.             box.right = box.left - 0x20 * obj.flip_x --hard width @ 033C5C
  365.         end
  366.         box.bottom = obj.pos_y
  367.         box.top    = obj.pos_y - rb(obj.base + 0x1E9)
  368.         if box.height then
  369.             box.bottom = box.top + box.height
  370.         end
  371.         box.val_x  = (box.left + box.right)/2
  372.         box.val_y  = (box.bottom + box.top)/2
  373.        
  374.         return box
  375.     end,
  376.     active = function() return any_true({
  377.         (rd(0xFF8004) == 0x40000 and bit.band(rd(0xFF8008), 0x8FFFF) == 0x80000),
  378.     }) end,
  379.     invulnerable = function(obj, box) return any_true({
  380.         rb(obj.base + 0x15D) > 0,
  381.         (rb(obj.base + 0x167) == 0 and
  382.         rbs(obj.base + 0x062) < 0 and
  383.         rb(obj.base + 0x12A) == 0),
  384.     }) end,
  385.     unpushable = function(obj, box) return any_true({
  386.         rb(obj.base + 0x1E6) > 0,
  387.         rb(obj.base + 0x04D) > 0,
  388.     }) end,
  389.     unthrowable = function(obj, box) return any_true({
  390.         rb(obj.base + 0x062) > 0,
  391.         rw(obj.base + 0x15D) > 0,
  392.     }) end,
  393. },
  394. {   games = {"nwarr"},
  395.     number = {players = 2, projectiles = 12},
  396.     address = {
  397.         player      = 0xFF8388,
  398.         projectile  = 0xFFA86E,
  399.         screen_left = 0xFF8F18,
  400.     },
  401.     offset = {
  402.         player_space = 0x500,
  403.         object_space = 0xC0,
  404.         flip_x       = 0x09,
  405.         hitbox_ptr   = {player = 0x5C, projectile = 0x5C},
  406.         character    = 0x4A1,
  407.     },
  408.     box = {val_x = 0x0, val_y = 0x4, rad_x = 0x2, rad_y = 0x6},
  409.     box_list = {
  410.         {anim_ptr = 0x1C, addr_table_ptr = 0x0A, id_ptr = 0x15, id_shift = 0x3, type = "push", no_projectile = true},
  411.         {anim_ptr = 0x1C, addr_table_ptr = 0x00, id_ptr = 0x10, id_shift = 0x3, type = "vulnerability"},
  412.         {anim_ptr = 0x1C, addr_table_ptr = 0x02, id_ptr = 0x11, id_shift = 0x3, type = "vulnerability", no_projectile = true},
  413.         {anim_ptr = 0x1C, addr_table_ptr = 0x04, id_ptr = 0x12, id_shift = 0x3, type = "vulnerability", no_projectile = true},
  414.         {anim_ptr = 0x1C, addr_table_ptr = 0x06, id_ptr = 0x13, id_shift = 0x3, type = "negate"},
  415.         {anim_ptr = 0x1C, addr_table_ptr = 0x08, id_ptr = 0x14, id_shift = 0x4, type = "attack"},
  416.     },
  417.     throw_box_list = {
  418.         {method = "range given", base_x = 0x010, range_x = 0x1E8, range_y = 0x1E9, air_state = 0x41, type = "throwable"},
  419.         {method = "range given", base_x = 0x192, range_x = 0x196, range_y = 0x194, type = "axis throw"}, --"no_box" projectiles
  420.     },
  421.     breakpoints = {
  422.         {["nwarr"] = 0x02A002, func = function() --attempt ground throws from any range
  423.             memory.setregister("m68000.d3", 0)
  424.         end},
  425.         {["nwarr"] = 0x02A172, func = function() --ground throws
  426.             local base = memory.getregister("m68000.a6")
  427.             local range = rws(rd(base + 0x1EA) + rb(base + 0x27))
  428.             insert_throw({range_x = range})
  429.         end},
  430.         {["nwarr"] = 0x029F5C, func = function() --air throws
  431.             local base = memory.getregister("m68000.a6")
  432.             local curr = rw(base + game.offset.player_space - 0x7A)
  433.             local prev = rw(base + game.offset.player_space - 0x78)
  434.             local range = bit.band(memory.getregister("m68000.d0"), 0xFF)
  435.             range = rws(rd(base + 0x1EA) + range)
  436.             if any_true({
  437.                 bit.band(curr, 0x07) == 0,
  438.                 bit.band(bit.band(bit.bnot(prev), curr), 0x60) == 0,
  439.                 range == 0,
  440.             }) then
  441.                 return --input check @ 0355CE
  442.             end
  443.             insert_throw({range_x = range, height = 0xC}) --vertical range @ 029F50
  444.         end},
  445.         {["nwarr"] = 0x0366C2, ["vhuntjr2"] = 0x036488, ["vhuntjr1"] = 0x0366F4, ["vhuntj"] = 0x0366F4,
  446.             func = function() --midnight pleasure
  447.                 insert_throw({range_x = 0x22}) --hard range @ 036706
  448.             end,
  449.         },
  450.     },
  451.     clones = {
  452.         ["nwarra"] = -0x282, ["nwarrb"] = 0, ["nwarrh"] = 0, ["nwarrud"] = 0, ["nwarru"] = 0,
  453.         ["vhuntjr2"] = -0x25E, ["vhuntjr1"] = 0x024, ["vhuntj"] = 0x024,
  454.     },
  455.     special_projectiles = {start = 0xFF9A6E, space = 0x80, number = 28, whitelist = {
  456.         0x56, --Demitri 263KK
  457.         0x4C, --Gallon 41236KK
  458.         0x5F, --Gallon 63214PP
  459.         0x50, --Lei-Lei 623P
  460.         0x54, --Lei-Lei 214P
  461.         0x6E, --Lei-Lei LK,HK,MP,MP,8
  462.         0x0C, --Morrigan LP,LP,6,LK,HP
  463.         0x40, --Morrigan LP,LP,6,MP,HP
  464.         0x44, --Felicia 41236KK
  465.         0x52, --Aulbath 41236PP
  466.         0x05, --Huitzil GC
  467.         0x22, --Huitzil 63214KK
  468.         0x5A, --Huitzil 623P
  469.         0x70, --Pyron 41236PP/KK
  470.     },
  471.     no_box = { --special throws
  472.         0x3F, --Rapter 623PP
  473.         0x68, --Anakaris 236P
  474.         0x3E, --Aulbath 623PP
  475.     }},
  476.     friends = {0x40, 0x4C, 0x50},
  477.     breakables = {start = 0xFFB16E, space = 0x80, number = 8},
  478.     process_throw = function(obj, box)
  479.         box.type   = "throw"
  480.         box.right  = obj.pos_x
  481.         box.left   = obj.pos_x + box.range_x * obj.flip_x
  482.         box.bottom = obj.pos_y
  483.         box.top    = obj.pos_y - rb(obj.base + 0x1E9)
  484.         if box.height then
  485.             box.bottom = box.top + box.height
  486.         end
  487.         box.val_x  = (box.left + box.right)/2
  488.         box.val_y  = (box.bottom + box.top)/2
  489.        
  490.         return box
  491.     end,
  492.     active = function() return any_true({
  493.         (rd(0xFF8004) == 0x40000 and bit.band(rd(0xFF8008), 0x8FFFF) == 0x80000),
  494.         (rw(0xFF8000) >= 0x0E and rd(0xFF8004) == 0),
  495.     }) end,
  496.     invulnerable = function(obj, box) return any_true({
  497.         (rb(obj.base + 0x49F) == 0 and rb(obj.base + 0x2AF) > 0),
  498.         rb(obj.base + 0x15D) > 0,
  499.         bit.band(rb(obj.base + 0x167), 0x0E) > 0,
  500.         (rb(obj.base + 0x167) == 0 and
  501.         rbs(obj.base + 0x062) < 0 and
  502.         rb(obj.base + 0x12A) == 0),
  503.     }) end,
  504.     unpushable = function(obj, box) return any_true({
  505.         rb(obj.base + 0x1E6) > 0,
  506.         rb(obj.base + 0x04C) > 0,
  507.     }) end,
  508.     unthrowable = function(obj, box) return any_true({
  509.         rb(obj.base + 0x062) > 0,
  510.         rb(obj.base + 0x15D) > 0,
  511.     }) end,
  512. },
  513. {   games = {"vsav", "vhunt2", "vsav2"},
  514.     number = {players = 2, projectiles = 32},
  515.     address = {
  516.         player      = 0xFF8400,
  517.         projectile  = 0xFF9400,
  518.         screen_left = 0xFF8290,
  519.     },
  520.     offset = {
  521.         object_space = 0x100,
  522.         flip_x       = 0x0B,
  523.         hitbox_ptr   = nil,
  524.     },
  525.     box_list = {
  526.         {anim_ptr =  nil, addr_table_ptr = 0x90, id_ptr = 0x97, id_shift = 0x3, type = "push"},
  527.         {anim_ptr =  nil, addr_table_ptr = 0x80, id_ptr = 0x94, id_shift = 0x3, type = "vulnerability"},
  528.         {anim_ptr =  nil, addr_table_ptr = 0x84, id_ptr = 0x95, id_shift = 0x3, type = "vulnerability"},
  529.         {anim_ptr =  nil, addr_table_ptr = 0x88, id_ptr = 0x96, id_shift = 0x3, type = "vulnerability"},
  530.         {anim_ptr =  nil, addr_table_ptr = 0x90, id_ptr = 0x97, id_shift = 0x3, type = "throwable"}, --identical to pushbox
  531.         {anim_ptr = 0x1C, addr_table_ptr = 0x8C, id_ptr = 0x0A, id_shift = 0x5, type = "attack"},
  532.     },
  533.     breakpoints = {
  534.         {["vsav"] = 0x029450, ["vsav2"] = 0x02874A, ["vhunt2"] = 0x028778,
  535.         func = function() --non-ranged throws
  536.             local stack, pc = rd(memory.getregister("m68000.a7")), memory.getregister("m68000.pc")
  537.             if stack ~= pc + 0x30 and stack ~= pc + 0xB2 and stack ~= pc + 0xBE then
  538.                 return --don't draw the initial range check of command throws
  539.             end
  540.             insert_throw({
  541.                 id = bit.band(memory.getregister("m68000.d0"), 0xFF),
  542.                 anim_ptr = nil, addr_table_ptr = 0x8C, id_ptr = 0x98, id_shift = 0x5, type = "throw",
  543.             })
  544.         end},
  545.         {["vsav"] = 0x0191A2, ["vsav2"] = 0x017BA4, ["vhunt2"] = 0x017BAA, ["vhunt2r1"] = 0x017B3A,
  546.         func = function() --attempt cmd throws from any range
  547.             local stack = {rd(memory.getregister("m68000.a7")), rd(memory.getregister("m68000.a7") + 4)}
  548.             local target = {["vsav"] = 0x029472, ["vsav2"] = 0x02876C, ["vhunt2"] = 0x02879A, ["vhunt2r1"] = 0x0286E8}
  549.             target = target[emu.romname()] or target[emu.parentname()]
  550.             if any_true({
  551.                 stack[1] ~= target, --must be a command throw setup
  552.                 stack[2] == target + 0x0E, --don't interfere with actual throw attempts
  553.                 stack[2] == target + 0x90, --don't interfere with attacks
  554.                 stack[2] == target + 0x9C, --don't interfere with vsav2/vhunt2 air throws
  555.                 }) then
  556.                 return
  557.             end
  558.             memory.setregister("m68000.d1", 0)
  559.         end},
  560.         {["vsav"] = 0x029638, ["vsav2"] = 0x02893E, ["vhunt2"] = 0x02896C,
  561.         func = function() --ranged throws
  562.             local base = memory.getregister("m68000.a4")
  563.             insert_throw({
  564.                 id = bit.band(memory.getregister("m68000.d0"), 0xFF),
  565.                 pos_x = get_x(rws(base + game.offset.pos_x)),
  566.                 pos_y = get_y(rws(base + game.offset.pos_y)),
  567.                 anim_ptr = nil, addr_table_ptr = 0x8C, id_ptr = 0x98, id_shift = 0x5, type = "throw",
  568.             })
  569.         end},
  570.     },
  571.     clones = {
  572.         ["vsava"] = 0, ["vsavd"] = 0, ["vsavh"] = 0, ["vsavj"] = 0, ["vsavu"] = 0,
  573.         ["vsav2"] = 0, ["vsav2d"] = 0, ["vhunt2"] = 0, ["vhunt2d"] = 0, ["vhunt2r1"] = -0xB2
  574.     },
  575.     process_throw = function(obj, box)
  576.         return define_box[game.box_type](obj, box)
  577.     end,
  578.     friends = {0x08, 0x10, 0x11, 0x37},
  579.     active = function() return any_true({
  580.         (rd(0xFF8004) == 0x40000 and rd(0xFF8008) == 0x40000),
  581.         (rw(0xFF8008) == 0x2 and rw(0xFF800A) > 0),
  582.     }) end,
  583.     invulnerable = function(obj, box) return any_true({
  584.         rb(obj.base + 0x134) > 0,
  585.         rb(obj.base + 0x147) > 0,
  586.         rb(obj.base + 0x11E) > 0,
  587.         rb(obj.base + 0x145) > 0 and rb(obj.base + 0x1A4) == 0,
  588.     }) end,
  589.     unpushable = function(obj, box) return any_true({
  590.         rb(obj.base + 0x134) > 0,
  591.     }) end,
  592.     unthrowable = function(obj, box) return any_true({
  593.         not (rw(obj.base + 0x004) == 0x0200 or rw(obj.base + 0x004) == 0x0204),
  594.         rb(obj.base + 0x143) > 0,
  595.         rb(obj.base + 0x147) > 0,
  596.         rb(obj.base + 0x11E) > 0,
  597.         bit.band(rd(obj.base + 0x094), 0xFFFFFF00) == 0,
  598.     }) end,
  599. },
  600. {   games = {"ringdest"},
  601.     number = {players = 2, projectiles = 28},
  602.     ground_level = 0x17,
  603.     address = {
  604.         player      = 0xFF8000,
  605.         projectile  = 0xFF9000,
  606.         screen_left = 0xFF72D2,
  607.         screen_top  = 0xFF72D4,
  608.     },
  609.     offset = {
  610.         object_space = 0x100,
  611.         flip_x       = 0x38,
  612.         id_ptr       = 0x4A,
  613.     },
  614.     box = {
  615.         radscale = 2,
  616.         val_x = 0x0, val_y = 0x4, rad_x = 0x2, rad_y = 0x6,
  617.     },
  618.     box_list = {
  619.         {addr_table_ptr  = 0x2D8,  type = "push"},
  620.         {addr_table_base = 0xC956, type = "throwable"},
  621.         {addr_table_base = 0xC92E, type = "vulnerability"},
  622.         {addr_table_base = 0xC936, type = "vulnerability"},
  623.         {addr_table_base = 0xC93E, type = "vulnerability"},
  624.         {addr_table_base = 0xC946, type = "attack"},
  625.         {addr_table_base = 0xC94E, type = "throw"},
  626.     },
  627.     active = function() return any_true({
  628.         rws(0xFF72D2) > 0,
  629.     }) end,
  630.     projectile_active = function(obj, box) return any_true({
  631.         rw(obj.base) > 0x0100 and rb(obj.base + 0x02) == 0x01,
  632.     }) end,
  633.     unpushable = function(obj, box) return any_true({
  634.         obj.projectile ~= nil,
  635.         rw(obj.base + 0x70) > 0,
  636.         rw(obj.base + 0x98) > 0,
  637.     }) end,
  638. },
  639. {   games = {"cybots"},
  640.     number = {players = 2, projectiles = 16},
  641.     address = {
  642.         player      = 0xFF81A0,
  643.         projectile  = 0xFF92A0,
  644.     },
  645.     offset = {
  646.         object_space = 0xC0,
  647.         flip_x       = 0x09,
  648.         pos_x        = 0x1A,
  649.         hitbox_ptr   = {player = 0x32, projectile = 0x32},
  650.     },
  651.     box_list = {
  652.         {anim_ptr = nil, addr_table_ptr = 0x08, id_ptr = 0x66, id_shift = 0x3, type = "push"},
  653.         {anim_ptr = nil, addr_table_ptr = 0x02, id_ptr = 0x63, id_shift = 0x3, type = "vulnerability"},
  654.         {anim_ptr = nil, addr_table_ptr = 0x04, id_ptr = 0x64, id_shift = 0x3, type = "vulnerability"},
  655.         {anim_ptr = nil, addr_table_ptr = 0x06, id_ptr = 0x65, id_shift = 0x3, type = "vulnerability"},
  656.         {method = "dimensions", dimensions = 0x19E, type = "throwable"},
  657.         {anim_ptr = nil, addr_table_ptr = 0x00, id_ptr = 0x62, id_shift = 0x4, type = "attack"},
  658.     },
  659.     breakpoints = {
  660.         {["cybots"] = 0x002DF0, func = function()
  661.             local base = memory.getregister("m68000.a6")
  662.             insert_throw({
  663.                 val_x = rws(base + 0x160),
  664.                 val_y = rws(base + 0x162),
  665.                 rad_x = rws(base + 0x164),
  666.                 rad_y = rws(base + 0x164), --Same as rad_x. Glitch?
  667.                 type = "axis throw",
  668.             }) end},
  669.     },
  670.     clones = {
  671.         ["cybotsj"] = 0, ["cybotsud"] = 0, ["cybotsu"] = 0,
  672.     },
  673.     process_throw = function(obj, box)
  674.         box.val_x  = obj.pos_x + box.val_x * obj.flip_x
  675.         box.val_y  = obj.pos_y - box.val_y
  676.         box.left   = box.val_x - box.rad_x
  677.         box.right  = box.val_x + box.rad_x
  678.         box.top    = box.val_y - box.rad_y
  679.         box.bottom = box.val_y + box.rad_y
  680.  
  681.         return box
  682.     end,
  683.     get_cam_ptr = function()
  684.         local slot = {
  685.             0x6CDA, 0x6C1A, 0x6CDA, 0x6C1A, 0x6CDA, 0x6CDA, 0x6D9A, 0x6CDA,
  686.             0x6CDA, 0x6D9A, 0x6D9A, 0x6D9A, 0x6C1A, 0x6D9A, 0x6CDA, 0x6CDA,
  687.         }
  688.         return 0xFF8000 + (slot[rw(0xFFEA1A) + 1] or slot[1]) + 0x1A
  689.     end,
  690.     active = function() return any_true({
  691.         bit.band(rd(0xFF8008), 0x10FFFF) == 0x100000,
  692.     }) end,
  693.     projectile_active = function(obj) return any_true({
  694.         rw(obj.base) > 0x0100 and bit.band(rw(obj.base + 0x2), 0x8) == 0,
  695.     }) end,
  696.     invulnerable = function(obj, box) return any_true({
  697.         bit.band(rw(obj.base + 0x126), 0x20) > 0,
  698.     }) end,
  699.     unpushable = function(obj, box) return any_true({
  700.         bit.band(rw(obj.base + 0x126), 0x20) > 0,
  701.     }) end,
  702.     unthrowable = function(obj, box)
  703.     local status = rw(obj.base + 0x126)
  704.     return any_true({
  705.         rw(obj.base + 0x152) > 0,
  706.         bit.band(status, 0x40) == 0 and bit.band(status, 0x30) > 0,
  707.         rb(obj.base + 0x1A6) > 0,
  708.     }) end,
  709. },
  710. {   games = {"sgemf"},
  711.     number = {players = 2, projectiles = 14},
  712.     address = {
  713.         player      = 0xFF8400,
  714.         projectile  = 0xFF8C00,
  715.         screen_left = 0xFF8290,
  716.     },
  717.     offset = {
  718.         object_space = 0x100,
  719.         flip_x       = 0x0B,
  720.         hitbox_ptr   = nil,
  721.     },
  722.     box = {radscale = 2},
  723.     box_list = {
  724.         {anim_ptr =  nil, addr_table_ptr = 0x8C, id_ptr = 0x93, id_shift = 0x3, type = "push"},
  725.         {anim_ptr =  nil, addr_table_ptr = 0x80, id_ptr = 0x90, id_shift = 0x3, type = "vulnerability"},
  726.         {anim_ptr =  nil, addr_table_ptr = 0x84, id_ptr = 0x91, id_shift = 0x3, type = "vulnerability"},
  727.         {anim_ptr = 0x1C, addr_table_ptr = 0x8C, id_ptr = 0x0B, id_shift = 0x3, type = "throwable"}, --same as pushbox?
  728.         {anim_ptr =  nil, addr_table_ptr = 0x88, id_ptr = 0x92, id_shift = 0x5, type = "attack"},
  729.     },
  730.     breakpoints = {
  731.         {["sgemf"] = 0x012800, func = function() insert_throw({
  732.             id = bit.band(memory.getregister("m68000.d0"), 0xFF),
  733.             anim_ptr = nil, addr_table_ptr = 0x88, id_ptr = 0x98, id_shift = 0x5, type = "throw",
  734.         }) end},
  735.     },
  736.     clones = {
  737.         ["pfghtj"] = 0, ["sgemfd"] = 0, ["sgemfa"] = 0, ["sgemfh"] = 0,
  738.     },
  739.     process_throw = function(obj, box)
  740.         return define_box[game.box_type](obj, box)
  741.     end,
  742.     active = function() return any_true({
  743.         rd(0xFF8004) == 0x40000 and rd(0xFF8008) == 0x40000,
  744.         rw(0xFF8008) == 0x2 and rw(0xFF800A) > 0,
  745.     }) end,
  746.     no_hit = function(obj, box) return any_true({
  747.         rb(obj.base + 0xB1) > 0,
  748.     }) end,
  749.     invulnerable = function(obj, box) return any_true({
  750.         rb(obj.base + 0x147) > 0,
  751.         rb(obj.base + 0x132) > 0,
  752.         rb(obj.base + 0x11B) > 0,
  753.     }) end,
  754.     unpushable = function(obj, box) return any_true({
  755.         rb(obj.base + 0x1AA) > 0,
  756.         rb(obj.base + 0x093) == 0,
  757.     }) end,
  758.     unthrowable = function(obj, box) return any_true({
  759.         rb(obj.base + 0x143) > 0,
  760.         rb(obj.base + 0x188) > 0,
  761.         rb(obj.base + 0x119) == 0 and rw(obj.base + 0x04) == 0x0202,
  762.         rw(box.id_base + 0x08) == 0,
  763.         rb(obj.base + 0x105) > 0 and rb(obj.base + 0x1BE) == 0 and rb(box.id_base + 0x17) == 0,
  764.     }) end,
  765. },
  766. }
  767.  
  768. --------------------------------------------------------------------------------
  769. -- post-process the modules
  770.  
  771. for game in ipairs(profile) do
  772.     local g = profile[game]
  773.     g.box_type = g.offset.id_ptr and "id ptr" or "hitbox ptr"
  774.     g.ground_level = g.ground_level or -0x0F
  775.     g.offset.player_space = g.offset.player_space or 0x400
  776.     g.offset.pos_x = g.offset.pos_x or 0x10
  777.     g.offset.pos_y = g.offset.pos_y or g.offset.pos_x + 0x4
  778.     g.offset.hitbox_ptr = g.offset.hitbox_ptr or {}
  779.     g.box = g.box or {}
  780.     g.box.radius_read = g.box.radius_read or rw
  781.     g.box.offset_read = g.box.radius_read == rw and rws or rbs
  782.     g.box.val_x    = g.box.val_x or 0x0
  783.     g.box.val_y    = g.box.val_y or 0x2
  784.     g.box.rad_x    = g.box.rad_x or 0x4
  785.     g.box.rad_y    = g.box.rad_y or 0x6
  786.     g.box.radscale = g.box.radscale or 1
  787.     g.no_hit       = g.no_hit       or function() end
  788.     g.invulnerable = g.invulnerable or function() end
  789.     g.unpushable   = g.unpushable   or function() end
  790.     g.unthrowable  = g.unthrowable  or function() end
  791.     g.projectile_active = g.projectile_active or function(obj)
  792.         if rw(obj.base) > 0x0100 and rb(obj.base + 0x04) == 0x02 then
  793.             return true
  794.         end
  795.     end
  796.     g.special_projectiles = g.special_projectiles or {number = 0}
  797.     g.breakables = g.breakables or {number = 0}
  798. end
  799.  
  800. for _, box in pairs(boxes) do
  801.     box.fill    = bit.lshift(box.color, 8) + (globals.no_alpha and 0x00 or box.fill)
  802.     box.outline = bit.lshift(box.color, 8) + (globals.no_alpha and 0xFF or box.outline)
  803. end
  804.  
  805. local projectile_type = {
  806.            ["attack"] = "proj. attack",
  807.     ["vulnerability"] = "proj. vulnerability",
  808. }
  809.  
  810. local DRAW_DELAY = 1
  811. if fba then
  812.     DRAW_DELAY = DRAW_DELAY + 1
  813. end
  814. emu.registerfuncs = fba and memory.registerexec --0.0.7+
  815.  
  816.  
  817. --------------------------------------------------------------------------------
  818. -- functions referenced in the modules
  819.  
  820. any_true = function(condition)
  821.     for n = 1, #condition do
  822.         if condition[n] == true then return true end
  823.     end
  824. end
  825.  
  826.  
  827. get_thrower = function(frame)
  828.     local base = bit.band(0xFFFFFF, memory.getregister("m68000.a6"))
  829.     for _, obj in ipairs(frame) do
  830.         if base == obj.base then
  831.             return obj
  832.         end
  833.     end
  834. end
  835.  
  836.  
  837. insert_throw = function(box)
  838.     local f = frame_buffer[DRAW_DELAY]
  839.     local obj = get_thrower(f)
  840.     if not f.match_active or not obj then
  841.         return
  842.     end
  843.     table.insert(throw_buffer[obj.base], game.process_throw(obj, box))
  844. end
  845.  
  846.  
  847. signed_register = function(register, bytes)
  848.     local bits = bit.lshift(4, bytes or 2)
  849.     local val = bit.band(memory.getregister("m68000." .. register), bit.lshift(1, bits) - 1)
  850.     if bit.arshift(val, bits-1) > 0 then
  851.         val = val - bit.lshift(1, bits)
  852.     end
  853.     return val
  854. end
  855.  
  856.  
  857. get_x = function(x)
  858.     return x - frame_buffer[DRAW_DELAY+1].screen_left
  859. end
  860.  
  861.  
  862. get_y = function(y)
  863.     return emu.screenheight() - (y + game.ground_level) + frame_buffer[DRAW_DELAY+1].screen_top
  864. end
  865.  
  866.  
  867. --------------------------------------------------------------------------------
  868. -- prepare the hitboxes
  869.  
  870. local process_box_type = {
  871.     ["vulnerability"] = function(obj, box)
  872.         if game.invulnerable(obj, box) or obj.friends then
  873.             return false
  874.         end
  875.     end,
  876.  
  877.     ["attack"] = function(obj, box)
  878.         if game.no_hit(obj, box) then
  879.             return false
  880.         end
  881.     end,
  882.  
  883.     ["push"] = function(obj, box)
  884.         if game.unpushable(obj, box) or obj.friends then
  885.             return false
  886.         end
  887.     end,
  888.  
  889.     ["negate"] = function(obj, box)
  890.     end,
  891.  
  892.     ["tripwire"] = function(obj, box)
  893.         box.id = bit.rshift(box.id, 1) + 0x3E
  894.         box.pos_x = box.pos_x or rws(obj.base + 0x1E4)
  895.         if box.pos_x == 0 or rb(obj.base + 0x102) ~= 0x0E then
  896.             return false
  897.         elseif box.clear and not (rb(obj.base + 0x07) == 0x04 and rb(obj.base + 0xAA) == 0x0C) then
  898.             memory.writeword(obj.base + 0x1E4, 0) --sfa3 w/o registerfuncs (bad)
  899.         end
  900.         box.pos_x = obj.pos_x + box.pos_x
  901.     end,
  902.  
  903.     ["throw"] = function(obj, box)
  904.         if box.clear then
  905.             memory.writebyte(obj.base + box.id_ptr, 0) --sfa3 w/o registerfuncs (bad)
  906.         end
  907.     end,
  908.  
  909.     ["axis throw"] = function(obj, box)
  910.     end,
  911.  
  912.     ["throwable"] = function(obj, box)
  913.         if game.unthrowable(obj, box) or obj.projectile then
  914.             return false
  915.         end
  916.     end,
  917. }
  918.  
  919.  
  920. define_box = {
  921.     ["hitbox ptr"] = function(obj, box_entry)
  922.         local box = copytable(box_entry)
  923.  
  924.         if obj.projectile and box.no_projectile then
  925.             return nil
  926.         end
  927.  
  928.         if not box.id then
  929.             box.id_base = (box.anim_ptr and rd(obj.base + box.anim_ptr)) or obj.base
  930.             box.id = rb(box.id_base + box.id_ptr)
  931.         end
  932.  
  933.         if process_box_type[box.type](obj, box) == false or box.id == 0 then
  934.             return nil
  935.         end
  936.  
  937.         local addr_table
  938.         if not obj.hitbox_ptr then
  939.             addr_table = rd(obj.base + box.addr_table_ptr)
  940.         else
  941.             local table_offset = obj.projectile and box.p_addr_table_ptr or box.addr_table_ptr
  942.             addr_table = obj.hitbox_ptr + rws(obj.hitbox_ptr + table_offset)
  943.         end
  944.         box.address = addr_table + bit.lshift(box.id, box.id_shift)
  945.  
  946.         box.rad_x = game.box.radius_read(box.address + game.box.rad_x)/game.box.radscale
  947.         box.rad_y = game.box.radius_read(box.address + game.box.rad_y)/game.box.radscale
  948.         box.val_x = game.box.offset_read(box.address + game.box.val_x)
  949.         box.val_y = game.box.offset_read(box.address + game.box.val_y)
  950.         if box.type == "push" then
  951.             obj.val_y, obj.rad_y = box.val_y, box.rad_y
  952.         end
  953.  
  954.         box.val_x  = (box.pos_x or obj.pos_x) + box.val_x * obj.flip_x
  955.         box.val_y  = (box.pos_y or obj.pos_y) - box.val_y
  956.         box.left   = box.val_x - box.rad_x
  957.         box.right  = box.val_x + box.rad_x
  958.         box.top    = box.val_y - box.rad_y
  959.         box.bottom = box.val_y + box.rad_y
  960.  
  961.         box.type = obj.projectile and not obj.friends and projectile_type[box.type] or box.type
  962.  
  963.         return box
  964.     end,
  965.  
  966.     ["id ptr"] = function(obj, box_entry) --ringdest only
  967.         local box = copytable(box_entry)
  968.  
  969.         if process_box_type[box.type](obj, box) == false then
  970.             return nil
  971.         end
  972.  
  973.         if box.addr_table_base then
  974.             box.address = box.addr_table_base + bit.lshift(obj.id_offset, 2)
  975.         else
  976.             box.address = rd(obj.base + box.addr_table_ptr)
  977.         end
  978.  
  979.         box.rad_x = game.box.radius_read(box.address + game.box.rad_x)/game.box.radscale
  980.         box.rad_y = game.box.radius_read(box.address + game.box.rad_y)/game.box.radscale
  981.         if box.rad_x == 0 or box.rad_y == 0 then
  982.             return nil
  983.         end
  984.         box.val_x = game.box.offset_read(box.address + game.box.val_x)
  985.         box.val_y = game.box.offset_read(box.address + game.box.val_y)
  986.  
  987.         box.val_x  = obj.pos_x + (box.rad_x + box.val_x) * obj.flip_x
  988.         box.val_y  = obj.pos_y - (box.rad_y + box.val_y)
  989.         box.left   = box.val_x - box.rad_x
  990.         box.right  = box.val_x + box.rad_x
  991.         box.top    = box.val_y - box.rad_y
  992.         box.bottom = box.val_y + box.rad_y
  993.  
  994.         box.type = obj.projectile and projectile_type[box.type] or box.type
  995.  
  996.         return box
  997.     end,
  998.  
  999.     ["range given"] = function(obj, box_entry) --dstlk/nwarr throwable; nwarr ranged
  1000.         local box = copytable(box_entry)
  1001.  
  1002.         box.base_x  = rw(obj.base + box.base_x)
  1003.         box.range_x = rb(obj.base + box.range_x)
  1004.         if process_box_type[box.type](obj, box) == false or box.base_x == 0 or box.range_x == 0 then
  1005.             return nil
  1006.         end
  1007.         box.right = get_x(box.base_x) - box.range_x
  1008.         box.left  = get_x(box.base_x) + box.range_x
  1009.         if box.type == "axis throw" then --nwarr ranged
  1010.             box.bottom = get_y(game.ground_level)
  1011.             box.top    = get_y(rw(obj.base + box.range_y))
  1012.         else
  1013.             box.top    = obj.pos_y - rb(obj.base + box.range_y)
  1014.             if rb(obj.base + box.air_state) > 0 then
  1015.                 box.bottom = box.top + 0xC --air throwable; verify range @ 033BE0 [dstlk] & 029F50 [nwarr]
  1016.             else
  1017.                 box.bottom = obj.pos_y --ground throwable
  1018.             end
  1019.         end
  1020.         box.val_x = (box.left + box.right)/2
  1021.         box.val_y = (box.bottom + box.top)/2
  1022.  
  1023.         return box
  1024.     end,
  1025.  
  1026.     ["dimensions"] = function(obj, box_entry) --cybots throwable
  1027.         local box = copytable(box_entry)
  1028.  
  1029.         if process_box_type[box.type](obj, box) == false then
  1030.             return nil
  1031.         end
  1032.         box.hval = rws(obj.base + box.dimensions + 0x0)
  1033.         box.vval = rws(obj.base + box.dimensions + 0x2)
  1034.         box.hrad =  rw(obj.base + box.dimensions + 0x4)
  1035.         box.vrad =  rw(obj.base + box.dimensions + 0x6)
  1036.  
  1037.         box.hval   = obj.pos_x + box.hval * obj.flip_x
  1038.         box.vval   = obj.pos_y - box.vval
  1039.         box.left   = box.hval - box.hrad
  1040.         box.right  = box.hval + box.hrad
  1041.         box.top    = box.vval - box.vrad
  1042.         box.bottom = box.vval + box.vrad
  1043.  
  1044.         return box
  1045.     end,
  1046. }
  1047.  
  1048.  
  1049. local get_ptr = {
  1050.     ["hitbox ptr"] = function(obj)
  1051.         obj.hitbox_ptr = obj.projectile and game.offset.hitbox_ptr.projectile or game.offset.hitbox_ptr.player
  1052.         obj.hitbox_ptr = obj.hitbox_ptr and rd(obj.base + obj.hitbox_ptr) or nil
  1053.     end,
  1054.  
  1055.     ["id ptr"] = function(obj) --ringdest only
  1056.         obj.id_offset = rw(obj.base + game.offset.id_ptr)
  1057.     end,
  1058. }
  1059.  
  1060.  
  1061. local update_object = function(obj)
  1062.     obj.flip_x = rb(obj.base + game.offset.flip_x) > 0 and -1 or 1
  1063.     obj.pos_x  = get_x(rws(obj.base + game.offset.pos_x))
  1064.     obj.pos_y  = get_y(rws(obj.base + game.offset.pos_y))
  1065.     get_ptr[game.box_type](obj)
  1066.     for _, box_entry in ipairs(game.box_list) do
  1067.         table.insert(obj, define_box[box_entry.method or game.box_type](obj, box_entry))
  1068.     end
  1069.     return obj
  1070. end
  1071.  
  1072.  
  1073. local friends_status = function(id)
  1074.     for _, friend in ipairs(game.friends or {}) do
  1075.         if id == friend then
  1076.             return true
  1077.         end
  1078.     end
  1079. end
  1080.  
  1081.  
  1082. local read_projectiles = function(f)
  1083.     for i = 1, game.number.projectiles do
  1084.         local obj = {base = game.address.projectile + (i-1) * game.offset.object_space}
  1085.         if game.projectile_active(obj) then
  1086.             obj.projectile = true
  1087.             obj.friends = friends_status(rb(obj.base + 0x02))
  1088.             table.insert(f, update_object(obj))
  1089.         end
  1090.     end
  1091.  
  1092.     for i = 1, game.special_projectiles.number do --for nwarr only
  1093.         local obj = {base = game.special_projectiles.start + (i-1) * game.special_projectiles.space}
  1094.         local id = rb(obj.base + 0x02)
  1095.         for _, valid in ipairs(game.special_projectiles.no_box) do
  1096.             if id == valid then
  1097.                 obj.pos_x = get_x(rws(obj.base + game.offset.pos_x))
  1098.                 obj.pos_y = get_y(rws(obj.base + game.offset.pos_y))
  1099.                 table.insert(f, obj)
  1100.                 break
  1101.             end
  1102.         end
  1103.         for _, valid in ipairs(game.special_projectiles.whitelist) do
  1104.             if id == valid then
  1105.                 obj.projectile, obj.hit_only, obj.friends = true, true, friends_status(id)
  1106.                 table.insert(f, update_object(obj))
  1107.                 break
  1108.             end
  1109.         end
  1110.     end
  1111. --[[
  1112.     for i = 1, game.breakables.number do --for dstlk, nwarr
  1113.         local obj = {base = game.breakables.start + (i-1) * game.breakables.space}
  1114.         local status = rb(obj.base + 0x04)
  1115.         if status == 0x02 then
  1116.             obj.projectile = true
  1117.             obj.x_adjust = 0x1C*((f.screen_left-0x100)/0xC0-1)
  1118.             table.insert(f, update_object(obj))
  1119.         end
  1120.     end
  1121. ]]
  1122. end
  1123.  
  1124.  
  1125. local update_hitboxes = function()
  1126.     if not game then
  1127.         return
  1128.     end
  1129.     local screen_left_ptr = game.address.screen_left or game.get_cam_ptr()
  1130.     local screen_top_ptr  = game.address.screen_top or screen_left_ptr + 0x4
  1131.  
  1132.     for f = 1, DRAW_DELAY do
  1133.         frame_buffer[f] = copytable(frame_buffer[f+1])
  1134.     end
  1135.  
  1136.     frame_buffer[DRAW_DELAY+1] = {
  1137.         match_active = game.active(),
  1138.         screen_left = rws(screen_left_ptr),
  1139.         screen_top  = rws(screen_top_ptr),
  1140.     }
  1141.     local f = frame_buffer[DRAW_DELAY+1]
  1142.     if not f.match_active then
  1143.         return
  1144.     end
  1145.  
  1146.     for p = 1, game.number.players do
  1147.         local player = {base = game.address.player + (p-1) * game.offset.player_space}
  1148.         if rb(player.base) > 0 then
  1149.             table.insert(f, update_object(player))
  1150.             local tb = throw_buffer[player.base]
  1151.             table.insert(player, tb[1])
  1152.             for frame = 1, #tb-1 do
  1153.                 tb[frame] = tb[frame+1]
  1154.             end
  1155.             table.remove(tb)
  1156.         end
  1157.     end
  1158.     read_projectiles(f)
  1159.  
  1160.     f = frame_buffer[DRAW_DELAY]
  1161.     for _, obj in ipairs(f or {}) do
  1162.         if obj.projectile then
  1163.             break
  1164.         end
  1165.         for _, box_entry in ipairs(game.throw_box_list or {}) do
  1166.             if not (emu.registerfuncs and box_entry.clear) then
  1167.                 table.insert(obj, define_box[box_entry.method or game.box_type](obj, box_entry))
  1168.             end
  1169.         end
  1170.     end
  1171.  
  1172.     f.max_boxes = 0
  1173.     for _, obj in ipairs(f or {}) do
  1174.         f.max_boxes = math.max(f.max_boxes, #obj)
  1175.     end
  1176.     f.max_boxes = f.max_boxes+1
  1177. end
  1178.  
  1179.  
  1180. emu.registerafter( function()
  1181.     update_hitboxes()
  1182. end)
  1183.  
  1184.  
  1185. --------------------------------------------------------------------------------
  1186. -- draw the hitboxes
  1187.  
  1188. local draw_hitbox = function(hb)
  1189.     if not hb or any_true({
  1190.         not globals.draw_pushboxes and hb.type == "push",
  1191.         not globals.draw_throwable_boxes and hb.type == "throwable",
  1192.     }) then return
  1193.     end
  1194.  
  1195.     if globals.draw_mini_axis then
  1196.         gui.drawline(hb.val_x, hb.val_y-globals.mini_axis_size, hb.val_x, hb.val_y+globals.mini_axis_size, boxes[hb.type].outline)
  1197.         gui.drawline(hb.val_x-globals.mini_axis_size, hb.val_y, hb.val_x+globals.mini_axis_size, hb.val_y, boxes[hb.type].outline)
  1198.     end
  1199.  
  1200.     gui.box(hb.left, hb.top, hb.right, hb.bottom, boxes[hb.type].fill, boxes[hb.type].outline)
  1201. end
  1202.  
  1203.  
  1204. local draw_axis = function(obj)
  1205.     gui.drawline(obj.pos_x, obj.pos_y-globals.axis_size, obj.pos_x, obj.pos_y+globals.axis_size, globals.axis_color)
  1206.     gui.drawline(obj.pos_x-globals.axis_size, obj.pos_y, obj.pos_x+globals.axis_size, obj.pos_y, globals.axis_color)
  1207.     --gui.text(obj.pos_x, obj.pos_y, string.format("%06X", obj.base)) --debug
  1208. end
  1209.  
  1210.  
  1211. local render_hitboxes = function()
  1212.     gui.clearuncommitted()
  1213.     local f = frame_buffer[1]
  1214.     if not f.match_active then
  1215.         return
  1216.     end
  1217.  
  1218.     if globals.blank_screen then
  1219.         gui.box(0, 0, emu.screenwidth(), emu.screenheight(), globals.blank_color)
  1220.     end
  1221.  
  1222.     for entry = 1, f.max_boxes or 0 do
  1223.         for _, obj in ipairs(f) do
  1224.             draw_hitbox(obj[entry])
  1225.         end
  1226.     end
  1227.  
  1228.     if globals.draw_axis then
  1229.         for _, obj in ipairs(f) do
  1230.             draw_axis(obj)
  1231.         end
  1232.     end
  1233. end
  1234.  
  1235.  
  1236. gui.register(function()
  1237.     render_hitboxes()
  1238. end)
  1239.  
  1240.  
  1241. --------------------------------------------------------------------------------
  1242. -- hotkey functions
  1243.  
  1244. input.registerhotkey(1, function()
  1245.     globals.blank_screen = not globals.blank_screen
  1246.     render_hitboxes()
  1247.     emu.message((globals.blank_screen and "activated" or "deactivated") .. " blank screen mode")
  1248. end)
  1249.  
  1250.  
  1251. input.registerhotkey(2, function()
  1252.     globals.draw_axis = not globals.draw_axis
  1253.     render_hitboxes()
  1254.     emu.message((globals.draw_axis and "showing" or "hiding") .. " object axis")
  1255. end)
  1256.  
  1257.  
  1258. input.registerhotkey(3, function()
  1259.     globals.draw_mini_axis = not globals.draw_mini_axis
  1260.     render_hitboxes()
  1261.     emu.message((globals.draw_mini_axis and "showing" or "hiding") .. " hitbox axis")
  1262. end)
  1263.  
  1264.  
  1265. input.registerhotkey(4, function()
  1266.     globals.draw_pushboxes = not globals.draw_pushboxes
  1267.     render_hitboxes()
  1268.     emu.message((globals.draw_pushboxes and "showing" or "hiding") .. " pushboxes")
  1269. end)
  1270.  
  1271.  
  1272. input.registerhotkey(5, function()
  1273.     globals.draw_throwable_boxes = not globals.draw_throwable_boxes
  1274.     render_hitboxes()
  1275.     emu.message((globals.draw_throwable_boxes and "showing" or "hiding") .. " throwable boxes")
  1276. end)
  1277.  
  1278.  
  1279. --------------------------------------------------------------------------------
  1280. -- initialize on game startup
  1281.  
  1282. local initialize_bps = function()
  1283.     for _, pc in ipairs(globals.breakpoints or {}) do
  1284.         memory.registerexec(pc, nil)
  1285.     end
  1286.     for _, addr in ipairs(globals.watchpoints or {}) do
  1287.         memory.registerwrite(addr, nil)
  1288.     end
  1289.     globals.breakpoints, globals.watchpoints = {}, {}
  1290. end
  1291.  
  1292.  
  1293. local initialize_fb = function()
  1294.     frame_buffer = {}
  1295.     for f = 1, DRAW_DELAY + 1 do
  1296.         frame_buffer[f] = {}
  1297.     end
  1298. end
  1299.  
  1300.  
  1301. local initialize_throw_buffer = function()
  1302.     throw_buffer = {}
  1303.     for p = 1, game.number.players do
  1304.         throw_buffer[game.address.player + (p-1) * game.offset.player_space] = {}
  1305.     end
  1306. end
  1307.  
  1308.  
  1309. local whatgame = function()
  1310.     print()
  1311.     game = nil
  1312.     initialize_fb()
  1313.     initialize_bps()
  1314.     for _, module in ipairs(profile) do
  1315.         for _, shortname in ipairs(module.games) do
  1316.             if emu.romname() == shortname or emu.parentname() == shortname then
  1317.                 print("drawing hitboxes for " .. emu.gamename())
  1318.                 game = module
  1319.                 initialize_throw_buffer()
  1320.                 if not emu.registerfuncs then
  1321.                     if game.breakpoints then
  1322.                         print("(FBA-rr 0.0.7+ can show throwboxes for this game.)")
  1323.                     end
  1324.                     return
  1325.                 end
  1326.                 for _, bp in ipairs(game.breakpoints or {}) do
  1327.                     local pc = bp[emu.romname()] or bp[shortname] + game.clones[emu.romname()]
  1328.                     memory.registerexec(pc, bp.func)
  1329.                     table.insert(globals.breakpoints, pc)
  1330.                 end
  1331.                 for _, wp in ipairs(game.watchpoints or {}) do
  1332.                     for p = 1, game.number.players do
  1333.                         local addr = game.address.player + (p-1) * game.offset.player_space + wp.offset
  1334.                         memory.registerwrite(addr, wp.size, wp.func)
  1335.                         table.insert(globals.watchpoints, addr)
  1336.                     end
  1337.                 end
  1338.                 return
  1339.             end
  1340.         end
  1341.     end
  1342.     print("unsupported game: " .. emu.gamename())
  1343. end
  1344.  
  1345.  
  1346. savestate.registerload(function()
  1347.     initialize_fb()
  1348. end)
  1349.  
  1350.  
  1351. emu.registerstart(function()
  1352.     whatgame()
  1353. end)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement