Punkline
Dr. Frankenstack
- Joined
- May 15, 2015
- Messages
- 423
I suppose this thread is a continuation of the Costume Dependant Sword Trail Colors thread. I thought about replying in there, but this goes off topic a bit.
I have so many open projects right now that most are neglected. I was working on something a while back though that was an exploration of the ways that sword trails can be manipulated with code. I went over some of the details in this post, but I didn’t include a mod because I was (and still am) unsure of what I wanted to implement (and how.)
I figure this new thread can serve as a more general purpose thread for discussing the nature of sword trails, and perhaps other joint “articles” or use of GX FIFO for primitive drawing.
I’ll be sharing code here, but it’s for the purpose of explanation and discussion. Feel free to contribute with your own codes/words.
Edits - cleaned up some stuff
---
Here’s the topmost fossil from the record that was my experiment:
This mostly-working concept is the result of lots of experimentation, but is more of a snapshot of a brainstorm rather than a final code. It could use some work.
It’s a bit involved, and--in anticipation of my habit of propagating new projects from unfinished ones--I left myself descriptive notes of the code and its functions:
To summarize, the code works by creating an isolated buffer that acts as a “common” set of sword trail arguments that can be repopulated at any time with new variables taken from storage. These “common” arguments satisfy the parameters used in the sword trail update/draw functions, and so the injection codes conditionally replace normal parameters with newly populated “common” arguments in order to take control of sword trail mechanics.
---
The experiment gives control over the following in real time (as in, changes are reflected on the next frame without the need to respawn the player)
CH - Character ID -- specify what character gets this sword trail (can be non-sword users)
CS - Costume ID -- specify what costume gets this sword trail
XtYtZt - XYZ translation (position offset from origin bone) (final coords are global, relative to stage)
XrYrZr - XYZ rotation (rotation offset from origin bone, not working as intended--WIP)
Vrt1 - 2-byte Sword Trail Vertex 1 (trail base, length from origin of pole)
Vrt2 - 2-byte Sword Trail vertex 2 (trail tip, length from origin of pole)
ff - Flags (see below for more info)
Bo - BoneID used for creating origin rotation/scale for pole geometry
Bt - BoneID used for translating the position of the final trail keyframe; can be different from Bo
Ag - Alpha gradient -- two 4-bit nibbles, one for Starting Alpha, one for Ending Alpha
R1G1B1 - Gradient RGB hexcolor 1 (trail base) (copy/pastable RRGGBB format)
E1 - Ending taper strength 1 (trail base, % of vertex 1 length)
R2G2B2 - Gradient RGB hexcolor 2 (trail tip)
E2 - Ending taper strength 2 (trail tip, % of vertex 2 length)
Signed shifts and the melee meter conversion create interpolations that approximate the following linear ranges as resulting floats. I’m not sure, but I think this is more commonly called “quantization” or “float packing” and is a very useful technique for compressing arrays of floats into a more manageable size:
Xt, Yt, Zt - Signed 8-bit range = -1600% -- 1600% (covers +/- 16.0 global units)
Xr, Yr, Zr - Signed 8-bit range = -100% -- 100% (rotation summarized as %) (currently a WIP…)
Vrt1, Vrt2 - Signed 16-bit range = 0xNN.NN (covers +/- 128.0 units relative to bone scale)
E1, E2 - Signed 8-bit range = -200% -- 200% (% of length defined by vertices)
“ff” Flags are used to create options for handling the XtYtZt XrYrZr bytes differently:
0xF0 = translation flags (position offset)
-- 0x70 = use X, Y, Z translation origin from TopN instead of Bt bone
-- 0x80 = use Bo instead of TopN values for above flag
0x0F = rotation flags (rotation offset)
-- 0x70 = use X, Y, Z rotation origin from TopN instead of Bo bone
-- 0x80 = use "0.0" instead of TopN values for above flag
You can modify these easily by using the ( Edit ) feature in MCM:
---
I was close to posting about this a while back when it was in development, and I have some screenshots that help explain what these variables do. Please note that these are from an earlier version, and the position of the variables are now different--but the meaning is the same:
Edit - and here's one I missed that covers using basic use of XtYtZt:
There are some major differences in version 0.70 of the experiment compared to these screenshots. I'll have to redo them if I ever work on this code again.
The alpha (opacity) bytes have been compressed into 4-bit nibbles, and a 2nd bone ID may now be used to create trail behavior that follows one bone but translates its origin to another bone.
To use sword trails created for non-sword characters, use the C4 event in Crazy Hand:
(this mario trail is included in the default examples)
---
@Achilles1515 @DRGN -- I designed this back in october with the intention of making a custom subaction event. In this sense, it would be doing something very similar to what you guys were discussing in the sword trail color thread--in that the sword trail data would come from inside of a character file rather than in the DOL.
I’m fascinated by the prospect of using file data to store arguments for flexible code. I really like DRGN’s idea of putting default trail arguments in the costume file to simplify costume designing. In a similar sense, a code that changes these colors on the fly would accomplish a similar simplification by existing as event data in the moveset file.
My code above is just an experiment, and includes a lot of stuff I'd like to gut. I had made the code use a Character ID and Costume ID to identify a set of sword trail arguments to be applied by default. If you look carefully, you’ll notice that this identification takes up 2 bytes at the beginning of the syntax I described.
These 2 bytes are only for implementation. They are parsed by the code, but only as a means of keying a character costume to a trail definition. The parse function that actually reads the argument data for creating the sword trail buffer skips these IDs.
In other words, in any other implementation, these 2 bytes are free. This allows them to be implemented according to Achilles’s specification for custom subaction events. I believe that we can apply these specifications to the C4 event--which happens to be the event used to toggle sword trails. In this way, we could write custom events that modify some or all of the above variables as a part of the event data in movesets. Since the code only overrides pointers it has stored away, the actual default trails are untouched in this way and can be reverted back.
---
Anyway, I’m just getting this out there. I got very caught up in the research phase of this project, which eventually turned into a hunt for other drawing functions--and then several other projects. I’ll eventually get back to this, but in the meantime I thought some of you might be interested in the details.
At some point I’m gonna try making the draw function either iterative or recursive so that it can be used to draw multiple sword trails per frame; and make modifications to the gradient function to allow for 4-point color interpolation. Give Mewtwo a kamehameha or something.
I have so many open projects right now that most are neglected. I was working on something a while back though that was an exploration of the ways that sword trails can be manipulated with code. I went over some of the details in this post, but I didn’t include a mod because I was (and still am) unsure of what I wanted to implement (and how.)
I figure this new thread can serve as a more general purpose thread for discussing the nature of sword trails, and perhaps other joint “articles” or use of GX FIFO for primitive drawing.
I’ll be sharing code here, but it’s for the purpose of explanation and discussion. Feel free to contribute with your own codes/words.
Edits - cleaned up some stuff
---
Here’s the topmost fossil from the record that was my experiment:
Code:
-==-
Custom Sword Trails v0.70
Define custom sword trails for any character.
Use the C4 subaction event to control trail toggles.
( Edit ) CSWT_getCustomTrailData for customization.
- follow the format detailed by the commented examples in order to add any number of custom sword trails to the game.
[Punkline]
Version -- DOL Offset ------ Hex to Replace ---------- ASM Code
1.02 ----- 0x800C3098 ---- 807F0004 -> Branch
# Mod Overview: CSWT_070
# 5 hooks @ 2 game functions, responsible for updating a sword trail.
# -- One for bone origin -> article spawn defines position/rotation/vertices
# -- The other for drawing the primitive accross the 3 last keyframes, stored in the player entity.
# The included data tables in this code uses a blrl to make its location arbitrary
# - This allows the data to be easily compiled into available MCM code-space; making it reusable/replacable
# - Without known static locations, the data can be called using the MCM special branch syntax: bl <get___>; mflr r3
# The data tables are used between several hooks in each function, and handled by functions written with MCM to create a reusable library.
# The override data table is used like a volatile, stack-independent buffer; populated with fake parameters before each sword trail update
# - Injections intercept the sword trail data from their functions' contexts, and use standalone functions to determine if they should be overridden
# -- If character/costume identiy matches a user definition, or another code using the provided library functions indicate there should be an override;
# -- then the real structures are overridden with pointers to fake data as they are passed through the update logic.
# The buffer consists of 3 data partitions:
# - 1st part is used to indicate whether or not data is available for overriding the next sword trail draw.
# These alone decide the logic of the override, so I often refer to them as "indicators" in my comments
# - 2 pointers in 0x0 and 0x4 tell the override function where to grab formatted sword trail information from before an update, in place of what it
# -- 0x0 - pointer is to fake/real sword trail parameters struct (like Marth's entity data--written to by trail color codes in the past)
# -- 0x4 - pointer is to XYZ position/rotation float coords that adjust each new swordtrail keyframe.
# -- 0x8 - pointer is to a specified bone instance, used to keep track of the ID specified in Bt.
# - 0 as a null vallue for any of these will cause the game to take default actions.
# - Bone adjustments are optional, but a trail parameters pointer is mandatory in order for an override to occur.
# - The next 2 partitions are what the code functions use in order to store data (to be pointed to by the override indicators.)
# -- 0x0C -- 0x28 - mimics a real sword trail struct, like those contained at the end of sword-user article data (and beam swords.)
# -- 0x2C -- 0x44 - mimics 1/3 of a the structure at internal player.0x20B0 (includes pos/rot global coords of 3 trail keyframes)
# - The indicators from part 1 are what determine the override, but these values are still volatile.
# - the indicators in part 1 are wiped to 0 as soon as they are used to override the sword trail update routines.
# -- the 2 update functions will update each player asyncronously from one another, so this wipe happens twice per frame per player.
# --- because of this, 2 hooks per function are responsible for initializing/populating/terminating the override indicators/data
# - a 3rd hook in the keyframe/bone update applies post-update adjustments to the coordinate information in the player entity.
# ---
# INJ: (BONE) OPEN REGISTER
# @ 800C3098 : lwz r3, 0x4(r31)
# r31 = internal player data pointer
# r5 = (future) sword trail struct pointer
# r29 is safe to use
# - This gets called just as the update recognizes the player is going to use a sword trail bone update
# -- Code uses this to 0 out an important register so that a later hook can recognize null pointers in place of a struct
# --- it also decides whether or not to populate the override pointers with user defined default trail parameters
lbz r3, 0x2101(r31)
andi. r3, r3, 1 # check bit 0x01 for beam sword trail condition
bne- return # don't use custom trails if the beam sword is active
# else, attempt to feed override struct any custom data
bl <CSWT_getTrailOverride> # <- not actually a function; just returns pointer in LR
mflr r29 # r29 = override struct
lwz r3, 0(r29) # sword trail parameters override indicator; gets wiped to 0 each trail update
andis. r3, r3, 0x8000 # check if there's already a pointer in the sword trail override indicator
bne- return # if there is, then use that instead of generating one from user definitions (library support)
mr r3, r31
bl <CSWT_returnCustomTrail> # else, see if the user has defined a trail that matches current character/costume
stw r3, 0(r29) # r3 returns pointer to fake sword trail parameters buffer
stw r4, 4(r29) # r4 returns pointer to start of bone adjustment floats in buffer; cast from XtYtZt and XrYrZr
stw r5, 8(r29) # r5 returns pointer to bone instance specified by bone ID in "Bt" byte parse
return:
li r5, 0 # 0 out register to make arbitrary data recognizable as "null"
# without this hook, the register will go unhandled and attempt to read arbitrary data
# this is because the game has hardcoded handles for sword users and beamswords; but not non-sword users without a beamsword
lwz r3, 0x4(r31)
.long 0x00000000
----------- 0x800c30d4 ---- C0050018 -> Branch
# INJ: (BONE) TRAIL UPDATE
# @ 800c30d4 : lfs f0, 0x0018 (r5) (setting up call that handles bone ID)
# r31 = internal player data pointer
# r5 = sword trail struct pointer
# - This gets called just as the game thinks it knows what sword trail it's going to use for the update.
# -- The code uses this as an opportunity to override the sword trail parameters that get used.
bl <CSWT_getTrailOverride># <- not actually a function; just returns pointer in LR
mflr r9 # pointer to sword trail override indicators/flags
lwz r10, 0(r9) # r10 = trail parameters override pointer
andis. r0, r10, 0x8000 # check if sword trail override indicator is a pointer
beq+ checkRegister
mr r5, r10 # if override is valid, it becomes our new sword trail struct
checkRegister:
andis. r0, r5, 0x8000 # check if value is a pointer
bne+ default
abort: # if no pointer is in r5, then it isn't safe to run the rest of the function
b 0x800c316c # so treat it as though the player weren't even flagged to render a trail (return from host function)
default:
lfs f0, 0x0018 (r5)
.long 0x00000000
----------- 0x800c3128 ---- 887F2101 -> Branch
# INJ: (BONE) ADJUST GLOBAL POSITION/ROTATION
# @ 800c3128 : lbz r3, 0x2101 (r31)
# r30 = internal player data offset 0x20B0 + (keyframeID*0x18)
# - This gets called just after the coordinates for the trail have all been stored
# -- The code uses this as an opportunity to adjust these coordinates each update
lbz r3, 0x2101 (r31)
andi. r0, r3, 1 # check for beamsword flag
bne- return # if beamsword flag is on, then our previous hooks didn't set up the override struct
mr r3, r31 # else, we apply the bone adjustments pointed to in 0x4 of the override struct
mr r4, r30
bl <CSWT_applyBoneAdjustments>
return:
lbz r3, 0x2101 (r31)
.long 0x00000000
----------- 0x800c2674 ---- 38600001 -> Branch
# INJ: (RENDER) OPEN REGISTER
# @ 800c2674 : li r3, 1
# r28 = internal player data pointer
# r30 = (future) sword trail struct pointer
# - This gets called just as the update recognizes the player is flagged for a sword trail render
# -- The code uses this as an opportunity to update the override indicator value with any user-defined trails
# --- It also 0s out an important register so that a later hook can recognize null pointers in place of a struct
bl <CSWT_getTrailOverride> # <- not actually a function; just returns pointer in LR
mflr r30 #r30 now holds a pointer to the custom (temporary) sword trail override structure
lwz r3, 0(r30)
andis. r0, r3, 0x8000 # check if there's a pointer in the sword trail override indicator
bne- zeroOut # if so, we leave the override alone (so that other codes may use it too)
lbz r3, 0x2101(r31) # we also want to leave it alone if there's an active beam sword
andi. r3, r3, 1 # check bit 0x01 for beam sword trail condition
bne- zeroOut # don't use custom trails if the beam sword is active
mr r3, r31 # else, we check to see if character/costume ID matches a user-defined custom trail
bl <CSWT_returnCustomTrail>
stw r3, 0(r30) # the function returns either 0 or a pointer, and the result is stored in the override
zeroOut:
li r30, 0 # 0 out register to make arbitrary data recognizable as "null" later on in r30
return:
li r3, 1
.long 0x00000000
----------- 0x800c27c8 ---- 887C2101 -> Branch
# INJ: (RENDER) TRAIL UPDATE
# @ 800c27c8 : lbz r3, 0x2101 (r28) (int becomes right shifted >> 1)
# r28 = internal player data pointer
# r30 = sword trail struct pointer
# - This gets called just as the game thinks it knows what sword trail it's going to use for the update.
# -- The code uses this as an opportunity to override what sword trail parameters get used.
bl <CSWT_getTrailOverride> # <- not actually a function; just returns pointer in LR
mflr r3
lwz r4, 0(r3) # load only the trail parameters override pointer; bones have already updated
li r0, 0
stw r0, 0(r3) # wipe override indicators
stw r0, 4(r3)
stw r0, 8(r3)
andis. r0, r4, 0x8000 # check if override value is a pointer
beq+ checkRegister
mr r30, r4 # if it is, overwrite the sword trail struct pointer
checkRegister:
andis. r0, r30, 0x8000 # check if final register value is a pointer
bne+ default
abort:
b 0x800C2f7c # if not, then abort the update as though the player weren't flagged for a sword trail
default:
lbz r3, 0x2101 (r28)
.long 0x00000000
<CSWT_getCustomTrailData> ALL
4E800021 # this "blrl" line allows the following data to be called like a function
# Sword trails are defined between this line and the ending "FFFFFFFF" line.
# You may create as many character/costume combinations as you wish.
# Use these with non-sword characters via subaction event C4 in Crazy Hand.
# MCM will ignore whitespace, so you can format these according to preference.
# Ch Cs XtYtZt XrYrZr ffBoBt AA Vrt1 Vrt2 R1G1B1 E1 R2G2B2 E2
# Mario Test (0 - NR)
00 # Ch - Character ID (Mario == 00)
00 # Cs - Costume ID (costume 0 == normal)
000000 # XtYtZt - Translate XYZ from bone
000000 # XrYrZr - Rotate XYZ from bone
00 # ff - flags, see below for more info
21 # Bo - Bo Bone ID Origin (used to specify origin rotation/scale)
21 # Bt - Bt Bone ID Translation (used to specify origin position)
F0 # AA - Alpha Gradient - ex: FF = 100%->100% -- F0 = 100%->0%
01C0 # Vrt1 - Vertex 1 (base)
09A1 # Vrt2 - Vertex 2 (tip)
FF7788 # R1G1B1 - Gradient Color 1 (Base, hexcolor)
0C # E1 - End taper 1 (% of defined vertices)
FFAA22 # R2G2B2 - Gradient Color 2 (Tip, hexcolor)
40 # E2 - End taper 2 (% of defined vertices)
# experimental ff flag options:
# 0xF0 = translation flags (position offset)
# -- 0x70 = use X, Y, Z translation origin from TopN instead of Bt bone
# -- 0x80 = use Bo instead of TopN values for above flags
# 0x0F = rotation flags (rotation offset)
# -- 0x70 = use X, Y, Z rotation origin from TopN instead of Bo bone
# -- 0x80 = use "0.0" instead of TopN values for above flags
# Ch Cs XtYtZt XrYrZr
# ffBoBt AA Vrt1 Vrt2
# R1G1B1 E1 R2G2B2 E2
#Marth (0 - CS NR) "Cherry Tipper"
12 00 000000 000000 # red streak from tip
004B4B F0 0800 0A00
C00000 30 FF4040 40
#Marth (1 - CS Red) "Shield Breaker"
12 01 000000 000000 # shield breaker colors with a fiery edge
004B4B F0 01C0 0B00
FF40FF E8 0040FF 58
#Marth (2 - CS Green) "Pinwheel"
12 02 202000 000000 # extends trail past hilt to create double edge
804B0B FF F380 0A00 # uses 2nd bone ID 0B to translate position
DC5E0A 00 FFC600 40
#Roy (0 - CS NR) "roy example"
1A 00 000000 000000
004D4D FF 029A 0B33
C62902 00 FBF481 40
#Ylink (0 - CS NR) "ylink example"
14 00 000000 000000
001B1B F0 0197 0897
73c478 26 daf4c7 33
#Link (0 - CS NR) "link example"
06 00 000000 000000
001919 F0 0197 0897
FF0000 26 FF5010 33
# (end of trail definitions)
FFFFFFFF # This termination line is important
<CSWT_returnCustomTrail> ALL
# CSWT_returnCustomTrail
# r3 = internal player data pointer
# returns 0 or a pointer to a newly constructed sword trail input if data matches player entity
# r3 = return parsed trail parameters
# r4 = return parsed bone adjustment floats for XYZpos/XYZrot
# r5 = return parsed flags and bones
# uses the space provided in the override struct
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x80(sp)
stmw r6, 0x8(sp) # *shrug* just to make it easier to implement from contexts that are using volatile registers
mr r31, r3 # and this, I guess
bl <CSWT_getCustomTrailData> # <- not actually a function; just returns pointer in LR
mflr r4 # r4 = start of custom sword trail data table
bl <CSWT_findCustomTrail_byCharacterCostume>
cmplwi r3, 0
li r4, 0
beq- return
bl <CSWT_getTrailOverride> # <- just returns pointer in lr
mflr r30 # r30 = saved sword trail override struct (0x0 override indicators/flags, 0xC vol trail parameters, 0x2C vol bone adjustments)
addi r4, r30, 0xC # r4 = start of volatile entity trail parameters
mr r5, r31 # r5 = internal player data pointer
bl <CSWT_parseDataBuffer> # interprets parse data and writes to buffer pointer passed in r4
#returned r3 = start of fake sword trail parameters, from volatile buffer
#returned r4 = start of fake sword trail bone adjustments, from volatile buffer
#returned r5 = parsed flags and bone IDs
return: # returns r3 and r4
lmw r6, 0x8(sp)
addi sp, sp, 0x80
lwz r0, 0x4(sp)
mtlr r0
blr
<CSWT_findCustomTrail_byCharacterCostume> ALL
# searchCustomTrailMatch
# r3 = internal player data pointer
# r4 = start of custom sword trail data parse array
# - returns 0 or pointer in r3 if player entity Character ID and Costume ID can be matched on table
# -- returned pointer is to a 0x18 block of custom sword trail data that can be parsed by "writeCustomTrailData"
# --- !! 0xFF for character ID terminates loop. Without a termination line, infinity will eat you.
subi r4, r4, 0x18 # set up r4 for lwzu in loop
matchCustomTrailDataLoop:
lwz r5, 0x4(r3) # r5 = player character ID
lbzu r0, 0x18(r4) # r0 = parsed character ID
cmplwi r0, 0xFF # !!
beq terminateLoop # !! 0xFF for character ID is termination code
cmplw r0, r5
bne+ matchCustomTrailDataLoop
lbz r5, 0x619(r3) # r5 = player costume ID
lbz r0, 0x1(r4) # r0 = parsed costume ID
cmplw r0, r5
bne+ matchCustomTrailDataLoop
mr r3, r4
b return
terminateLoop:
li r3, 0
return:
blr
<CSWT_parseDataBuffer> ALL
# CSWT_parseDataBuffer
# r3 = 0x18 bytes of parse data (-- -- XtYtZt XrYrZr AsAe BoBt Vrt1Vrt2 R1G1B1 E1 R2G2B2 E2)
# r4 = 0x38 bytes of space for storing ouput (buffer)
# r5 = internal player data pointer
# - parses 0x16 bytes of data from r3 and populates buffer in r4 with 0x38 bytes of formated data
# - r3 returns pointer to fake sword trail parameters in buffer
# - r4 returns pointer to array of 6 floats; bone adjustments to XYZ position, rotation
# - r5 returns parsed bones and flags
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x30(sp)
stmw r22, 0x8(sp)
mr r29, r3 # r29 = location of parsable data
mr r30, r4 # r30 = location to dump output
mr r31, r5 # r31 = internal player data pointer
# data we're parsing looks like this:
# 0 2 4 5 8 A
# ---- XtYtZt XrYrZr ffBoBt AA
# Vrt1Vrt2 R1G1B1 E1 R2G2B2 E2
# C 10 13 14 17
lbz r3, 0x9(r29) # load parsed 8-bit Bo boneID (origin for rotation and scale)
stw r3, 0x14(r30) # store 32-bit boneID in sword trail struct data
lbz r5, 0xB(r29) # SE are a pair of 4-bit nibbles describing Start and End Alpha %
rlwinm r4, r5, 0, 28, 31
rlwinm r3, r5, 28, 28, 31
rlwimi r3, r3, 4, 24, 27 # r3 = 0xSS
rlwimi r4, r4, 4, 24, 27 # r4 = 0xEE
stb r3, 0x8(r30)
stb r4, 0x9(r30) #store formated Alpha bytes in sword trail parameters
addi r3, r30, 0xA # r3 = location of RRGGBB--RRGGBB start in sword trail struct
addi r4, r29, 0x10 # r4 = location of RRGGBBE1RRGGBB start in parse data
li r5, 7 # copy 7 bytes
bl 0x800031F4 # SYS_memcpy (this does not require porting between Melee versions)
li r3, 0
stb r3, 0xD(r30) # 0 out the included E1 byte, because it's not actually used by the game
# at this point, all unsigned bytes have been copied
# now we need to shift the signed bytes and convert them into floats using the melemeter coefficient
# the writeArray utility used here has a method of calling that can summarize r3 and r4 within the UIMM range of r5
# r3 == r5 0x-FF- = number of SINTs in array
# r4 == r5 0xF--- = amount to signed left shift each SINT by
# r5 == r5 0x---3 = 0-based byte count of each SINT (max 3 = 32-bit SINT)
# this is simply designed to make sequential calls like this easier to make with fewer lines
#Vert1/2 - vertices
# r4 = 0 - no shift, use 0xNN.NN
# r3 = 02 - hwords Vrt1 Vrt2
li r5, 0x0021 # r5 = 1 - 2 bytes each SINT (0-based)
addi r6, r29, 0xC # start hwords to parse
addi r7, r30, 0x18 # start of floats to dump
bl <util_slawSINTtoMeleeMeters_writeArray>
#E1 taper vertex
# r4 = 2 - shift byte left 2 times (creates -200% -- +200% range for taper)
# r3 = 01 - byte E1
li r5, 0x2010 # r5 = 0 - (0-based byte count) (the rest of these are 1-byte as well)
addi r6, r29, 0x13 # start of bytes array in parse data
addi r7, r30, 0x0 # start of float array in sword trail struct
bl <util_slawSINTtoMeleeMeters_writeArray>
#E2 taper vertex -- use previous r3-r5
addi r6, r29, 0x17 # start of bytes array in parse data
addi r7, r30, 0x4 # start of float array in sword trail struct
bl <util_slawSINTtoMeleeMeters_writeArray>
#XtYtZt - bone translations
# r4 = 5 - shift left 5 times
# r3 = 03 - 3 bytes Xt Yt Zt
li r5, 0x5030 # r5 = 0
addi r6, r29, 0x02 # start of bytes array in parse data
addi r7, r30, 0x20 # start of float array in bone adjustments
bl <util_slawSINTtoMeleeMeters_writeArray>
#XrYrZr - bone rotations
li r4, 1 # use previous r3 and r5, but change r4 left shift to 1
addi r6, r29, 0x05 # start of bytes array in parse data
addi r7, r30, 0x2C # start of float array in bone adjustments
bl <util_slawSINTtoMeleeMeters_writeArray>
# we've created 10 floats from only 12 bytes of input data.
# now we just make sure to pass back all of the related info
mr r3, r30 # return r3 = pointer to fresh trail parameters
addi r4, r30, 0x20 # return r4 = pointer to fresh bone adjustments
lwz r5, 8(r29)
srwi r5, r5, 8 # return r5 = --ffBoBt
return:
lmw r22, 0x8(sp)
addi sp, sp, 0x30
lwz r0, 0x4(sp)
mtlr r0
blr
<CSWT_applyBoneAdjustments> ALL
# applyBoneAdjustments
# r3 = internal player data pointer
# r4 = location to dump bone adjustments
# -- flag logic is a bit involved, so I moved this to a function for portability
# this will attempt to apply a pending update to bone adjustments at the end of the keyframe update
# it voids all the override indicators and applies flag logic according to a previous parse from CSWT_parseDataBuffer
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x80(sp)
stmw r3, 0x8(sp)
mr r31, r3 # r31 = internal player data pointer
mr r30, r4 # r30 = assumed to be current trail keyframe location
bl <CSWT_getTrailOverride> # <- not actually a function; just returns pointer in LR
mflr r5
lwz r4, 0x4(r5) # r4 = bone adjustments pointer
lwz r12, 0x8(r5) # (saved 0x--ffBoBt : 3 bytes from parse)
li r0, 0
stw r0, 0(r5)
stw r0, 4(r5)
stw r0, 8(r5) # 0 out volatile indicators so the next updates have a null base
andis. r0, r4, 0x8000
beq- return # if no bone adjustments indicator, then just return
# else, apply adjustments
lwz r9, 0x5E8(r31) # r9 = Bone Lookup table, used to find bone instances in a player
rlwinm r10, r12, 4, 20, 27 # r10 = parsed Bt << 4 (bone ID for Bone Origin -- Translation)
rlwinm r11, r12, 28, 20, 27 # r11 = parsed Bo << 4 (bone ID for Bone Origin -- rot/scale)
rlwinm r12, r12, 16, 24, 31 # r12 = parsed flags (options for translation and rotation)
lfs f2, 0x2C(r31) # f2 = facing direction
li r5, 6 # for each of 6 floats
# the following loop adds offsets to origin XYZ translation/rotation floats
# the origin XYZ values that the offsets apply to are logically selected by the flags in r12
# 2 bone IDs and a set of pair of flag nibbles are included in the parse:
# Rotation/scale uses Bo (bone 1) and translation uses Bt (bone 2) by default
# the flags however may specify that:
# - rotations can instead use TopN or static 0.0 origin values
# - translations can instead use TopN, or Bo origin values
writeBoneAdjustments: # loop start
subi r5, r5, 1 # r5 -= 1 decrement loop count
cmpwi r5, 2 # check if this iteration is for translation or rotation
ble checkPos # positions and rotations are handled differently
checkRot: # if r5 > 2, then this is a rotation adjustment
subi r6, r5, 3 # XYZ ID ( 0 = X, 1 = Y, 2 = Z)
li r7, 0x44 # global rotation starts at 0x44 in bone instance
rlwinm r0, r12, 29, 0, 2 # masked out XYZ rotation flags, shifted for RLWNM
rlwnm. r0, r0, r6, 0, 0 # flag pertaining to THIS X Y or Z rotation
mr r8, r11 # r8 = Bo by default (bone origin ID)
beq+ finalizeOrigin # if no flag option, then finalize with default Bo
li r8, 0 # else, r8 = 0 (TopN offset)
andi. r0, r12, 0x0008 # check rotation "use 0.0" flag
beq+ finalizeOrigin
lfs f1, -0x778C(rtoc) # if true, load static origin value of 0.0 instead of using a bone
b addOffset
checkPos: # if r5 <= 2, then this is a position adjustment ; also r5 already == XYZ ID
mr r6, r5 # we want r6 to commonly keep an XYZ ID for a following step
li r7, 0x50 # global position starts at 0x50 in bone instance
rlwinm r0, r12, 25, 0, 2 # masked out XYZ Translation flags, shifted for RLWNM
rlwnm. r0, r0, r5, 0, 0 # flag pertaining to THIS X Y or Z translation
mr r8, r10 # r8 = Bt by default (bone translation ID)
beq+ finalizeOrigin # if no flag option, then finalize with default Bt
li r8, 0 # else, r8 = 0 (TopN offset)
andi. r0, r12, 0x0080 # check translation "use Bo" flag
beq+ finalizeOrigin
mr r8, r11 # if true, don't use Bt origin at all
finalizeOrigin:
slwi r6, r6, 4 # r6 still holds XYZ ID, so multiply that by 0x10 to align to global MTX values
lwzx r8, r8, r9 # r8 now = final specificaiton of bone instance (BA)
add r7, r7, r6 # r7 now = finalized offset from specified bone instance (displacement)
lfsx f1, r7, r8 # f1 now = finalized origin float
addOffset: # f1 = finalized origin float
slwi. r6, r5, 2 # r6 = word alignment, also checks for 0 (final loop increment, XPos)
lfsx f0, r4, r6 # f0 = bone adjustment float
bne+ notXPos
fmuls f0, f2, f0 # if this is Xpos, multiply offset by facing direction
notXPos:
fadds f1, f1, f0 # add that mofo
stfsx f1, r6, r30 # store finalized global coordinate in current keyframe slot
bne+ writeBoneAdjustments # continue loop until all floats have been handled
return: # all writes are done to the passed r4 pointer, so nothing needs to be returned
lmw r3, 0x8(sp)
addi sp, sp, 0x80
lwz r0, 0x4(sp)
mtlr r0
blr
<util_slawSINTtoMeleeMeters> ALL
# util_slawSINTtoMeleeMeters
# r3 = signed int; convert to float; * melee meter coef
# r4 = signed shift r3 left amount (0 = 0xNNNNNN.NN, 4 = 0xNNNNNNN.N, 8 = 0xNNNNNNNN, 12 = 0xNNNNNNN0, etc)
# -- r4 is returned without being modified
# f1 = returned float value
andis. r0, r3, 0x8000 # save signed bit
slw r3, r3, r4
or r3, r3, r0 # as far as I know, there isn't a slaw instruction...
lis r0, 0x4330
xoris r3, r3, 0x8000
stw r0, -0xC(sp)
stw r3, -0x8(sp) # store double in redzone
lfd f0, -0xC(sp) # immediately load from redzone (now it's garbage)
lfd f2, -0x7730(rtoc) # 43300000 80000000 (for casting)
lfs f3, -0x7740(rtoc) # Melee Meters coef (useful for more than melee meters!)
fsubs f0, f0, f2 # cast
fmuls f1, f3, f0 # conversion returned in f1
blr
<util_slawSINTtoMeleeMeters_writeArray> ALL
# util_slawSINTtoMeleeMeters_writeArray
# r3 = number of SINTs to convert
# r4 = signed shift left amount (0 = 0xNNNNNN.NN, 4 = 0xNNNNNNN.N, 8 = 0xNNNNNNNN, 12 = 0xNNNNNNN0, etc)
# r5 = 0-based 2-bit number of bytes in each SINT (0 = 8-bit, 1 = 16-bit, 2 = 24-bit, 3 = 32-bit)
# -- if value overflows from 2-bit range, then r3 and r4 are filled from UIMM in r5:
# --- r3 becomes zero-shifted 0x-----FF-
# --- r4 becomes zero-shifted 0x----F---
# r6 = location of SINT array start
# r7 = location of float array start
# uses the previous utility function in order to operate on an array of SINTs
# also automates the process of storing results
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x20(sp)
stmw r26, 0x8(sp)
cmpwi r5, 3
ble+ startLoop # if r5 contains a value within a 2-bit range, use passed r3 and r4
# else, interpret r3 and r4 from UIMM in r5 (a shorthand)
rlwinm r3, r5, 28, 24, 31 # 0x-----FF- mask
rlwinm r4, r5, 20, 28, 31 # 0x----F--- mask
rlwinm r5, r5, 0, 30, 31 # 0x-------3 mask
# from this point, arguments r3-r31 are unmodified and treated as read-only in this function
# this is to make it easy for the function to be reused from the same context in sequence
# CTR is also unused
startLoop:
mr r26, r3 # save r3
mr r30, r26 # r30 = decrementing 1-based loop count
arrayLoop:
subi r30, r30, 1 # decrement 1-based loop count
addi r29, r5, 1
mullw r29, r29, r30 # r29 = offset of next SINT to fetch in decrementing loop
lwzx r3, r6, r29 # load 32-bit word from designated location
li r27, 3
sub r27, r27, r5 # r27 = 3 - 0-based byte count
slwi r27, r27, 3 # 0 = 0, 1 = 8, 2 = 16, 3 = 24 (byte count to bit count)
sraw r3, r3, r27 # r3 = signed right shift 32-bit word to correct the length of SINT
bl <util_slawSINTtoMeleeMeters> # r4 = shift amount
# returns converted float in f1
slwi r29, r30, 2 # word alignment from 0-based CTR
stfsx f1, r29, r7 # store float in output array from r7
cmpwi r30, 0
bgt+ arrayLoop # loop decrements at start
return:
mr r3, r26
lmw r26, 0x8(sp)
addi sp, sp, 0x20
lwz r0, 0x4(sp)
mtlr r0
blr
<CSWT_getTrailOverride> ALL
# getTrailOverride
blrl # allows the following data to be called like function
# 0x0 override indicators - pointers determine what data to use during override
.long 0x00000000 # 0x0 - "Override Data" - a pointer to a sword trail struct, for overriding update
.long 0x00000000 # 0x4 - "Bone Adjustments" - a pointer to a set of floats that adjust pos/rotation from origin bone
.long 0x00000000 # 0x8 - flags and bone IDs saved from parse--used with the Bone Adjustments values
# 0xC fake sword trail struct - can be used and pointed to by 0x0 "Override Data"
.long 0x00000000 # trail End taper % 1
.long 0x00000000 # trail End taper % 2
.long 0x00000000 # As, Ae, R1, G1
.long 0x00000000 # B1, --, R2, G2
.long 0x00000000 # B2, --, --, -- (blank bytes appear to be unused)
.long 0x00000000 # Bone ID (used to generate global scale/rot coords that define pole origin for vertices)
.long 0x00000000 # Vertex 1 (base) -- stored at internal player 0x20F8 (these seem to scale with bone size)
.long 0x00000000 # Vertex 2 (tip) -- stored at internal player 0x20FC
# 0x2C Bone adjustment floats - can be used and pointed to by 0x4 "Bone Adjustments"
# (these offset *global* coordinates written to internal player data offset 0x20B0 + ((player.0x2101 >> 1) * 0x18)
# how they apply is heavily dependent on what's stored in 0x8 (ff flags, Bo bone ID, and Bt bone ID)
.long 0x00000000 # X translation (from Bt bone, TopN bone, or Bo bone)
.long 0x00000000 # Y translation
.long 0x00000000 # Z translation
.long 0x00000000 # X rotation (from Bo bone, TopN bone, or 0)
.long 0x00000000 # Y rotation
.long 0x00000000 # Z rotation
This mostly-working concept is the result of lots of experimentation, but is more of a snapshot of a brainstorm rather than a final code. It could use some work.
It’s a bit involved, and--in anticipation of my habit of propagating new projects from unfinished ones--I left myself descriptive notes of the code and its functions:
“Custom sword trails” - experiment notes
The code creates 2 portable data tables that can be called like functions in order to access their data from an arbitrary location in RAM.
The first table is a list of user definitions for custom trails. They are like a compressed version of the actual data used by Marth/Roy, and Link/Ylink.
The 2 “portable data tables” I mentioned are actually MCM Standalone functions that do nothing but point to a body of included data. For clarity, I’m trying to develop a habit of using the prefix “get” to describe such functions:
(edit: These could feasibly be modified to instead work with file data instead of just the DOL)
<CSWT_getCustomTrailData>
4E800021 # blrl line allows the data to be called like a function (putting a pointer in the LR on return)
# nothing following this line is read as an instruction--only pointed to as data
# <-- any number of user definitions go here. See instructions below this spoiler for the syntax.
FFFFFFFF # this line terminates the table; letting the code know when it’s reached the last definition.
---
The second table is used like a permanently allocated buffer. The code uses it as a place to store mock sword trail parameters in between hooks. It’s possible to use the stack for this purpose, but assigning these to a callable buffer allows the code to be written in a way that doesn’t require us to overhaul the entire sword trail system.
(edit: I think this was written before actually writing the code. Now that it consists of so many injections, it might be more appropriate to use the stack to save code space~)
The buffer is partitioned into 3 parts. The first part contains pointers that indicate where a mock-structure is located for use in the override, while the other 2 parts mimic the format of real structures used for sword trails in the player data.
An included function in the mod can be called to populate these structures with data pulled from a parse of the user definitions; from which the injection codes may reference via the indicator pointers in the first part of this table when creating a sword trail update:
<CSWT_getTrailOverride>
4E800021 # blrl line allows the data to be called like a function
# 1 - @0x00: structure for the code to monitor and use in order to decide how to override the updates
# these values are wiped after each player update (once for bone, once for render; each player/frame)
0x00 - Sword Trail Parameters Override Pointer - 0 = take default action
0x04 - Sword Trail Keyframe Adjustments Pointer - 0 = take default action
0x08 - flags and bone IDs, used for transformations (00 ff Bo Bt)
# 2 - @0x0C: the mod uses this space to write volatile Sword Trail Parameters from parse data
# The game stores similar variables describing vertices and colors as sword user article data
(8 words of parameters described by Bo AA Vrt1 Vrt2 R1G1B1 E1 R2G2B2 E2)
# 3 - @0x2C: the mod uses this to write volatile Keyframe Adjustments from parse data
# The game stores global coords pulled from a player bone to write the sword trail article
# stored at internal player 0x20B0 + ((internal player byte 0x2101 >> 1) * 0x18)
(6 floats cast from bytes XtYtZt XrYrZr)
# these floats are used to offset the final keyframe coords
---
The two tables are called with MCM’s special branch syntax like so:
bl <CSWT_getTrailOverride>
mflr r3 # r3 holds pointer to override struct
bl <CSWT_getCustomTrailData>
mflr r4 #r4 holds pointer to custom trail definitions
<CSWT_findCustomTrail_byCharacterCostume>
- searches each entry in the custom trail definitions for a character/costume identity that matches a given player entity
- returns the position of any match in the data, which should include bytes usable for the following parse
<CSWT_parseDataBuffer>
- take a 6-word input and parse it for -- -- XtYtZt XrYrZr ffBoBt AA Vrt1 Vrt2 R1G1B1 E1 R2G2B2 E2
- place the resulting 14 words of data in the volatile override buffer struct included with the mod
- this information is “volatile” in that--once it’s done being used--it’s no longer reliably retrievable
- returns pointers to the beginning of each partition in the buffer data.
<util_slawSINTtoMeleeMeters>
- this utility takes advantage of the Melee Meter coefficient in order to provide a function that allows ints to represent a linear range of floats with an 8-bit mantissa/significand.
- r3 = a signed int to cast to melee meters, and r4 = the amount to signed shift r3 left before casting
- can be used to write approximate float data from compact bytes (or smaller, potentially)
<util_arraySlawSINTtoMeleeMeters>
- this utility expands on the previous one by allowing it to be called to handle an array of SINTs of similar size/parameters
- limited to 1, 2, 3, or 4-byte SINTs
<CSWT_returnCustomTrail>
makes use of the other functions/tables in order to:
- find an appropriate match (if any) in the custom trail definitions for a player
- parse the data if it exists, and store it in the volatile buffer
- return pointers (or 0) to each of the locations in the buffer. The injections may then store these pointers at the top of the override structure in order to tell the rest of the code that it will be used to render the next sword trail frame.
<CSWT_applyBoneAdjustments>
- takes parsed flags and bone IDs and uses them to work out logic for applying bone adjustments; assuming there is a valid indicator pointer in the override struct for bone adjustments.
- A specified pair of bone IDs (Bo and Bt) are used for an origin point, and a translation transformation respectively.
- Then, according to the flags, 3 rotation and translation offsets are then applied to the origin of Bo, Bt, or TopN.
---
Finally, 5 injections make use of these functions to provide the following order of operations each player/frame.
INJ # specifies custom code, where a lowercase letter specifies a step in the host function:
The code creates 2 portable data tables that can be called like functions in order to access their data from an arbitrary location in RAM.
The first table is a list of user definitions for custom trails. They are like a compressed version of the actual data used by Marth/Roy, and Link/Ylink.
The 2 “portable data tables” I mentioned are actually MCM Standalone functions that do nothing but point to a body of included data. For clarity, I’m trying to develop a habit of using the prefix “get” to describe such functions:
(edit: These could feasibly be modified to instead work with file data instead of just the DOL)
<CSWT_getCustomTrailData>
4E800021 # blrl line allows the data to be called like a function (putting a pointer in the LR on return)
# nothing following this line is read as an instruction--only pointed to as data
# <-- any number of user definitions go here. See instructions below this spoiler for the syntax.
FFFFFFFF # this line terminates the table; letting the code know when it’s reached the last definition.
---
The second table is used like a permanently allocated buffer. The code uses it as a place to store mock sword trail parameters in between hooks. It’s possible to use the stack for this purpose, but assigning these to a callable buffer allows the code to be written in a way that doesn’t require us to overhaul the entire sword trail system.
(edit: I think this was written before actually writing the code. Now that it consists of so many injections, it might be more appropriate to use the stack to save code space~)
The buffer is partitioned into 3 parts. The first part contains pointers that indicate where a mock-structure is located for use in the override, while the other 2 parts mimic the format of real structures used for sword trails in the player data.
An included function in the mod can be called to populate these structures with data pulled from a parse of the user definitions; from which the injection codes may reference via the indicator pointers in the first part of this table when creating a sword trail update:
<CSWT_getTrailOverride>
4E800021 # blrl line allows the data to be called like a function
# 1 - @0x00: structure for the code to monitor and use in order to decide how to override the updates
# these values are wiped after each player update (once for bone, once for render; each player/frame)
0x00 - Sword Trail Parameters Override Pointer - 0 = take default action
0x04 - Sword Trail Keyframe Adjustments Pointer - 0 = take default action
0x08 - flags and bone IDs, used for transformations (00 ff Bo Bt)
# 2 - @0x0C: the mod uses this space to write volatile Sword Trail Parameters from parse data
# The game stores similar variables describing vertices and colors as sword user article data
(8 words of parameters described by Bo AA Vrt1 Vrt2 R1G1B1 E1 R2G2B2 E2)
# 3 - @0x2C: the mod uses this to write volatile Keyframe Adjustments from parse data
# The game stores global coords pulled from a player bone to write the sword trail article
# stored at internal player 0x20B0 + ((internal player byte 0x2101 >> 1) * 0x18)
(6 floats cast from bytes XtYtZt XrYrZr)
# these floats are used to offset the final keyframe coords
---
The two tables are called with MCM’s special branch syntax like so:
bl <CSWT_getTrailOverride>
mflr r3 # r3 holds pointer to override struct
bl <CSWT_getCustomTrailData>
mflr r4 #r4 holds pointer to custom trail definitions
<CSWT_findCustomTrail_byCharacterCostume>
- searches each entry in the custom trail definitions for a character/costume identity that matches a given player entity
- returns the position of any match in the data, which should include bytes usable for the following parse
<CSWT_parseDataBuffer>
- take a 6-word input and parse it for -- -- XtYtZt XrYrZr ffBoBt AA Vrt1 Vrt2 R1G1B1 E1 R2G2B2 E2
- place the resulting 14 words of data in the volatile override buffer struct included with the mod
- this information is “volatile” in that--once it’s done being used--it’s no longer reliably retrievable
- returns pointers to the beginning of each partition in the buffer data.
<util_slawSINTtoMeleeMeters>
- this utility takes advantage of the Melee Meter coefficient in order to provide a function that allows ints to represent a linear range of floats with an 8-bit mantissa/significand.
- r3 = a signed int to cast to melee meters, and r4 = the amount to signed shift r3 left before casting
- can be used to write approximate float data from compact bytes (or smaller, potentially)
<util_arraySlawSINTtoMeleeMeters>
- this utility expands on the previous one by allowing it to be called to handle an array of SINTs of similar size/parameters
- limited to 1, 2, 3, or 4-byte SINTs
<CSWT_returnCustomTrail>
makes use of the other functions/tables in order to:
- find an appropriate match (if any) in the custom trail definitions for a player
- parse the data if it exists, and store it in the volatile buffer
- return pointers (or 0) to each of the locations in the buffer. The injections may then store these pointers at the top of the override structure in order to tell the rest of the code that it will be used to render the next sword trail frame.
<CSWT_applyBoneAdjustments>
- takes parsed flags and bone IDs and uses them to work out logic for applying bone adjustments; assuming there is a valid indicator pointer in the override struct for bone adjustments.
- A specified pair of bone IDs (Bo and Bt) are used for an origin point, and a translation transformation respectively.
- Then, according to the flags, 3 rotation and translation offsets are then applied to the origin of Bo, Bt, or TopN.
---
Finally, 5 injections make use of these functions to provide the following order of operations each player/frame.

