03-09-2018, 09:25 PM
Back in late January I discovered how the room culling in Inside Castle, Big Boo's Haunt, and Hazy Maze Cave worked. It is a fairly simple process that involves the level script command 0x2F, and the geometry layout switch command (0xE). Each of the three level's geometry layouts contain a 0xE switch command that uses the ASM function 0x8029DBD4.
Geometry Layout for Inside Castle Area 1: https://pastebin.com/raw/iZdm0zNs
The function 0x8029DBD4 will find the collision triangle below Mario, and use the byte at offset 0x5 in the collision triangle structure to determine which switch case to use.
The purpose of the level script command 0x2F is to setup the pointer of an array of byte values that will populate the collision triangles. This pointer will be stored at offset 0x0C in the structure of the current area being processed. The length of this byte array is the same as the number of collision triangles inside the current area. If the level script doesn't have a 0x2F command, then each collision triangle will have zero as their offset 0x5.
The collision triangle's offset 0x05 is populated from a loop in the function 0x80383068, which is eventually called from the level initialize command ([ 11 08 0000 8024BCD8 ]) before the main level loop command starts ([ 12 08 0001 8024BCD8 ]).
Here's a video showing me editing the switch byte in the collision triangle underneath where Mario is standing:
Geometry Layout for Inside Castle Area 1: https://pastebin.com/raw/iZdm0zNs
The function 0x8029DBD4 will find the collision triangle below Mario, and use the byte at offset 0x5 in the collision triangle structure to determine which switch case to use.
Code:
#define MARIO_OBJ_POINTER 0x80361158
typedef char s8;
int proc_8029DBD4(int a0, Node* a1) // function 0x8029DBD4
{
if(a0 == 1 && *(MARIO_OBJ_POINTER) != NULL)
{
*((short*)0x80361182) = 1;
collisionTriangle* triangle;
Object* mario_obj = *(MARIO_OBJ_POINTER);
// Function that finds triangle underneath XYZ position.
proc_80381900(mario_obj->x_pos, mario_obj->y_pos, mario_obj->z_pos, &triangle);
if((*triangle) != NULL)
{
*((short*)0x80361250) = (s8)(triangle->_0x5);
short sp_26 = (s8)(triangle->_0x5) - 1;
// Some printing function that calls PrintInt. 0x803377B0 = "areainfo %d"
proc_802CA5B8((char*)0x803377B0, (s8)(triangle->_0x5));
if(sp_26 >= 0)
{
a1->_0x1E = sp_26; // Switch value = sp_26
}
}
else
{
a1->_0x1E = 0; // Switch value = 0
}
}
return 0;
}
The purpose of the level script command 0x2F is to setup the pointer of an array of byte values that will populate the collision triangles. This pointer will be stored at offset 0x0C in the structure of the current area being processed. The length of this byte array is the same as the number of collision triangles inside the current area. If the level script doesn't have a 0x2F command, then each collision triangle will have zero as their offset 0x5.
Code:
typedef char s8;
void LevelScript2F()
{
// 0x8038B8AC = current area index being processed in the level scripts
if(*((short*)0x8038B8AC) != -1)
{
Area* t8 = *((Area**)0x8032DDC8); // 0x8032DDC8 = pointer to start of area struct array.
int t0 = (*((short*)0x8038B8AC) * 0x3C);
Area* area_struct = (Area*)(t8 + t0);
area_struct->_0xC = SegmentedToVirtual(*((int*)(*((int*)0x8038BE28) + 4)));
}
*((int*)0x8038BE28) += *((s8*)(*((int*)0x8038BE28) + 1)); // Go to the next level script command
}
The collision triangle's offset 0x05 is populated from a loop in the function 0x80383068, which is eventually called from the level initialize command ([ 11 08 0000 8024BCD8 ]) before the main level loop command starts ([ 12 08 0001 8024BCD8 ]).
Here's a video showing me editing the switch byte in the collision triangle underneath where Mario is standing: