Additional synchronization fixes for end cutscene.

This commit is contained in:
MysterD 2020-09-26 12:07:43 -07:00
parent 469fa2ff33
commit f5df807a0c
8 changed files with 223 additions and 93 deletions

View file

@ -370,6 +370,11 @@ struct MarioState
/*????*/ u8 isSnoring;
/*????*/ struct Object* bubbleObj;
/*????*/ u8 freeze;
// Variables for a spline curve animation (used for the flight path in the grand star cutscene)
/*????*/ Vec4s* splineKeyframe;
/*????*/ float splineKeyframeFraction;
/*????*/ int splineState;
};
#define PLAY_MODE_NORMAL 0

View file

@ -7,11 +7,6 @@
#include "trig_tables.inc.c"
// Variables for a spline curve animation (used for the flight path in the grand star cutscene)
Vec4s *gSplineKeyframe;
float gSplineKeyframeFraction;
int gSplineState;
// These functions have bogus return values.
// Disable the compiler warning.
#pragma GCC diagnostic push
@ -824,14 +819,14 @@ f32 atan2f(f32 y, f32 x) {
* [0, 0, 0, 0, 1, 2, ... n-1, n, n, n, n]
* TODO: verify the classification of the spline / figure out how polynomials were computed
*/
void spline_get_weights(Vec4f result, f32 t, UNUSED s32 c) {
void spline_get_weights(struct MarioState* m, Vec4f result, f32 t, UNUSED s32 c) {
f32 tinv = 1 - t;
f32 tinv2 = tinv * tinv;
f32 tinv3 = tinv2 * tinv;
f32 t2 = t * t;
f32 t3 = t2 * t;
switch (gSplineState) {
switch (m->splineState) {
case CURVE_BEGIN_1:
result[0] = tinv3;
result[1] = t3 * 1.75f - t2 * 4.5f + t * 3.0f;
@ -873,10 +868,10 @@ void spline_get_weights(Vec4f result, f32 t, UNUSED s32 c) {
* The array should end with three entries with s=0 (infinite keyframe duration).
* That's because the spline has a 3rd degree polynomial, so it looks 3 points ahead.
*/
void anim_spline_init(Vec4s *keyFrames) {
gSplineKeyframe = keyFrames;
gSplineKeyframeFraction = 0;
gSplineState = 1;
void anim_spline_init(struct MarioState* m, Vec4s *keyFrames) {
m->splineKeyframe = keyFrames;
m->splineKeyframeFraction = 0;
m->splineState = 1;
}
/**
@ -884,33 +879,33 @@ void anim_spline_init(Vec4s *keyFrames) {
* anim_spline_init should be called before polling for vectors.
* Returns TRUE when the last point is reached, FALSE otherwise.
*/
s32 anim_spline_poll(Vec3f result) {
s32 anim_spline_poll(struct MarioState* m, Vec3f result) {
Vec4f weights;
s32 i;
s32 hasEnded = FALSE;
vec3f_copy(result, gVec3fZero);
spline_get_weights(weights, gSplineKeyframeFraction, gSplineState);
spline_get_weights(m, weights, m->splineKeyframeFraction, m->splineState);
for (i = 0; i < 4; i++) {
result[0] += weights[i] * gSplineKeyframe[i][1];
result[1] += weights[i] * gSplineKeyframe[i][2];
result[2] += weights[i] * gSplineKeyframe[i][3];
result[0] += weights[i] * m->splineKeyframe[i][1];
result[1] += weights[i] * m->splineKeyframe[i][2];
result[2] += weights[i] * m->splineKeyframe[i][3];
}
if ((gSplineKeyframeFraction += gSplineKeyframe[0][0] / 1000.0f) >= 1) {
gSplineKeyframe++;
gSplineKeyframeFraction--;
switch (gSplineState) {
if ((m->splineKeyframeFraction += m->splineKeyframe[0][0] / 1000.0f) >= 1) {
m->splineKeyframe++;
m->splineKeyframeFraction--;
switch (m->splineState) {
case CURVE_END_2:
hasEnded = TRUE;
break;
case CURVE_MIDDLE:
if (gSplineKeyframe[2][0] == 0) {
gSplineState = CURVE_END_1;
if (m->splineKeyframe[2][0] == 0) {
m->splineState = CURVE_END_1;
}
break;
default:
gSplineState++;
m->splineState++;
break;
}
}

View file

@ -76,8 +76,8 @@ s32 approach_s32(s32 current, s32 target, s32 inc, s32 dec);
f32 approach_f32(f32 current, f32 target, f32 inc, f32 dec);
s16 atan2s(f32 y, f32 x);
f32 atan2f(f32 a, f32 b);
void spline_get_weights(Vec4f result, f32 t, UNUSED s32 c);
void anim_spline_init(Vec4s *keyFrames);
s32 anim_spline_poll(Vec3f result);
void spline_get_weights(struct MarioState* m, Vec4f result, f32 t, UNUSED s32 c);
void anim_spline_init(struct MarioState* m, Vec4s *keyFrames);
s32 anim_spline_poll(struct MarioState* m, Vec3f result);
#endif // MATH_UTIL_H

View file

@ -68,6 +68,9 @@ void bhv_grand_star_loop(void) {
} else {
cur_obj_become_tangible();
if (o->oInteractStatus & INT_STATUS_INTERACTED) {
if (gMarioStates[0].action != ACT_JUMBO_STAR_CUTSCENE) {
set_mario_action(&gMarioStates[0], ACT_JUMBO_STAR_CUTSCENE, 0);
}
obj_mark_for_deletion(o);
o->oInteractStatus = 0;
}

View file

@ -1228,6 +1228,7 @@ u8 player_is_sliding(struct MarioState* m) {
u32 interact_player(struct MarioState* m, UNUSED u32 interactType, struct Object* o) {
if (gServerSettings.playerInteractions == PLAYER_INTERACTIONS_NONE) { return FALSE; }
if (m->action == ACT_JUMBO_STAR_CUTSCENE) { return FALSE; }
struct MarioState* m2 = NULL;
for (int i = 0; i < MAX_PLAYERS; i++) {
@ -1237,6 +1238,7 @@ u32 interact_player(struct MarioState* m, UNUSED u32 interactType, struct Object
}
}
if (m2 == NULL) { return FALSE; }
if (m2->action == ACT_JUMBO_STAR_CUTSCENE) { return FALSE; }
// don't do further interactions if we've hopped on top
if (resolve_player_collision(m, m2)) {

View file

@ -1940,6 +1940,11 @@ static s32 act_intro_cutscene(struct MarioState *m) {
return FALSE;
}
static void jumbo_star_offset(struct MarioState* m) {
m->pos[0] += 300.0f * sins(m->faceAngle[1] + 0x4000 * m->playerIndex);
m->pos[2] += 300.0f * coss(m->faceAngle[1] + 0x4000 * m->playerIndex);
}
// jumbo star cutscene: Mario lands after grabbing the jumbo star
static void jumbo_star_cutscene_falling(struct MarioState *m) {
if (m->actionState == 0) {
@ -1947,7 +1952,7 @@ static void jumbo_star_cutscene_falling(struct MarioState *m) {
m->flags |= (MARIO_WING_CAP | MARIO_CAP_ON_HEAD);
m->faceAngle[1] = -0x8000;
m->pos[0] = 0.0f;
m->pos[0] = 100.0f * m->playerIndex;
m->pos[2] = 0.0f;
mario_set_forward_vel(m, 0.0f);
@ -2011,6 +2016,8 @@ static s32 jumbo_star_cutscene_taking_off(struct MarioState *m) {
}
vec3f_set(m->pos, 0.0f, 307.0, marioObj->rawData.asF32[0x22]);
m->pos[0] += 100.0f * m->playerIndex;
update_mario_pos_for_anim(m);
vec3f_copy(marioObj->header.gfx.pos, m->pos);
vec3s_set(marioObj->header.gfx.angle, 0, m->faceAngle[1], 0);
@ -2033,15 +2040,19 @@ static s32 jumbo_star_cutscene_flying(struct MarioState *m) {
switch (m->actionState) {
case 0:
set_mario_animation(m, MARIO_ANIM_WING_CAP_FLY);
anim_spline_init(sJumboStarKeyframes);
anim_spline_init(m, sJumboStarKeyframes);
m->actionState++;
// fall through
case 1:
if (anim_spline_poll(targetPos)) {
if (anim_spline_poll(m, targetPos)) {
// does this twice
set_mario_action(m, ACT_FREEFALL, 0);
m->actionState++;
} else {
targetPos[0] += 100.0f * m->playerIndex;
float heightScalar = min(m->actionTimer / 30.0f, 1.0f);
targetPos[1] -= 100.0f * m->playerIndex * heightScalar;
targetDX = targetPos[0] - m->pos[0];
targetDY = targetPos[1] - m->pos[1];
targetDZ = targetPos[2] - m->pos[2];
@ -2064,7 +2075,7 @@ static s32 jumbo_star_cutscene_flying(struct MarioState *m) {
vec3f_copy(m->marioObj->header.gfx.pos, m->pos);
m->particleFlags |= PARTICLE_SPARKLES;
if (m->actionTimer++ == 500) {
if (m->actionTimer++ == 500 && m->playerIndex == 0) {
level_trigger_warp(m, WARP_OP_CREDITS_START);
}

View file

@ -2,15 +2,23 @@
#include "game/level_update.h"
#include "game/camera.h"
#include "pc/network/network.h"
#include "game/mario.h"
#include "sm64.h"
#ifdef DEBUG
static u8 warpToLevel = LEVEL_CCM;
#define SCANCODE_0 0x0B
#define SCANCODE_1 0x02
#define SCANCODE_2 0x03
#define SCANCODE_3 0x04
#define SCANCODE_4 0x05
#define SCANCODE_5 0x06
#define SCANCODE_6 0x07
#define SCANCODE_7 0x08
#define SCANCODE_8 0x09
#define SCANCODE_9 0x0A
static void debug_breakpoint_here(void) {
// create easy breakpoint position for debugging
@ -92,6 +100,10 @@ static void debug_warp_area() {
}
}
static void debug_grand_star(void) {
set_mario_action(&gMarioStates[0], ACT_JUMBO_STAR_CUTSCENE, 0);
}
static void debug_suicide(void) {
gMarioStates[0].hurtCounter = 31;
}
@ -103,6 +115,7 @@ void debug_keyboard_on_key_down(int scancode) {
#ifdef DEVELOPMENT
case SCANCODE_6: debug_warp_level(warpToLevel); break;
case SCANCODE_7: debug_warp_area(); break;
case SCANCODE_9: debug_grand_star(); break;
case SCANCODE_0: debug_suicide(); break;
#endif
}

View file

@ -10,120 +10,221 @@
#define SET_BIT(val, num) ((((u8)(val)) & 0x01) << (num));
#define GET_BIT(val, num) (((val) >> (num)) & 0x01)
#pragma pack(1)
struct PacketPlayerData {
u32 rawData[80];
struct Controller controller;
s16 nodeFlags;
u16 input;
u32 flags;
u32 particleFlags;
u32 action;
u32 prevAction;
u16 actionState;
u16 actionTimer;
u32 actionArg;
f32 intendedMag;
s16 intendedYaw;
s16 invincTimer;
u8 framesSinceA;
u8 framesSinceB;
u8 wallKickTimer;
u8 doubleJumpTimer;
Vec3s faceAngle;
Vec3s angleVel;
s16 slideYaw;
s16 twirlYaw;
Vec3f pos;
Vec3f vel;
f32 forwardVel;
f32 slideVelX;
f32 slideVelZ;
s16 health;
u8 squishTimer;
f32 peakHeight;
s16 currentRoom;
u8 customFlags;
u32 heldSyncID;
u32 heldBySyncID;
};
static void read_packet_data(struct PacketPlayerData* data, struct MarioState* m) {
u32 heldSyncID = (m->heldObj != NULL) ? m->heldObj->oSyncID : 0;
u32 heldBySyncID = (m->heldByObj != NULL) ? m->heldByObj->oSyncID : 0;
u8 customFlags = SET_BIT((m->freeze > 0), 0);
memcpy(data->rawData, m->marioObj->rawData.asU32, sizeof(u32) * 80);
data->nodeFlags = m->marioObj->header.gfx.node.flags;
data->controller = *m->controller;
data->input = m->input;
data->flags = m->flags;
data->particleFlags = m->particleFlags;
data->action = m->action;
data->prevAction = m->prevAction;
data->actionState = m->actionState;
data->actionTimer = m->actionTimer;
data->actionArg = m->actionArg;
data->intendedMag = m->intendedMag;
data->intendedYaw = m->intendedYaw;
data->invincTimer = m->invincTimer;
data->framesSinceA = m->framesSinceA;
data->framesSinceB = m->framesSinceB;
data->wallKickTimer = m->wallKickTimer;
data->doubleJumpTimer = m->doubleJumpTimer;
memcpy(data->faceAngle, m->faceAngle, sizeof(s16) * 3);
memcpy(data->angleVel, m->angleVel, sizeof(s16) * 3);
data->slideYaw = m->slideYaw;
data->twirlYaw = m->twirlYaw;
memcpy(data->pos, m->pos, sizeof(f32) * 3);
memcpy(data->vel, m->vel, sizeof(f32) * 3);
data->forwardVel = m->forwardVel;
data->slideVelX = m->slideVelX;
data->slideVelZ = m->slideVelZ;
data->health = m->health;
data->squishTimer = m->squishTimer;
data->peakHeight = m->peakHeight;
data->currentRoom = m->currentRoom;
data->customFlags = customFlags;
data->heldSyncID = heldSyncID;
data->heldBySyncID = heldBySyncID;
}
static void write_packet_data(struct PacketPlayerData* data, struct MarioState* m,
u8* customFlags, u32* heldSyncID, u32* heldBySyncID) {
memcpy(m->marioObj->rawData.asU32, data->rawData, sizeof(u32) * 80);
m->marioObj->header.gfx.node.flags = data->nodeFlags;
*m->controller = data->controller;
m->input = data->input;
m->flags = data->flags;
m->particleFlags = data->particleFlags;
m->action = data->action;
m->prevAction = data->prevAction;
m->actionState = data->actionState;
m->actionTimer = data->actionTimer;
m->actionArg = data->actionArg;
m->intendedMag = data->intendedMag;
m->intendedYaw = data->intendedYaw;
m->invincTimer = data->invincTimer;
m->framesSinceA = data->framesSinceA;
m->framesSinceB = data->framesSinceB;
m->wallKickTimer = data->wallKickTimer;
m->doubleJumpTimer = data->doubleJumpTimer;
memcpy(m->faceAngle, data->faceAngle, sizeof(s16) * 3);
memcpy(m->angleVel, data->angleVel, sizeof(s16) * 3);
m->slideYaw = data->slideYaw;
m->twirlYaw = data->twirlYaw;
memcpy(m->pos, data->pos, sizeof(f32) * 3);
memcpy(m->vel, data->vel, sizeof(f32) * 3);
m->forwardVel = data->forwardVel;
m->slideVelX = data->slideVelX;
m->slideVelZ = data->slideVelZ;
m->health = data->health;
m->squishTimer = data->squishTimer;
m->peakHeight = data->peakHeight;
m->currentRoom = data->currentRoom;
*customFlags = data->customFlags;
*heldSyncID = data->heldSyncID;
*heldBySyncID = data->heldBySyncID;
}
void network_send_player(void) {
if (gMarioStates[0].marioObj == NULL) { return; }
u32 heldSyncID = (gMarioStates[0].heldObj != NULL)
? gMarioStates[0].heldObj->oSyncID
: 0;
u32 heldBySyncID = (gMarioStates[0].heldByObj != NULL)
? gMarioStates[0].heldByObj->oSyncID
: 0;
u8 customFlags = SET_BIT((gMarioStates[0].freeze > 0), 0);
struct PacketPlayerData data = { 0 };
read_packet_data(&data, &gMarioStates[0]);
struct Packet p;
packet_init(&p, PACKET_PLAYER, false);
packet_write(&p, &gMarioStates[0], sizeof(u32) * 24);
packet_write(&p, gMarioStates[0].controller, 20);
packet_write(&p, gMarioStates[0].marioObj->rawData.asU32, sizeof(u32) * 80);
packet_write(&p, &gMarioStates[0].health, sizeof(s16));
packet_write(&p, &gMarioStates[0].marioObj->header.gfx.node.flags, sizeof(s16));
packet_write(&p, &gMarioStates[0].actionState, sizeof(u16));
packet_write(&p, &gMarioStates[0].actionTimer, sizeof(u16));
packet_write(&p, &gMarioStates[0].actionArg, sizeof(u32));
packet_write(&p, &gMarioStates[0].currentRoom, sizeof(s16));
packet_write(&p, &gMarioStates[0].squishTimer, sizeof(u8));
packet_write(&p, &gMarioStates[0].peakHeight, sizeof(f32));
packet_write(&p, &customFlags, sizeof(u8));
packet_write(&p, &heldSyncID, sizeof(u32));
packet_write(&p, &heldBySyncID, sizeof(u32));
packet_write(&p, &data, sizeof(struct PacketPlayerData));
network_send(&p);
}
void network_receive_player(struct Packet* p) {
if (gMarioStates[1].marioObj == NULL) { return; }
struct MarioState* m = &gMarioStates[1];
if (m == NULL || m->marioObj == NULL) { return; }
// save previous state
u32 heldSyncID = 0;
u32 heldBySyncID = 0;
u16 playerIndex = gMarioStates[1].playerIndex;
u8 customFlags = 0;
u32 oldAction = gMarioStates[1].action;
u16 oldActionState = gMarioStates[1].actionState;
u16 oldActionArg = gMarioStates[1].actionArg;
u32 oldBehParams = gMarioStates[1].marioObj->oBehParams;
struct PacketPlayerData oldData = { 0 };
read_packet_data(&oldData, m);
u16 playerIndex = m->playerIndex;
u32 oldBehParams = m->marioObj->oBehParams;
// load mario information from packet
packet_read(p, &gMarioStates[1], sizeof(u32) * 24);
packet_read(p, gMarioStates[1].controller, 20);
packet_read(p, &gMarioStates[1].marioObj->rawData.asU32, sizeof(u32) * 80);
packet_read(p, &gMarioStates[1].health, sizeof(s16));
packet_read(p, &gMarioStates[1].marioObj->header.gfx.node.flags, sizeof(s16));
packet_read(p, &gMarioStates[1].actionState, sizeof(u16));
packet_read(p, &gMarioStates[1].actionTimer, sizeof(u16));
packet_read(p, &gMarioStates[1].actionArg, sizeof(u32));
packet_read(p, &gMarioStates[1].currentRoom, sizeof(s16));
packet_read(p, &gMarioStates[1].squishTimer, sizeof(u8));
packet_read(p, &gMarioStates[1].peakHeight, sizeof(f32));
packet_read(p, &customFlags, sizeof(u8));
packet_read(p, &heldSyncID, sizeof(u32));
packet_read(p, &heldBySyncID, sizeof(u32));
struct PacketPlayerData data = { 0 };
packet_read(p, &data, sizeof(struct PacketPlayerData));
// check to see if we should just drop this packet
if (oldData.action == ACT_JUMBO_STAR_CUTSCENE && data.action == ACT_JUMBO_STAR_CUTSCENE) {
return;
}
// apply data from packet to mario state
u32 heldSyncID = 0;
u32 heldBySyncID = 0;
u8 customFlags = 0;
write_packet_data(&data, m, &customFlags, &heldSyncID, &heldBySyncID);
// read custom flags
gMarioStates[1].freeze = GET_BIT(customFlags, 0);
m->freeze = GET_BIT(customFlags, 0);
// reset player index
gMarioStates[1].playerIndex = playerIndex;
gMarioStates[1].marioObj->oBehParams = oldBehParams;
m->playerIndex = playerIndex;
m->marioObj->oBehParams = oldBehParams;
// reset mario sound play flag so that their jump sounds work
if (gMarioStates[1].action != oldAction) {
gMarioStates[1].flags &= ~(MARIO_ACTION_SOUND_PLAYED | MARIO_MARIO_SOUND_PLAYED);
if (m->action != oldData.action) {
m->flags &= ~(MARIO_ACTION_SOUND_PLAYED | MARIO_MARIO_SOUND_PLAYED);
}
// find and set their held object
if (heldSyncID != 0 && gSyncObjects[heldSyncID].o != NULL) {
// TODO: do we have to move graphics nodes around to make this visible?
struct Object* heldObj = gSyncObjects[heldSyncID].o;
if (gMarioStates[0].heldObj == heldObj && gNetworkType == NT_CLIENT) { // two-player hack: needs priority
if (m->heldObj == heldObj && gNetworkType == NT_CLIENT) { // two-player hack: needs priority
mario_drop_held_object(&gMarioStates[0]);
force_idle_state(&gMarioStates[0]);
}
gMarioStates[1].heldObj = heldObj;
m->heldObj = heldObj;
heldObj->oHeldState = HELD_HELD;
heldObj->heldByPlayerIndex = 1;
} else {
gMarioStates[1].heldObj = NULL;
m->heldObj = NULL;
}
// find and set their held-by object
if (heldBySyncID != 0 && gSyncObjects[heldBySyncID].o != NULL) {
// TODO: do we have to move graphics nodes around to make this visible?
gMarioStates[1].heldByObj = gSyncObjects[heldBySyncID].o;
m->heldByObj = gSyncObjects[heldBySyncID].o;
} else {
gMarioStates[1].heldByObj = NULL;
m->heldByObj = NULL;
}
// jump kicking: restore action state, otherwise it won't play
if (gMarioStates[1].action == ACT_JUMP_KICK) {
gMarioStates[1].actionState = oldActionState;
if (m->action == ACT_JUMP_KICK) {
m->actionState = oldData.actionState;
}
// punching:
if ((gMarioStates[1].action == ACT_PUNCHING || gMarioStates[1].action == ACT_MOVE_PUNCHING)) {
if ((m->action == ACT_PUNCHING || m->action == ACT_MOVE_PUNCHING)) {
// play first punching sound, otherwise it will be missed
if (gMarioStates[1].action != oldAction) {
play_sound(SOUND_MARIO_PUNCH_YAH, gMarioStates[1].marioObj->header.gfx.cameraToObject);
if (m->action != oldData.action) {
play_sound(SOUND_MARIO_PUNCH_YAH, m->marioObj->header.gfx.cameraToObject);
}
// make the first punch large, otherwise it will be missed
if (gMarioStates[1].actionArg == 2 && oldActionArg == 1) {
gMarioStates[1].marioBodyState->punchState = (0 << 6) | 4;
if (m->actionArg == 2 && oldData.actionArg == 1) {
m->marioBodyState->punchState = (0 << 6) | 4;
}
}
// action changed, reset timer
if (gMarioStates[1].action != oldAction) {
gMarioStates[1].actionTimer = 0;
if (m->action != oldData.action) {
m->actionTimer = 0;
}
}