INJ # specifies custom code, where a lowercase letter specifies a step in the host function:
- Bone data update - small function finds handles for various sword trail conditions and uses an assigned player bone to update global coordinates during an article spawn responsible for creating sword trails.
- a - if internal player 0x2100 “render status” byte is 0xFF (-1) then return (from function) without updating. (this is a termination flag used by the game)
- b - else, host function checks for beamsword conditions. If beamsword trail is active, handle bone keyframe coordinates differently than normal swords.
- INJ 1 - If all beamsword handle conditions are false, code takes first opportunity to 0 out the now free (volatile) r5 register; which (later in the host function) will define a null-pointer in cases where no struct is found for the inappropriate use of the C4 event. If the override struct from the mod library has no indicator pointers already populating it, then attempt to populate it with user definitions from the custom sword trails data table. These will be checked in INJ 2 and 3.
- c - Host function checks for Marth/Roy or Link/Ylink article data. In cases where player is not one of these characters, the vanilla game assumes there is a valid struct already in r5 and will attempt to read variables from it like a pointer. We exploit this.
- INJ 2 - If the override struct has override indicators pointing to mock data (from INJ 1, or other codes) then use that in place of whatever is in r5. Else, if r5 holds a valid default struct (like vanilla sword trails) then use that for the update. Else, abort the host function, and return without updating the bone data to prevent consequences of improperly using the C4 event.
- d - Host function updates the current keyframe vertices, and prepares a call that “spawns” articles using the specified bone from sword trail parameters.
- e - On return, host function has already updated the current keyframe position--as well as a performed calculation with data from every bone instance connected to the specified bone up to TopN. Rotation data returned from the function is applied as a final update to the current keyframe bone coordinates starting at internal player 0x20B0.
- INJ 3 - If the second override indicator pointer for “bone adjustments” is valid, then read the float data from it and add adjustments to the finalized keyframe coordinates. 0 out volatile override indicators to refresh their null status. (the volatile struct can now be used again with new data)
- f - the host function increments the keyframe count, which overflows at 2. The keyframe count determines where these coordinate floats are stored in array starting at 0x20B0. It also increments the render status by 1 if it is not already >= 3.
- a - if internal player 0x2100 “render status” byte is 0xFF (-1) then return (from function) without updating. (this is a termination flag used by the game)
- Draw data update - a much larger function that takes the data created from these keyframe coordinates and uses it (with the color information from the sword trail parameters) to draw the primitives used for sword trails.
- g - This host function is a part of a much larger loop, and will only run when the loop is on its 3rd iteration this frame. Its first and 2nd iteration will immediately return because of this g section.
- h - This tiny check causes the host function to immediately return if the byte at internal player 0x2100 is at <= 1. The bone update increments this by 1 each frame that it is below 3, and C4 events usually initialize trails by setting it to 0. Later on during the render, it is used in several parts of the draw calculation; including a CTR count for a loop that draws part of the arc in the trail. I’ve been calling this variable “render status” because it seems to increase the strength of the trail opacity as the number increments.
- INJ 4 - The sword trail struct in this host function is stored in a saved register (r30) so the first opportunity to 0 it out is very early on. Like in the previous host function, this INJ also sets up the volatile override struct with any matching user definitions using similar logic.
- i - several calls that write to GX FIFO; see this post in CSM for more info
- j - this portion of the code contains handles for using default trails and beamswords.
- INJ 5 - Similar to INJ 2 and 3, this final portion of the code applies any override struct pointed to by the override indicators to r30, and 0s out the override struct indicators to restore their null status.
- k - the rest of this function takes the struct in r30, loads variables from it that are used in various calculations, populates the stack with the results, and feeds it all into GX FIFO.
- g - This host function is a part of a much larger loop, and will only run when the loop is on its 3rd iteration this frame. Its first and 2nd iteration will immediately return because of this g section.
To summarize, the code works by creating an isolated buffer that acts as a “common” set of sword trail arguments that can be repopulated at any time with new variables taken from storage. These “common” arguments satisfy the parameters used in the sword trail update/draw functions, and so the injection codes conditionally replace normal parameters with newly populated “common” arguments in order to take control of sword trail mechanics.
---
The experiment gives control over the following in real time (as in, changes are reflected on the next frame without the need to respawn the player)
CH - Character ID -- specify what character gets this sword trail (can be non-sword users)
CS - Costume ID -- specify what costume gets this sword trail
XtYtZt - XYZ translation (position offset from origin bone) (final coords are global, relative to stage)
XrYrZr - XYZ rotation (rotation offset from origin bone, not working as intended--WIP)
Vrt1 - 2-byte Sword Trail Vertex 1 (trail base, length from origin of pole)
Vrt2 - 2-byte Sword Trail vertex 2 (trail tip, length from origin of pole)
ff - Flags (see below for more info)
Bo - BoneID used for creating origin rotation/scale for pole geometry
Bt - BoneID used for translating the position of the final trail keyframe; can be different from Bo
Ag - Alpha gradient -- two 4-bit nibbles, one for Starting Alpha, one for Ending Alpha
R1G1B1 - Gradient RGB hexcolor 1 (trail base) (copy/pastable RRGGBB format)
E1 - Ending taper strength 1 (trail base, % of vertex 1 length)
R2G2B2 - Gradient RGB hexcolor 2 (trail tip)
E2 - Ending taper strength 2 (trail tip, % of vertex 2 length)
Signed shifts and the melee meter conversion create interpolations that approximate the following linear ranges as resulting floats. I’m not sure, but I think this is more commonly called “quantization” or “float packing” and is a very useful technique for compressing arrays of floats into a more manageable size:
Xt, Yt, Zt - Signed 8-bit range = -1600% -- 1600% (covers +/- 16.0 global units)
Xr, Yr, Zr - Signed 8-bit range = -100% -- 100% (rotation summarized as %) (currently a WIP…)
Vrt1, Vrt2 - Signed 16-bit range = 0xNN.NN (covers +/- 128.0 units relative to bone scale)
E1, E2 - Signed 8-bit range = -200% -- 200% (% of length defined by vertices)
“ff” Flags are used to create options for handling the XtYtZt XrYrZr bytes differently:
0xF0 = translation flags (position offset)
-- 0x70 = use X, Y, Z translation origin from TopN instead of Bt bone
-- 0x80 = use Bo instead of TopN values for above flag
0x0F = rotation flags (rotation offset)
-- 0x70 = use X, Y, Z rotation origin from TopN instead of Bo bone
-- 0x80 = use "0.0" instead of TopN values for above flag
You can modify these easily by using the ( Edit ) feature in MCM:

