Water boxes
#1
I haven't seen any in-depth information about SM64's water boxes on the web anywhere, so I thought it would be helpful to have a thread about them.

Generally speaking, when I say "water box" I am referring to a boxed region in the level that might have some effect on Mario. This is mostly used for water, which makes Mario go into his swimming state. That is why I refer to them as water boxes. They are also used for mist in JRB, which does nothing to Mario, and the toxic haze in HMC which slowly drains Mario's health.

To add a water box in a level, you need to setup both the collision and the visual part. I'll be using the castle grounds level data as an example.


Setting up the collision

The collision for water boxes is defined as part of the Level script 0x2E command loading routine. The sub-routine is pretty simple and straight forward, as the first two bytes after [00 44] is just the number of water boxes to be loaded. Then every 12 bytes will define the properties of the collision for that water box.

Code:
00 44 // Start defining water boxes
00 02 // Number of water boxes to be loaded.
00 00 E4 27 E3 CA 20 3D FF C6 FF AF // Water box 1 collision data
00 01 04 00 FF C6 20 26 1F C9 FF AF // Water box 2 collision data
Code:
struct WaterBoxCollision { 
     s16 id; // id that is used to link to the visual part of water box.
     s16 x1; // x coordinate of first end-point
     s16 z1; // z coordinate of first end-point 
     s16 x2; // x coordinate of second end-point 
     s16 z2; // z coordinate of second end-point 
     s16 y;  // y coordinate representing how tall the water box should be
};
The id also defines what the water box will do. If the id is smaller than 0x32, then it will be water. If the id is equal to 0x32 or 0xF0, then it will be toxic haze. If it's greater than 0x32 and is not a toxic haze value like 0xF0, then it will have no effect on Mario. The lava box inside the volcano in LLL is actually just a normal water-box that you can't touch due to an invisible lava-collision floor. If you raise the height of the water box, then you can freely swim around inside the volcano.

[Image: Ywyl5Nc.png]


Setting up the visual part

Now comes the more complicated part of water boxes. Water boxes are drawn from one or more geo layout 0x18 commands that use the asm function 0x802D104C.

Code:
[ 18 00 XX XX 80 2D 10 4C ] 
XXXX = Hard-coded parameter, which is used to get a segmented address to the visual data (from function 0x802D0C84)

Parameters to choose from:
0x0400 = 07026E24
0x0401 = 07026E34
0x0501 = 07016708
0x0600 = 070790F0
0x0612 = 07079100
0x0701 = 0702B900
0x0702 = 0702B950
0x0801 = 07012778
0x0851 = 070127C8
0x1001 = 0700FA70
0x1101 = 07018748
0x1102 = 07018778
0x1201 = 0700D2CC
0x1202 = 0701139C
0x1205 = 0700D304
0x1301 = 0700E31C
0x1302 = 0700E39C
0x1601 = 07011738
0x2202 = 07028780
0x2301 = 0700FCB4
0x2302 = 0700FD00
0x2401 = 07011E08
0x2601 = 07006E6C
0x3601 = 07017124
Code:
00454DC4 / 0E0007E4 [ 18 00 16 01 80 2D 10 4C ] //  Loads the water boxes in the castle grounds level
--------------------------------
// 1601 = 07011738
0000 0000 070116F8 // Water box 1
0001 0000 07011718 // Water box 2
FFFF // End of list of water boxes
Code:
struct WaterBox { 
     s16 id; // id that is used to link to the collision of water box.
     s16 _0x02;
     u32 quad_seg_ptr; // Segmented pointer to the water box visual data. (which I refer to as "struct WaterBoxQuad")
};
Code:
struct WaterBoxQuad {
     s16 visibilty; // -1 = Make all water boxes in list invisible, 0 = invisible, 1 = visible
     s16 _0x02;
     s16 rotation_speed; // negative values make it rotate the opposite way.
     s16 scale; // Number of repeats in texture
     s16 x1; // X position of vertex 1
     s16 z1; // Z position of vertex 1
     s16 x2; // X position of vertex 2
     s16 z2; // Z position of vertex 2
     s16 x3; // X position of vertex 3
     s16 z3; // Z position of vertex 3
     s16 x4; // X position of vertex 4
     s16 z4; // Z position of vertex 4
     u16 rotation_direction; // 0x00 = clockwise, 0x01 = counter-clockwise. (Useless?)
     u8 _0x1A;
     u8 transparency // 0x00 = fully transparent, 0xff = fully opague
     u16 texture_id // 0 = water, 1 = mist, 2 = JRB water, 4 = lava, etc.
     u16 _0x1E;
};
Here you will see some familiar options if you've used water boxes in skelux's level editor tool. You can change the rotation, texture, scale, and even transparency of the texture on the quad.

You can modify the look of the castle grounds water by changing the texture_id value from 0 (water) to 2 (JRB water), but if you try to change it to the mist texture then it will look weird. Why is that?

[Image: 6LGKsbN.png] [Image: FWTqIY0.png]

The reason for the weird color texture comes from the fact your trying to render a color image (RGBA5551) using a grayscale texture (IA16). The information gets interpreted wrong and it comes out looking like a mess.

Remember those hard-coded values I mentioned above? Those also determine if the image is going to be a grayscale texture or a colored texture. The code that determines this is under the function 0x802D0F28. Basically, if the hard-coded value is equal to 0x0702, 0x0851, or 0x1205, then the texture should be a grayscale texture. Otherwise it will just be a color texture.

This does make it a huge pain to add in water boxes for our custom levels, but frauber did come up with a solution to somewhat help the issue.


Changes made with the SM64 Editor and custom levels

Instead of using a bunch of presets that return specific segmented addresses, custom levels use 3 new presets for all of their water boxes.

Code:
0x5000 = 19001800 // Water
0x5001 = 19001850 // Toxic Haze
0x5002 = 190018A0 // Mist
This simple hack makes it easy to add in water boxes into custom levels. You can find list for the WaterBoxQuad data starting at 19001C00. 

However this does cause the issue that if you add more than 9 water boxes, then you will start overwriting the data that is meant for the toxic haze. Be careful when you're making your amazing water level.


Variables, Functions, and other Segmented-Pointers

Code:
Variables:
0x80361184 - Pointer to the level's water collision data. You can adjust the height of a water box in-game here.
Code:
Functions:
0x802D0C84 - function that gets a segmented address to water box list from hard-coded parameter
0x802D0F28 - function that determines if the water box texture should be RGBA5551 or IA16 based on hard-coded parameter
0x802D104C - Geo layout 0x18 function to draw water box quads
0x803833B8 - function that parses the water box data from level script command 0x2E 
Code:
Segmented Pointers:
0x020175F0 - Start of water box RGBA5551 fast3d display list.
0x02017630 - Start of water box IA16 fast3d display list.
0x02017670 - End of water box fast3d display list.
Reply
#2
This seems like it might be the appropriate place to talk about water currents? This is more about water in general but I think people might look here for it.

The speed and direction of the water box's current will actually be derived from the floor triangles underneath it. The collision type for most triangles intended to be below water is "0D", however type "0E" will move Mario according to the speed and angle provided in the following bytes.

Code:
Offset     Type     Description
0x00     u16     collision_type
0x02     u8     flowing speed LUT value (0x00=metal cap water, 0x02=castle grounds water)
0x03     u8     flowing direction

The value at 0x02 functions as a Look Up Table byte, as such the force at which flow speed pushes Mario is not linear. These two bytes are normally 0, so changing the collision type of a floor collision triangle above which Mario is swimming from "0D" to "0E" will use the flow speed of 00 in the lookup table, which happens to be the force of the water in the Cavern of the Metal Cap, against which a casual player will not make headway (maybe perfect swimming can?).

Code:
Key     Value     Description
0x00     0x001C     metal cap water
0x02     0x0008     castle grounds water
0x27     0x0000     SSL gradual quicksand
0x18     0x1A14     SSL instant quicksand //traps mario
0xEE     0x0100     No in game use, very fast

Play around to find a value you like. Most values point to stuff surely unintended for water currents, so try stuff out. Check the current value by breaking at 0x8027056C and checking the T8 register.

The flow direction is a straight forward angle byte with 0x00 to X+, 0x40 to Z+, and so on counterclockwise.

When injecting your own code, the "Push Mario With Current" function at 0x80270500 is a good place to start to directly modify the current speed or current direction applied to mario. It even works in unconventional water like in the Cavern of the Metal Cap that do not follow normal waterbox rules. This code will let you flip the direction of all currents and water boxes by holding the L button:

Code:
.defineLabel func_waterCurrent_ROM_start, 0x7F0000
.defineLabel func_waterCurrent_RAM_start, 0x80370000

.defineLabel func_waterRotation_ROM_start, 0x7F0084
.defineLabel func_waterRotation_RAM_start, 0x80370084

.orga 0x2B538//0x80270538
J 0x80370000
LW T9, 0x0048(SP)

.orga func_waterCurrent_ROM_start
.area 0x0084
LW    T0, 0x0068 (T9)
LH    T1, 0x0002 (T0) //first current load

//if L is held, modify our boy
.f_testInput BUTTON_L, true, waterNoL1
AND T5, T1, T1
ADDIU T5, T5, 0x0080
ANDI T5, T5, 0x00FF
ANDI T1, T1, 0xFF00
OR T1, T1, T5

waterNoL1:
SLL    T2, T1, 8
SH    T2, 0x003E (SP)
LW    T3, 0x0048 (SP)
LUI    T8, 0x8033
LW    T4, 0x0068 (T3)
LH    T5, 0x0002 (T4) //second current load

//if L is held, modify our boy
.f_testInput BUTTON_L, true, waterNoL2
AND AT, T5, T5
ADDIU AT, AT, 0x0080
ANDI AT, AT, 0x00FF
ANDI T5, T5, 0xFF00
OR T5, T5, AT

waterNoL2:
AND A0, T6, T6
J 0x80270560
SRA    T6, T5, 8
.endarea

//Flip water rotation
.orga 0x8B5D0;802D05D0
J func_waterRotation_RAM_start;BNEZ    T5, 0x802D06EC
;NOP

.orga func_waterRotation_ROM_start
.f_testInput BUTTON_L, true, waterRotPostXOR

LI T8, 0x0001
XOR T5, T5, T8
waterRotPostXOR:
BNEZ T5, waterRotBranch
NOP
J 0x802D05D8
NOP
waterRotBranch:
J 0x802D06EC
NOP


// Call our custom function with DMACopy from the top-most levelscript.
.orga 0x108A18
.word 0x11080000, 0x8024B940

//************** DmaCopy function **************//
.orga 0x6940 ; Overwrite the unused function 0x8024B940
.area 0x64 ; Set data import limit to 0x64 bytes
addiu sp, sp, -0x18
sw ra, 0x14 (sp)
sw a0, 0x10 (sp) ; lvl cmd 0x11 safeguard

// These two are nessecary because of what we overwrote at 0x108A18.
.f_osViBlack FALSE ; Set screen blackout to false
sw r0, 0x8038BE24 ; Set level accumulator to 0

// Copies 0x100 bytes from ROM addr 0x7F0000 to RAM addr 0x80370000
.f_DmaCopy func_waterCurrent_RAM_start, func_waterCurrent_ROM_start, 0x7F00F4

lw v0, 0x10 (sp) ; lvl cmd 0x11 safeguard
lw ra, 0x14 (sp)
jr ra
addiu sp, sp, 0x18
.endarea
David liked this post
Reply




Users browsing this thread: 1 Guest(s)