• 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 Random Damage Multiplier (Normal and Projectiles)

abysspartyy

Smash Cadet
Joined
May 11, 2015
Messages
55

* note: the match above has a damage ratio of 0.25 so players aren't killed in one hit
  • Every hit will roll a number between 0 to 100 and multiply the hit's damage by that number divided by 10
    • Example: so if a hit does 25 damage and our random number is 26, the final damage calculation is as follows:
      • (25 * 26) / 10 = 65.0 damage
        In other words, it did 2.6 times the damage.
By default, the maximum multiplier is set as 101 (0x65 in hex) which means that a hit can do a maximum of 10 times ((101 - 1) / 10) the damage!

If you want to change the maximum multiplier, change the highlighted part in orange to whatever multiplier you want in hex.
For example, I want it to do between 0 and 3 times the damage only:
  1. Add 1 to 3 = 4
  2. Multiply 4 by 10 = 40
  3. Convert it to hex (use a hex converter online) = 28
  4. Change the highlighted part from 65 to 28
Rich (BB code):
$Random Damage Multiplier [sushie]
*Every hit has a random multiplier applied to it
C2089284 00000007
38600065 3D808038
618C0580 7D8903A6
4E800421 3C63000A
90610014 E3E15014
102107DA 1021F824
48000004 8001002C
60000000 00000000

Python:
import geckon, ../melee

const
    MaxMultiplier = 10.0
    Divisor = 10
    OriginalCodeLine = ppc: lwz r0, 0x002C(sp)

defineCodes:
    createCode "Random Damage Multiplier":
        authors "sushie"
        description "Every hit has a random multiplier applied to it"
        patchInsertAsm "80089284":
            # free to use: f31, r0, r30, r31
            {hsdRandi(maxVal = (MaxMultiplier * 10).intVal, inclusive = true)}
            # Cast the Divisor and our multiplier as floats into f31
            addis r3, r3, {Divisor}
            stw r3, 0x14(sp)
            psq_l f31, 0x14(sp), 0, 5
            # Multiply damage by multiplier
            ps_muls1 f1, f1, f31
            # Divide our new damage by 10 and store in f1
            ps_div f1, f1, f31
            b OriginalExit
         
            OriginalExit:        
                {OriginalCodeLine}

Rich (BB code):
$Random Damage Multiplier (Non-projectile) [sushie]
C207AC30 00000012
FC400890 38600065
3D808038 618C0580
7D8903A6 4E800421
48000029 EC220072
48000019 7C6802A6
C0430000 EC211024
FC00081E 48000054
4E800021 41200000
7C0802A6 90010004
9421FF00 BE810008
D0410038 3C004330
C84298A8 6C638000
900100F0 906100F4
C82100F0 EC211028
C0410038 BA810008
80010104 38210100
7C0803A6 4E800020
7FE3FB78 00000000
Rich (BB code):
$Random Damage Multiplier (Projectiles) [sushie]
*Every projectile hit has a random damage multiplier applied to it
C22724A8 00000012
7C7E1B78 38600191
3D808038 618C0580
7D8903A6 4E800421
2C030000 4081006C
48000021 48000015
7C6802A6 C0430000
EC411024 48000054
4E800021 41200000
7C0802A6 90010004
9421FF00 BE810008
D0410038 3C004330
C84298A8 6C638000
900100F0 906100F4
C82100F0 EC211028
C0410038 BA810008
80010104 38210100
7C0803A6 4E800020
7FC3F378 00000000
Python:
import geckon

const
    FuncIntToFloat = "IntToFloat"
    MultiplierMax = 40.0 # up to 40 times the damage
    RegisterMultiplier = f2
    RegisterRandomMultiplier = f1
    OriginalCodeLine = ppc:
        mr r30, r3

defineCodes:

    createCode "Random Damage Multiplier (Projectiles)":
        authors "Odante"
        description "Every projectile hit has a random damage multiplier applied to it"

        patchInsertAsm "802724A8":
            {OriginalCodeLine}

            # get random multiplier in r3
            {hsdRandi(max = (MultiplierMax * 10).int, inclusive = true)}
            # multiplier shouldn't be 0
            CheckIfValidMultiplier:
                cmpwi r3, 0
                ble Exit
                bl {FuncIntToFloat} # convert to float, result is now in f1

            # divide multiplier by 10
            # safe to use f2 because we are going to overwrite it anyways
            bl MultiplierFloats
            mflr r3
            # load the float 10 into f2
            lfs {RegisterMultiplier}, DivisorOffset(r3)
            # divide our random multiplier (f1) by 10 (f2) and store it back into f2
            fdivs {RegisterMultiplier}, {RegisterRandomMultiplier}, {RegisterMultiplier}
            b Exit

            MultiplierFloats:
                blrl
                `.set` DivisorOffset, 0x0
                `.float` 10.0

            {FuncIntToFloat}:
                {defineIntToFloat()}

            Exit:
                mr r3, r30 # restore r3
Code:
####################################################
Random Damage Multiplier (Non-projectiles) [Odante]
####################################################

PatchHookBranchLink 0x8007ac30 {
    .macro branchl reg, address
    lis \reg, \address @h
    ori \reg,\reg,\address @l
    mtctr \reg
    bctrl
    .endm

    # maximum random multiplier / 10
    # example: 100 = 10 times the damage
    # 50 = 5 times the damage
    # 5 = 0.5 times the damage
    .set MultiplierMax, 100

    # f1 contains our original damage
    # after all calculations including model scale and
    # smash attack charging
    # r3 is free to use here, no backup needed

    DamageMultiplier:
    fmr f2, f1 # backup f1 - original damage to deal

    li r3, MultiplierMax + 1 # set max num to generate (from 0 to max (not including max))
    branchl r12, HSD_Randi
    bl IntToFloat # convert to float and store result in f1

    fmuls f1, f2, f1 # multiply damage (in f2) by multiplier (in f1) and store in f1

    # f2 is free to use now

    # Divide our damage by 10
    bl MultiplierFloat
    mflr r3
    lfs f2, 0x0(r3) # load 10.0 into f2
    fdivs f1,f1,f2 # divide damage (in f1) by 10 (in f2) and store in f1

    fctiwz f0,f1
    b Original_Exit

    MultiplierFloat:
    blrl
    .float 10.0

    ##############################
    # Credits to UnclePunch
    # From: https://github.com/UnclePunch/Training-Mode/blob/master/ASM/training-mode/Onscreen%20Display/Wavedash%20Display/Wavedash%20Timing%20Display.asm
    IntToFloat:
    mflr r0
    stw r0, 0x4(r1)
    stwu    r1,-0x100(r1)    # make space for 12 registers
    stmw  r20,0x8(r1)
    stfs  f2,0x38(r1)

    lis    r0, 0x4330
    lfd    f2, -0x6758 (rtoc)
    xoris    r3, r3,0x8000
    stw    r0,0xF0(sp)
    stw    r3,0xF4(sp)
    lfd    f1,0xF0(sp)
    fsubs    f1,f1,f2        #Convert To Float

    lfs  f2,0x38(r1)
    lmw  r20,0x8(r1)
    lwz r0, 0x104(r1)
    addi    r1,r1,0x100    # release the space
    mtlr r0
    blr
    ##############################

    Original_Exit:
    mr r3, r31
}

Thank you to UnclePunch UnclePunch for the IntToFloat function from Wavedash Timing Display.asm.
Also thank you to Punkline Punkline for information regarding hardware casting. Helped reduce the code size considerably!
 
Last edited:

abysspartyy

Smash Cadet
Joined
May 11, 2015
Messages
55

I wrote a code that works for projectiles/items too. By default, it's maximum multiplier is also 10x time damage. Please see the main post for the new code.
 
Last edited:

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Haha, this is cool. It's always fun having more code writers around here, I've been enjoying looking over these recent codes you've been posting.


---

Since you're playing around with float conversion stuff, I wanted to mention that you have a good opportunity here to use hardware casting instead of software casting -- which lets you cast up to 2 16-bit integers into floating points using just 1 instruction. This is possible because Melee uses the OS library to set up default fastcast quantization scales useful for quickly casting small integers without any setup.


The hardware casting is invoked as part of a load instruction (like lfs or lwz) in a special instruction called psq_l "paired-singles quantized load". These special ps* instructions are specially available to the ppc architectures used for the gamecube and the wii, and use an odd formatting inside the 64-bit floating point registers that basically splits them into 2 32-bit floating points.

Looking at the injection context of your first code here, you can see that there are 64-bits in the temporary stack frame at byte offset 0x18(sp) that's used to cast this float back into an int for storage in the struct in r31:




-- if you use this offset in your injection to store your int temporarily, you can then immediately load it with psq_l to cast the int in the same amount of time it would take to simply load it.

Since the float is used in a fctiwz instruction afterwards though, you also need to ensure it's formatted properly after cast, which can be done by simply moving it to a new register with fmr.



So these 3 instructions can be used to safely implement hardware casting in place of the call to your software casting function:

Code:
fastcast.u16 = 5
# GQR5 is what the OS sets up for casting 16-bit unsigned integers

sth r3, 0x18(sp)
# store 16-bit integer into temp memory

psq_l f0, 0x18(sp), 1, fastcast.u16
# load single into paired vector using the correct GQR for our fastcast setting

fmr f1, f0
# ensure ps-formatted float is now a 64-bit double, for propper fctiw conversion


You can only rely on hardware casting for ints you can read as 8 or 16 bits, so it's not always possible to utilize -- but when you can, it's useful for making your gecko codes a little more compact.

They're also very useful for skipping multiplication/division by powers of 2, if you use QR6 or QR7 to make your own compression scale for fixed point conversion. I have some more info in this post.
 
Last edited:

abysspartyy

Smash Cadet
Joined
May 11, 2015
Messages
55
Haha, this is cool. It's always fun having more code writers around here, I've been enjoying looking over these recent codes you've been posting.
...
Wow thank you for the valuable information. I'm still new to writing codes and I'm looking for all the help and information I can get. I'll update my current and future codes to utilize hardware casting.
 

abysspartyy

Smash Cadet
Joined
May 11, 2015
Messages
55
Punkline Punkline

Hey I manged to combine the 2 codes together and reduce the total lines to just 10 lines with your information! I also used a different injection address that allowed me to just multiply the damage for both normal and item hits.

Edit: see main post for updated gecko code and asm notes

I'm not 100% sure if I could use 0x14(sp) though. If you could provide any feedback, I would appreciate it.
 
Last edited:

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Punkline Punkline


Hey I manged to combine the 2 codes together and reduce the total lines to just 10 lines with your information! I also used a different injection address that allowed me to just multiply the damage for both normal and item hits.


Edit: see main post for updated gecko code and asm notes


I'm not 100% sure if I could use 0x14(sp) though. If you could provide any feedback, I would appreciate it.

VERY nice job! Playing code golf in ASM can be really satisfying, lol. Especially when it's all injections/overwrites.

0x14(sp) looks to be safe in that context. Usually -- just like you've done here -- the end of a function (the 'epilog') is the safest place to exploit padding in the stack for purposes like this.


---

One thing to note about paired singles is that it's a sort of supervisor-level 'mode' in the CPU that Melee has to be in in order to do all of the matrix transformation math used in animating and projecting model joints/textures in 3D space (quickly). It's always on, but it means that it sets a pseudo-permanent protocol for how floating points work inside the fprs.

The technical tradeoff for having this 'mode' on that Melee pays is that all normal single-precision floating point instructions are basically handled as pairs that duplicate the first float to create a redundant pair. Because of this however, there are some exploitable rules about how regular float instructions sort of 'mix' the formats by handling them with exception subroutines that I believe run as interrupts.

This basically means that -- in most cases -- you're free to operate on paired singles using regular 'single' float operations; however, only the first float in the pair will be operated on.



So for example, fadds and ps_add are essentially the same instruction -- fadds just duplicates its single floating point into the second float for the 'pair' in a paired singles instruction. Attempting to perform a regular fadds on pair will just cause only the first float in the pair to be worked with.

Using the 1 argument in the instruction psq_l fD, xOffset(rA), 1, 5 tells it to basically just create a 'dummy' float 1.0 for the second float in the vector pair. You can -- for most purposes -- basically treat a casting like this as a regular float, and interact with it as such. The exceptions caused by format issues will be caught and handled automatically by this special 'mode' in the CPU.



The context of your old code involved an interaction with a 'double' precision value that went through a fctiwz instruction. Normally, the exceptions I mentioned would basically handle converting this value into the right format before operating on it -- but the fctiw instruction family has a note in its documentation that explains that the instruction doesn't have any procedure for handling the denormalized float being converted into an integer while formatted as a pair. It relies on a proper input because it takes no steps to remove the 'exponent' part of the lower word in the double float, where it would still technically be in a paired-singles format.

So basically, it was just a special case.



In this new case though, your context code doesn't have direct contact with a fctiw instruction, and you're only using the first float in the pair. Because of this, you could operate on the loaded floats like regular singles, and make the last operation's destination register be the same as the target destination of the fmr instruction at the end; and then forgo the fmr.


---

I'm not sure if it would save any more space, but you might also be able to figure out a way to hardcode the multiplier constant as your second float in the pair casting, for free -- basically allowing you to declare it as an immediate instead of as a loaded data value. This might only save space because it would then remove the need for an inline blrl data table, which adds some extra instructions as an overhead.

To do this, you would just need to store your integer input as a 32-bit value with stw instead of sth, and build it with 2 instruction to give it a high and low immediate value; lis r3, HIGH_BITS; ori r3, r3, LOW_BITS. You could then set the 1 to a 0 in your psq_l and load 2 values at once -- basically making the loaded 10.0 const unnecessary.


If you wanted to do this while still supporting a fractional component to the multiplier constant, then you could devise a custom scale using QR7 instead of using one of the predefined fast-cast integer scales. The value 10.0 won't need this thanks to fastcast params set up by the OS, but if you wanted to be more specific I can help you figure that out.

Edits: typos
 
Last edited:

abysspartyy

Smash Cadet
Joined
May 11, 2015
Messages
55
VERY nice job! Playing code golf in ASM can be really satisfying, lol. Especially when it's all injections/overwrites.
...
Yes it was extremely satisfying seeing the reduction in the number of code lines lol. I modified the code based on some of your suggestion and stored both the new random multiplier and the divisor constant as a pair so it did help get rid of the blrl data table. The code is now 8 lines compared to the original 38 (19 + 19 for normal and projectiles) lines of code!!!

If you wanted to do this while still supporting a fractional component to the multiplier constant, then you could devise a custom scale using QR7 instead of using one of the predefined fast-cast integer scales. The value 10.0 won't need this thanks to fastcast params set up by the OS, but if you wanted to be more specific I can help you figure that out.
Awesome! I'll ask you if I have any questions when I try this out for future codes! Once again thank you for all of the info. It was pretty fun trying to play code golf in ASM haha
 
Last edited:
Top Bottom