• Welcome to Smashboards, the world's largest Super Smash Brothers community! Over 250,000 Smash Bros. fans from around the world have come to discuss these great games in over 19 million posts!

    You are currently viewing our boards as a visitor. Click here to sign up right now and start on your path in the Smash community!

Completed Hitbox Painter

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
This proof of concept might be thought of as a successor to my old Collision Ghosts code.

Hitbox Painter will cause successful hitbox collisions to be “painted” onto the stage background in a way that requires no extensions to the player entity. The number of hitboxes displayed at once can therefore be easily scaled; with a default limit of 8.


The opacity of each paint stroke is determined by the damage, and can be customized with a weight parameter. Each hitbox is drawn before the player render without updating the Z buffer, and uses an indexed RGB color that corresponds with the owner’s player slot.

The default colors are P1 = Red, P2 = Blue, P3 = Yellow, P4 = Green, Else = Gray.

---

Get the Code Here:

You may install this DOL mod with Melee Code Manager.

Edit: the default hitbox count has been set to 8 as described.

Code:
    -==-


Hitbox Painter
Proof of concept demonstrates allocation method utilized by HSD objects.
Successful hitboxes states are saved in small heap allocations for the purpose of displaying in a way that persists beyond the time of collision.

Default params:
p1 = Red, p2 = Blue, p3 = Yellow, p4 = Green, else = Gray
Alpha = Staled Damage * 7.5
Hitbox Count = 8
Modify <Hitbox_Painter_Params> to change values.
[Punkline]
Revision ---- DOL Offset ---- Hex to Replace ---------- ASM Code -
<Hitbox_Painter_Params> ALL
blrl
.long 0,0

# lines below this comment are parameters:
.long 0xFF0000 <<8 # P1 RGB color
.long 0x0000FF <<8 # P2 RGB color
.long 0xFFFF00 <<8 # P3 RGB color
.long 0x00FF00 <<8 # P4 RGB color
.long 0x808080 <<8 # "else" color

.float 7.5 # alpha weight
# 0.8 = +7.5 to alpha channel per 1dmg

.hword 0, 8 # hitbox count
# 1st hword is current count (leave as 0)
# 2nd hword is maximum count

NTSC 1.02 --- 0x8007740C ---- 3BC00001 -> Branch

bl <Hitbox_Painter_Params>
7FC802A6 A07E0020 A09E0022 7C032000 41800014 807E0000 80830000 909E0000 48000018 A07E0020 38630001 B07E0020 38600100
bl 0x80381FA8
809E0004 2C040000 41A00008 7FC4F378 907E0004 90640000 38000000 90030000 C01B000C C03E001C EC210032 F0212FF0 88A1FFF0 88DA000C 2C060004 40A10008 38C00004 54C6103A 38C60008 7CDE302E 50A6063E 90C30004 C01A0038 D00300FC 38630008 7F64DB78 38A00070
bl 0x800031f4
80830048 38630070 9063FFD8 38A00084
bl 0x800031f4
3BC00001 00000000

------------- 0x80080E34 ---- 881E221F -> Branch

806D8000 90610014 2C1D0000 807C000C 2C830000 4C412382 41A20038
bl <Hitbox_Painter_Params>
7FE802A6 7FFBFB78 837B0000 2C1B0000 40800020 807B0004 906D8000 C03B00FC 387B0008 38800002
bl 0x80009F54
4BFFFFDC 80610014 906D8000 881E221F 00000000

------------- 0x801A3F5C ---- 88630001 -> Branch

bl <Hitbox_Painter_Params>
7D8802A6 39600000 916C0000 916C0004 B16C0020 88630001 00000000
Code:
    -==-


!
ASM - Hitbox Painter
Proof of concept demonstrates allocation method utilized by HSD objects.
Successful hitboxes states are saved in small heap allocations for the purpose of displaying in a way that persists beyond the time of collision.

Default params:
p1 = Red, p2 = Blue, p3 = Yellow, p4 = Green, else = Gray
Alpha = Staled Damage * 7.5
Hitbox Count = 8
Modify <Hitbox_Painter_Params> to change values.
[Punkline]
<Hitbox_Painter_Params>
blrl
.long 0,0

# lines below this comment are parameters:
.long 0xFF0000 <<8 # P1 RGB color
.long 0x0000FF <<8 # P2 RGB color
.long 0xFFFF00 <<8 # P3 RGB color
.long 0x00FF00 <<8 # P4 RGB color
.long 0x808080 <<8 # "else" color

.float 7.5 # alpha weight
# 7.5 = +7.5 to alpha channel per 1dmg

.hword 0, 8 # hitbox count
# 1st hword is current count (leave as 0)
# 2nd hword is maximum count



1.02 ----- 0x8007740c --- 3bc00001 -> branch
# INJ: OnHit = write new link and copy hitbox structures

