diff --git a/README.md b/README.md
index 6d584a155..7db0f1115 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
Online cooperative multiplayer mod for SM64, aiming to synchronize all entities and every level for two players.
Fork of [sm64pc/sm64ex](https://github.com/sm64pc/sm64ex).
+Build instructions are available on the [sm64ex wiki](https://github.com/sm64pc/sm64ex/wiki).
+
Feel free to report bugs and contribute, but remember, there must be **no upload of any copyrighted asset**.
Run `./extract_assets.py --clean && make clean` or `make distclean` to remove ROM-originated content.
diff --git a/actors/luigi/geo.inc.c b/actors/luigi/geo.inc.c
index aa714c0ba..e11583e9e 100644
--- a/actors/luigi/geo.inc.c
+++ b/actors/luigi/geo.inc.c
@@ -788,6 +788,7 @@ const GeoLayout luigi_geo[] = {
GEO_OPEN_NODE(),
GEO_SCALE(0, 17202),
GEO_OPEN_NODE(),
+ GEO_ASM(0, geo_mario_set_player_colors),
GEO_ASM(0, geo_mirror_mario_backface_culling),
GEO_ASM(0, geo_mirror_mario_set_alpha),
GEO_SWITCH_CASE(0, geo_switch_mario_stand_run),
diff --git a/actors/luigi/model.inc.c b/actors/luigi/model.inc.c
index dac16ab4c..e3a2f823c 100644
--- a/actors/luigi/model.inc.c
+++ b/actors/luigi/model.inc.c
@@ -3225,7 +3225,8 @@ Gfx mat_luigi_body[] = {
gsDPPipeSync(),
gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_16b, 8, 0, 0, 0, G_TX_WRAP | G_TX_NOMIRROR, 5, 0, G_TX_WRAP | G_TX_NOMIRROR, 5, 0),
gsDPSetTileSize(0, 0, 0, 124, 124),
- gsSPSetLights1(luigi_body_lights),
+ gsSPCopyLightEXT(1, 3), // gsSPSetLights1(luigi_body_lights)
+ gsSPCopyLightEXT(2, 4), //
gsSPEndDisplayList(),
};
@@ -3265,7 +3266,8 @@ Gfx mat_luigi_cap[] = {
gsDPPipeSync(),
gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_16b, 8, 0, 0, 0, G_TX_CLAMP | G_TX_NOMIRROR, 5, 0, G_TX_CLAMP | G_TX_NOMIRROR, 5, 0),
gsDPSetTileSize(0, 0, 0, 124, 124),
- gsSPSetLights1(luigi_cap_lights),
+ gsSPCopyLightEXT(1, 5), // gsSPSetLights1(luigi_cap_lights)
+ gsSPCopyLightEXT(2, 6), //
gsSPEndDisplayList(),
};
diff --git a/actors/mario/geo.inc.c b/actors/mario/geo.inc.c
index 736d31f04..fce1ac75a 100644
--- a/actors/mario/geo.inc.c
+++ b/actors/mario/geo.inc.c
@@ -1811,6 +1811,7 @@ const GeoLayout mario_geo[] = {
GEO_OPEN_NODE(),
GEO_SCALE(0x00, 16384),
GEO_OPEN_NODE(),
+ GEO_ASM(0, geo_mario_set_player_colors),
GEO_ASM(0, geo_mirror_mario_backface_culling),
GEO_ASM(0, geo_mirror_mario_set_alpha),
GEO_SWITCH_CASE(0, geo_switch_mario_stand_run),
diff --git a/actors/mario/model.inc.c b/actors/mario/model.inc.c
index 9d39ba244..1d9c8df74 100644
--- a/actors/mario/model.inc.c
+++ b/actors/mario/model.inc.c
@@ -381,8 +381,8 @@ const Gfx mario_butt_dl[] = {
const Gfx mario_butt[] = {
gsDPPipeSync(),
gsDPSetCombineMode(G_CC_SHADEFADEA, G_CC_SHADEFADEA),
- gsSPLight(&mario_blue_lights_group.l, 1),
- gsSPLight(&mario_blue_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 3), // gsSPLight(&mario_blue_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 4), // gsSPLight(&mario_blue_lights_group.a, 2),
gsSPDisplayList(mario_butt_dl),
gsSPEndDisplayList(),
};
@@ -499,8 +499,8 @@ const Gfx mario_left_arm_shared_dl[] = {
// 0x0400D1D8 - 0x0400D1F8
const Gfx mario_left_arm[] = {
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_left_arm_shared_dl),
gsSPEndDisplayList(),
};
@@ -760,8 +760,8 @@ const Gfx mario_right_arm_shared_dl[] = {
// 0x0400DDE8 - 0x0400DE08
const Gfx mario_right_arm[] = {
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_right_arm_shared_dl),
gsSPEndDisplayList(),
};
@@ -979,8 +979,8 @@ const Gfx mario_left_thigh_dl[] = {
const Gfx mario_left_thigh[] = {
gsDPPipeSync(),
gsDPSetCombineMode(G_CC_SHADEFADEA, G_CC_SHADEFADEA),
- gsSPLight(&mario_blue_lights_group.l, 1),
- gsSPLight(&mario_blue_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 3), // gsSPLight(&mario_blue_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 4), // gsSPLight(&mario_blue_lights_group.a, 2),
gsSPDisplayList(mario_left_thigh_dl),
gsSPEndDisplayList(),
};
@@ -1160,8 +1160,8 @@ const Gfx mario_right_thigh_shared_dl[] = {
// 0x0400EFB8 - 0x0400EFD8
const Gfx mario_right_thigh[] = {
- gsSPLight(&mario_blue_lights_group.l, 1),
- gsSPLight(&mario_blue_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 3), // gsSPLight(&mario_blue_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 4), // gsSPLight(&mario_blue_lights_group.a, 2),
gsSPDisplayList(mario_right_thigh_shared_dl),
gsSPEndDisplayList(),
};
@@ -1589,8 +1589,8 @@ const Gfx mario_tshirt_shared_dl[] = {
// 0x04010348 - 0x04010370
const Gfx mario_torso_dl[] = {
gsSPDisplayList(mario_pants_overalls_shared_dl),
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_tshirt_shared_dl),
gsSPEndDisplayList(),
};
@@ -2080,8 +2080,8 @@ const Gfx mario_face_back_hair_cap_on_dl[] = {
// 0x04011960 - 0x040119A0
const Gfx mario_face_cap_on_dl[] = {
gsSPDisplayList(mario_face_part_cap_on_dl),
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_face_cap_dl),
gsSPLight(&mario_brown2_lights_group.l, 1),
gsSPLight(&mario_brown2_lights_group.a, 2),
@@ -3270,8 +3270,8 @@ const Gfx mario_medium_poly_butt_dl[] = {
const Gfx mario_medium_poly_butt[] = {
gsDPPipeSync(),
gsDPSetCombineMode(G_CC_SHADEFADEA, G_CC_SHADEFADEA),
- gsSPLight(&mario_blue_lights_group.l, 1),
- gsSPLight(&mario_blue_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 3), // gsSPLight(&mario_blue_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 4), // gsSPLight(&mario_blue_lights_group.a, 2),
gsSPDisplayList(mario_medium_poly_butt_dl),
gsSPEndDisplayList(),
};
@@ -3322,8 +3322,8 @@ const Gfx mario_medium_poly_left_arm_shared_dl[] = {
// 0x04014840 - 0x04014860
const Gfx mario_medium_poly_left_arm[] = {
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_medium_poly_left_arm_shared_dl),
gsSPEndDisplayList(),
};
@@ -3483,8 +3483,8 @@ const Gfx mario_medium_poly_right_arm_shared_dl[] = {
// 0x04014F40 - 0x04014F60
const Gfx mario_medium_poly_right_arm[] = {
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_medium_poly_right_arm_shared_dl),
gsSPEndDisplayList(),
};
@@ -3659,8 +3659,8 @@ const Gfx mario_medium_poly_left_thigh_dl[] = {
const Gfx mario_medium_poly_left_thigh[] = {
gsDPPipeSync(),
gsDPSetCombineMode(G_CC_SHADEFADEA, G_CC_SHADEFADEA),
- gsSPLight(&mario_blue_lights_group.l, 1),
- gsSPLight(&mario_blue_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 3), // gsSPLight(&mario_blue_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 4), // gsSPLight(&mario_blue_lights_group.a, 2),
gsSPDisplayList(mario_medium_poly_left_thigh_dl),
gsSPEndDisplayList(),
};
@@ -3808,8 +3808,8 @@ const Gfx mario_medium_poly_right_thigh_shared_dl[] = {
// 0x04015D00 - 0x04015D20
const Gfx mario_medium_poly_right_thigh[] = {
- gsSPLight(&mario_blue_lights_group.l, 1),
- gsSPLight(&mario_blue_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 3), // gsSPLight(&mario_blue_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 4), // gsSPLight(&mario_blue_lights_group.a, 2),
gsSPDisplayList(mario_medium_poly_right_thigh_shared_dl),
gsSPEndDisplayList(),
};
@@ -4081,8 +4081,8 @@ const Gfx mario_medium_poly_tshirt_shared_dl[] = {
// 0x040168A0 - 0x040168C8
const Gfx mario_medium_poly_torso_dl[] = {
gsSPDisplayList(mario_medium_poly_pants_overalls_shared_dl),
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_medium_poly_tshirt_shared_dl),
gsSPEndDisplayList(),
};
@@ -4149,8 +4149,8 @@ const Gfx mario_low_poly_butt_dl[] = {
const Gfx mario_low_poly_butt[] = {
gsDPPipeSync(),
gsDPSetCombineMode(G_CC_SHADEFADEA, G_CC_SHADEFADEA),
- gsSPLight(&mario_blue_lights_group.l, 1),
- gsSPLight(&mario_blue_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 3), // gsSPLight(&mario_blue_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 4), // gsSPLight(&mario_blue_lights_group.a, 2),
gsSPDisplayList(mario_low_poly_butt_dl),
gsSPEndDisplayList(),
};
@@ -4196,8 +4196,8 @@ const Gfx mario_low_poly_left_arm_shared_dl[] = {
// 0x04016C70 - 0x04016C90
const Gfx mario_low_poly_left_arm[] = {
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_low_poly_left_arm_shared_dl),
gsSPEndDisplayList(),
};
@@ -4287,8 +4287,8 @@ const Gfx mario_low_poly_right_arm_shared_dl[] = {
// 0x04016FB0 - 0x04016FD0
const Gfx mario_low_poly_right_arm[] = {
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_low_poly_right_arm_shared_dl),
gsSPEndDisplayList(),
};
@@ -4394,8 +4394,8 @@ const Gfx mario_low_poly_left_thigh_dl[] = {
const Gfx mario_low_poly_left_thigh[] = {
gsDPPipeSync(),
gsDPSetCombineMode(G_CC_SHADEFADEA, G_CC_SHADEFADEA),
- gsSPLight(&mario_blue_lights_group.l, 1),
- gsSPLight(&mario_blue_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 3), // gsSPLight(&mario_blue_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 4), // gsSPLight(&mario_blue_lights_group.a, 2),
gsSPDisplayList(mario_low_poly_left_thigh_dl),
gsSPEndDisplayList(),
};
@@ -4513,8 +4513,8 @@ const Gfx mario_low_poly_right_thigh_shared_dl[] = {
// 0x04017818 - 0x04017838
const Gfx mario_low_poly_right_thigh[] = {
- gsSPLight(&mario_blue_lights_group.l, 1),
- gsSPLight(&mario_blue_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 3), // gsSPLight(&mario_blue_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 4), // gsSPLight(&mario_blue_lights_group.a, 2),
gsSPDisplayList(mario_low_poly_right_thigh_shared_dl),
gsSPEndDisplayList(),
};
@@ -4684,8 +4684,8 @@ const Gfx mario_low_poly_tshirt_shared_dl[] = {
// 0x04017E78 - 0x04017EA0
const Gfx mario_low_poly_torso_dl[] = {
gsSPDisplayList(mario_low_poly_pants_overalls_shared_dl),
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_low_poly_tshirt_shared_dl),
gsSPEndDisplayList(),
};
@@ -4854,8 +4854,8 @@ const Gfx mario_low_poly_face_back_hair_cap_on_dl[] = {
// 0x04018420 - 0x04018460
const Gfx mario_low_poly_face_cap_on_dl[] = {
gsSPDisplayList(mario_low_poly_face_part_cap_on_dl),
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_low_poly_face_cap_dl),
gsSPLight(&mario_brown2_lights_group.l, 1),
gsSPLight(&mario_brown2_lights_group.a, 2),
@@ -6625,8 +6625,8 @@ const Gfx mario_cap_unused_dl[] = {
gsDPSetTextureImage(G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, mario_texture_m_logo),
gsDPLoadSync(),
gsDPLoadBlock(G_TX_LOADTILE, 0, 0, 32 * 32 - 1, CALC_DXT(32, G_IM_SIZ_16b_BYTES)),
- gsSPLight(&mario_red_lights_group.l, 1),
- gsSPLight(&mario_red_lights_group.a, 2),
+ gsSPCopyLightEXT(1, 5), // gsSPLight(&mario_red_lights_group.l, 1),
+ gsSPCopyLightEXT(2, 6), // gsSPLight(&mario_red_lights_group.a, 2),
gsSPDisplayList(mario_cap_unused_m_logo_dl),
gsSPTexture(0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF),
gsDPPipeSync(),
diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj
index dc68a31d2..d536ee689 100644
--- a/build-windows-visual-studio/sm64ex.vcxproj
+++ b/build-windows-visual-studio/sm64ex.vcxproj
@@ -3974,6 +3974,7 @@
+
diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters
index 49a460886..a41039554 100644
--- a/build-windows-visual-studio/sm64ex.vcxproj.filters
+++ b/build-windows-visual-studio/sm64ex.vcxproj.filters
@@ -15087,6 +15087,9 @@
Source Files\src\game
+
+ Source Files\src\pc\network\packets
+
diff --git a/developer/network.sh b/developer/network.sh
index 795ded532..8d0b2e08f 100644
--- a/developer/network.sh
+++ b/developer/network.sh
@@ -18,9 +18,9 @@ fi
#exit
# no debug, direct
-#$FILE --server 27015 --configfile sm64config_server.txt &
-#$FILE --client 127.0.0.1 27015 --configfile sm64config_client.txt &
-#exit
+$FILE --server 27015 --configfile sm64config_server.txt &
+$FILE --client 127.0.0.1 27015 --configfile sm64config_client.txt &
+exit
# debug on server
#$FILE --client 127.0.0.1 27015 --configfile sm64config_client.txt &
diff --git a/developer/proto-4.sh b/developer/proto-4.sh
index 623295c62..128afe9c8 100644
--- a/developer/proto-4.sh
+++ b/developer/proto-4.sh
@@ -12,26 +12,6 @@ if [ ! -f "$FILE" ]; then
FILE=./build/us_pc/sm64.us.f3dex2e
fi
-# no debug, discord
-#$FILE --discord 2 --configfile sm64config_server.txt &
-#$FILE --discord 1 --configfile sm64config_client.txt &
-#exit
-
-# no debug, direct
-#$FILE --server 27015 --configfile sm64config_server.txt &
-#$FILE --client 127.0.0.1 27015 --configfile sm64config_client.txt &
-#exit
-
-# debug on server
-#$FILE --client 127.0.0.1 27015 --configfile sm64config_client.txt &
-#winpty cgdb $FILE -ex 'break debug_breakpoint_here' -ex 'run --server 27015 --configfile sm64config_server.txt' -ex 'quit'
-#exit
-
-###################
-# debug on client #
-###################
-
-#winpty cgdb $FILE -ex 'break debug_breakpoint_here' -ex 'run --server 27015 --configfile sm64config_p1.txt' -ex 'quit'
$FILE --server 27015 --configfile sm64config_p1.txt &
sleep 2
$FILE --client 127.0.0.1 27015 --configfile sm64config_p2.txt &
@@ -39,4 +19,7 @@ sleep 2
$FILE --client 127.0.0.1 27015 --configfile sm64config_p3.txt &
sleep 2
$FILE --client 127.0.0.1 27015 --configfile sm64config_p4.txt &
+
+#sleep 2
+#winpty cgdb $FILE -ex 'break debug_breakpoint_here' -ex 'run --server 27015 --configfile sm64config_p1.txt' -ex 'quit'
#winpty cgdb $FILE -ex 'break debug_breakpoint_here' -ex 'run --client 127.0.0.1 27015 --configfile sm64config_p4.txt' -ex 'quit'
diff --git a/include/PR/gbi.h b/include/PR/gbi.h
index bd981be84..dd1bb4400 100644
--- a/include/PR/gbi.h
+++ b/include/PR/gbi.h
@@ -118,6 +118,11 @@
#define G_SPECIAL_2 0xd4
#define G_SPECIAL_3 0xd3
+#ifdef F3DEX_GBI_2E
+/* extended commands */
+#define G_COPYMEM 0xd2
+#endif
+
#define G_VTX 0x01
#define G_MODIFYVTX 0x02
#define G_CULLDL 0x03
@@ -1790,6 +1795,22 @@ typedef union {
(uintptr_t)(adrs) \
}}
+#ifdef F3DEX_GBI_2E
+#define gCopyMemEXT(pkt, c, idx, dst, src, len) \
+{ \
+ Gfx *_g = (Gfx *)(pkt); \
+ _g->words.w0 = (_SHIFTL((c),24,8)|_SHIFTL((src)/8,16,8)| \
+ _SHIFTL((dst)/8,8,8)|_SHIFTL((idx),0,8)); \
+ _g->words.w1 = (uintptr_t)(((len)-1)/8); \
+}
+#define gsCopyMemEXT(c, idx, dst, src, len) \
+{{ \
+ (_SHIFTL((c),24,8)|_SHIFTL((src)/8,16,8)| \
+ _SHIFTL((dst)/8,8,8)|_SHIFTL((idx),0,8)), \
+ (uintptr_t)(((len)-1)/8) \
+}}
+#endif
+
#define gSPNoOp(pkt) gDma0p(pkt, G_SPNOOP, 0, 0)
#define gsSPNoOp() gsDma0p(G_SPNOOP, 0, 0)
@@ -2541,6 +2562,17 @@ typedef union {
gsDma1p( G_MOVEMEM, l, sizeof(Light),((n)-1)*2+G_MV_L0)
#endif /* F3DEX_GBI_2 */
+/*
+ * EXTENDED COMMAND
+ * Copy one light's parameters to the other.
+ */
+#ifdef F3DEX_GBI_2E
+# define gSPCopyLightEXT(pkt, dst, src) \
+ gCopyMemEXT((pkt),G_COPYMEM,G_MV_LIGHT,(dst)*24+24,(src)*24+24,sizeof(Light))
+# define gsSPCopyLightEXT(dst, src) \
+ gsCopyMemEXT( G_COPYMEM,G_MV_LIGHT,(dst)*24+24,(src)*24+24,sizeof(Light))
+#endif
+
/*
* gSPLightColor changes color of light without recalculating light direction
* col is a 32 bit word with r,g,b,a (alpha is ignored)
diff --git a/include/types.h b/include/types.h
index 5c08c8f2e..41195c5fc 100644
--- a/include/types.h
+++ b/include/types.h
@@ -392,7 +392,7 @@ struct MarioState
// HOWEVER, simply increasing this to 3 will not magically work
// many things will have to be overhauled!
#ifdef UNSTABLE_BRANCH
-#define MAX_PLAYERS 4
+#define MAX_PLAYERS 16
#else
#define MAX_PLAYERS 2
#endif
diff --git a/src/game/area.c b/src/game/area.c
index aff256358..cafd4207d 100644
--- a/src/game/area.c
+++ b/src/game/area.c
@@ -257,6 +257,15 @@ void load_area(s32 index) {
load_obj_warp_nodes();
geo_call_global_function_nodes(&gCurrentArea->unk04->node, GEO_CONTEXT_AREA_LOAD);
}
+
+ if (!network_is_warp_2_duplicate()) {
+ if (gNetworkType != NT_NONE) {
+ network_send_level_warp_2(TRUE, gNetworkPlayerLocal->globalIndex);
+ }
+ if (gNetworkType == NT_CLIENT) {
+ sCurrPlayMode = PLAY_MODE_SYNC_LEVEL;
+ }
+ }
}
void unload_area(void) {
@@ -442,15 +451,8 @@ void render_game(void) {
}
// only render 'synchronizing' text if we've been waiting for a while
- static u8 syncLevelTime = 0;
if (sCurrPlayMode == PLAY_MODE_SYNC_LEVEL) {
- if (syncLevelTime < 30) {
- syncLevelTime++;
- } else {
- render_sync_level_screen();
- }
- } else {
- syncLevelTime = 0;
+ render_sync_level_screen();
}
D_8032CE74 = NULL;
diff --git a/src/game/chat.c b/src/game/chat.c
index bfca10662..1f346237a 100644
--- a/src/game/chat.c
+++ b/src/game/chat.c
@@ -5,6 +5,7 @@
#include "chat.h"
#include "game_init.h"
#include "ingame_menu.h"
+#include "mario_misc.h"
#include "segment2.h"
#include "gfx_dimensions.h"
#include "config.h"
@@ -23,6 +24,7 @@ struct ChatMessage {
u8 dialog[CHAT_DIALOG_MAX];
enum ChatMessageType type;
u16 life;
+ u8 color[3];
};
static char inputMessage[CHAT_DIALOG_MAX] = { 0 };
@@ -70,21 +72,34 @@ static void render_chat_message(struct ChatMessage* chatMessage, u8 index) {
u8 textR, textG, textB;
switch (chatMessage->type) {
- case CMT_LOCAL: textR = 200; textG = 200; textB = 255; break;
- case CMT_INPUT: textR = 0; textG = 0; textB = 0; break;
- case CMT_SYSTEM: textR = 255; textG = 255; textB = 190; break;
- default: textR = 255; textG = 255; textB = 255;
+ case CMT_LOCAL: textR = 200; textG = 200; textB = 255; break;
+ case CMT_INPUT: textR = 0; textG = 0; textB = 0; break;
+ case CMT_SYSTEM: textR = 255; textG = 255; textB = 190; break;
+ default: textR = 255; textG = 255; textB = 255; break;
}
gSPDisplayList(gDisplayListHead++, dl_ia_text_begin);
+
gDPSetEnvColor(gDisplayListHead++, textR, textG, textB, 255 * alphaScale);
print_generic_string(CHAT_X, CHAT_Y, chatMessage->dialog);
+
+ if (chatMessage->type == CMT_REMOTE || chatMessage->type == CMT_SYSTEM) {
+ // if it's someone else's message, highlight the icon with their color
+ u8 starR = chatMessage->color[0];
+ u8 starG = chatMessage->color[1];
+ u8 starB = chatMessage->color[2];
+ gDPSetEnvColor(gDisplayListHead++, starR, starG, starB, 255 * alphaScale);
+ create_dl_translation_matrix(MENU_MTX_PUSH, CHAT_X, CHAT_Y, 0.0f);
+ render_generic_char(chatMessage->dialog[0]);
+ gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
+ }
+
gSPDisplayList(gDisplayListHead++, dl_ia_text_end);
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
}
-void chat_add_message(char* ascii, enum ChatMessageType chatMessageType) {
+void chat_add_message_ext(char* ascii, enum ChatMessageType chatMessageType, const u8 color[3]) {
u8 character = '?';
switch (chatMessageType) {
case CMT_INPUT:
@@ -98,10 +113,18 @@ void chat_add_message(char* ascii, enum ChatMessageType chatMessageType) {
str_ascii_to_dialog(ascii, &msg->dialog[2], MIN(strlen(ascii), CHAT_DIALOG_MAX - 3));
msg->life = (sSelectedFileNum != 0) ? CHAT_LIFE_MAX : CHAT_LIFE_MAX / 3;
msg->type = chatMessageType;
+ msg->color[0] = color[0];
+ msg->color[1] = color[1];
+ msg->color[2] = color[2];
onMessageIndex = (onMessageIndex + 1) % CHAT_MESSAGES_MAX;
play_sound((msg->type == CMT_LOCAL) ? SOUND_MENU_MESSAGE_DISAPPEAR : SOUND_MENU_MESSAGE_APPEAR, gDefaultSoundArgs);
}
+void chat_add_message(char* ascii, enum ChatMessageType chatMessageType) {
+ const u8 defaultColor[3] = { 255, 255, 255 };
+ chat_add_message_ext(ascii, chatMessageType, defaultColor);
+}
+
static void chat_stop_input(void) {
sInChatInput = FALSE;
keyboard_stop_text_input();
@@ -112,7 +135,8 @@ static void chat_send_input(void) {
keyboard_stop_text_input();
if (strlen(gTextInput) == 0) { return; }
chat_add_message(gTextInput, CMT_LOCAL);
- network_send_chat(gTextInput);
+ // our message has the same color as our shirt
+ network_send_chat(gTextInput, get_player_color(gNetworkPlayerLocal->globalIndex, 0));
}
void chat_start_input(void) {
diff --git a/src/game/chat.h b/src/game/chat.h
index e1dfd0a17..4e9034fd7 100644
--- a/src/game/chat.h
+++ b/src/game/chat.h
@@ -10,6 +10,7 @@ enum ChatMessageType {
void render_chat(void);
void chat_add_message(char* ascii, enum ChatMessageType chatMessageType);
+void chat_add_message_ext(char* ascii, enum ChatMessageType chatMessageType, const u8 color[3]);
void chat_start_input(void);
#endif
\ No newline at end of file
diff --git a/src/game/level_update.c b/src/game/level_update.c
index 52d8def9f..a334233a4 100644
--- a/src/game/level_update.c
+++ b/src/game/level_update.c
@@ -53,6 +53,10 @@ u8 gControlledWarpGlobalIndex = 0;
extern s8 sReceivedLoadedActNum;
u8 gRejectInstantWarp = 0;
+s16 gChangeLevel = -1;
+s16 gChangeAreaIndex = -1;
+s16 gChangeActNum = -1;
+
#ifdef VERSION_JP
const char *credits01[] = { "1GAME DIRECTOR", "SHIGERU MIYAMOTO" };
const char *credits02[] = { "2ASSISTANT DIRECTORS", "YOSHIAKI KOIZUMI", "TAKASHI TEZUKA" };
@@ -1090,15 +1094,9 @@ s32 play_mode_normal(void) {
}
} else if (!gReceiveWarp.received) {
if (sWarpDest.type == WARP_TYPE_CHANGE_LEVEL) {
- set_play_mode(PLAY_MODE_SYNC_LEVEL);
- network_send_level_warp_begin();
+ set_play_mode(PLAY_MODE_CHANGE_LEVEL);
} else if (sTransitionTimer != 0) {
- if (sWarpDest.type == WARP_TYPE_CHANGE_AREA) {
- set_play_mode(PLAY_MODE_SYNC_LEVEL);
- network_send_level_warp_begin();
- } else {
- set_play_mode(PLAY_MODE_CHANGE_AREA);
- }
+ set_play_mode(PLAY_MODE_CHANGE_AREA);
} else if (sCurrPlayMode == PLAY_MODE_NORMAL && pressed_pause()) {
lower_background_noise(1);
cancel_rumble();
@@ -1129,8 +1127,7 @@ s32 play_mode_paused(void) {
fade_into_special_warp(0, 0);
gSavedCourseNum = COURSE_NONE;
}
- set_play_mode(PLAY_MODE_SYNC_LEVEL);
- network_send_level_warp_begin();
+ set_play_mode(PLAY_MODE_CHANGE_LEVEL);
} else if (gPauseScreenMode == 3) {
// We should only be getting "int 3" to here
initiate_warp(LEVEL_CASTLE, 1, 0x1F, 0);
@@ -1150,7 +1147,7 @@ s32 play_mode_sync_level(void) {
set_menu_mode(-1);
gCameraMovementFlags &= ~CAM_MOVE_PAUSE_SCREEN;
- check_received_warp();
+ //check_received_warp();
return 0;
}
@@ -1262,6 +1259,15 @@ static s32 play_mode_unused(void) {
s32 update_level(void) {
s32 changeLevel = 0;
+ if (gChangeLevel != -1) {
+ gHudDisplay.flags = HUD_DISPLAY_NONE;
+ sTransitionTimer = 0;
+ sTransitionUpdate = NULL;
+ changeLevel = gChangeLevel;
+ gChangeLevel = -1;
+ return changeLevel;
+ }
+
switch (sCurrPlayMode) {
case PLAY_MODE_NORMAL:
changeLevel = play_mode_normal();
diff --git a/src/game/level_update.h b/src/game/level_update.h
index eeea9baac..52f5d9a2e 100644
--- a/src/game/level_update.h
+++ b/src/game/level_update.h
@@ -76,6 +76,10 @@ extern s16 sTransitionTimer;
extern void (*sTransitionUpdate)(s16 *);
extern u8 unused3[4];
+extern s16 gChangeLevel;
+extern s16 gChangeAreaIndex;
+extern s16 gChangeActNum;
+
struct WarpDest {
u8 type;
u8 levelNum;
diff --git a/src/game/mario.c b/src/game/mario.c
index 0e10061a5..953f0ccf1 100644
--- a/src/game/mario.c
+++ b/src/game/mario.c
@@ -2123,7 +2123,7 @@ static void init_single_mario(struct MarioState* m) {
}
// set mario/luigi model
- enum CharacterType characterType = (gNetworkPlayers[0].globalIndex == 1) ? CT_LUIGI : CT_MARIO;
+ enum CharacterType characterType = (globalIndex == 0) ? CT_MARIO : CT_LUIGI;
m->character = &gCharacters[characterType];
m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[m->character->modelId];
}
diff --git a/src/game/mario_misc.c b/src/game/mario_misc.c
index 43f81eb6d..a62abf3d3 100644
--- a/src/game/mario_misc.c
+++ b/src/game/mario_misc.c
@@ -23,6 +23,7 @@
#include "save_file.h"
#include "skybox.h"
#include "sound_init.h"
+#include "pc/network/network.h"
#define TOAD_STAR_1_REQUIREMENT 12
#define TOAD_STAR_2_REQUIREMENT 25
@@ -51,6 +52,11 @@ enum UnlockDoorStarStates {
UNLOCK_DOOR_STAR_DONE
};
+struct PlayerColor {
+ Lights1 shirt;
+ Lights1 pants;
+};
+
/**
* The eye texture on succesive frames of Mario's blink animation.
* He intentionally blinks twice each time.
@@ -72,12 +78,76 @@ static s8 gMarioAttackScaleAnimation[3 * 6] = {
struct MarioBodyState gBodyStates[MAX_PLAYERS];
struct GraphNodeObject gMirrorMario[MAX_PLAYERS]; // copy of Mario's geo node for drawing mirror Mario
+// ambient color is always half the diffuse color, so we can pull a macro
+#define DEFINE_PLAYER_COLOR(sr, sg, sb, pr, pg, pb) \
+ { \
+ gdSPDefLights1((sr >> 1), (sg >> 1), (sb >> 1), sr, sg, sb, 0x28, 0x28, 0x28), \
+ gdSPDefLights1((pr >> 1), (pg >> 1), (pb >> 1), pr, pg, pb, 0x28, 0x28, 0x28), \
+ }
+
+struct PlayerColor gPlayerColors[] = {
+ // default mario
+ DEFINE_PLAYER_COLOR(0xff, 0x00, 0x00, /**/ 0x00, 0x00, 0xff),
+ // default luigi
+ DEFINE_PLAYER_COLOR(0x00, 0x98, 0x00, /**/ 0x00, 0x00, 0xfe),
+#if MAX_PLAYERS > 2
+ // fake waluigi
+ DEFINE_PLAYER_COLOR(0x6d, 0x3c, 0x9a, /**/ 0x2c, 0x26, 0x3f),
+ // fake wario
+ DEFINE_PLAYER_COLOR(0xf9, 0xeb, 0x30, /**/ 0x7f, 0x20, 0x7a),
+ // light blue
+ DEFINE_PLAYER_COLOR(0x00, 0xdf, 0xff, /**/ 0x00, 0x00, 0xf0),
+ // sponge
+ DEFINE_PLAYER_COLOR(0xff, 0x7f, 0x00, /**/ 0x00, 0x7f, 0xa0),
+ // blue man group
+ DEFINE_PLAYER_COLOR(0x00, 0x00, 0xf0, /**/ 0x00, 0x00, 0x4f),
+ // thanks doc
+ DEFINE_PLAYER_COLOR(0xff, 0x00, 0xff, /**/ 0x00, 0xff, 0x00),
+ // white
+ DEFINE_PLAYER_COLOR(0xff, 0xff, 0xff, /**/ 0x10, 0x10, 0x10),
+ // grey
+ DEFINE_PLAYER_COLOR(0x6f, 0x6f, 0x6f, /**/ 0xe0, 0xe0, 0xe0),
+#endif
+};
+
+static const size_t gNumPlayerColors = sizeof(gPlayerColors) / sizeof(*gPlayerColors);
+
// This whole file is weirdly organized. It has to be the same file due
// to rodata boundaries and function aligns, which means the programmer
// treated this like a "misc" file for vaguely Mario related things
// (message NPC related things, the Mario head geo, and Mario geo
// functions)
+/**
+ * Set the Light1 struct from player colors.
+ * The 4th component is the shade factor (difference between ambient and diffuse),
+ * usually set to 1.
+ */
+void set_player_colors(u8 globalIndex, const u8 shirt[4], const u8 pants[4]) {
+ // choose the last color in the table for extra players
+ if (globalIndex >= gNumPlayerColors) globalIndex = gNumPlayerColors - 1;
+ const u8 pAmb[3] = { pants[0] >> pants[4], pants[1] >> pants[4], pants[2] >> pants[4] };
+ const u8 sAmb[3] = { shirt[0] >> shirt[4], shirt[1] >> shirt[4], shirt[2] >> shirt[4] };
+ gPlayerColors[globalIndex].pants =
+ (Lights1) gdSPDefLights1(pAmb[0], pAmb[1], pAmb[2], pants[0], pants[1], pants[2], 0x28, 0x28, 0x28);
+ gPlayerColors[globalIndex].shirt =
+ (Lights1) gdSPDefLights1(sAmb[0], sAmb[1], sAmb[2], shirt[0], shirt[1], shirt[2], 0x28, 0x28, 0x28);
+}
+
+/**
+ * Return the specified color for player globalIndex.
+ * 0 = shirt, 1 = pants
+ * Returns RGB, not RGBA!
+ */
+u8 *get_player_color(u8 globalIndex, const int which) {
+ // choose the last color in the table for extra players
+ if (globalIndex >= gNumPlayerColors) globalIndex = gNumPlayerColors - 1;
+ if (which == 0)
+ return gPlayerColors[globalIndex].shirt.l[0].l.col;
+ else
+ return gPlayerColors[globalIndex].pants.l[0].l.col;
+}
+
/**
* Geo node script that draws Mario's head on the title screen.
*/
@@ -405,7 +475,8 @@ Gfx* geo_switch_mario_eyes(s32 callContext, struct GraphNode* node, UNUSED Mat4*
Gfx* geo_mario_tilt_torso(s32 callContext, struct GraphNode* node, Mat4* mtx) {
Mat4 * curTransform = mtx;
struct GraphNodeGenerated* asGenerated = (struct GraphNodeGenerated*) node;
- struct MarioBodyState* bodyState = geo_get_body_state();
+ u8 plrIdx = geo_get_processing_object_index();
+ struct MarioBodyState* bodyState = &gBodyStates[plrIdx];
s32 action = bodyState->action;
if (callContext == GEO_CONTEXT_RENDER) {
@@ -418,6 +489,11 @@ Gfx* geo_mario_tilt_torso(s32 callContext, struct GraphNode* node, Mat4* mtx) {
rotNode->rotation[0] = bodyState->torsoAngle[1];
rotNode->rotation[1] = bodyState->torsoAngle[2];
rotNode->rotation[2] = bodyState->torsoAngle[0];
+ if (plrIdx != 0) {
+ // only interpolate angles for the local player
+ vec3s_copy(rotNode->prevRotation, rotNode->rotation);
+ rotNode->prevTimestamp = gGlobalTimer;
+ }
// update torso position in bodyState
get_pos_from_transform_mtx(bodyState->torsoPos, *curTransform, *gCurGraphNodeCamera->matrixPtr);
}
@@ -429,7 +505,8 @@ Gfx* geo_mario_tilt_torso(s32 callContext, struct GraphNode* node, Mat4* mtx) {
*/
Gfx* geo_mario_head_rotation(s32 callContext, struct GraphNode* node, UNUSED Mat4* c) {
struct GraphNodeGenerated* asGenerated = (struct GraphNodeGenerated*) node;
- struct MarioBodyState* bodyState = geo_get_body_state();
+ u8 plrIdx = geo_get_processing_object_index();
+ struct MarioBodyState* bodyState = &gBodyStates[plrIdx];
s32 action = bodyState->action;
if (callContext == GEO_CONTEXT_RENDER) {
@@ -449,6 +526,12 @@ Gfx* geo_mario_head_rotation(s32 callContext, struct GraphNode* node, UNUSED Mat
vec3s_set(bodyState->headAngle, 0, 0, 0);
vec3s_set(rotNode->rotation, 0, 0, 0);
}
+
+ if (plrIdx != 0) {
+ // only interpolate angles for the local player
+ vec3s_copy(rotNode->prevRotation, rotNode->rotation);
+ rotNode->prevTimestamp = gGlobalTimer;
+ }
}
return NULL;
}
@@ -707,4 +790,32 @@ Gfx* geo_mirror_mario_backface_culling(s32 callContext, struct GraphNode* node,
asGenerated->fnNode.node.flags = (asGenerated->fnNode.node.flags & 0xFF) | (LAYER_OPAQUE << 8);
}
return gfx;
-}
\ No newline at end of file
+}
+
+/**
+ * Generate DL that sets player color depending on player number.
+ */
+Gfx* geo_mario_set_player_colors(s32 callContext, struct GraphNode* node, UNUSED Mat4* c) {
+ struct GraphNodeGenerated* asGenerated = (struct GraphNodeGenerated*) node;
+ Gfx* gfx = NULL;
+ u8 index = geo_get_processing_object_index();
+ u8 colorIndex = gNetworkPlayers[index].globalIndex;
+ struct MarioBodyState* bodyState = &gBodyStates[index];
+
+ if (callContext == GEO_CONTEXT_RENDER) {
+ // extra players get last color
+ if (colorIndex >= gNumPlayerColors) colorIndex = gNumPlayerColors - 1;
+ gfx = alloc_display_list(5 * sizeof(*gfx));
+ // put the player colors into lights 3, 4, 5, 6
+ // they will be later copied to lights 1, 2 with gsSPCopyLightEXT
+ gSPLight(gfx + 0, &gPlayerColors[colorIndex].pants.l, 3);
+ gSPLight(gfx + 1, &gPlayerColors[colorIndex].pants.a, 4);
+ gSPLight(gfx + 2, &gPlayerColors[colorIndex].shirt.l, 5);
+ gSPLight(gfx + 3, &gPlayerColors[colorIndex].shirt.a, 6);
+ gSPEndDisplayList(gfx + 4);
+ // put on transparent layer if vanish effect, opaque otherwise
+ const u32 layer = ((bodyState->modelState >> 8) & 1) ? LAYER_TRANSPARENT : LAYER_OPAQUE;
+ asGenerated->fnNode.node.flags = (asGenerated->fnNode.node.flags & 0xFF) | (layer << 8);
+ }
+ return gfx;
+}
diff --git a/src/game/mario_misc.h b/src/game/mario_misc.h
index 9a1f956fc..3c4a76b6b 100644
--- a/src/game/mario_misc.h
+++ b/src/game/mario_misc.h
@@ -9,6 +9,9 @@
extern struct GraphNodeObject gMirrorMario[MAX_PLAYERS];
extern struct MarioBodyState gBodyStates[MAX_PLAYERS];
+void set_player_colors(u8 globalIndex, const u8 shirt[4], const u8 pants[4]);
+u8 *get_player_color(u8 globalIndex, const int which);
+
Gfx *geo_draw_mario_head_goddard(s32 callContext, struct GraphNode *node, Mat4 *c);
void bhv_toad_message_loop(void);
void bhv_toad_message_init(void);
@@ -27,5 +30,6 @@ Gfx *geo_mario_rotate_wing_cap_wings(s32 callContext, struct GraphNode *node, UN
Gfx *geo_switch_mario_hand_grab_pos(s32 callContext, struct GraphNode *b, Mat4 *mtx);
Gfx *geo_render_mirror_mario(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c);
Gfx *geo_mirror_mario_backface_culling(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c);
+Gfx *geo_mario_set_player_colors(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c);
#endif // MARIO_MISC_H
diff --git a/src/pc/gfx/gfx_pc.c b/src/pc/gfx/gfx_pc.c
index a78f2ffdd..876d9f54c 100644
--- a/src/pc/gfx/gfx_pc.c
+++ b/src/pc/gfx/gfx_pc.c
@@ -47,7 +47,7 @@
#define RATIO_Y (gfx_current_dimensions.height / (2.0f * HALF_SCREEN_HEIGHT))
#define MAX_BUFFERED 256
-#define MAX_LIGHTS 2
+#define MAX_LIGHTS 8
#define MAX_VERTICES 64
#ifdef EXTERNAL_DATA
@@ -1123,6 +1123,10 @@ static void gfx_sp_movemem(uint8_t index, uint8_t offset, const void* data) {
case G_MV_L0:
case G_MV_L1:
case G_MV_L2:
+ case G_MV_L3:
+ case G_MV_L4:
+ case G_MV_L5:
+ case G_MV_L6:
// NOTE: reads out of bounds if it is an ambient light
memcpy(rsp.current_lights + (index - G_MV_L0) / 2, data, sizeof(Light_t));
break;
@@ -1130,6 +1134,18 @@ static void gfx_sp_movemem(uint8_t index, uint8_t offset, const void* data) {
}
}
+#ifdef F3DEX_GBI_2E
+static void gfx_sp_copymem(uint8_t idx, uint8_t dstofs, uint8_t srcofs, uint8_t words) {
+ if (idx == G_MV_LIGHT) {
+ const int srcidx = srcofs / 24 - 2;
+ const int dstidx = dstofs / 24 - 2;
+ if (srcidx <= MAX_LIGHTS && dstidx <= MAX_LIGHTS) {
+ memcpy(rsp.current_lights + dstidx, rsp.current_lights + srcidx, sizeof(Light_t));
+ }
+ }
+}
+#endif
+
static void gfx_sp_moveword(uint8_t index, uint16_t offset, uint32_t data) {
switch (index) {
case G_MW_NUMLIGHT:
@@ -1538,6 +1554,11 @@ static void gfx_run_dl(Gfx* cmd) {
gfx_sp_moveword(C0(0, 8), C0(8, 16), cmd->words.w1);
#endif
break;
+#ifdef F3DEX_GBI_2E
+ case (uint8_t)G_COPYMEM:
+ gfx_sp_copymem(C0(0, 8), C0(8, 8) * 8, C0(16, 8) * 8, C1(0, 8));
+ break;
+#endif
case (uint8_t)G_TEXTURE:
#ifdef F3DEX_GBI_2
gfx_sp_texture(C1(16, 16), C1(0, 16), C0(11, 3), C0(8, 3), C0(1, 7));
diff --git a/src/pc/network/discord/lobby.c b/src/pc/network/discord/lobby.c
index d556dad72..1d2561f3f 100644
--- a/src/pc/network/discord/lobby.c
+++ b/src/pc/network/discord/lobby.c
@@ -18,7 +18,7 @@ static void on_lobby_create_callback(UNUSED void* data, enum EDiscordResult resu
gCurActivity.type = DiscordActivityType_Playing;
snprintf(gCurActivity.party.id, 128, "%lld", lobby->id);
gCurActivity.party.size.current_size = 1;
- gCurActivity.party.size.max_size = 2;
+ gCurActivity.party.size.max_size = MAX_PLAYERS;
char secretJoin[128] = "";
snprintf(secretJoin, 128, "%lld:%s", lobby->id, lobby->secret);
@@ -59,7 +59,7 @@ void discord_lobby_create(void) {
struct IDiscordLobbyTransaction* txn = { 0 };
DISCORD_REQUIRE(app.lobbies->get_lobby_create_transaction(app.lobbies, &txn));
- txn->set_capacity(txn, 2);
+ txn->set_capacity(txn, MAX_PLAYERS);
txn->set_type(txn, DiscordLobbyType_Public);
//txn->set_metadata(txn, "a", "123");
@@ -94,4 +94,4 @@ struct IDiscordLobbyEvents* discord_lobby_initialize(void) {
events.on_member_disconnect = on_member_disconnect;
events.on_network_message = discord_network_on_message;
return &events;
-}
\ No newline at end of file
+}
diff --git a/src/pc/network/network_player.c b/src/pc/network/network_player.c
index 7c85fdbb6..8c8716ba9 100644
--- a/src/pc/network/network_player.c
+++ b/src/pc/network/network_player.c
@@ -1,6 +1,7 @@
#include
#include "network_player.h"
#include "game/chat.h"
+#include "game/mario_misc.h"
#include "pc/debuglog.h"
struct NetworkPlayer gNetworkPlayers[MAX_PLAYERS] = { 0 };
@@ -105,7 +106,7 @@ u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex) {
gNetworkSystem->save_id(i);
for (int j = 0; j < MAX_SYNC_OBJECTS; j++) { gSyncObjects[j].rxEventId[i] = 0; }
if (type == NPT_SERVER) { gNetworkPlayerServer = np; }
- else { chat_add_message("player connected", CMT_SYSTEM); }
+ else { chat_add_message_ext("player connected", CMT_SYSTEM, get_player_color(np->globalIndex, 0)); }
LOG_INFO("player connected, local %d, global %d", i, np->globalIndex);
extern s16 sCurrPlayMode;
if (gNetworkType == NT_SERVER && sCurrPlayMode == PLAY_MODE_SYNC_LEVEL) {
@@ -142,7 +143,7 @@ u8 network_player_disconnected(u8 globalIndex) {
gNetworkSystem->clear_id(i);
for (int j = 0; j < MAX_SYNC_OBJECTS; j++) { gSyncObjects[j].rxEventId[i] = 0; }
LOG_INFO("player disconnected, local %d, global %d", i, globalIndex);
- chat_add_message("player disconnected", CMT_SYSTEM);
+ chat_add_message_ext("player disconnected", CMT_SYSTEM, get_player_color(globalIndex, 0));
return i;
}
return UNKNOWN_GLOBAL_INDEX;
diff --git a/src/pc/network/packets/packet.c b/src/pc/network/packets/packet.c
index 800cc8db6..b7d44f683 100644
--- a/src/pc/network/packets/packet.c
+++ b/src/pc/network/packets/packet.c
@@ -56,6 +56,7 @@ void packet_receive(struct Packet* p) {
case PACKET_INSTANT_WARP: network_receive_instant_warp(p); break;
case PACKET_NETWORK_PLAYERS: network_receive_network_players(p); break;
case PACKET_DEATH: network_receive_death(p); break;
+ case PACKET_LEVEL_WARP_2: network_receive_level_warp_2(p); break;
///
case PACKET_CUSTOM: network_receive_custom(p); break;
default: LOG_ERROR("received unknown packet: %d", p->buffer[0]);
diff --git a/src/pc/network/packets/packet.h b/src/pc/network/packets/packet.h
index e14b0db3c..77734d2dc 100644
--- a/src/pc/network/packets/packet.h
+++ b/src/pc/network/packets/packet.h
@@ -33,6 +33,7 @@ enum PacketType {
PACKET_INSTANT_WARP,
PACKET_NETWORK_PLAYERS,
PACKET_DEATH,
+ PACKET_LEVEL_WARP_2,
///
PACKET_CUSTOM = 255,
};
@@ -140,7 +141,7 @@ void network_send_custom(u8 customId, bool reliable, bool levelAreaMustMatch, vo
void network_receive_custom(struct Packet* p);
// packet_chat.c
-void network_send_chat(char* message);
+void network_send_chat(char* message, u8 rgb[3]);
void network_receive_chat(struct Packet* p);
// packet_kick.c
@@ -171,4 +172,9 @@ void network_receive_network_players(struct Packet* p);
void network_send_death(void);
void network_receive_death(struct Packet* p);
+// packet_level_warp_2.c
+void network_send_level_warp_2(u8 eventBegins, u8 controlledGlobalIndex);
+void network_receive_level_warp_2(struct Packet* p);
+u8 network_is_warp_2_duplicate(void);
+
#endif
diff --git a/src/pc/network/packets/packet_chat.c b/src/pc/network/packets/packet_chat.c
index 59b0a5813..ed98c6dc4 100644
--- a/src/pc/network/packets/packet_chat.c
+++ b/src/pc/network/packets/packet_chat.c
@@ -17,10 +17,11 @@ static void print_sync_object_table(void) {
}
#endif
-void network_send_chat(char* message) {
+void network_send_chat(char* message, u8 rgb[3]) {
u16 messageLength = strlen(message);
struct Packet p;
packet_init(&p, PACKET_CHAT, true, false);
+ packet_write(&p, rgb, 3 * sizeof(u8));
packet_write(&p, &messageLength, sizeof(u16));
packet_write(&p, message, messageLength * sizeof(u8));
network_send(&p);
@@ -34,13 +35,15 @@ void network_send_chat(char* message) {
void network_receive_chat(struct Packet* p) {
u16 remoteMessageLength = 0;
char remoteMessage[255] = { 0 };
+ u8 rgb[3] = { 255, 255, 255};
+ packet_read(p, rgb, 3 * sizeof(u8));
packet_read(p, &remoteMessageLength, sizeof(u16));
if (remoteMessageLength > 255) { remoteMessageLength = 254; }
packet_read(p, &remoteMessage, remoteMessageLength * sizeof(u8));
// add the message
- chat_add_message(remoteMessage, CMT_REMOTE);
+ chat_add_message_ext(remoteMessage, CMT_REMOTE, rgb);
LOG_INFO("rx chat: %s", remoteMessage);
#ifdef DEVELOPMENT
diff --git a/src/pc/network/packets/packet_level_warp_2.c b/src/pc/network/packets/packet_level_warp_2.c
new file mode 100644
index 000000000..9cb574d43
--- /dev/null
+++ b/src/pc/network/packets/packet_level_warp_2.c
@@ -0,0 +1,143 @@
+#include "../network.h"
+#include "game/level_update.h"
+#include "game/object_list_processor.h"
+//#define DISABLE_MODULE_LOG
+#include "pc/debuglog.h"
+
+#define SERVER_RETAIN_WARP_SECONDS 1
+
+extern u8 gControlledWarpGlobalIndex;
+extern float gPaintingMarioYEntry;
+
+#pragma pack(1)
+struct PacketLevelWarp2Data {
+ s16 levelNum;
+ s16 areaIndex;
+ s16 actNum;
+
+ u8 warpType;
+ u8 warpLevelNum;
+ u8 warpAreaIdx;
+ u8 warpNodeId;
+ u32 warpArg;
+
+ s8 inWarpCheckpoint;
+ s16 ttcSpeedSetting;
+ s16 D_80339EE0;
+ f32 paintingMarioYEntry;
+ u8 controlledWarpGlobalIndex;
+};
+
+struct PacketLevelWarp2Data sSavedLevelWarp2Data = { 0 };
+static clock_t sSavedClockTime = 0;
+
+static void populate_packet_data(struct PacketLevelWarp2Data* data) {
+ data->levelNum = gCurrLevelNum;
+ data->areaIndex = gCurrAreaIndex;
+ data->actNum = gCurrActNum;
+
+ data->warpType = sWarpDest.type;
+ data->warpLevelNum = sWarpDest.levelNum;
+ data->warpAreaIdx = sWarpDest.areaIdx;
+ data->warpNodeId = sWarpDest.nodeId;
+ data->warpArg = sWarpDest.arg;
+
+ data->inWarpCheckpoint = gInWarpCheckpoint;
+ data->ttcSpeedSetting = gTTCSpeedSetting;
+ data->D_80339EE0 = D_80339EE0;
+ data->paintingMarioYEntry = gPaintingMarioYEntry;
+ data->controlledWarpGlobalIndex = gControlledWarpGlobalIndex;
+}
+
+void network_send_level_warp_2(u8 eventBegins, u8 controlledGlobalIndex) {
+ struct PacketLevelWarp2Data data = { 0 };
+ if (eventBegins) {
+ gControlledWarpGlobalIndex = controlledGlobalIndex;
+ populate_packet_data(&data);
+ if (gNetworkType == NT_SERVER) {
+ sSavedLevelWarp2Data = data;
+ sSavedClockTime = clock();
+ }
+ } else {
+ data = sSavedLevelWarp2Data;
+ }
+
+ struct Packet p;
+ packet_init(&p, PACKET_LEVEL_WARP_2, true, false);
+ packet_write(&p, &data, sizeof(struct PacketLevelWarp2Data));
+
+ if (gNetworkType == NT_SERVER) {
+ network_send(&p);
+ } else {
+ network_send_to(gNetworkPlayerServer->localIndex, &p);
+ }
+
+ LOG_INFO("send warp: %d, %d, %d", gCurrLevelNum, gCurrAreaIndex, gCurrActNum);
+}
+
+static void do_warp(struct PacketLevelWarp2Data* data) {
+ if (gCurrLevelNum != data->levelNum ) { gChangeLevel = data->levelNum; }
+
+ sWarpDest.type = data->warpType;
+ sWarpDest.levelNum = data->warpLevelNum;
+ sWarpDest.areaIdx = data->warpAreaIdx;
+ sWarpDest.nodeId = data->warpNodeId;
+ sWarpDest.arg = data->warpArg;
+
+ gInWarpCheckpoint = data->inWarpCheckpoint;
+ gTTCSpeedSetting = data->ttcSpeedSetting;
+ D_80339EE0 = data->D_80339EE0;
+ gPaintingMarioYEntry = data->paintingMarioYEntry;
+ gControlledWarpGlobalIndex = data->controlledWarpGlobalIndex;
+
+ gCurrLevelNum = data->levelNum;
+ gCurrAreaIndex = data->areaIndex;
+ gCurrActNum = data->actNum;
+
+ LOG_INFO("do warp: %d, %d, %d", gCurrLevelNum, gCurrAreaIndex, gCurrActNum);
+}
+
+void network_receive_level_warp_2(struct Packet* p) {
+ struct PacketLevelWarp2Data remote = { 0 };
+ packet_read(p, &remote, sizeof(struct PacketLevelWarp2Data));
+ LOG_INFO("rx warp: %d, %d, %d", remote.levelNum, remote.areaIndex, remote.actNum);
+
+ u8 levelOrAreaDifference = (gCurrLevelNum != remote.levelNum) || (gCurrAreaIndex != remote.areaIndex);
+
+ if (gNetworkType == NT_SERVER) {
+ f32 elapsed = (clock() - sSavedClockTime) / (f32)CLOCKS_PER_SEC;
+ if (elapsed < SERVER_RETAIN_WARP_SECONDS || !levelOrAreaDifference) {
+ network_send_level_warp_2(FALSE, gNetworkPlayerLocal->globalIndex);
+ return;
+ }
+ }
+
+ if (levelOrAreaDifference) {
+ do_warp(&remote);
+ }
+
+ if (gNetworkType == NT_CLIENT) {
+ sSavedLevelWarp2Data = remote;
+ sSavedClockTime = clock();
+ }
+
+ if (gNetworkType == NT_SERVER) {
+ network_send_level_warp_2(TRUE, remote.controlledWarpGlobalIndex);
+ } else {
+ sCurrPlayMode = PLAY_MODE_NORMAL;
+ network_on_init_level();
+ }
+}
+
+u8 network_is_warp_2_duplicate(void) {
+ struct PacketLevelWarp2Data data = { 0 };
+ populate_packet_data(&data);
+
+ if (data.levelNum == 1 && data.areaIndex == 1) { return TRUE; }
+
+ if (gNetworkType == NT_SERVER) {
+ f32 elapsed = (clock() - sSavedClockTime) / (f32)CLOCKS_PER_SEC;
+ if (elapsed >= SERVER_RETAIN_WARP_SECONDS) { return FALSE; }
+ }
+ return (memcmp(&sSavedLevelWarp2Data, &data, sizeof(struct PacketLevelWarp2Data)) == 0);
+}
\ No newline at end of file
diff --git a/src/pc/network/packets/packet_player.c b/src/pc/network/packets/packet_player.c
index 768be58a9..1f5101784 100644
--- a/src/pc/network/packets/packet_player.c
+++ b/src/pc/network/packets/packet_player.c
@@ -10,6 +10,7 @@
#include "engine/surface_collision.h"
#include "game/object_list_processor.h"
#include "game/chat.h"
+#include "game/mario_misc.h"
#include "pc/configfile.h"
#pragma pack(1)
@@ -336,7 +337,7 @@ void network_receive_player(struct Packet* p) {
// inform of player death
if (oldData.action != ACT_BUBBLED && data.action == ACT_BUBBLED) {
- chat_add_message("player died", CMT_SYSTEM);
+ chat_add_message_ext("player died", CMT_SYSTEM, get_player_color(globalIndex, 0));
}
// action changed, reset timer
@@ -345,7 +346,7 @@ void network_receive_player(struct Packet* p) {
}
// set model
- enum CharacterType characterType = (np->globalIndex == 1) ? CT_LUIGI : CT_MARIO;
+ enum CharacterType characterType = (np->globalIndex == 0) ? CT_MARIO : CT_LUIGI;
m->character = &gCharacters[characterType];
m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[m->character->modelId];
}
diff --git a/src/pc/network/socket/socket_windows.c b/src/pc/network/socket/socket_windows.c
index a9a172209..c524a41a3 100644
--- a/src/pc/network/socket/socket_windows.c
+++ b/src/pc/network/socket/socket_windows.c
@@ -27,6 +27,17 @@ SOCKET socket_initialize(void) {
return INVALID_SOCKET;
}
+#if MAX_PLAYERS > 4
+ // on windows, the send buffer for the socket needs to be increased
+ // for the many players case to avoid WSAEWOULDBLOCK on send
+ // not actually sure this is the "proper" way to fix it
+ int bufsiz = 128 * 1024; // 128kb, default is apparently 8kb or 16kb
+ rc = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char *)&bufsiz, sizeof(bufsiz));
+ if (rc != NO_ERROR) {
+ LOG_ERROR("setsockopt(SO_SNDBUF) failed with error: %d", rc);
+ }
+#endif
+
return sock;
}
diff --git a/src/pc/network/version.h b/src/pc/network/version.h
index 573456d61..b65d13606 100644
--- a/src/pc/network/version.h
+++ b/src/pc/network/version.h
@@ -2,7 +2,7 @@
#define VERSION_H
#define UNSTABLE_BRANCH
-#define VERSION_NUMBER 2
+#define VERSION_NUMBER 3
#define MAX_VERSION_LENGTH 10
char* get_version(void);