diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj index 8a3634df1..36b382676 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj +++ b/build-windows-visual-studio/sm64ex.vcxproj @@ -3944,6 +3944,7 @@ + diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index 5cb2b734f..832884b23 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -2761,13 +2761,12 @@ s16 render_sync_level_screen(void) { gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); - // text + // synchronizing text u8 colorFade = sins(gDialogColorFadeTimer) * 50.0f + 200.0f; gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); gDPSetEnvColor(gDisplayListHead++, colorFade, colorFade, colorFade, 255); - u8 synchronizing[] = { 0x1C,0x22,0x17,0x0C,0x11,0x1B,0x18,0x17,0x12,0x02,0x12,0x17,0x10,0xFF }; - // s y n c h r o n i z i n g \0 - print_hud_lut_string(HUD_LUT_GLOBAL, 70, 200, synchronizing); + u8 synchronizing[] = { TEXT_SYNCHRONIZING }; + print_hud_lut_string(HUD_LUT_GLOBAL, 80, 200, synchronizing); gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); return 0; diff --git a/src/game/level_update.c b/src/game/level_update.c index 392bac20b..aa4c99208 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -174,6 +174,11 @@ 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: @@ -657,38 +662,48 @@ struct WarpNode *get_painting_warp_node(void) { */ void initiate_painting_warp(void) { if (gCurrentArea->paintingWarpNodes != NULL && gMarioState->floor != NULL) { - struct WarpNode warpNode; 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) { - warpNode = *pWarpNode; - - if (!(warpNode.destLevel & 0x80)) { - D_8032C9E0 = check_warp_checkpoint(&warpNode); - } - - initiate_warp(warpNode.destLevel & 0x7F, warpNode.destArea, warpNode.destNode, 0); - check_if_should_set_warp_checkpoint(&warpNode); - - play_transition_after_delay(WARP_TRANSITION_FADE_INTO_COLOR, 30, 255, 255, 255, 45); - level_set_transition(74, basic_update); - + initiate_painting_warp_node(pWarpNode, false); + gControlPainting = true; + gWaitingForRemotePainting = true; set_mario_action(gMarioState, ACT_DISAPPEARED, 0); - gMarioState->marioObj->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE; - - play_sound(SOUND_MENU_STAR_SOUND, gDefaultSoundArgs); - fadeout_music(398); - queue_rumble_data(80, 70); - func_sh_8024C89C(1); } } } } +void initiate_painting_warp_node(struct WarpNode *pWarpNode, u8 instant) { + if (pWarpNode->id == 0) { return; } + + gControlPainting = false; + gWaitingForRemotePainting = false; + gInsidePainting = true; + + gPaintingWarpNode = *pWarpNode; + struct WarpNode warpNode = *pWarpNode; + + if (!(warpNode.destLevel & 0x80)) { + D_8032C9E0 = check_warp_checkpoint(&warpNode); + } + + initiate_warp(warpNode.destLevel & 0x7F, warpNode.destArea, warpNode.destNode, 0); + check_if_should_set_warp_checkpoint(&warpNode); + + play_transition_after_delay(WARP_TRANSITION_FADE_INTO_COLOR, 30, 255, 255, 255, instant ? 1 : 45); + level_set_transition(instant ? 1 : 74, basic_update); + + play_sound(SOUND_MENU_STAR_SOUND, gDefaultSoundArgs); + fadeout_music(398); + queue_rumble_data(80, 70); + func_sh_8024C89C(1); +} + /** * 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. diff --git a/src/game/level_update.h b/src/game/level_update.h index e01481314..45764822c 100644 --- a/src/game/level_update.h +++ b/src/game/level_update.h @@ -65,6 +65,10 @@ 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; @@ -132,4 +136,7 @@ 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 16c5b7d3f..30d0673d4 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -190,6 +190,7 @@ s16 find_mario_anim_flags_and_translation(struct Object *obj, s32 yaw, Vec3s tra f32 dz; struct Animation *curAnim = (void *) obj->header.gfx.unk38.curAnim; + if (curAnim == NULL) { return 0; } s16 animFrame = geo_update_animation_frame(&obj->header.gfx.unk38, NULL); u16 *animIndex = segmented_to_virtual((void *) curAnim->index); s16 *animValues = segmented_to_virtual((void *) curAnim->values); @@ -1846,6 +1847,8 @@ s32 execute_mario_action(UNUSED struct Object *o) { **************************************************/ void init_mario(void) { + gInsidePainting = false; + bool isMario = (gMarioState == &gMarioStates[0]); if (isMario && gMarioObject == NULL) { goto skippy; } if (!isMario && gLuigiObject == NULL) { goto skippy; } diff --git a/src/menu/star_select.c b/src/menu/star_select.c index 08b988e65..03856a26b 100644 --- a/src/menu/star_select.c +++ b/src/menu/star_select.c @@ -43,11 +43,11 @@ static s8 sVisibleStars; static u8 sInitSelectedActNum; // Index value of the act selected in the act menu. -static s8 sSelectedActIndex = 0; +s8 sSelectedActIndex = 0; // Index value of the star that is selectable in the act menu. // Excluding the next star, it doesn't count other transparent stars. -static s8 sSelectableStarIndex = 0; +s8 sSelectableStarIndex = 0; // Act Selector menu timer that keeps counting until you choose an act. static s32 sActSelectorMenuTimer = 0; @@ -172,7 +172,7 @@ 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; - handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sObtainedStars); + if (gControlPainting) { handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sObtainedStars); } starIndexCounter = sSelectableStarIndex; for (i = 0; i < sVisibleStars; i++) { // Can the star be selected (is it either already completed or the first non-completed mission) @@ -186,7 +186,7 @@ void bhv_act_selector_loop(void) { } } else { // If all stars are collected then they are all selectable. - handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sVisibleStars - 1); + if (gControlPainting) { handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sVisibleStars - 1); } sSelectedActIndex = sSelectableStarIndex; } @@ -257,6 +257,17 @@ void print_course_number(void) { * Print act selector strings, some with special checks. */ void print_act_selector_strings(void) { + // synchronizing text + if (!gControlPainting || gWaitingForRemotePainting) { + static int fadeTimer = 0; + u8 colorFade = sin(fadeTimer++ * 0.2f) * 50.0f + 200.0f; + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); + gDPSetEnvColor(gDisplayListHead++, colorFade, colorFade, colorFade, 255); + u8 synchronizing[] = { TEXT_SYNCHRONIZING }; + print_hud_lut_string(HUD_LUT_GLOBAL, 80, 8, synchronizing); + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); + } + #ifdef VERSION_EU unsigned char myScore[][10] = { {TEXT_MYSCORE}, {TEXT_MY_SCORE_FR}, {TEXT_MY_SCORE_DE} }; #else @@ -414,7 +425,8 @@ 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) { - if (sActSelectorMenuTimer >= 11) { + u8 allowSelection = (gControlPainting && !gWaitingForRemotePainting); + if (sActSelectorMenuTimer >= 11 && allowSelection) { // If any of these buttons are pressed, play sound and go to course act #ifndef VERSION_EU if ((gPlayer3Controller->buttonPressed & A_BUTTON) @@ -423,17 +435,7 @@ s32 lvl_update_obj_and_load_act_button_actions(UNUSED s32 arg, UNUSED s32 unused #else if ((gPlayer3Controller->buttonPressed & (A_BUTTON | START_BUTTON | B_BUTTON | Z_TRIG))) { #endif -#if defined(VERSION_JP) || defined(VERSION_SH) - play_sound(SOUND_MENU_STAR_SOUND, gDefaultSoundArgs); -#else - play_sound(SOUND_MENU_STAR_SOUND_LETS_A_GO, gDefaultSoundArgs); -#endif - if (sInitSelectedActNum >= sSelectedActIndex + 1) { - sLoadedActNum = sSelectedActIndex + 1; - } else { - sLoadedActNum = sInitSelectedActNum; - } - gDialogCourseActNum = sSelectedActIndex + 1; + star_select_finish_selection(); } } @@ -441,3 +443,17 @@ s32 lvl_update_obj_and_load_act_button_actions(UNUSED s32 arg, UNUSED s32 unused sActSelectorMenuTimer++; return sLoadedActNum; } + +void star_select_finish_selection(void) { +#if defined(VERSION_JP) || defined(VERSION_SH) + play_sound(SOUND_MENU_STAR_SOUND, gDefaultSoundArgs); +#else + play_sound(SOUND_MENU_STAR_SOUND_LETS_A_GO, gDefaultSoundArgs); +#endif + if (sInitSelectedActNum >= sSelectedActIndex + 1) { + sLoadedActNum = sSelectedActIndex + 1; + } else { + sLoadedActNum = sInitSelectedActNum; + } + gDialogCourseActNum = sSelectedActIndex + 1; +} \ No newline at end of file diff --git a/src/pc/network/network.c b/src/pc/network/network.c index c6ee49f4d..051f3a014 100644 --- a/src/pc/network/network.c +++ b/src/pc/network/network.c @@ -76,7 +76,10 @@ void network_send(struct Packet* p) { void network_update(void) { if (networkType == NT_NONE) { return; } - if (sCurrPlayMode == PLAY_MODE_SYNC_LEVEL) { + // TODO: refactor the way we do these update functions, it will get messy quick + if (gInsidePainting && sCurrPlayMode == PLAY_MODE_CHANGE_LEVEL) { + network_update_inside_painting(); + } else if (sCurrPlayMode == PLAY_MODE_SYNC_LEVEL) { network_update_level_warp(); } else { network_update_player(); @@ -101,6 +104,7 @@ void network_update(void) { case PACKET_PLAYER: network_receive_player(&p); break; case PACKET_OBJECT: network_receive_object(&p); break; case PACKET_LEVEL_WARP: network_receive_level_warp(&p); break; + case PACKET_INSIDE_PAINTING: network_receive_inside_painting(&p); break; default: printf("%s received unknown packet: %d\n", NETWORKTYPESTR, p.buffer[0]); } diff --git a/src/pc/network/network.h b/src/pc/network/network.h index 3aed9c007..a9b1e8973 100644 --- a/src/pc/network/network.h +++ b/src/pc/network/network.h @@ -1,6 +1,7 @@ #ifndef NETWORK_H #define NETWORK_H +#include #include #include #include "../cliopts.h" @@ -14,6 +15,7 @@ enum PacketType { PACKET_PLAYER, PACKET_OBJECT, PACKET_LEVEL_WARP, + PACKET_INSIDE_PAINTING, }; struct Packet { @@ -32,6 +34,7 @@ struct SyncObject { }; extern struct MarioState gMarioStates[]; +extern u8 gInsidePainting; extern s16 sCurrPlayMode; extern enum NetworkType networkType; extern struct SyncObject syncObjects[]; @@ -57,4 +60,7 @@ void network_receive_object(struct Packet* p); void network_update_level_warp(void); void network_receive_level_warp(struct Packet* p); +void network_update_inside_painting(void); +void network_receive_inside_painting(struct Packet* p); + #endif diff --git a/src/pc/network/packets/packet_inside_painting.c b/src/pc/network/packets/packet_inside_painting.c new file mode 100644 index 000000000..f8a00ae75 --- /dev/null +++ b/src/pc/network/packets/packet_inside_painting.c @@ -0,0 +1,97 @@ +#include +#include "../network.h" +#include "src/game/level_update.h" +#include "src/game/area.h" + +extern struct WarpNode gPaintingWarpNode; +extern u8 sSelectableStarIndex; +extern u8 sSelectedActIndex; + +struct PacketDataInsidePainting { + u8 insidePainting; + u8 controlPainting; + u8 starIndex; + u8 actIndex; + struct WarpNode warpNode; +}; + +static clock_t lastSentTime = 0; +static float minUpdateRate = 0.5f; +static struct PacketDataInsidePainting lastSentData = { 0 }; + +static void populate_packet_data(struct PacketDataInsidePainting* data) { + data->insidePainting = gInsidePainting; + data->controlPainting = gControlPainting; + data->starIndex = sSelectableStarIndex; + data->actIndex = sSelectedActIndex; + data->warpNode = gPaintingWarpNode; +} + +void network_send_inside_painting(void) { + struct PacketDataInsidePainting data = { 0 }; + populate_packet_data(&data); + + struct Packet p; + packet_init(&p, PACKET_INSIDE_PAINTING); + packet_write(&p, &data, sizeof(struct PacketDataInsidePainting)); + network_send(&p); + + lastSentData = data; + lastSentTime = clock(); +} + +void network_receive_inside_painting(struct Packet* p) { + struct PacketDataInsidePainting remote = { 0 }; + packet_read(p, &remote, sizeof(struct PacketDataInsidePainting)); + + if (networkType == NT_CLIENT && gControlPainting && remote.controlPainting) { + // we both think we should control the painting, host wins the tie + gControlPainting = false; + } + + if (!gControlPainting && remote.controlPainting) { + // update star/act index to show the one in control's selection + sSelectableStarIndex = remote.starIndex; + sSelectedActIndex = remote.actIndex; + } + + // see if the warp nodes are the same + int compareNodes = memcmp(&gPaintingWarpNode, &remote.warpNode, sizeof(struct WarpNode)); + + if (gControlPainting && !remote.controlPainting && (compareNodes == 0)) { + // remote is well behaved now, we can control the painting + gWaitingForRemotePainting = false; + } + + bool shouldJumpInside = !gControlPainting && (!gInsidePainting && remote.insidePainting); + + // ERROR: THE DESTINATION MISMATCH DOESN'T MOVE THE CLIENT TO THE CORRECT SCREEN! + bool destinationMismatch = !gControlPainting && (compareNodes != 0); + + if (shouldJumpInside || destinationMismatch) { + initiate_painting_warp_node(&remote.warpNode, true); + set_play_mode(PLAY_MODE_CHANGE_LEVEL); + } + + if (gControlPainting && !remote.controlPainting && !gInsidePainting && remote.insidePainting) { + // we're in control and no longer in the painting, let remote know + network_send_inside_painting(); + } + + if (!gControlPainting && remote.controlPainting && !remote.insidePainting) { + // remote is in control and in game, we should be too + star_select_finish_selection(); + } +} + +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(); + } +} \ No newline at end of file diff --git a/src/pc/network/packets/packet_level_warp.c b/src/pc/network/packets/packet_level_warp.c index 4f2e7f023..ee84023af 100644 --- a/src/pc/network/packets/packet_level_warp.c +++ b/src/pc/network/packets/packet_level_warp.c @@ -12,7 +12,7 @@ void network_send_level_warp(void) { packet_write(&p, &gCurrLevelNum, 2); packet_write(&p, &sDelayedWarpArg, 4); packet_write(&p, &sSourceWarpNodeId, 2); - + network_send(&p); } @@ -22,7 +22,6 @@ void network_receive_level_warp(struct Packet* p) { s16 remoteLevelNum; s32 remoteWarpArg; s16 remoteWarpNodeId; - struct WarpDest remoteWarpDest; packet_read(p, &remotePlayMode, 2); packet_read(p, &remoteLevelNum, 2);