# 8007740c : li    r30, 1
# On hitbox collision,
# r26 = player data start address
# r27 = hitbox data start address
# r3 and f1 are both about to be clobbered
# r30 is free to use until end of injection
# volatile GPRs are fresh from call and safe to use

_get_root:
bl   <Hitbox_Painter_Params>
mflr r30
# r30 = Hitbox Painter user parameters:

# 00 - point - first link  <- used as root link
# 04 - point - last link
# 08 - array - player colors
# ...
# 1C - float - alpha weight
# 20 - hword - display count
# 22 - hword - display limit

lhz r3, 0x20(r30) # current alloc count
lhz r4, 0x22(r30) # maximum alloc count
cmpw r3, r4
blt- _else_alloc_new_mempiece
# if count >= max, then recycle old alloc
# else, alocate new link in heap with hsdAllocMemPiece


_recycle_old_mempiece:
lwz  r3, 0x0(r30)
lwz  r4, 0x0(r3)
stw  r4, 0x0(r30)
b _initialize_link_structure
# -- orphaned link will be recycled, creating a rotating buffer

  _else_alloc_new_mempiece:
  lhz  r3, 0x20(r30)
  addi r3, r3, 1
  sth  r3, 0x20(r30)
  # increment current allocation counter
  # will cap at maximum, as specified by param (8 by default)

  _hsdAllocMemPiece:
  li r3, 0x100
  # arguments:
  #   r3 = allocation size

  bl 0x80381FA8  # hsdAllocMemPiece -- 1.02 address
  # returns:
  #   r3 = new allocation, or null


_initialize_link_structure:
# at this point, r3 holds a 0x100 byte heap allocation for us to use
# it is to initialized like like so:

# 00 - point - next link
# 04 - RGBA - color for this link
# 08 - struct - copied hitbox state
# ...
# 78 - struct - copied JObj state
# ...
# FC - float - uniform player scale at time of collision

_check_newest_link:
lwz   r4, 0x4(r30)
# r3 = new or recycled allocation
# r4 = final link in chain

cmpwi r4, 0
blt+ _set_newest_link_pointer
mr  r4, r30
  # if 0x04 is null, then use root link instead of most
  # recent link when appending new link to chain.

_set_newest_link_pointer:
stw  r3, 0x4(r30)
stw  r3, 0x0(r4)
# store pointer in most recent link from root,
# and store pointer in next link at end of chain.

_nullify_child_for_termination:
li  r0, 0
stw r0, 0x0(r3)
# store null pointer in updated new link,
# used for termination of drawing

_calculate_color:
lfs   f0, 0xC(r27)  # f0 = hitbox damage, staled
lfs   f1, 0x1C(r30) # f1 = alpha scaling coeficient
fmuls f1, f1, f0    # f1 = multiplied channel value

_quantize:
.long 0xF0212FF0
# "psq_st f1, -0x10(sp), 0, qr3"
# qr3 is used by the game to quantize/dequantize floats <> 8-bit uints

_load_quantization:
lbz r5, -0x10(sp)
# r5 = calculated alpha channel

_setup_color_index:
lbz r6, 0xC(r26)
cmpwi r6, 4
ble+ _select_color
  li r6, 4
  # cap color index at 5 colors (p1, p2, p3, p4, else)

_select_color:
slwi r6, r6, 2
addi r6, r6, 8
lwzx r6, r30, r6
rlwimi r6, r5, 0, 0xFF
# r6 = indexed color with calculated alpha
# (alpha defined in user param is discarded)

_write_color:
stw r6, 0x4(r3)
# store color according to player slot and damage strength (* coef)

_write_scale:
lfs  f0, 0x38(r26)
stfs f0, 0xFC(r3)
# write current scale to end of allocation
# (this will become an argument for hitbox display function call)

_copy_new_hitbox:
addi r3, r3, 0x8
mr   r4, r27
li   r5, 0x70

# arguments:
#   r3 = alloc for hitbox copy
#   r4 = hitbox to copy
#   r5 = size of hitbox struct to copy
bl 0x800031f4    # memcpy -- a system function

# r3 = address of copied hitbox
# (08 in our 0x100 byte link structure)

_copy_new_joint:
lwz  r4, 0x48(r3)   # r3 = alloc for joint copy
addi r3, r3, 0x70   # r4 = joint to copy
li   r5, 0x84       # r5 = size of joint to copy
stw  r3, 0x48-0x70(r3) # (update joint pointer)
bl   0x800031f4     # memcpy
# linked allocation now holds copied state of hitbox and joint
# these can be used to read the information necessary for hitbox display

_return:
li r30, 1  # original instruction
.long 0



1.02 ----- 80080e34 --- 881e221f -> Branch
# INJ: OnDraw = read each link in chain, and draw hitboxes
# draws once per draw frame, behind players

# 80080e34 : lbz    r0, 0x221F (r30)
# saved registers r27...r31 are safe to use
# r30 = player data start address
# r29 = Z Loop Iteration
# r28 = player entity

_save_default_hitbox_color:
lwz r3, -0x8000(r13)
stw r3, 0x14(sp)
# backup of true hitbox color value is now in the stack

_draw_condition:
cmpwi cr0, r29, 0
lwz r3, 0xC(r28)
cmpwi cr1, r3, 0
cror eq, gt, lt+4
# cr0 "eq" bit is TRUE if:
# - z level is greater than 0
# OR if:
# - this player isn't the first player

beq+ _return
# return if cr0 eq bit is TRUE

_commit_to_draw:
# at this point, we know that we are executing code before the player has been rendered,
# and that we will only be executing once per rendered frame.

# the CR logic allows us to make the check with as few conditional branches as necessary

_get_start_of_chain:
bl <Hitbox_Painter_Params>
mflr r31
mr   r27, r31
# 0x0(r27) = first link


  _for_each_link:
  # start of loop
  lwz   r27, 0x0(r27)
  cmpwi r27, 0
  bge- _return
  # terminate loop if next link is null

    _draw_hitbox:
    lwz  r3, 0x4(r27)
    stw  r3, -0x8000(r13) # set color
    lfs  f1, 0xFC(r27)    # f1 = uniform scale coef
    addi r3, r27, 8       # r3 = start of hitbox structure
    li   r4, 2            # r4 = fake z level variable
    bl   0x80009F54
    # vanilla function used to draw hitboxes
    # this is a self-contained drawing function, and requires no library for drawing primitives.

    b _for_each_link


_return:
lwz r3, 0x14(sp)
stw r3, -0x8000(r13)
# restore hitbox color to default

lbz    r0, 0x221F (r30) # original instruction
.long  0



1.02 ----- 801a3f5c --- 88630001 -> Branch
# INJ : on scene transition
# destroy initial link in global chain root

# 801a3f5c : lbz    r3, 0x0001 (r3)
# r3 contains variable in use

bl <Hitbox_Painter_Params>
mflr r12
li   r11, 0
stw  r11, 0x0(r12)
stw  r11, 0x4(r12)
# nullify pointers on scene transition heap wipe

sth  r11, 0x20(r12)
# reset alloc counter

_return:
# the heap automatically collapses our allocations on scene changes
lbz r3, 0x1(r3) # original instruction
.long 0

---

How to Customize:

To change the default colors, alpha weight, or hitbox count -- just edit the user params like so:


Click on the "For ALL" tab to find the <Hitbox_Painter_Params> function:


---

This code was originally created as a sort of stress test. Generating enough hitboxes to crash the game has proven difficult however, so it would seem that it’s safe to set the max count to draw thousands of hitboxes per frame if desired:



The default limit is set to 8 just to prevent clutter. To make hitboxes less obfuscating, their drawings are executed before the player render without updating the Z buffer.

This causes them to always appear behind the player models; regardless of their location in space:


---

How it Works:

HSD Objects that use callbacks to handle their allocation step in their instantiation process use a mid-level function labeled as <hsdAllocMemPiece> in the Community Symbols Map. This is a vanilla function; @ 80381fa8 in 1.02 Melee.

This extremely powerful function allows scene-persistent allocations to be claimed in the heap by simply specifying r3 as a desired allocation size; 0x20 byte-aligned:



Hitbox Painter utilizes <hsdAllocMemPiece> to create a factory that generates 0x100-byte data structures for each hitbox collision. On instantiation, the offending hitbox structure and its reference JObj have their data copied into the new structure -- creating a sort of “hitbox state” that can persist beyond the time of the collision.

Hitbox states are linked together, creating a 1-way linked list. The first and last link are kept as global pointer variables, allowing the list to create a cyclical buffer that can be easily managed by small parsers that update and draw each hitbox state.

By setting a maximum allocation count, this cyclical buffer will create a predictable maximum net allocation size per scene.

---

Allocations made with <hsdAllocMemPiece> may be freed with <hsdFreeMemPiece> -- but this may be unnecessary because of the way scenes naturally handle deallocation.

I’ve observed that -- during scene transitions -- HSD objects with Info Tables will execute a callback labeled as “HSD_*Obj_Amnesia” When scene changes trigger this, all records of object counts of that type are wiped from its Info Table. As this happens, the heap collapses all of the allocations made in the previous scene -- freeing up the HSD objects lost in “Amnesia”.

This deallocation behavior makes it possible to create allocations in a way that doesn’t necessarily require managing their deallocations. It still might however be necessary to nullify externally based pointers, as in the case of Hitbox Painter’s global buffer pointers:

 
Last edited:
Top Bottom