---
I was close to posting about this a while back when it was in development, and I have some screenshots that help explain what these variables do. Please note that these are from an earlier version, and the position of the variables are now different--but the meaning is the same:




Edit - and here's one I missed that covers using basic use of XtYtZt:

There are some major differences in version 0.70 of the experiment compared to these screenshots. I'll have to redo them if I ever work on this code again.
The alpha (opacity) bytes have been compressed into 4-bit nibbles, and a 2nd bone ID may now be used to create trail behavior that follows one bone but translates its origin to another bone.
To use sword trails created for non-sword characters, use the C4 event in Crazy Hand:

(this mario trail is included in the default examples)
---
@Achilles1515 @DRGN -- I designed this back in october with the intention of making a custom subaction event. In this sense, it would be doing something very similar to what you guys were discussing in the sword trail color thread--in that the sword trail data would come from inside of a character file rather than in the DOL.
I’m fascinated by the prospect of using file data to store arguments for flexible code. I really like DRGN’s idea of putting default trail arguments in the costume file to simplify costume designing. In a similar sense, a code that changes these colors on the fly would accomplish a similar simplification by existing as event data in the moveset file.
My code above is just an experiment, and includes a lot of stuff I'd like to gut. I had made the code use a Character ID and Costume ID to identify a set of sword trail arguments to be applied by default. If you look carefully, you’ll notice that this identification takes up 2 bytes at the beginning of the syntax I described.
These 2 bytes are only for implementation. They are parsed by the code, but only as a means of keying a character costume to a trail definition. The parse function that actually reads the argument data for creating the sword trail buffer skips these IDs.
In other words, in any other implementation, these 2 bytes are free. This allows them to be implemented according to Achilles’s specification for custom subaction events. I believe that we can apply these specifications to the C4 event--which happens to be the event used to toggle sword trails. In this way, we could write custom events that modify some or all of the above variables as a part of the event data in movesets. Since the code only overrides pointers it has stored away, the actual default trails are untouched in this way and can be reverted back.
---
Anyway, I’m just getting this out there. I got very caught up in the research phase of this project, which eventually turned into a hunt for other drawing functions--and then several other projects. I’ll eventually get back to this, but in the meantime I thought some of you might be interested in the details.
At some point I’m gonna try making the draw function either iterative or recursive so that it can be used to draw multiple sword trails per frame; and make modifications to the gradient function to allow for 4-point color interpolation. Give Mewtwo a kamehameha or something.
Last edited: