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.
This commit is contained in:
MysterD 2020-09-10 00:17:30 -07:00
parent e48a9c25ab
commit 1e6c734ced
15 changed files with 274 additions and 174 deletions

View file

@ -4305,6 +4305,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\behavior_table.h" />
<ClInclude Include="..\src\pc\debuglog.h" />
<ClInclude Include="..\src\pc\network\network.h" />
<ClInclude Include="..\src\pc\network\socket\socket.h" />
<ClInclude Include="..\src\pc\network\socket\socket_linux.h" />

View file

@ -15928,5 +15928,8 @@
<ClInclude Include="..\include\behavior_table.h">
<Filter>Header Files\include</Filter>
</ClInclude>
<ClInclude Include="..\src\pc\debuglog.h">
<Filter>Source Files\src\pc</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -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

View file

@ -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);

View file

@ -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();

View file

@ -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 <stdio.h>
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;

View file

@ -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

View file

@ -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]);

View file

@ -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); }
}

34
src/pc/debuglog.h Normal file
View file

@ -0,0 +1,34 @@
#include <stdio.h>
#include <time.h>
#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(&ltime));
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

View file

@ -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();
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -1,73 +1,114 @@
#include <stdio.h>
#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);
}

View file

@ -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]);
}