sm64pc/src/game/mario_misc.c

572 lines
21 KiB
C

#include <ultra64.h>
#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.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 "object_helpers2.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
};
static s8 D_8032CDF0[7] = { 0x01, 0x02, 0x01, 0x00, 0x01, 0x02, 0x01 };
static s8 D_8032CDF8[] = { 0x0a, 0x0c, 0x10, 0x18, 0x0a, 0x0a, 0x0a, 0x0e, 0x14, 0x1e,
0x0a, 0x0a, 0x0a, 0x10, 0x14, 0x1a, 0x1a, 0x14, 0x00, 0x00 };
struct GraphNodeObject D_80339FE0;
struct MarioBodyState gBodyStates[2]; // 2nd is never accessed in practice, most likely Luigi related
// 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)
// mario head geo
Gfx *Geo18_802764B0(s32 callContext, struct GraphNode *node, Mat4 *c) {
Gfx *sp24 = NULL;
s16 sp22 = 0;
struct GraphNodeGenerated *sp1C = (struct GraphNodeGenerated *) node;
UNUSED Mat4 *sp18 = c;
if (callContext == GEO_CONTEXT_RENDER) {
if (gPlayer1Controller->controllerData != NULL && gWarpTransition.isActive == 0) {
gd_copy_p1_contpad(gPlayer1Controller->controllerData);
}
sp24 = (Gfx *) PHYSICAL_TO_VIRTUAL(gdm_gettestdl(sp1C->parameter));
D_8032C6A0 = gd_vblank;
sp22 = gd_sfx_to_play();
play_menu_sounds(sp22);
}
return sp24;
}
static void bhvToadMessage_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 bhvToadMessage_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 bhvToadMessage_talking(void) {
if (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 bhvToadMessage_opacifying(void) {
if ((gCurrentObject->oOpacity += 6) == 255) {
gCurrentObject->oToadMessageState = TOAD_MESSAGE_OPAQUE;
}
}
static void bhvToadMessage_fading(void) {
if ((gCurrentObject->oOpacity -= 6) == 81) {
gCurrentObject->oToadMessageState = TOAD_MESSAGE_FADED;
}
}
void bhvToadMessage_loop(void) {
if (gCurrentObject->header.gfx.node.flags & 1) {
gCurrentObject->oInteractionSubtype = 0;
switch (gCurrentObject->oToadMessageState) {
case TOAD_MESSAGE_FADED:
bhvToadMessage_faded();
break;
case TOAD_MESSAGE_OPAQUE:
bhvToadMessage_opaque();
break;
case TOAD_MESSAGE_OPACIFYING:
bhvToadMessage_opacifying();
break;
case TOAD_MESSAGE_FADING:
bhvToadMessage_fading();
break;
case TOAD_MESSAGE_TALKING:
bhvToadMessage_talking();
break;
}
}
}
void bhvToadMessage_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 {
mark_object_for_deletion(gCurrentObject);
}
}
static void bhvUnlockDoorStar_spawn_particle(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 bhvUnlockDoorStar_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;
scale_object(gCurrentObject, 0.5f);
}
void bhvUnlockDoorStar_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
scale_object(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
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.
bhvUnlockDoorStar_spawn_particle(0);
bhvUnlockDoorStar_spawn_particle(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) {
mark_object_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
}
}
static Gfx *func_802769E0(struct GraphNodeGenerated *node, s16 b) {
Gfx *sp2C;
Gfx *sp28 = NULL;
if (b == 255) {
node->fnNode.node.flags = (node->fnNode.node.flags & 0xFF) | 0x100;
sp28 = alloc_display_list(2 * sizeof(*sp28));
sp2C = sp28;
} else {
node->fnNode.node.flags = (node->fnNode.node.flags & 0xFF) | 0x500;
sp28 = alloc_display_list(3 * sizeof(*sp28));
sp2C = sp28;
gDPSetAlphaCompare(sp2C++, G_AC_DITHER);
}
gDPSetEnvColor(sp2C++, 255, 255, 255, b);
gSPEndDisplayList(sp2C);
return sp28;
}
Gfx *Geo18_802770A4(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
UNUSED u8 unused1[4];
Gfx *sp28 = NULL;
struct GraphNodeGenerated *sp24 = (struct GraphNodeGenerated *) node;
struct MarioBodyState *sp20 = &gBodyStates[sp24->parameter];
s16 sp1E;
UNUSED u8 unused2[4];
if (callContext == GEO_CONTEXT_RENDER) {
sp1E = (sp20->modelState & 0x100) ? (sp20->modelState & 0xFF) : 255;
sp28 = func_802769E0(sp24, sp1E);
}
return sp28;
}
Gfx *geo_switch_mario_stand_run(s32 callContext, struct GraphNode *node, UNUSED Mat4 *mtx) {
struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node;
struct MarioBodyState *sp0 = &gBodyStates[switchCase->numCases];
if (callContext == GEO_CONTEXT_RENDER) {
// assign result. 0 if moving, 1 if stationary.
switchCase->selectedCase = ((sp0->action & ACT_FLAG_STATIONARY) == FALSE);
}
return NULL;
}
Gfx *geo_switch_mario_eyes(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node;
struct MarioBodyState *sp8 = &gBodyStates[switchCase->numCases];
s16 sp6;
if (callContext == GEO_CONTEXT_RENDER) {
if (sp8->eyeState == 0) {
sp6 = ((switchCase->numCases * 32 + gAreaUpdateCounter) >> 1) & 0x1F;
if (sp6 < 7) {
switchCase->selectedCase = D_8032CDF0[sp6];
} else {
switchCase->selectedCase = 0;
}
} else {
switchCase->selectedCase = sp8->eyeState - 1;
}
}
return NULL;
}
Gfx *Geo18_80277294(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
struct GraphNodeGenerated *sp24 = (struct GraphNodeGenerated *) node;
struct MarioBodyState *sp20 = &gBodyStates[sp24->parameter];
s32 action = sp20->action;
if (callContext == GEO_CONTEXT_RENDER) {
struct GraphNodeRotation *sp18 = (struct GraphNodeRotation *) node->next;
if (action != 0x00840452 && action != 0x00840454 && action != 0x04000440
&& action != 0x20810446) {
vec3s_copy(sp20->unkC, gVec3sZero);
}
sp18->rotation[0] = sp20->unkC[1];
sp18->rotation[1] = sp20->unkC[2];
sp18->rotation[2] = sp20->unkC[0];
}
return NULL;
}
Gfx *Geo18_802773A4(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
struct GraphNodeGenerated *sp2C = (struct GraphNodeGenerated *) node;
struct MarioBodyState *sp28 = &gBodyStates[sp2C->parameter];
s32 action = sp28->action;
if (callContext == GEO_CONTEXT_RENDER) {
struct GraphNodeRotation *sp20 = (struct GraphNodeRotation *) node->next;
struct Camera *camera = gCurGraphNodeCamera->config.camera;
if (camera->mode == CAMERA_MODE_C_UP) {
sp20->rotation[0] = gPlayerCameraState->headRotation[1];
sp20->rotation[2] = gPlayerCameraState->headRotation[0];
} else if (action & 0x20000000) {
sp20->rotation[0] = sp28->unk12[1];
sp20->rotation[1] = sp28->unk12[2];
sp20->rotation[2] = sp28->unk12[0];
} else {
vec3s_set(sp28->unk12, 0, 0, 0);
vec3s_set(sp20->rotation, 0, 0, 0);
}
}
return NULL;
}
Gfx *geo_switch_mario_hand(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node;
struct MarioBodyState *sp0 = &gBodyStates[0];
if (callContext == GEO_CONTEXT_RENDER) {
if (sp0->handState == 0) {
switchCase->selectedCase = ((sp0->action & ACT_FLAG_SWIMMING_OR_FLYING) != 0);
} else {
if (switchCase->numCases == 0) {
switchCase->selectedCase = (sp0->handState < 5) ? sp0->handState : 1;
} else {
switchCase->selectedCase = (sp0->handState < 2) ? sp0->handState : 0;
}
}
}
return NULL;
}
Gfx *Geo18_802775CC(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
static s16 D_8032CE0C = 0;
struct GraphNodeGenerated *spC = (struct GraphNodeGenerated *) node;
struct GraphNodeScale *sp8 = (struct GraphNodeScale *) node->next;
struct MarioBodyState *sp4 = &gBodyStates[0];
if (callContext == GEO_CONTEXT_RENDER) {
sp8->scale = 1.0f;
if (spC->parameter == sp4->unk0B >> 6) {
if (D_8032CE0C != gAreaUpdateCounter && (sp4->unk0B & 0x3F) > 0) {
sp4->unk0B -= 1;
D_8032CE0C = gAreaUpdateCounter;
}
sp8->scale = D_8032CDF8[spC->parameter * 6 + (sp4->unk0B & 0x3F)] / 10.0f;
}
}
return NULL;
}
Gfx *geo_switch_mario_cap_effect(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node;
struct MarioBodyState *sp0 = &gBodyStates[switchCase->numCases];
if (callContext == GEO_CONTEXT_RENDER) {
switchCase->selectedCase = sp0->modelState >> 8;
}
return NULL;
}
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 *sp4 = &gBodyStates[switchCase->numCases];
if (callContext == GEO_CONTEXT_RENDER) {
switchCase->selectedCase = sp4->capState & 1;
while (next != node) {
if (next->type == 21) {
if (sp4->capState & 2) {
next->flags |= 1;
} else {
next->flags &= ~1;
}
}
next = next->next;
}
}
return NULL;
}
Gfx *Geo18_80277824(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
s16 spE;
struct GraphNodeGenerated *sp8 = (struct GraphNodeGenerated *) node;
if (callContext == GEO_CONTEXT_RENDER) {
struct GraphNodeRotation *sp4 = (struct GraphNodeRotation *) node->next;
if (gBodyStates[sp8->parameter >> 1].unk07 == 0) {
spE = (coss((gAreaUpdateCounter & 0xF) << 12) + 1.0f) * 4096.0f;
} else {
spE = (coss((gAreaUpdateCounter & 7) << 13) + 1.0f) * 6144.0f;
}
if (!(sp8->parameter & 1)) {
sp4->rotation[0] = -spE;
} else {
sp4->rotation[0] = spE;
}
}
return NULL;
}
Gfx *geo_switch_mario_hand_grab_pos(s32 callContext, struct GraphNode *b, Mat4 *c) {
struct GraphNodeHeldObject *sp2C = (struct GraphNodeHeldObject *) b;
Mat4 *sp28 = c;
struct MarioState *sp24 = &gMarioStates[sp2C->playerIndex];
if (callContext == GEO_CONTEXT_RENDER) {
sp2C->objNode = NULL;
if (sp24->heldObj != NULL) {
sp2C->objNode = sp24->heldObj;
switch (sp24->marioBodyState->grabPos) {
case GRAB_POS_LIGHT_OBJ:
if (sp24->action & ACT_FLAG_THROWING) {
vec3s_set(sp2C->translation, 50, 0, 0);
} else {
vec3s_set(sp2C->translation, 50, 0, 110);
}
break;
case GRAB_POS_HEAVY_OBJ:
vec3s_set(sp2C->translation, 145, -173, 180);
break;
case GRAB_POS_BOWSER:
vec3s_set(sp2C->translation, 80, -270, 1260);
break;
}
}
} else if (callContext == GEO_CONTEXT_HELD_OBJ) {
get_pos_from_transform_mtx(sp24->marioBodyState->unk18, *sp28, gCurGraphNodeCamera->matrixPtr);
}
return NULL;
}
Gfx *geo_render_mirror_mario(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
f32 sp34;
struct Object *sp30 = gMarioStates->marioObj;
switch (callContext) {
case GEO_CONTEXT_CREATE:
init_graph_node_object(NULL, &D_80339FE0, NULL, gVec3fZero, gVec3sZero, gVec3fOne);
break;
case GEO_CONTEXT_AREA_LOAD:
geo_add_child(node, &D_80339FE0.node);
break;
case GEO_CONTEXT_AREA_UNLOAD:
geo_remove_child(&D_80339FE0.node);
break;
case GEO_CONTEXT_RENDER:
if (sp30->header.gfx.pos[0] > 1700.0f) {
// TODO: Is this a geo layout copy or a graph node copy?
D_80339FE0.sharedChild = sp30->header.gfx.sharedChild;
D_80339FE0.unk18 = sp30->header.gfx.unk18;
vec3s_copy(D_80339FE0.angle, sp30->header.gfx.angle);
vec3f_copy(D_80339FE0.pos, sp30->header.gfx.pos);
vec3f_copy(D_80339FE0.scale, sp30->header.gfx.scale);
// FIXME: why does this set unk38, an inline struct, to a ptr to another one? wrong
// GraphNode types again?
D_80339FE0.unk38 = *(struct GraphNodeObject_sub *) &sp30->header.gfx.unk38.animID;
sp34 = 4331.53 - D_80339FE0.pos[0];
D_80339FE0.pos[0] = sp34 + 4331.53;
D_80339FE0.angle[1] = -D_80339FE0.angle[1];
D_80339FE0.scale[0] *= -1.0f;
// FIXME: Why doesn't this match?
// D_80339FE0.node.flags |= 1;
((s16 *) &D_80339FE0)[1] |= 1;
} else {
// FIXME: Why doesn't this match?
// D_80339FE0.node.flags &= ~1;
((s16 *) &D_80339FE0)[1] &= ~1;
}
break;
}
return NULL;
}
Gfx *geo_mirror_mario_backface_culling(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
struct GraphNodeGenerated *sp34 = (struct GraphNodeGenerated *) node;
Gfx *sp30 = NULL;
if (callContext == GEO_CONTEXT_RENDER && gCurGraphNodeObject == &D_80339FE0) {
sp30 = alloc_display_list(3 * sizeof(*sp30));
if (sp34->parameter == 0) {
gSPClearGeometryMode(&sp30[0], G_CULL_BACK);
gSPSetGeometryMode(&sp30[1], G_CULL_FRONT);
gSPEndDisplayList(&sp30[2]);
} else {
gSPClearGeometryMode(&sp30[0], G_CULL_FRONT);
gSPSetGeometryMode(&sp30[1], G_CULL_BACK);
gSPEndDisplayList(&sp30[2]);
}
sp34->fnNode.node.flags = (sp34->fnNode.node.flags & 0xFF) | 0x100;
}
return sp30;
}