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