#include #include "sm64.h" #include "area.h" #include "audio/external.h" #include "camera.h" #include "mario_misc.h" #include "behavior_actions.h" #include "behavior_data.h" #include "engine/behavior_script.h" #include "game_init.h" #include "engine/graph_node.h" #include "envfx_snow.h" #include "level_update.h" #include "engine/math_util.h" #include "memory.h" #include "object_helpers.h" #include "goddard/renderer.h" #include "rendering_graph_node.h" #include "save_file.h" #include "sound_init.h" #include "skybox.h" #include "interaction.h" #include "object_list_processor.h" #include "dialog_ids.h" #define TOAD_STAR_1_REQUIREMENT 12 #define TOAD_STAR_2_REQUIREMENT 25 #define TOAD_STAR_3_REQUIREMENT 35 #define TOAD_STAR_1_DIALOG DIALOG_082 #define TOAD_STAR_2_DIALOG DIALOG_076 #define TOAD_STAR_3_DIALOG DIALOG_083 #define TOAD_STAR_1_DIALOG_AFTER DIALOG_154 #define TOAD_STAR_2_DIALOG_AFTER DIALOG_155 #define TOAD_STAR_3_DIALOG_AFTER DIALOG_156 enum ToadMessageStates { TOAD_MESSAGE_FADED, TOAD_MESSAGE_OPAQUE, TOAD_MESSAGE_OPACIFYING, TOAD_MESSAGE_FADING, TOAD_MESSAGE_TALKING }; enum UnlockDoorStarStates { UNLOCK_DOOR_STAR_RISING, UNLOCK_DOOR_STAR_WAITING, UNLOCK_DOOR_STAR_SPAWNING_PARTICLES, UNLOCK_DOOR_STAR_DONE }; /** * The eye texture on succesive frames of Mario's blink animation. * He intentionally blinks twice each time. */ static s8 gMarioBlinkAnimation[7] = { 1, 2, 1, 0, 1, 2, 1 }; /** * The scale values per frame for Mario's foot/hand for his attack animation * There are 3 scale animations in groups of 6 frames. * The first animation starts at frame index 3 and goes down, the others start at frame index 5. * The values get divided by 10 before assigning, so e.g. 12 gives a scale factor 1.2. * All combined, this means e.g. the first animation scales Mario's fist by {2.4, 1.6, 1.2, 1.0} on * succesive frames. */ static s8 gMarioAttackScaleAnimation[3 * 6] = { 10, 12, 16, 24, 10, 10, 10, 14, 20, 30, 10, 10, 10, 16, 20, 26, 26, 20, }; struct MarioBodyState gBodyStates[2]; // 2nd is never accessed in practice, most likely Luigi related struct GraphNodeObject gMirrorMario; // copy of Mario's geo node for drawing mirror Mario // This whole file is weirdly organized. It has to be the same file due // to rodata boundries and function aligns, which means the programmer // treated this like a "misc" file for vaguely mario related things // (message NPC related things, the mario head geo, and mario geo // functions) /** * Geo node script that draws Mario's head on the title screen. */ Gfx *geo_draw_mario_head_goddard(s32 callContext, struct GraphNode *node, Mat4 *c) { Gfx *gfx = NULL; s16 sfx = 0; struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node; UNUSED Mat4 *transform = c; if (callContext == GEO_CONTEXT_RENDER) { if (gPlayer1Controller->controllerData != NULL && gWarpTransition.isActive == 0) { gd_copy_p1_contpad(gPlayer1Controller->controllerData); } gfx = (Gfx *) PHYSICAL_TO_VIRTUAL(gdm_gettestdl(asGenerated->parameter)); D_8032C6A0 = gd_vblank; sfx = gd_sfx_to_play(); play_menu_sounds(sfx); } return gfx; } static void toad_message_faded(void) { if (gCurrentObject->oDistanceToMario > 700.0f) { gCurrentObject->oToadMessageRecentlyTalked = 0; } if (gCurrentObject->oToadMessageRecentlyTalked == 0 && gCurrentObject->oDistanceToMario < 600.0f) { gCurrentObject->oToadMessageState = TOAD_MESSAGE_OPACIFYING; } } static void toad_message_opaque(void) { if (gCurrentObject->oDistanceToMario > 700.0f) { gCurrentObject->oToadMessageState = TOAD_MESSAGE_FADING; } else { if (gCurrentObject->oToadMessageRecentlyTalked == 0) { gCurrentObject->oInteractionSubtype = INT_SUBTYPE_NPC; if (gCurrentObject->oInteractStatus & INT_STATUS_INTERACTED) { gCurrentObject->oInteractStatus = 0; gCurrentObject->oToadMessageState = TOAD_MESSAGE_TALKING; play_toads_jingle(); } } } } static void toad_message_talking(void) { if (cur_obj_update_dialog_with_cutscene(3, 1, CUTSCENE_DIALOG, gCurrentObject->oToadMessageDialogId) != 0) { gCurrentObject->oToadMessageRecentlyTalked = 1; gCurrentObject->oToadMessageState = TOAD_MESSAGE_FADING; switch (gCurrentObject->oToadMessageDialogId) { case TOAD_STAR_1_DIALOG: gCurrentObject->oToadMessageDialogId = TOAD_STAR_1_DIALOG_AFTER; bhv_spawn_star_no_level_exit(0); break; case TOAD_STAR_2_DIALOG: gCurrentObject->oToadMessageDialogId = TOAD_STAR_2_DIALOG_AFTER; bhv_spawn_star_no_level_exit(1); break; case TOAD_STAR_3_DIALOG: gCurrentObject->oToadMessageDialogId = TOAD_STAR_3_DIALOG_AFTER; bhv_spawn_star_no_level_exit(2); break; } } } static void toad_message_opacifying(void) { if ((gCurrentObject->oOpacity += 6) == 255) { gCurrentObject->oToadMessageState = TOAD_MESSAGE_OPAQUE; } } static void toad_message_fading(void) { if ((gCurrentObject->oOpacity -= 6) == 81) { gCurrentObject->oToadMessageState = TOAD_MESSAGE_FADED; } } void bhv_toad_message_loop(void) { if (gCurrentObject->header.gfx.node.flags & GRAPH_RENDER_ACTIVE) { gCurrentObject->oInteractionSubtype = 0; switch (gCurrentObject->oToadMessageState) { case TOAD_MESSAGE_FADED: toad_message_faded(); break; case TOAD_MESSAGE_OPAQUE: toad_message_opaque(); break; case TOAD_MESSAGE_OPACIFYING: toad_message_opacifying(); break; case TOAD_MESSAGE_FADING: toad_message_fading(); break; case TOAD_MESSAGE_TALKING: toad_message_talking(); break; } } } void bhv_toad_message_init(void) { s32 saveFlags = save_file_get_flags(); s32 starCount = save_file_get_total_star_count(gCurrSaveFileNum - 1, 0, 24); s32 dialogId = (gCurrentObject->oBehParams >> 24) & 0xFF; s32 enoughStars = TRUE; switch (dialogId) { case TOAD_STAR_1_DIALOG: enoughStars = (starCount >= TOAD_STAR_1_REQUIREMENT); if (saveFlags & (1 << 24)) { dialogId = TOAD_STAR_1_DIALOG_AFTER; } break; case TOAD_STAR_2_DIALOG: enoughStars = (starCount >= TOAD_STAR_2_REQUIREMENT); if (saveFlags & (1 << 25)) { dialogId = TOAD_STAR_2_DIALOG_AFTER; } break; case TOAD_STAR_3_DIALOG: enoughStars = (starCount >= TOAD_STAR_3_REQUIREMENT); if (saveFlags & (1 << 26)) { dialogId = TOAD_STAR_3_DIALOG_AFTER; } break; } if (enoughStars) { gCurrentObject->oToadMessageDialogId = dialogId; gCurrentObject->oToadMessageRecentlyTalked = 0; gCurrentObject->oToadMessageState = TOAD_MESSAGE_FADED; gCurrentObject->oOpacity = 81; } else { obj_mark_for_deletion(gCurrentObject); } } static void star_door_unlock_spawn_particles(s16 angleOffset) { struct Object *sparkleParticle = spawn_object(gCurrentObject, 0, bhvSparkleSpawn); sparkleParticle->oPosX += 100.0f * sins((gCurrentObject->oUnlockDoorStarTimer * 0x2800) + angleOffset); sparkleParticle->oPosZ += 100.0f * coss((gCurrentObject->oUnlockDoorStarTimer * 0x2800) + angleOffset); // Particles are spawned lower each frame sparkleParticle->oPosY -= gCurrentObject->oUnlockDoorStarTimer * 10.0f; } void bhv_unlock_door_star_init(void) { gCurrentObject->oUnlockDoorStarState = UNLOCK_DOOR_STAR_RISING; gCurrentObject->oUnlockDoorStarTimer = 0; gCurrentObject->oUnlockDoorStarYawVel = 0x1000; gCurrentObject->oPosX += 30.0f * sins(gMarioState->faceAngle[1] - 0x4000); gCurrentObject->oPosY += 160.0f; gCurrentObject->oPosZ += 30.0f * coss(gMarioState->faceAngle[1] - 0x4000); gCurrentObject->oMoveAngleYaw = 0x7800; obj_scale(gCurrentObject, 0.5f); } void bhv_unlock_door_star_loop(void) { UNUSED u8 unused1[4]; s16 prevYaw = gCurrentObject->oMoveAngleYaw; UNUSED u8 unused2[4]; // Speed up the star every frame if (gCurrentObject->oUnlockDoorStarYawVel < 0x2400) { gCurrentObject->oUnlockDoorStarYawVel += 0x60; } switch (gCurrentObject->oUnlockDoorStarState) { case UNLOCK_DOOR_STAR_RISING: gCurrentObject->oPosY += 3.4f; // Raise the star up in the air gCurrentObject->oMoveAngleYaw += gCurrentObject->oUnlockDoorStarYawVel; // Apply yaw velocity obj_scale(gCurrentObject, gCurrentObject->oUnlockDoorStarTimer / 50.0f + 0.5f); // Scale the star to be bigger if (++gCurrentObject->oUnlockDoorStarTimer == 30) { gCurrentObject->oUnlockDoorStarTimer = 0; gCurrentObject->oUnlockDoorStarState++; // Sets state to UNLOCK_DOOR_STAR_WAITING } break; case UNLOCK_DOOR_STAR_WAITING: gCurrentObject->oMoveAngleYaw += gCurrentObject->oUnlockDoorStarYawVel; // Apply yaw velocity if (++gCurrentObject->oUnlockDoorStarTimer == 30) { play_sound(SOUND_MENU_STAR_SOUND, gCurrentObject->header.gfx.cameraToObject); // Play final sound cur_obj_hide(); // Hide the object gCurrentObject->oUnlockDoorStarTimer = 0; gCurrentObject ->oUnlockDoorStarState++; // Sets state to UNLOCK_DOOR_STAR_SPAWNING_PARTICLES } break; case UNLOCK_DOOR_STAR_SPAWNING_PARTICLES: // Spawn two particles, opposite sides of the star. star_door_unlock_spawn_particles(0); star_door_unlock_spawn_particles(0x8000); if (gCurrentObject->oUnlockDoorStarTimer++ == 20) { gCurrentObject->oUnlockDoorStarTimer = 0; gCurrentObject->oUnlockDoorStarState++; // Sets state to UNLOCK_DOOR_STAR_DONE } break; case UNLOCK_DOOR_STAR_DONE: // The object stays loaded for an additional 50 frames so that the // sound doesn't immediately stop. if (gCurrentObject->oUnlockDoorStarTimer++ == 50) { obj_mark_for_deletion(gCurrentObject); } break; } // Checks if the angle has cycled back to 0. // This means that the code will execute when the star completes a full revolution. if (prevYaw > (s16) gCurrentObject->oMoveAngleYaw) { play_sound( SOUND_GENERAL_SHORT_STAR, gCurrentObject->header.gfx.cameraToObject); // Play a sound every time the star spins once } } /** * Generate a display list that sets the correct blend mode and color for * mirror Mario. */ static Gfx *make_gfx_mario_alpha(struct GraphNodeGenerated *node, s16 alpha) { Gfx *gfx; Gfx *gfxHead = NULL; if (alpha == 255) { node->fnNode.node.flags = (node->fnNode.node.flags & 0xFF) | (LAYER_OPAQUE << 8); gfxHead = alloc_display_list(2 * sizeof(*gfxHead)); gfx = gfxHead; } else { node->fnNode.node.flags = (node->fnNode.node.flags & 0xFF) | (LAYER_TRANSPARENT << 8); gfxHead = alloc_display_list(3 * sizeof(*gfxHead)); gfx = gfxHead; gDPSetAlphaCompare(gfx++, G_AC_DITHER); } gDPSetEnvColor(gfx++, 255, 255, 255, alpha); gSPEndDisplayList(gfx); return gfxHead; } /** * Sets the correct blend mode and color for mirror Mario. */ Gfx *geo_mirror_mario_set_alpha(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { UNUSED u8 unused1[4]; Gfx *gfx = NULL; struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node; struct MarioBodyState *bodyState = &gBodyStates[asGenerated->parameter]; s16 alpha; UNUSED u8 unused2[4]; if (callContext == GEO_CONTEXT_RENDER) { alpha = (bodyState->modelState & 0x100) ? (bodyState->modelState & 0xFF) : 255; gfx = make_gfx_mario_alpha(asGenerated, alpha); } return gfx; } /** * Determines if Mario is standing or running for the level of detail of his model. * If Mario is standing still, he is always high poly. If he is running, * his level of detail depends on the distance to the camera. */ Gfx *geo_switch_mario_stand_run(s32 callContext, struct GraphNode *node, UNUSED Mat4 *mtx) { struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node; struct MarioBodyState *bodyState = &gBodyStates[switchCase->numCases]; if (callContext == GEO_CONTEXT_RENDER) { // assign result. 0 if moving, 1 if stationary. switchCase->selectedCase = ((bodyState->action & ACT_FLAG_STATIONARY) == FALSE); } return NULL; } /** * Geo node script that makes Mario blink */ Gfx *geo_switch_mario_eyes(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node; struct MarioBodyState *bodyState = &gBodyStates[switchCase->numCases]; s16 blinkFrame; if (callContext == GEO_CONTEXT_RENDER) { if (bodyState->eyeState == 0) { blinkFrame = ((switchCase->numCases * 32 + gAreaUpdateCounter) >> 1) & 0x1F; if (blinkFrame < 7) { switchCase->selectedCase = gMarioBlinkAnimation[blinkFrame]; } else { switchCase->selectedCase = 0; } } else { switchCase->selectedCase = bodyState->eyeState - 1; } } return NULL; } /** * Makes Mario's upper body tilt depending on the rotation stored in his bodyState */ Gfx *geo_mario_tilt_torso(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node; struct MarioBodyState *bodyState = &gBodyStates[asGenerated->parameter]; s32 action = bodyState->action; if (callContext == GEO_CONTEXT_RENDER) { struct GraphNodeRotation *rotNode = (struct GraphNodeRotation *) node->next; if (action != ACT_BUTT_SLIDE && action != ACT_HOLD_BUTT_SLIDE && action != ACT_WALKING && action != ACT_RIDING_SHELL_GROUND) { vec3s_copy(bodyState->torsoAngle, gVec3sZero); } rotNode->rotation[0] = bodyState->torsoAngle[1]; rotNode->rotation[1] = bodyState->torsoAngle[2]; rotNode->rotation[2] = bodyState->torsoAngle[0]; } return NULL; } /** * Makes Mario's head rotate with the camera angle when in C-up mode */ Gfx *geo_mario_head_rotation(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node; struct MarioBodyState *bodyState = &gBodyStates[asGenerated->parameter]; s32 action = bodyState->action; if (callContext == GEO_CONTEXT_RENDER) { struct GraphNodeRotation *rotNode = (struct GraphNodeRotation *) node->next; struct Camera *camera = gCurGraphNodeCamera->config.camera; if (camera->mode == CAMERA_MODE_C_UP) { rotNode->rotation[0] = gPlayerCameraState->headRotation[1]; rotNode->rotation[2] = gPlayerCameraState->headRotation[0]; } else if (action & ACT_FLAG_WATER_OR_TEXT) { rotNode->rotation[0] = bodyState->headAngle[1]; rotNode->rotation[1] = bodyState->headAngle[2]; rotNode->rotation[2] = bodyState->headAngle[0]; } else { vec3s_set(bodyState->headAngle, 0, 0, 0); vec3s_set(rotNode->rotation, 0, 0, 0); } } return NULL; } /** * Switch between hand models. * Possible options are described in the MarioHandGSCId enum. */ Gfx *geo_switch_mario_hand(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node; struct MarioBodyState *bodyState = &gBodyStates[0]; if (callContext == GEO_CONTEXT_RENDER) { if (bodyState->handState == MARIO_HAND_FISTS) { // switch between fists (0) and open (1) switchCase->selectedCase = ((bodyState->action & ACT_FLAG_SWIMMING_OR_FLYING) != 0); } else { if (switchCase->numCases == 0) { switchCase->selectedCase = (bodyState->handState < 5) ? bodyState->handState : MARIO_HAND_OPEN; } else { switchCase->selectedCase = (bodyState->handState < 2) ? bodyState->handState : MARIO_HAND_FISTS; } } } return NULL; } /** * Increase Mario's hand / foot size when he punches / kicks. * Since animation geo nodes only support rotation, this scaling animation * was scripted separately. The node with this script should be placed before * a scaling node containing the hand / foot geo layout. * ! Since the animation gets updated in GEO_CONTEXT_RENDER, drawing Mario multiple times * (such as in the mirror room) results in a faster and desynced punch / kick animation. */ Gfx *geo_mario_hand_foot_scaler(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { static s16 sMarioAttackAnimCounter = 0; struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node; struct GraphNodeScale *scaleNode = (struct GraphNodeScale *) node->next; struct MarioBodyState *bodyState = &gBodyStates[0]; if (callContext == GEO_CONTEXT_RENDER) { scaleNode->scale = 1.0f; if (asGenerated->parameter == bodyState->punchState >> 6) { if (sMarioAttackAnimCounter != gAreaUpdateCounter && (bodyState->punchState & 0x3F) > 0) { bodyState->punchState -= 1; sMarioAttackAnimCounter = gAreaUpdateCounter; } scaleNode->scale = gMarioAttackScaleAnimation[asGenerated->parameter * 6 + (bodyState->punchState & 0x3F)] / 10.0f; } } return NULL; } /** * Switch between normal cap, wing cap, vanish cap and metal cap. */ Gfx *geo_switch_mario_cap_effect(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node; struct MarioBodyState *bodyState = &gBodyStates[switchCase->numCases]; if (callContext == GEO_CONTEXT_RENDER) { switchCase->selectedCase = bodyState->modelState >> 8; } return NULL; } /** * Determine whether Mario's head is drawn with or without a cap on. * Also sets the visibility of the wing cap wings on or off. */ Gfx *geo_switch_mario_cap_on_off(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { struct GraphNode *next = node->next; struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node; struct MarioBodyState *bodyState = &gBodyStates[switchCase->numCases]; if (callContext == GEO_CONTEXT_RENDER) { switchCase->selectedCase = bodyState->capState & 1; while (next != node) { if (next->type == GRAPH_NODE_TYPE_TRANSLATION_ROTATION) { if (bodyState->capState & 2) { next->flags |= GRAPH_RENDER_ACTIVE; } else { next->flags &= ~GRAPH_RENDER_ACTIVE; } } next = next->next; } } return NULL; } /** * Geo node script that makes the wings on Mario's wing cap flap. * Should be placed before a rotation node. */ Gfx *geo_mario_rotate_wing_cap_wings(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { s16 rotX; struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node; if (callContext == GEO_CONTEXT_RENDER) { struct GraphNodeRotation *rotNode = (struct GraphNodeRotation *) node->next; if (gBodyStates[asGenerated->parameter >> 1].wingFlutter == FALSE) { rotX = (coss((gAreaUpdateCounter & 0xF) << 12) + 1.0f) * 4096.0f; } else { rotX = (coss((gAreaUpdateCounter & 7) << 13) + 1.0f) * 6144.0f; } if (!(asGenerated->parameter & 1)) { rotNode->rotation[0] = -rotX; } else { rotNode->rotation[0] = rotX; } } return NULL; } /** * Geo node that updates the held object node and the HOLP. */ Gfx *geo_switch_mario_hand_grab_pos(s32 callContext, struct GraphNode *b, Mat4 *mtx) { struct GraphNodeHeldObject *asHeldObj = (struct GraphNodeHeldObject *) b; Mat4 *curTransform = mtx; struct MarioState *marioState = &gMarioStates[asHeldObj->playerIndex]; if (callContext == GEO_CONTEXT_RENDER) { asHeldObj->objNode = NULL; if (marioState->heldObj != NULL) { asHeldObj->objNode = marioState->heldObj; switch (marioState->marioBodyState->grabPos) { case GRAB_POS_LIGHT_OBJ: if (marioState->action & ACT_FLAG_THROWING) { vec3s_set(asHeldObj->translation, 50, 0, 0); } else { vec3s_set(asHeldObj->translation, 50, 0, 110); } break; case GRAB_POS_HEAVY_OBJ: vec3s_set(asHeldObj->translation, 145, -173, 180); break; case GRAB_POS_BOWSER: vec3s_set(asHeldObj->translation, 80, -270, 1260); break; } } } else if (callContext == GEO_CONTEXT_HELD_OBJ) { // ! The HOLP is set here, which is why it only updates when the held object is drawn. // This is why it won't update during a pause buffered hitstun or when the camera is very far // away. get_pos_from_transform_mtx(marioState->marioBodyState->heldObjLastPosition, *curTransform, gCurGraphNodeCamera->matrixPtr); } return NULL; } // X position of the mirror #define MIRROR_X 4331.53 /** * Geo node that creates a clone of Mario's geo node and updates it to becomes * a mirror image of the player. */ Gfx *geo_render_mirror_mario(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { f32 mirroredX; struct Object *mario = gMarioStates->marioObj; switch (callContext) { case GEO_CONTEXT_CREATE: init_graph_node_object(NULL, &gMirrorMario, NULL, gVec3fZero, gVec3sZero, gVec3fOne); break; case GEO_CONTEXT_AREA_LOAD: geo_add_child(node, &gMirrorMario.node); break; case GEO_CONTEXT_AREA_UNLOAD: geo_remove_child(&gMirrorMario.node); break; case GEO_CONTEXT_RENDER: if (mario->header.gfx.pos[0] > 1700.0f) { // TODO: Is this a geo layout copy or a graph node copy? gMirrorMario.sharedChild = mario->header.gfx.sharedChild; gMirrorMario.unk18 = mario->header.gfx.unk18; vec3s_copy(gMirrorMario.angle, mario->header.gfx.angle); vec3f_copy(gMirrorMario.pos, mario->header.gfx.pos); vec3f_copy(gMirrorMario.scale, mario->header.gfx.scale); // FIXME: why does this set unk38, an inline struct, to a ptr to another one? wrong // GraphNode types again? gMirrorMario.unk38 = *(struct GraphNodeObject_sub *) &mario->header.gfx.unk38.animID; mirroredX = MIRROR_X - gMirrorMario.pos[0]; gMirrorMario.pos[0] = mirroredX + MIRROR_X; gMirrorMario.angle[1] = -gMirrorMario.angle[1]; gMirrorMario.scale[0] *= -1.0f; // FIXME: Why doesn't this match? // gMirrorMario.node.flags |= 1; ((s16 *) &gMirrorMario)[1] |= 1; } else { // FIXME: Why doesn't this match? // gMirrorMario.node.flags &= ~1; ((s16 *) &gMirrorMario)[1] &= ~1; } break; } return NULL; } /** * Since Mirror Mario has an x scale of -1, the mesh becomes inside out. * This node corrects that by changing the culling mode accordingly. */ Gfx *geo_mirror_mario_backface_culling(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) { struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node; Gfx *gfx = NULL; if (callContext == GEO_CONTEXT_RENDER && gCurGraphNodeObject == &gMirrorMario) { gfx = alloc_display_list(3 * sizeof(*gfx)); if (asGenerated->parameter == 0) { gSPClearGeometryMode(&gfx[0], G_CULL_BACK); gSPSetGeometryMode(&gfx[1], G_CULL_FRONT); gSPEndDisplayList(&gfx[2]); } else { gSPClearGeometryMode(&gfx[0], G_CULL_FRONT); gSPSetGeometryMode(&gfx[1], G_CULL_BACK); gSPEndDisplayList(&gfx[2]); } asGenerated->fnNode.node.flags = (asGenerated->fnNode.node.flags & 0xFF) | (LAYER_OPAQUE << 8); } return gfx; }