Completed If-Statement Subaction Event v1.0

Joined
Nov 9, 2014
Messages
632
#1
I've always felt Melee's subaction syntax could use an if/else event. It's possible to make some pretty powerful character edits when combined with Achilles' Character Data Modification code and my Enter Action State code.

This code lets you skip parts of the subaction based on player data (which can be found here). Things you can base if statements on are facing direction (0x2C), horizontal or vertical velocity (0x80 and 0x84 respectively), costume ID (0x619), analog stick X/Y (0x620 and 0x624), buttons held (0x65C), percentage (0x1830), etc.



Event Format:

E9ABXXXX YYYYYYYY ZZZZZZZZ 00000000

E9 = Event ID

A = data size
0 = byte [8-bit]
1 = half word [16-bit]
2 = word [32-bit]
3 = float [32-bit]

B = compare operation
0 = if equal
1 = if not equal
2 = if greater
3 = if greater or equal
4 = if less than
5 = if less than or equal

XXXX = character data offset

YYYYYYYY = value to compare data with

ZZZZZZZZ = length of code to skip if the check fails (IN HEX)

00000000 = nothing, just so crazy hand doesn't have a cow, man


________________________________________________________


Code:
$If-Statement Subaction Event v1.0 [UnclePunch]
C2073118 0000003B
80A30008 88A50000
2C0500E8 418201C4
9421FFBC BE810008
7C0802A6 90010040
7C9F2378 83DF0008
7C7C1B78 83A3002C
A09E0002 887E0001
54630636 2C030000
4182001C 2C030001
41820020 2C030002
41820024 2C030003
41820028 7C84E8AE
88BE0007 4800002C
7C84EA2E A0BE0006
48000020 7C84E82E
80BE0004 48000014
7C84EA14 C03E0004
C0440000 48000084
887E0001 5463073E
2C030000 4182002C
2C030001 41820030
2C030002 41820034
2C030003 41820038
2C030004 4182003C
2C030005 41820040
7C042800 418200C4
480000DC 7C042800
408200B8 480000D0
7C042800 418100AC
480000C4 7C042800
408000A0 480000B8
7C042800 41800094
480000AC 7C042800
40810088 480000A0
887E0001 5463073E
2C030000 4182002C
2C030001 41820030
2C030002 41820034
2C030003 41820038
2C030004 4182003C
2C030005 41820040
FC011040 41820044
4800005C FC011040
40820038 48000050
FC011040 4181002C
48000044 FC011040
40800020 48000038
FC011040 41800014
4800002C FC011040
40810008 48000020
387E0010 907F0008
80010040 7C0803A6
BA810008 38210044
4E800020 807E0008
38630010 7C63F214
907F0008 80010040
7C0803A6 BA810008
38210044 4E800020
7C0802A6 00000000
Code:
Inject @ 80073118



#GET SUBACTION
lwz r5,0x8(r3)
#GET OPCODE
lbz r5,0x0(r5)
cmpwi r5,0xE8
beq exit

######################

/*

Event Format:
E9ABXXXX YYYYYYYY ZZZZZZZZ 00000000

A = data size
    0 = byte [8-bit]
    1 = half word [16-bit]
    2 = word [32-bit]
    3 = float [32-bit]
   
B = compare operation
    0 = if equal
    1 = if not equal
    2 = if greater
    3 = if greater or equal
    4 = if less than
    5 = if less than or equal

XXXX = character data offset
   
YYYYYYYY = value to compare data with

ZZZZZZZZ = length of code to skip if the check fails (IN HEX)

00000000 = nothing, just so crazy hand doesn't have a cow man

*/

#INIT FUNCTION
backup
mr r31,r4
lwz r30,0x8(r31)
mr r28,r3
lwz r29,0x2c(r3)

    #GET CHAR DATA OFFSET
    lhz r4,0x2(r30)

    #GET DATA SIZE
    lbz r3,0x1(r30)
    rlwinm r3,r3,0,24,27
   
        cmpwi r3,0x0
        beq loadByte
        cmpwi r3,0x1
        beq loadHalf
        cmpwi r3,0x2
        beq loadWord
        cmpwi r3,0x3
        beq loadFloat

        loadByte:
            lbzx r4,r4,r29
            lbz r5,0x7(r30)
            b compare
        loadHalf:
            lhzx r4,r4,r29
            lhz r5,0x6(r30)
            b compare
        loadWord:
            lwzx r4,r4,r29
            lwz r5,0x4(r30)
            b compare
        loadFloat:
            add r4,r4,r29
            lfs f1,0x4(r30)
            lfs f2,0x0(r4)
            b floatCompare
           
    compare:
    #GET COMPARE OPERATION
    lbz r3,0x1(r30)
    rlwinm r3,r3,0,28,31
   
        cmpwi r3,0x0
        beq compareEqual
        cmpwi r3,0x1
        beq compareNotEqual
        cmpwi r3,0x2
        beq compareGreater
        cmpwi r3,0x3
        beq compareGreaterEqual
        cmpwi r3,0x4
        beq compareLessThan
        cmpwi r3,0x5
        beq compareLessThanEqual
       
        compareEqual:
        cmpw r4,r5
        beq Success
        b Failed
       
        compareNotEqual:
        cmpw r4,r5
        bne Success
        b Failed
       
        compareGreater:
        cmpw r4,r5
        bgt Success
        b Failed
       
        compareGreaterEqual:
        cmpw r4,r5
        bge Success
        b Failed
       
        compareLessThan:
        cmpw r4,r5
        blt Success
        b Failed
       
        compareLessThanEqual:
        cmpw r4,r5
        ble Success
        b Failed
       
    floatCompare:
    #GET COMPARE OPERATION
    lbz r3,0x1(r30)
    rlwinm r3,r3,0,28,31
   
        cmpwi r3,0x0
        beq floatCompareEqual
        cmpwi r3,0x1
        beq floatCompareNotEqual
        cmpwi r3,0x2
        beq floatCompareGreater
        cmpwi r3,0x3
        beq floatCompareGreaterEqual
        cmpwi r3,0x4
        beq floatCompareLessThan
        cmpwi r3,0x5
        beq floatCompareLessThanEqual
       
        floatCompareEqual:
        fcmpo cr0,f1,f2
        beq Success
        b Failed
       
        floatCompareNotEqual:
        fcmpo cr0,f1,f2
        bne Success
        b Failed
       
        floatCompareGreater:
        fcmpo cr0,f1,f2
        bgt Success
        b Failed
       
        floatCompareGreaterEqual:
        fcmpo cr0,f1,f2
        bge Success
        b Failed
       
        floatCompareLessThan:
        fcmpo cr0,f1,f2
        blt Success
        b Failed
       
        floatCompareLessThanEqual:
        fcmpo cr0,f1,f2
        ble Success
        b Failed


    Success:
    addi r3,r30,0x10            #if statement length
    stw r3,0x8(r31)
    restore
    blr
   
    Failed:
    lwz r3,0x8(r30)            #code to run length
    addi r3,r3,0x10            #if statement length   
    add r3,r3,r30            #increment sub pointer
    stw r3,0x8(r31)
    restore
    blr   
   

######################
exit:
mflr    r0
 
Joined
Feb 15, 2018
Messages
1
#2
How do I use this with Crazy Hand? I started on Melee modding just yesterday and have some idea of what I'm doing since I came from Brawl modding, but raw code always gets me.
 

TDRR

Smash Journeyman
Joined
Sep 18, 2017
Messages
237
Location
Venezuela
#3
Cool stuff, might be useful for all the custom characters i want to do.

What are all the other codes that add/modify subactions?
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
355
#4
Cool stuff, might be useful for all the custom characters i want to do.

What are all the other codes that add/modify subactions?
- Damage Self or Heal Self
- Projectile/Item Spawn
- Character Data Modifications
- Action State Logic
- Inline FSM Event
- Turnaround
- Interrupt Swap
- Create Reflect Hitboxes
- Enter Action State
- Transformation

Edit -- added some more that I missed

---

With tools like Melee Code Manager available, I've often thought about how all of these codes might benefit from a unifying platform... I have a ton of ideas I'd like to sink my teeth into regarding custom subaction events, but the current methods for claiming opcodes is kind of difficult to manage with so many separate codes.

Achilles offered a brilliantly simple solution back in the day when this was all new, which was to extend the opcode with a secondary one. I'd be interested in pursuing that solution in order to make it easier for new event syntaxes to be made without conflicts. I really think that subaction events are a powerful tool for hacking melee, because they can connect file-based moveset data to code -- which can be written to do pretty much anything.

UnclePunch UnclePunch : could you imagine if players each had their own set of persistent "registers" and "condition registers" for storing values? The impact it might have on codes like this -- or Achilles' original vision of modifying character data -- might give character modders the power to basically write their own character codes through subaction events.

What if we deconstructed Achilles' character data modification code into several smaller syntaxes that dealt with values stored in persistent "registers" that were allocated for each player? What if Achilles' code were extended to be able to navigate pointers? There's so much power in this idea that it drives me nuts.
 
Last edited:

TDRR

Smash Journeyman
Joined
Sep 18, 2017
Messages
237
Location
Venezuela
#5
Awesome stuff!

So, is it possible to make a "Hold button to get a different attack" (eg. Normal Pichu N-Air would happen if you tap the A button, but holding it gives it multiple electrical hitboxes, like Mewtwo's N-Air)

Because if i could do that then i would instantly start making a true Melee Minus :p

EDIT: Ok, so theoretically the thing i just said would be E910, then 065C, the currently pressed buttons for that player, then 00000100, then the length of a GoTo event maybe branching off somewhere in the DOL (Without counting the following 00000000 from this event, right?) and the ending 00000000, is this correct?

And to make a DOL event finish correctly, how do i do that?
 
Last edited:
Joined
Nov 9, 2014
Messages
632
#6
Awesome stuff!

So, is it possible to make a "Hold button to get a different attack" (eg. Normal Pichu N-Air would happen if you tap the A button, but holding it gives it multiple electrical hitboxes, like Mewtwo's N-Air)

Because if i could do that then i would instantly start making a true Melee Minus :p

EDIT: Ok, so theoretically the thing i just said would be E910, then 065C, the currently pressed buttons for that player, then 00000100, then the length of a GoTo event maybe branching off somewhere in the DOL (Without counting the following 00000000 from this event, right?) and the ending 00000000, is this correct?

And to make a DOL event finish correctly, how do i do that?
sounds right, i believe you just end the dol event with 00000000 for it to terminate.
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
355
#7
UnclePunch UnclePunch -- I’ve noticed some odd behavior with this code. I looked it over and found a few bugs:


This check for the vanilla E8 opcode at the beginning of the code is reading from the player GObj argument, not the event controller structure in its data. This coincidentally loads a pointer at the expected 0x8 displacement, which in this case is the “next” player pointer.

This of course means that the vanilla opcode syntax is not preserved as intended.

---



I think you already know this, but the LR goes in the previous stack frame -- not the new one. Doing this correctly will prevent the callstack from becoming confused about what's currently being executed.

---



Only “byte” data types appear to be recognized by this code because the parser does not finish extracting the type nibble as a zero-shifted int.

Code:
rlwinm r3, r3, 28, 0xF
Also just in case you were unaware, you can specify the 32-bit mask (post-rotation) of an rlwinm in place of the beginning/end bits like I've done above. I find that it's often easier to read and write for cases like this.

---

I also have a suggestion:

That last blank word in your syntax could be useful as a 32-bit mask. If the loaded value being compared (and the query input) were ANDed by this mask, then individual bits could be compared.

So, for example, if the buttons bool field at 0x65C were loaded with a mask of 00000200, then the B button could be checked for without other buttons interfering with the logic.

Edit: Alternatively, an inverted mask would allow for the same thing by specifying which bits to block; making the current 00000000 line represent an unmasked comparison. This would make the suggested change more compatible with the current syntax.
 
Last edited:
Joined
Nov 9, 2014
Messages
632
#8
UnclePunch UnclePunch -- I’ve noticed some odd behavior with this code. I looked it over and found a few bugs:


This check for the vanilla E8 opcode at the beginning of the code is reading from the player GObj argument, not the event controller structure in its data. This coincidentally loads a pointer at the expected 0x8 displacement, which in this case is the “next” player pointer.

This of course means that the vanilla opcode syntax is not preserved as intended.

---



I think you already know this, but the LR goes in the previous stack frame -- not the new one. Doing this correctly will prevent the callstack from becoming confused about what's currently being executed.

---



Only “byte” data types appear to be recognized by this code because the parser does not finish extracting the type nibble as a zero-shifted int.

Code:
rlwinm r3, r3, 28, 0xF
Also just in case you were unaware, you can specify the 32-bit mask (post-rotation) of an rlwinm in place of the beginning/end bits like I've done above. I find that it's often easier to read and write for cases like this.

---

I also have a suggestion:

That last blank word in your syntax could be useful as a 32-bit mask. If the loaded value being compared (and the query input) were ANDed by this mask, then individual bits could be compared.

So, for example, if the buttons bool field at 0x65C were loaded with a mask of 00000200, then the B button could be checked for without other buttons interfering with the logic.

Edit: Alternatively, an inverted mask would allow for the same thing by specifying which bits to block; making the current 00000000 line represent an unmasked comparison. This would make the suggested change more compatible with the current syntax.
Yeah I fixed the stack frame stuff in my macros since this. The pointer and bit extraction were mistakes on my part. Thanks for bringing it to my attention. I'll have to fix this at some point in the future, I'm a bit busy atm though.
 

Dodoshian

Smash Rookie
Joined
Jul 7, 2016
Messages
20
Location
Mobile, Alabama
#9
I am having some difficulty implementing your code. It is seemingly random if the if statement passes.

E9321830 42E60000 000000F4 00000000 is the event I am using. Shouldn't this check the percentage, and skip f4 bytes of code unless the percentage is over 115? It doesn't work that way for me. Sometimes it will never trigger, and sometimes it will trigger only at arbitrary percentages. I don't know what I am doing wrong. I must not be taking something into account.

EDIT: Upon further testing, it seems that your code breaks the vanilla game's aesthetic wind effect event because it uses the same flag. Any attack that uses this event, such as ganon's up tilt, will immediately crash the game. I can only assume that this code is still a work in progress. Thank You.
 
Last edited:
Top