Punkline
Dr. Frankenstack
- Joined
- May 15, 2015
- Messages
- 423
The Cape
requested a code that fixes ledge grab port priority; a situation where two or more characters attempting to grab a ledge on the same frame will always result in the lower port number winning over higher port numbers.
This code delays all “CliffCatch” actions until after each player’s ECB calculations have finished updating, and then measures the distance between any characters competing for the same ledge. Once measured, only the closest character to the ledge gets the cliffcatch action -- regardless of port number:
Get the code here:
Let me know if anyone finds any bugs, has any suggestions, or questions.
This code delays all “CliffCatch” actions until after each player’s ECB calculations have finished updating, and then measures the distance between any characters competing for the same ledge. Once measured, only the closest character to the ledge gets the cliffcatch action -- regardless of port number:
Get the code here:
Code:
-==-
Ledge Grabs Prioritized by Distance
CliffCatch prioritizes ECB distance over port number.
(Fixes port priority.)
[Punkline]
<ledge_grab_distance_priority_vars> All
4E800021 00000000 00000000 00000000 00000001
<get_player_GObj_ID> All
7C641B78 38A0FFFF 8004000C 38A50001 2C000000 4080000C 7C040378 4BFFFFEC 4E800020
1.02 ------ 80081388 --- 8063002c -> Branch
bl <ledge_grab_distance_priority_vars>
7CC802A6 80060008 80E6000C 2C000000 2C870000 4C423382 41820028
bl <get_player_GObj_ID>
2C050020 4080001C 38800001 7C852830 80060004 7CA50378 90A60004
b 0x800814ec
8063002C 00000000
1.02 ------ 8006c3a8 --- 7fa3eb78 -> Branch
801D0008 2C000000 40A20050 7FA3EB78
bl <get_player_GObj_ID>
bl <ledge_grab_distance_priority_vars>
7FC802A6 A0DE0000 807E0004 7C053000 2C830000 4C461342 B0BE0000 38000000 B0DE0002 901E0008 901E0004 41820008
bl <recursive_cliffcatch_by_distance>
38000001 901E0008 83DD002C 7FA3EB78 00000000
<recursive_cliffcatch_by_distance> 1.02
38000020 7C0903A6 38A00000 7C0802A6 90010004 9421FFF0 2C040000 40800034 70600001 5463F87E 40820014 80840008 2C040000 4100FFEC 48000018 8004002C 38A50001 9001000C 80840008 4BFFFFC1 7CA903A6 34A5FFFF 418000D8 4FFFF982 38E1000C 85070010 41BF000C 7D044379 408000C0 2C080000 408000A4 89880824 88640824 558C07BE 558307BE 7C0C1800 4082008C 5589FFFE 558A07FE 554B103A 396B0730 7C64582E 7D88582E 7C036000 40A2006C 814DAE1C 546B1838 7D4A582E 5520083C 7D6A022E 814DAE18 1D6B0018 396B0008 106A580C E0880784 E00800B0 1084002A 10041828 10600032 100318D4 FC800034 EC440032 FC011040 4C00FA02 41800014 FC201090 7D044378 4FE00042 4800000C 38000000 90070000 4200FF48 80640000 90A10008
bl 0x80081370
80A10008 38210010 80010004 7C0803A6 4E800020
#
Code:
-==-
!
ASM - Ledge Grabs Prioritized by Distance
CliffCatch prioritizes ECB distance over port number.
(Fixes port priority.)
[Punkline]
<ledge_grab_distance_priority_vars> All
blrl
.long 0, 0, 0, 1
# allocations for variables
# last word is a flag that enables/disables the code. set to 0 to disable
<get_player_GObj_ID> All
# r3 = player GObj
# returns:
# r3 = unchanged
# r4 = first player GObj
# r5 = ID
mr r4, r3
li r5, -1
# r3 = this GObj
# r4 = counted GObj
# r5 = counter
_while_first_GObj_not_counted:
lwz r0, 0xC(r4)
addi r5, r5, 1
cmpwi r0, 0
bge- _return
mr r4, r0
b _while_first_GObj_not_counted
# once first GObj is reached, this GObj ID will be finalized
_return:
blr
1.02 ------ 80081388 --- 8063002c -> Branch
# whenever cliffcatch action is called
# we record it in a 32-bit flag field, indexed by player GObj
# and skip the action for now, so that it can be called later on.
# -- if a GObj is 32nd or higher in the chain, it is not delayed or recorded.
.set xGate, 0x8
.set open, 0
.set xEnabled, 0xC
# r30 == r3
# r3 must be maintained if returning to function
bl <ledge_grab_distance_priority_vars>
mflr r6
# r3 = unchanged
# r6 = variables base address
lwz r0, xGate(r6)
lwz r7, xEnabled(r6)
cmpwi cr0, r0, open
cmpwi cr1, r7, 0
cror eq, eq, eq+4
beq- _default
# if gate is open, allow call to go through
# otherwise, prevent the call and save it for later as an indexed bool
# We also treat the code being disabled like having the gate always open.
# this will cause the bools to be blank at te end of collision measurements,
# so it will prevent the code from having any effect.
bl <get_player_GObj_ID>
# r3 = unchanged
# r5 = player ID
# r6 = unchanged
cmpwi r5, 32
bge- _default
# also, don't bother with GObjs that we can't keep track of (32-bit field)
# if for some reason there are that many players, then they won't be affected by the gate logic
# r5 = player ID
# r6 = variables base address
li r4, 1
slw r5, r4, r5
lwz r0, 0x4(r6)
or r5, r5, r0
stw r5, 0x4(r6)
# update flagfield to include this ID
_skip:
b 0x800814ec
# if skipping function, return to its epilog
# else, default returns execution to prolog
_default:
lwz r3, 0x002C (r3)
.long 0
1.02 ------ 8006c3a8 --- 7fa3eb78 -> Branch
# after collision callback event has returned
# -- if this is the final GObj being checked, then
# we select which GObjs actually get to execute cliffcatch action change
# variable offsets:
.set xThisCount, 0x0
.set xPrevCount, 0x2
.set xBools, 0x4
.set xGate, 0x8 # opens/closes access to cliffcatch action changes when called
.set xEnabled, 0xC
# Gate states:
.set open, 0 # when open, calling cliffcatch action change will behave normally
.set close, 1 # when closed, attempting cliffcatch will log player in xBools field
# -- closed gate will not affect player GObjs with IDs larger than bool field (32)
# r29 = this player GObj
# r30 = this player data
lwz r0, 0x8(r29)
cmpwi r0, 0
bne+ _return
# return immediately if this is not the last player GObj
_if_last_player:
mr r3, r29
bl <get_player_GObj_ID>
# r4 = first player GObj ID
_update_GObj_max:
bl <ledge_grab_distance_priority_vars>
mflr r30
# r30 must now be recovered before returning
# we can use 0x2C(r29) to restore it before returning
lhz r6, xThisCount(r30)
lwz r3, xBools(r30)
cmpw cr0, r5, r6
# the new value for xThisCount is in r5
# if it's != r6, then a player GObj has been added/destroyed in the chain
cmpwi cr1, r3, 0
crorc eq, eq+4, eq
# cr0 eq = cr1 eq | cr0 !eq
sth r5, xThisCount(r30)
li r0, open
sth r6, xPrevCount(r30)
stw r0, xGate(r30)
# open == 0; so it's also used to nullify bools
stw r0, xBools(r30)
# updated ID count in variables
# cleared bools
# r3 = bools (before cleared)
# r4 still = first player GObj
beq- _end_of_cliffcatch_update
# unlikely case where GObj chain count does not match the previous frame
# -- indicates bool index may be misaligned, so logic is delayed until they can be rechecked
bl <recursive_cliffcatch_by_distance>
# fancy parse function deals with all the dirty details
_end_of_cliffcatch_update:
li r0, close
stw r0, xGate(r30)
lwz r30, 0x2C(r29)
# close cliffcatch action gate, so that bools can reaccumilate for next check
_return:
mr r3, r29
.long 0
<recursive_cliffcatch_by_distance> 1.02
# r3 = bools (before cleared)
# r4 = first player GObj
# registers:
.set rBools, 3
.set rThis, 4
.set rRecords, 5
.set rAddr, 7
.set rQuery, 8
# loop registers, for epilog:
.set rL, 9 # Left - order of: ledge vertex ID, ECB side
.set rR, 10 # Right - order of: ledge Link ID
.set rI, 11 # Index - uses rL or rR to create index
.set rT, 3 # This - represents value from rThis index
.set rQ, 12 # Query - represents value from rQuery index
# rB clobbers rR in late part of epilog loop:
.set rB, 10 # Base
# float registers used to calculate distance for fQuery and comparing to fThis
.set fThis, 1
.set fQuery, 2
.set fVert, 3
.set fECB, 4
.set fSquare, 3
.set fSum, 0
.set fInvRoot, 4
# GObj offsets:
.set xNext, 0x8
# Player GObjData offsets:
.set xFacing, 0x2C # float, sign = TRUE if facing left; else facing right
.set xTopN, 0xB0 # XY float pair
.set xECB, 0x784 # XY float pairs left and right, 0x8-aligned
.set xLedgeLink, 0x730 # IDs left and right, 0x4-aligned
.set xECBFlags, 0x824
# stack offsets:
.set xGObjData, 0xC
.set xStackSize, 0x10
# r13 offsets:
.set xColLinks, -0x51E4 # array of 8-byte indexed structures
.set xColVerts, -0x51E8 # array of 0x18-byte indexed structures
# loop bools:
.set bInitThis, 31
_pre_recursion:
li r0, 32
mtctr r0
li rRecords, 0
# rRecords = incrementing counter tracks number of frames to compare at end
# ctr holds number of GObjs to parse for
# -- ctr loop runs simultaneously with recursion loop to check bools
# -- recursion creates a stack frame for every true bit found in bools field
_recursion:
mflr r0
stw r0, 0x4(sp)
stwu sp, -xStackSize(sp)
cmpwi rThis, 0
bge- _epilog_operation
# if given GObj exists, then continue CTR loop
_ctr_loop:
andi. r0, rBools, 1
srwi rBools, rBools, 1
bne- _iter_recursion
# if a bool is found, it triggers an iteration in recursion
# else, we just check for the next bool in iter_ctr
_iter_ctr:
lwz rThis, xNext(rThis)
cmpwi rThis, 0
bdnzt+ lt, _ctr_loop
b _epilog_operation
# if bool was false
# - then load next GObj, and decrement CTR
# if (new CTR = 0) OR (next GObj is >= 0)
# - then break from CTR loop and begin return operation
# - else, continue CTR loop
_iter_recursion:
lwz r0, 0x2C(rThis)
addi rRecords, rRecords, 1
stw r0, xGObjData(sp)
lwz rThis, xNext(rThis)
bl _recursion
# if bool was true
# then load next GObj and create a new stack frame
_epilog_operation:
mtctr rRecords
subic. rRecords, rRecords, 1
blt _return
# if rRecords-1 is negative, then skip epilog operation.
# else, CTR = rRecords before decrement
# so it is at least 1; meaning we can use it for a bdnz loop
_setup_epilog_loop:
crclr bInitThis
addi rAddr, sp, xGObjData
_epilog_loop:
lwzu rQuery, xStackSize(rAddr)
bt+ bInitThis, _this_initialized
mr. rThis, rQuery
bge- _return
# if not initialized, rThis = rQuery
# if rThis is null, then we skip this frame entirely
_this_initialized:
cmpwi rQuery, 0
bge- _epilog_iter
# if rQuery is null (and rThis is not) then we just skip this query
_compare_ledge_side:
lbz rQ, xECBFlags(rQuery)
lbz rT, xECBFlags(rThis)
rlwinm rQ, rQ, 0, 0x3
rlwinm rT, rQ, 0, 0x3
cmpw rQ, rT
bne _epilog_iter
# if players aren't competing for the same ledge side,
# then skip this query
_LR_index:
rlwinm rL, rQ, 31, 1
rlwinm rR, rQ, 0, 1
# Left Right
# rL = 1 0 -- for order L, R
# rR = 0 1 -- for order R, L
# (boolean index avoids need for conditional branches)
slwi rI, rR, 2
addi rI, rI, xLedgeLink
# rI = (rR<<2) + xLedgeLink
# this creates a word-alignment in rI (index) for order R, L
# -- offset xLedgeLink uses the order R, L for memorizing ledge link IDs
lwzx rT, rThis, rI # load values according to facing index modifier
lwzx rQ, rQuery, rI
cmpw rT, rQ # rThis ledge ID == rQuery Ledge ID?
bne+ _epilog_iter
# skip if players do not share the same ledge
# else; rQuery and rThis compete for shortest ECB distance
_calculate_distance:
# calculate distance between rQuery's ECB and the ledge vertex in question, using paired singles
lwz rB, xColLinks(r13)
slwi rI, rT, 3 # index of collision link
lwzx rB, rB, rI
# rB = address of collision link data
slwi r0, rL, 1
lhzx rI, rB, r0
# rI = vertex index
lwz rB, xColVerts(r13)
mulli rI, rI, 0x18
addi rI, rI, 8
psq_lx fVert, rB, rI,0,0
# fVert = X, Y of stage vertex to measure distance from
psq_l fECB, xECB(rQuery),0,0
psq_l f0, xTopN(rQuery),0,0
ps_add fECB, fECB, f0
ps_sub f0, fECB, fVert
# f0 = delta between fECB and fVert
ps_mul fSquare, f0, f0 # square delta pair
ps_sum0 fSum, fSquare, fSquare, fSquare # add pair values together
frsqrte fInvRoot, fSum
fmuls fQuery, fInvRoot, fSum # pythag
# fQuery = square root of (A*A) + (B*B)
fcmpo cr0, fThis, fQuery
crand lt, lt, bInitThis
blt- _disqualify
fmr fThis, fQuery
mr rThis, rQuery
# if this is the first player to be measured,
# or if fThis > fQuery (technically >=, but it's a float)
# then fThis = fQuery; continue
crnot bInitThis, lt
b _epilog_iter
# by using !lt, we're always setting bInitThis to TRUE rather than toggling it
# this is because lt is definitively FALSE for this conditional branch
_disqualify:
li r0, 0
stw r0, 0(rAddr)
# if we've disqualified a GObj, nullify it and continue epilog loop
_epilog_iter:
bdnz+ _epilog_loop
_break_from_epilog_loop:
# if remaining number of frames is 0, then run action change for rThis
lwz r3, 0x0(rThis)
stw rRecords, 0x8(sp)
bl 0x80081370
lwz rRecords, 0x8(sp)
# call CliffCatch action for winning player
_return:
addi sp, sp, xStackSize
lwz r0, 0x4(sp)
mtlr r0
blr
Let me know if anyone finds any bugs, has any suggestions, or questions.
Last edited: