From 1e6c734ced8cbcf8b3229fc4dfd8210fb5e7e694 Mon Sep 17 00:00:00 2001 From: MysterD Date: Thu, 10 Sep 2020 00:17:30 -0700 Subject: [PATCH] Completely rewrote level transition synchronization code Should be snappier and less prone to crashes. I tested as many scenarios as I could think of and it has been rock solid. But time will tell. Also created a new debug log system, just so I could understand what the hell was going on with this code. --- build-windows-visual-studio/sm64ex.vcxproj | 1 + .../sm64ex.vcxproj.filters | 3 + network.sh | 2 + src/game/area.c | 1 + src/game/ingame_menu.c | 6 - src/game/level_update.c | 105 ++++++++----- src/game/level_update.h | 5 - src/game/mario.c | 2 - src/menu/star_select.c | 31 ++-- src/pc/debuglog.h | 34 ++++ src/pc/network/network.c | 5 +- src/pc/network/network.h | 5 +- .../network/packets/packet_inside_painting.c | 100 ++++++------ src/pc/network/packets/packet_level_warp.c | 147 +++++++++++------- src/pc/network/packets/packet_object.c | 1 + 15 files changed, 274 insertions(+), 174 deletions(-) create mode 100644 src/pc/debuglog.h diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj index b32a53cb3..44599c174 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj +++ b/build-windows-visual-studio/sm64ex.vcxproj @@ -4305,6 +4305,7 @@ + diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters index 6d367b0c5..e81a80252 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj.filters +++ b/build-windows-visual-studio/sm64ex.vcxproj.filters @@ -15928,5 +15928,8 @@ Header Files\include + + Source Files\src\pc + \ No newline at end of file diff --git a/network.sh b/network.sh index 41300fc82..71d232d34 100755 --- a/network.sh +++ b/network.sh @@ -12,6 +12,8 @@ if [ ! -f "$FILE" ]; then fi $FILE --server 27015 --configfile sm64config_server.txt & +#$FILE --client 127.0.0.1 27015 --configfile sm64config_client.txt & +#exit # debug if cgdb exists if ! [ -x "$(command -v cgdb)" ]; then diff --git a/src/game/area.c b/src/game/area.c index 68c421491..d5633c6e4 100644 --- a/src/game/area.c +++ b/src/game/area.c @@ -254,6 +254,7 @@ void load_area(s32 index) { } void unload_area(void) { + network_clear_sync_objects(); if (gCurrentArea != NULL) { unload_objects_from_area(0, gCurrentArea->index); geo_call_global_function_nodes(&gCurrentArea->unk04->node, GEO_CONTEXT_AREA_UNLOAD); diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index 8fd5cf117..35f323cc5 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -3123,12 +3123,6 @@ s16 render_course_complete_screen(void) { gInGameLanguage = eu_get_language(); #endif - // if we went into a painting, no more save menu! - if (gInsidePainting == TRUE) { - gMenuMode = -1; - return 0; - } - switch (gDialogBoxState) { case DIALOG_STATE_OPENING: render_course_complete_lvl_info_and_hud_str(); diff --git a/src/game/level_update.c b/src/game/level_update.c index 652602caf..b679dad66 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -46,6 +46,10 @@ #define WARP_NODE_CREDITS_MIN 0xF8 +u8 gControlledWarp = 0; +u8 gReceiveWarp = 0; +struct WarpDest gReceiveWarpDest = { 0 }; + #ifdef VERSION_JP const char *credits01[] = { "1GAME DIRECTOR", "SHIGERU MIYAMOTO" }; const char *credits02[] = { "2ASSISTANT DIRECTORS", "YOSHIAKI KOIZUMI", "TAKASHI TEZUKA" }; @@ -169,11 +173,6 @@ s8 D_8032C9E0 = 0; u8 unused3[4]; u8 unused4[2]; -u8 gInsidePainting = false; -u8 gControlPainting = false; -u8 gWaitingForRemotePainting = false; -struct WarpNode gPaintingWarpNode = { 0 }; - u16 level_control_timer(s32 timerOp) { switch (timerOp) { case TIMER_CONTROL_SHOW: @@ -361,7 +360,17 @@ void set_mario_initial_action(struct MarioState *m, u32 spawnType, u32 actionArg set_mario_initial_cap_powerup(m); } +#include + void init_mario_after_warp(void) { + printf("===== init mario =====\n"); + printf("areaIdx = %d\n", sWarpDest.areaIdx); + printf("arg = %d\n", sWarpDest.arg); + printf("levelNum = %d\n", sWarpDest.levelNum); + printf("nodeId = %d\n", sWarpDest.nodeId); + printf("type = %d\n", sWarpDest.type); + fflush(stdout); + struct ObjectWarpNode *spawnNode = area_get_warp_node(sWarpDest.nodeId); u32 marioSpawnType = get_mario_spawn_type(spawnNode->object); @@ -671,35 +680,7 @@ struct WarpNode *get_painting_warp_node(void) { return warpNode; } -/** - * Check is Mario has entered a painting, and if so, initiate a warp. - */ -void initiate_painting_warp(void) { - if (gCurrentArea->paintingWarpNodes != NULL && gMarioState->floor != NULL) { - struct WarpNode *pWarpNode = get_painting_warp_node(); - - if (pWarpNode != NULL) { - if (gMarioState->action & ACT_FLAG_INTANGIBLE) { - play_painting_eject_sound(); - } else if (pWarpNode->id != 0) { - initiate_painting_warp_node(pWarpNode, false); - gControlPainting = true; - gWaitingForRemotePainting = (gNetworkType != NT_NONE); - set_mario_action(gMarioState, ACT_DISAPPEARED, 0); - gMarioState->marioObj->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE; - } - } - } -} - -void initiate_painting_warp_node(struct WarpNode *pWarpNode, u8 instant) { - if (pWarpNode->id == 0) { return; } - - gControlPainting = false; - gWaitingForRemotePainting = false; - gInsidePainting = true; - - gPaintingWarpNode = *pWarpNode; +static void initiate_painting_warp_node(struct WarpNode *pWarpNode, u8 instant) { struct WarpNode warpNode = *pWarpNode; if (!(warpNode.destLevel & 0x80)) { @@ -718,6 +699,26 @@ void initiate_painting_warp_node(struct WarpNode *pWarpNode, u8 instant) { func_sh_8024C89C(1); } +/** + * Check is Mario has entered a painting, and if so, initiate a warp. + */ +void initiate_painting_warp(void) { + if (gCurrentArea->paintingWarpNodes != NULL && gMarioState->floor != NULL) { + struct WarpNode *pWarpNode = get_painting_warp_node(); + + if (pWarpNode != NULL) { + if (gMarioState->action & ACT_FLAG_INTANGIBLE) { + play_painting_eject_sound(); + } else if (pWarpNode->id != 0) { + initiate_painting_warp_node(pWarpNode, false); + set_mario_action(gMarioState, ACT_DISAPPEARED, 0); + gMarioState->marioObj->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE; + } + } + } +} + + /** * If there is not already a delayed warp, schedule one. The source node is * based on the warp operation and sometimes Mario's used object. @@ -727,7 +728,6 @@ s16 level_trigger_warp(struct MarioState *m, s32 warpOp) { // only allow for local player if (m != &gMarioStates[0]) { return 0; } - gControlPainting = TRUE; s32 val04 = TRUE; if (sDelayedWarpOp == WARP_OP_NONE) { @@ -996,6 +996,29 @@ void basic_update(UNUSED s16 *arg) { } } +static void check_for_received_warp(void) { + if (!gReceiveWarp) { return; } + gReceiveWarp = FALSE; + sWarpDest = gReceiveWarpDest; + + if (!gControlledWarp) { + // force well behaved state + extern s16 gMenuMode; + reset_dialog_render_state(); + level_set_transition(0, 0); + sTransitionUpdate = NULL; + gMenuMode = -1; + gPauseScreenMode = 1; + gSaveOptSelectIndex = 0; + gMarioStates[0].action = (gMarioStates[0].pos[1] <= gMarioStates[0].waterLevel) ? ACT_WATER_IDLE : ACT_IDLE; + gCameraMovementFlags &= ~CAM_MOVE_PAUSE_SCREEN; + } + + set_play_mode((sWarpDest.type == WARP_TYPE_CHANGE_LEVEL) + ? PLAY_MODE_CHANGE_LEVEL + : PLAY_MODE_CHANGE_AREA); +} + int gPressedStart = 0; s32 play_mode_normal(void) { @@ -1036,14 +1059,14 @@ s32 play_mode_normal(void) { set_play_mode(PLAY_MODE_CHANGE_LEVEL); } else { set_play_mode((gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL); - network_send_level_warp(); + network_send_level_warp(FALSE); } } else if (sTransitionTimer != 0) { if (sWarpDest.type == WARP_TYPE_NOT_WARPING || gCurrentArea->index == sWarpDest.areaIdx) { set_play_mode(PLAY_MODE_CHANGE_AREA); } else { set_play_mode((gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_AREA); - network_send_level_warp(); + network_send_level_warp(FALSE); } } else if (pressed_pause()) { lower_background_noise(1); @@ -1053,6 +1076,8 @@ s32 play_mode_normal(void) { } } + check_for_received_warp(); + return 0; } @@ -1073,7 +1098,7 @@ s32 play_mode_paused(void) { gSavedCourseNum = COURSE_NONE; } set_play_mode((gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL); - network_send_level_warp(); + network_send_level_warp(FALSE); } else if (gPauseScreenMode == 3) { // We should only be getting "int 3" to here initiate_warp(LEVEL_CASTLE, 1, 0x1F, 0); @@ -1087,6 +1112,7 @@ s32 play_mode_paused(void) { } s32 play_mode_sync_level(void) { + check_for_received_warp(); return 0; } @@ -1123,8 +1149,6 @@ void level_set_transition(s16 length, void (*updateFunction)(s16 *)) { * Play the transition and then return to normal play mode. */ s32 play_mode_change_area(void) { - network_on_init_level(); - //! This maybe was supposed to be sTransitionTimer == -1? sTransitionUpdate // is never set to -1. if (sTransitionUpdate == (void (*)(s16 *)) - 1) { @@ -1222,7 +1246,6 @@ s32 update_level(void) { s32 init_level(void) { reset_dialog_render_state(); - network_on_init_level(); s32 val4 = 0; diff --git a/src/game/level_update.h b/src/game/level_update.h index cef0a926d..a756ae429 100644 --- a/src/game/level_update.h +++ b/src/game/level_update.h @@ -69,10 +69,6 @@ extern struct CreditsEntry *gCurrCreditsEntry; extern struct MarioState gMarioStates[]; extern struct MarioState *gMarioState; -extern u8 gInsidePainting; -extern u8 gControlPainting; -extern u8 gWaitingForRemotePainting; -extern struct WarpNode gPaintingWarpNode; extern s16 sCurrPlayMode; extern u16 D_80339ECA; @@ -140,7 +136,6 @@ void basic_update(UNUSED s16 *arg); s32 init_level(void); -void initiate_painting_warp_node(struct WarpNode *pWarpNode, u8 instant); void star_select_finish_selection(void); #endif // LEVEL_UPDATE_H diff --git a/src/game/mario.c b/src/game/mario.c index 2f60b35bb..45583e84b 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -2083,8 +2083,6 @@ static void init_single_mario(struct MarioState* m) { } void init_mario(void) { - gInsidePainting = false; - for (int i = 0; i < MAX_PLAYERS; i++) { gMarioStates[i].playerIndex = i; init_single_mario(&gMarioStates[i]); diff --git a/src/menu/star_select.c b/src/menu/star_select.c index adebc6eeb..6aa8ae62c 100644 --- a/src/menu/star_select.c +++ b/src/menu/star_select.c @@ -33,7 +33,7 @@ static struct Object *sStarSelectorModels[8]; // The act the course is loaded as, affects whether some objects spawn. -static s8 sLoadedActNum; +s8 sLoadedActNum; // Number of obtained stars, excluding the coin star. static u8 sObtainedStars; @@ -54,6 +54,8 @@ s8 sSelectableStarIndex = 0; // Act Selector menu timer that keeps counting until you choose an act. static s32 sActSelectorMenuTimer = 0; +extern u8 gControlledWarp; + /** * Act Selector Star Type Loop Action * Defines a select type for a star in the act selector. @@ -156,7 +158,10 @@ void bhv_act_selector_init(void) { } render_100_coin_star(stars); - gInsidePainting = TRUE; + + if (gControlledWarp) { + network_send_inside_painting(TRUE, FALSE); + } } /** @@ -175,7 +180,11 @@ void bhv_act_selector_loop(void) { // Sometimes, stars are not selectable even if they appear on the screen. // This code filters selectable and non-selectable stars. sSelectedActIndex = 0; - if (gControlPainting) { handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sObtainedStars); } + if (gControlledWarp) { + s8 oldIndex = sSelectableStarIndex; + handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sObtainedStars); + if (oldIndex != sSelectableStarIndex) { network_send_inside_painting(FALSE, FALSE); } + } starIndexCounter = sSelectableStarIndex; for (i = 0; i < sVisibleStars; i++) { // Can the star be selected (is it either already completed or the first non-completed mission) @@ -189,7 +198,11 @@ void bhv_act_selector_loop(void) { } } else { // If all stars are collected then they are all selectable. - if (gControlPainting) { handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sVisibleStars - 1); } + if (gControlledWarp) { + s8 oldIndex = sSelectableStarIndex; + handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sVisibleStars - 1); + if (oldIndex != sSelectableStarIndex) { network_send_inside_painting(FALSE, FALSE); } + } sSelectedActIndex = sSelectableStarIndex; } @@ -289,7 +302,7 @@ void print_act_selector_strings(void) { create_dl_ortho_matrix(); // display disclaimer that the other player has to select - if (!gControlPainting || gWaitingForRemotePainting) { + if (!gControlledWarp) { gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); u8 a = ((gGlobalTimer % 24) >= 12) ? 160 : 130; gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, a); @@ -426,8 +439,7 @@ s32 lvl_init_act_selector_values_and_stars(UNUSED s32 arg, UNUSED s32 unused) { * Also updates objects and returns act number selected after is chosen. */ s32 lvl_update_obj_and_load_act_button_actions(UNUSED s32 arg, UNUSED s32 unused) { - u8 allowSelection = (gControlPainting && !gWaitingForRemotePainting); - if (sActSelectorMenuTimer >= 11 && allowSelection) { + if (gControlledWarp && sActSelectorMenuTimer >= 11) { // If any of these buttons are pressed, play sound and go to course act #ifndef VERSION_EU if ((gPlayer3Controller->buttonPressed & A_BUTTON) @@ -458,8 +470,5 @@ void star_select_finish_selection(void) { } gDialogCourseActNum = sSelectedActIndex + 1; - gInsidePainting = FALSE; - if (gControlPainting) { - network_send_inside_painting(TRUE); - } + if (gControlledWarp) { network_send_inside_painting(FALSE, TRUE); } } diff --git a/src/pc/debuglog.h b/src/pc/debuglog.h new file mode 100644 index 000000000..b42361816 --- /dev/null +++ b/src/pc/debuglog.h @@ -0,0 +1,34 @@ +#include +#include +#include "pc/network/network.h" + +#if defined(DEBUG) && !defined(DISABLE_MODULE_LOG) + static void debuglog_print_timestamp(void) { + time_t ltime = time(NULL); + char* str = asctime(localtime(<ime)); + printf("%.*s", (int)strlen(str) - 1, str); + } + + static void debuglog_print_network_type(void) { + printf(" [%s] ", NETWORKTYPESTR); + } + + static void debuglog_print_short_filename(char* filename) { + char* last = strrchr(filename, '/'); + if (last != NULL) { + printf("%s: ", last + 1); + } else { + printf("???: "); + } + } + + static void debuglog_print_log(char* filename) { + debuglog_print_timestamp(); + debuglog_print_network_type(); + debuglog_print_short_filename(filename); + } + + #define LOG_INFO(...) ( debuglog_print_log(__FILE__), printf(__VA_ARGS__), printf("\n") ) +#else + #define LOG_INFO(...) +#endif diff --git a/src/pc/network/network.c b/src/pc/network/network.c index b13e375cc..d702eafe2 100644 --- a/src/pc/network/network.c +++ b/src/pc/network/network.c @@ -6,7 +6,6 @@ #include "pc/configfile.h" // Mario 64 specific externs -extern u8 gInsidePainting; extern s16 sCurrPlayMode; enum NetworkType gNetworkType = NT_NONE; @@ -105,9 +104,7 @@ void network_update(void) { } // figure out which update loop to run - if (gInsidePainting && sCurrPlayMode == PLAY_MODE_CHANGE_LEVEL) { - network_update_inside_painting(); - } else if (sCurrPlayMode == PLAY_MODE_NORMAL || sCurrPlayMode == PLAY_MODE_PAUSED) { + if (sCurrPlayMode == PLAY_MODE_NORMAL || sCurrPlayMode == PLAY_MODE_PAUSED) { network_update_player(); network_update_objects(); } diff --git a/src/pc/network/network.h b/src/pc/network/network.h index 61f867927..dbf35d5f4 100644 --- a/src/pc/network/network.h +++ b/src/pc/network/network.h @@ -131,12 +131,11 @@ void network_send_spawn_star(struct Object* o, u8 starType, f32 x, f32 y, f32 z, void network_receive_spawn_star(struct Packet* p); // packet_level_warp.c -void network_send_level_warp(void); +void network_send_level_warp(u8 done); void network_receive_level_warp(struct Packet* p); // packet_inside_painting.c -void network_update_inside_painting(void); -void network_send_inside_painting(bool reliable); +void network_send_inside_painting(u8 startOfEvent, u8 endOfEvent); void network_receive_inside_painting(struct Packet* p); // packet_collect_star.c diff --git a/src/pc/network/packets/packet_inside_painting.c b/src/pc/network/packets/packet_inside_painting.c index 7affa111d..2862873f9 100644 --- a/src/pc/network/packets/packet_inside_painting.c +++ b/src/pc/network/packets/packet_inside_painting.c @@ -2,81 +2,83 @@ #include "../network.h" #include "src/game/level_update.h" #include "src/game/area.h" +#define DISABLE_MODULE_LOG +#include "pc/debuglog.h" + +extern u8 gControlledWarp; -extern struct WarpNode gPaintingWarpNode; extern u8 sSelectableStarIndex; extern u8 sSelectedActIndex; +extern s8 sLoadedActNum; -struct PacketDataInsidePainting { - u8 insidePainting; - u8 controlPainting; +#pragma pack(1) +struct PacketInsidePaintingData { + u8 seqId; + u8 eventId; u8 starIndex; u8 actIndex; + u8 loadedActNum; }; -static clock_t lastSentTime = 0; -static float minUpdateRate = 5.0f; -static struct PacketDataInsidePainting lastSentData = { 0 }; +static u8 eventId = 0; +static u8 remoteFinishedEventId = (u8)-1; -static void populate_packet_data(struct PacketDataInsidePainting* data) { - data->insidePainting = gInsidePainting; - data->controlPainting = gControlPainting; +static u8 seqId = 0; +static u8 remoteLastSeqId = (u8)-1; + +static void populate_packet_data(struct PacketInsidePaintingData* data) { + data->seqId = seqId; + data->eventId = eventId; data->starIndex = sSelectableStarIndex; data->actIndex = sSelectedActIndex; + data->loadedActNum = sLoadedActNum; } -void network_send_inside_painting(bool reliable) { - struct PacketDataInsidePainting data = { 0 }; +void network_send_inside_painting(u8 startOfEvent, u8 endOfEvent) { + if (startOfEvent) { eventId++; } + struct PacketInsidePaintingData data = { 0 }; populate_packet_data(&data); struct Packet p; - packet_init(&p, PACKET_INSIDE_PAINTING, reliable); - packet_write(&p, &data, sizeof(struct PacketDataInsidePainting)); + packet_init(&p, PACKET_INSIDE_PAINTING, true); + packet_write(&p, &data, sizeof(struct PacketInsidePaintingData)); network_send(&p); - - lastSentData = data; - lastSentTime = clock(); + seqId++; } void network_receive_inside_painting(struct Packet* p) { - struct PacketDataInsidePainting remote = { 0 }; - packet_read(p, &remote, sizeof(struct PacketDataInsidePainting)); + struct PacketInsidePaintingData local = { 0 }; + populate_packet_data(&local); - if (gNetworkType == NT_CLIENT && gControlPainting && remote.controlPainting) { - // we both think we should control the painting, host wins the tie - gControlPainting = false; + struct PacketInsidePaintingData remote = { 0 }; + packet_read(p, &remote, sizeof(struct PacketInsidePaintingData)); + + // de-dup + if (remote.seqId == remoteLastSeqId) { + LOG_INFO("we've seen this packet, escape!"); + return; + } + remoteLastSeqId = remote.seqId; + if (remote.eventId == remoteFinishedEventId || (remote.eventId == remoteFinishedEventId - 1)) { + LOG_INFO("we've finished this event, escape!"); + return; } - if (!gControlPainting && remote.controlPainting) { - // update star/act index to show the one in control's selection - sSelectableStarIndex = remote.starIndex; - sSelectedActIndex = remote.actIndex; + // two-player hack: gControlledWarp is a bool instead of an index + if (gControlledWarp) { + LOG_INFO("this should never happen, received inside_painting when gControlledWarp"); + return; } - if (gControlPainting && !remote.controlPainting) { - // remote is well behaved now, we can control the painting - gWaitingForRemotePainting = false; - } + LOG_INFO("received update"); + eventId = remote.eventId; + sSelectableStarIndex = remote.starIndex; + sSelectedActIndex = remote.actIndex; + sLoadedActNum = remote.loadedActNum; - if (gControlPainting && !remote.controlPainting && !gInsidePainting && remote.insidePainting) { - // we're in control and no longer in the painting, let remote know - network_send_inside_painting(false); - } - - if (!gControlPainting && remote.controlPainting && !remote.insidePainting) { - // remote is in control and in game, we should be too - star_select_finish_selection(); + if (sLoadedActNum != 0) { + LOG_INFO("finished with painting"); + remoteFinishedEventId = remote.eventId; } } -void network_update_inside_painting(void) { - struct PacketDataInsidePainting data = { 0 }; - populate_packet_data(&data); - int compareData = memcmp(&data, &lastSentData, sizeof(struct PacketDataInsidePainting)); - - float timeSinceSend = (clock() - lastSentTime) / CLOCKS_PER_SEC; - - if (compareData != 0 || timeSinceSend > minUpdateRate) { - network_send_inside_painting(timeSinceSend > 5); - } -} diff --git a/src/pc/network/packets/packet_level_warp.c b/src/pc/network/packets/packet_level_warp.c index 3cc07bae0..0560cabe2 100644 --- a/src/pc/network/packets/packet_level_warp.c +++ b/src/pc/network/packets/packet_level_warp.c @@ -1,73 +1,114 @@ #include -#include "../network.h" -#include "src/game/level_update.h" -#include "src/game/area.h" -#include "src/game/ingame_menu.h" #include "sm64.h" +#include "../network.h" +#include "game/level_update.h" +#include "game/area.h" +#include "game/ingame_menu.h" +#define DISABLE_MODULE_LOG +#include "pc/debuglog.h" -int matchCount = 0; +static u8 eventId = 0; +static u8 remoteFinishedEventId = (u8)-1; -extern s16 gMenuMode; +static u8 seqId = 0; +static u8 remoteLastSeqId = (u8)-1; -void network_send_level_warp(void) { - struct Packet p; - packet_init(&p, PACKET_LEVEL_WARP, true); - packet_write(&p, &sCurrPlayMode, sizeof(s16)); - packet_write(&p, &sWarpDest, sizeof(struct WarpDest)); +extern u8 gControlledWarp; // two-player hack +extern u8 gReceiveWarp; +extern struct WarpDest gReceiveWarpDest; - network_send(&p); +struct WarpDest savedWarpNode = { 0 }; + +#pragma pack(1) +struct PacketLevelWarpData { + u8 seqId; + u8 eventId; + u8 done; + u8 controlledWarp; + struct WarpDest warpDest; +}; + +static void populate_packet_data(struct PacketLevelWarpData* data, bool done) { + data->seqId = seqId; + data->eventId = eventId; + data->done = done; + data->controlledWarp = gControlledWarp; + data->warpDest = savedWarpNode; } -static void force_well_behaved_state(void) { - reset_dialog_render_state(); - level_set_transition(0, 0); - gMenuMode = -1; - gPauseScreenMode = 1; - gSaveOptSelectIndex = 0; - gMarioStates[0].action = (gMarioStates[0].pos[1] <= (gMarioStates[0].waterLevel - 100)) ? ACT_WATER_IDLE : ACT_IDLE; - gCameraMovementFlags &= ~CAM_MOVE_PAUSE_SCREEN; +void network_send_level_warp(u8 done) { + if (!done) { + savedWarpNode = sWarpDest; + gControlledWarp = true; + eventId++; + LOG_INFO("new event [%d]!", eventId); + } + + struct PacketLevelWarpData data = { 0 }; + populate_packet_data(&data, done); + + struct Packet p; + packet_init(&p, PACKET_LEVEL_WARP, true); + packet_write(&p, &data, sizeof(struct PacketLevelWarpData)); + network_send(&p); + + seqId++; +} + +static void do_warp(void) { + gReceiveWarpDest = savedWarpNode; + gReceiveWarp = TRUE; } void network_receive_level_warp(struct Packet* p) { - s16 remotePlayMode; - struct WarpDest remoteWarpDest; + struct PacketLevelWarpData remote = { 0 }; + packet_read(p, &remote, sizeof(struct PacketLevelWarpData)); - packet_read(p, &remotePlayMode, sizeof(s16)); - packet_read(p, &remoteWarpDest, sizeof(struct WarpDest)); - - bool matchingDest = memcmp(&remoteWarpDest, &sWarpDest, sizeof(struct WarpDest)) == 0; - - if (remotePlayMode == PLAY_MODE_SYNC_LEVEL && (sCurrPlayMode == PLAY_MODE_NORMAL || sCurrPlayMode == PLAY_MODE_PAUSED)) { - if (remoteWarpDest.type == WARP_TYPE_NOT_WARPING) { return; } - sCurrPlayMode = PLAY_MODE_SYNC_LEVEL; - sWarpDest = remoteWarpDest; - force_well_behaved_state(); - network_send_level_warp(); + // de-dup + if (remote.seqId == remoteLastSeqId) { + LOG_INFO("we've seen this packet, escape!"); + return; + } + remoteLastSeqId = remote.seqId; + LOG_INFO("rx event [%d] last [%d]!", remote.eventId, remoteFinishedEventId); + if (remote.eventId == remoteFinishedEventId || (remote.eventId == remoteFinishedEventId - 1)) { + LOG_INFO("we've finished this event, escape!"); return; } - if (remotePlayMode == PLAY_MODE_SYNC_LEVEL && sCurrPlayMode == PLAY_MODE_SYNC_LEVEL) { - if (matchingDest) { - switch (sWarpDest.type) { - case WARP_TYPE_CHANGE_AREA: sCurrPlayMode = PLAY_MODE_CHANGE_AREA; break; - case WARP_TYPE_CHANGE_LEVEL: sCurrPlayMode = PLAY_MODE_CHANGE_LEVEL; break; - } + if (gNetworkType == NT_SERVER) { + if (sCurrPlayMode != PLAY_MODE_SYNC_LEVEL) { + // client initiated warp + LOG_INFO("client initiated warp!"); + gControlledWarp = FALSE; + savedWarpNode = remote.warpDest; + eventId = remote.eventId; + remoteFinishedEventId = remote.eventId; + LOG_INFO("finished event [%d]!", remote.eventId); + do_warp(); + network_send_level_warp(TRUE); + return; + } else if (remote.done) { + // client done with warp + LOG_INFO("client is done with warp, lets-a-go!"); + remoteFinishedEventId = remote.eventId; + do_warp(); + return; } else { - if (gNetworkType == NT_CLIENT) { - if (remoteWarpDest.type == WARP_TYPE_NOT_WARPING) { return; } - // two-player hack: would need to use player index as priority - sWarpDest = remoteWarpDest; - } + LOG_INFO("client initiated warp, but server is already warping!"); + return; } - network_send_level_warp(); - return; } - if ((remotePlayMode == PLAY_MODE_CHANGE_LEVEL || remotePlayMode == PLAY_MODE_CHANGE_AREA) && sCurrPlayMode == PLAY_MODE_SYNC_LEVEL) { - if (remoteWarpDest.type == WARP_TYPE_NOT_WARPING) { return; } - switch (sWarpDest.type) { - case WARP_TYPE_CHANGE_AREA: sCurrPlayMode = PLAY_MODE_CHANGE_AREA; break; - case WARP_TYPE_CHANGE_LEVEL: sCurrPlayMode = PLAY_MODE_CHANGE_LEVEL; break; - } - } + assert(gNetworkType == NT_CLIENT); + + // server initiated warp + LOG_INFO("server initiated warp!"); + gControlledWarp = !remote.controlledWarp; // two-player hack + savedWarpNode = remote.warpDest; + eventId = remote.eventId; + remoteFinishedEventId = remote.eventId; + LOG_INFO("finished event [%d]!", remote.eventId); + do_warp(); + network_send_level_warp(TRUE); } diff --git a/src/pc/network/packets/packet_object.c b/src/pc/network/packets/packet_object.c index e29ecc36d..c3fd7957f 100644 --- a/src/pc/network/packets/packet_object.c +++ b/src/pc/network/packets/packet_object.c @@ -83,6 +83,7 @@ bool network_sync_object_initialized(struct Object* o) { } void network_clear_sync_objects(void) { + network_on_init_level(); for (u16 i = 0; i < MAX_SYNC_OBJECTS; i++) { network_forget_sync_object(&gSyncObjects[i]); }