diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj
index e0687edc3..64231817a 100644
--- a/build-windows-visual-studio/sm64ex.vcxproj
+++ b/build-windows-visual-studio/sm64ex.vcxproj
@@ -3944,6 +3944,8 @@
+
+
diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters
index 2dcc0a82c..29e88cc85 100644
--- a/build-windows-visual-studio/sm64ex.vcxproj.filters
+++ b/build-windows-visual-studio/sm64ex.vcxproj.filters
@@ -14961,6 +14961,12 @@
Source Files\src\pc\network\packets
+
+ Source Files\src\pc\network\packets
+
+
+ Source Files\src\pc\network\packets
+
diff --git a/include/types.h b/include/types.h
index 87f43fc8b..104ca5971 100644
--- a/include/types.h
+++ b/include/types.h
@@ -376,5 +376,15 @@ struct MarioState
// HOWEVER, simply increasing this to 3 will not magically work
// many things will have to be overhauled!
#define MAX_PLAYERS 2
+// still deciding to increase it?
+// networking will have to be rewritten to have more than one destination. 'reliable' messages would need to be sent per-player
+// things that base priority on whether they are the host or not would need priority based on player index instead
+// player 2's mario2.geo file will need a different one for player 3, 4, 5, etc... and will need values within it adjusted in a similar manner (diff them)
+// read all of the code surrounding a search through the entire codebase of the following:
+// gLuigiObject
+// gMarioObject
+// gMarioState[0]
+// gMarioState[1]
+// luigi
#endif // _SM64_TYPES_H_
diff --git a/src/engine/behavior_script.c b/src/engine/behavior_script.c
index 4eaeab93e..7c39a339b 100644
--- a/src/engine/behavior_script.c
+++ b/src/engine/behavior_script.c
@@ -29,7 +29,6 @@
#define BHV_CMD_GET_ADDR_OF_CMD(index) (uintptr_t)(&gCurBhvCommand[index])
static u16 gRandomSeed16;
-static u16 gSyncRandom;
// Unused function that directly jumps to a behavior command and resets the object's stack index.
static void goto_behavior_unused(const BehaviorScript *bhvAddr) {
@@ -37,52 +36,34 @@ static void goto_behavior_unused(const BehaviorScript *bhvAddr) {
gCurrentObject->bhvStackIndex = 0;
}
-void random_sync_reset(void) {
- // seed the sync'd random seed with enough synchronzied information to be "unique enough"
- gSyncRandom = (u16)gCurrentObject->oPosX
- ^ (u16)gCurrentObject->oPosY
- ^ (u16)gCurrentObject->oPosZ
- ^ (u16)gCurrentObject->oVelX
- ^ (u16)gCurrentObject->oVelY
- ^ (u16)gCurrentObject->oVelZ
- ^ (u16)gCurrentObject->oAction;
-}
-
-// Generate a pseudorandom integer from 0 to 65535 from the synchronized seed, and update the seed.
-u16 random_sync_u16(void) {
- u16 temp1, temp2;
-
- if (gSyncRandom == 22026) {
- gSyncRandom = 0;
+void force_replicable_seed(u8 always) {
+ // force the seed to consistent values
+ extern u16 gRandomSeed16;
+ extern u32 gGlobalTimer;
+ static u32 lastTimer = 0;
+ static f32 lastPos[3] = { 0 };
+ if (gGlobalTimer == lastTimer
+ && lastPos[0] == gCurrentObject->oPosX / 10
+ && lastPos[1] == gCurrentObject->oPosY / 10
+ && lastPos[2] == gCurrentObject->oPosZ / 10
+ && !always) {
+ return;
}
-
- temp1 = (gSyncRandom & 0x00FF) << 8;
- temp1 = temp1 ^ gSyncRandom;
-
- gSyncRandom = ((temp1 & 0x00FF) << 8) + ((temp1 & 0xFF00) >> 8);
-
- temp1 = ((temp1 & 0x00FF) << 1) ^ gSyncRandom;
- temp2 = (temp1 >> 1) ^ 0xFF80;
-
- if ((temp1 & 1) == 0) {
- if (temp2 == 43605) {
- gSyncRandom = 0;
- }
- else {
- gSyncRandom = temp2 ^ 0x1FF4;
- }
+ gRandomSeed16 = (u16)(gCurrentObject->oPosX / 1000.0f)
+ ^ (u16)(gCurrentObject->oPosY / 1000.0f)
+ ^ (u16)(gCurrentObject->oPosZ / 1000.0f);
+ if (!always) {
+ lastPos[0] = gCurrentObject->oPosX / 10;
+ lastPos[1] = gCurrentObject->oPosY / 10;
+ lastPos[2] = gCurrentObject->oPosZ / 10;
+ lastTimer = gGlobalTimer;
}
- else {
- gSyncRandom = temp2 ^ 0x8180;
- }
-
- return gSyncRandom;
}
// Generate a pseudorandom integer from 0 to 65535 from the random seed, and update the seed.
u16 random_u16(void) {
// override this function for synchronized entities
- if (gCurrentObject->oSyncID != 0) { return random_sync_u16(); }
+ if (gCurrentObject->oSyncID != 0) { force_replicable_seed(FALSE); }
u16 temp1, temp2;
diff --git a/src/engine/behavior_script.h b/src/engine/behavior_script.h
index 92ba10993..e99e6abca 100644
--- a/src/engine/behavior_script.h
+++ b/src/engine/behavior_script.h
@@ -19,6 +19,7 @@
#define obj_and_int(object, offset, value) object->OBJECT_FIELD_S32(offset) &= (s32)(value)
+void force_replicable_seed(u8 always);
u16 random_u16(void);
float random_float(void);
s32 random_sign(void);
diff --git a/src/game/behaviors/bully.inc.c b/src/game/behaviors/bully.inc.c
index efe0ee180..67493f68b 100644
--- a/src/game/behaviors/bully.inc.c
+++ b/src/game/behaviors/bully.inc.c
@@ -176,6 +176,7 @@ void bully_step(void) {
}
void bully_spawn_coin(void) {
+ force_replicable_seed(TRUE);
struct Object *coin = spawn_object(o, MODEL_YELLOW_COIN, bhvMovingYellowCoin);
#ifdef VERSION_JP //TODO: maybe move this ifdef logic to the header?
cur_obj_play_sound_2(SOUND_GENERAL_COIN_SPURT);
diff --git a/src/game/behaviors/coin.inc.c b/src/game/behaviors/coin.inc.c
index 9b7099deb..80387b2ae 100644
--- a/src/game/behaviors/coin.inc.c
+++ b/src/game/behaviors/coin.inc.c
@@ -49,6 +49,7 @@ void bhv_temp_coin_loop(void) {
}
void bhv_coin_init(void) {
+ force_replicable_seed(FALSE);
o->oVelY = random_float() * 10.0f + 30 + o->oCoinUnk110;
o->oForwardVel = random_float() * 10.0f;
o->oMoveAngleYaw = random_u16();
diff --git a/src/game/behaviors/goomba.inc.c b/src/game/behaviors/goomba.inc.c
index babe7e959..3a1ca208c 100644
--- a/src/game/behaviors/goomba.inc.c
+++ b/src/game/behaviors/goomba.inc.c
@@ -285,7 +285,6 @@ void huge_goomba_weakly_attacked(void) {
*/
void bhv_goomba_update(void) {
// PARTIAL_UPDATE
- random_sync_reset();
f32 animSpeed;
diff --git a/src/game/game_init.h b/src/game/game_init.h
index ca1b7631c..15f48bb39 100644
--- a/src/game/game_init.h
+++ b/src/game/game_init.h
@@ -32,7 +32,7 @@ extern OSMesg D_80339CD4;
extern struct VblankHandler gGameVblankHandler;
extern uintptr_t gPhysicalFrameBuffers[3];
extern uintptr_t gPhysicalZBuffer;
-extern void *D_80339CF0[2];
+extern void *D_80339CF0[MAX_PLAYERS];
extern void *D_80339CF4;
extern struct SPTask *gGfxSPTask;
extern Gfx *gDisplayListHead;
@@ -51,7 +51,7 @@ extern struct DemoInput gRecordedDemoInput;
// this area is the demo input + the header. when the demo is loaded in, there is a header the size
// of a single word next to the input list. this word is the current ID count.
-extern struct MarioAnimation D_80339D10[2];
+extern struct MarioAnimation D_80339D10[MAX_PLAYERS];
extern struct MarioAnimation gDemo;
extern u8 gMarioAnims[];
diff --git a/src/game/interaction.c b/src/game/interaction.c
index 7465f7e3b..aee362c79 100644
--- a/src/game/interaction.c
+++ b/src/game/interaction.c
@@ -748,6 +748,11 @@ void reset_mario_pitch(struct MarioState *m) {
}
u32 interact_coin(struct MarioState *m, UNUSED u32 interactType, struct Object *o) {
+ if (m != &gMarioStates[0]) {
+ // only collect locally
+ return FALSE;
+ }
+
m->numCoins += o->oDamageOrCoinValue;
m->healCounter += 4 * o->oDamageOrCoinValue;
@@ -762,6 +767,8 @@ u32 interact_coin(struct MarioState *m, UNUSED u32 interactType, struct Object *
queue_rumble_data(5, 80);
}
+ network_send_collect_coin(o);
+
return FALSE;
}
diff --git a/src/game/obj_behaviors.c b/src/game/obj_behaviors.c
index 2c1e2c05b..ab0e4c03a 100644
--- a/src/game/obj_behaviors.c
+++ b/src/game/obj_behaviors.c
@@ -672,6 +672,7 @@ s32 obj_find_wall_displacement(Vec3f dist, f32 x, f32 y, f32 z, f32 radius) {
void obj_spawn_yellow_coins(struct Object *obj, s8 nCoins) {
struct Object *coin;
s8 count;
+ force_replicable_seed(TRUE);
for (count = 0; count < nCoins; count++) {
coin = spawn_object(obj, MODEL_YELLOW_COIN, bhvMovingYellowCoin);
diff --git a/src/game/obj_behaviors_2.c b/src/game/obj_behaviors_2.c
index 7ee925abc..a7c7db73e 100644
--- a/src/game/obj_behaviors_2.c
+++ b/src/game/obj_behaviors_2.c
@@ -743,6 +743,7 @@ static s32 obj_handle_attacks(struct ObjectHitbox *hitbox, s32 attackedMarioActi
break;
case ATTACK_HANDLER_SQUISHED_WITH_BLUE_COIN:
+ force_replicable_seed(TRUE);
o->oNumLootCoins = -1;
obj_set_squished_action();
break;
diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c
index 99d0702f7..b13d3d9bd 100644
--- a/src/game/object_helpers.c
+++ b/src/game/object_helpers.c
@@ -1634,6 +1634,7 @@ static void obj_spawn_loot_coins(struct Object *obj, s32 numCoins, f32 sp30,
spawnHeight = obj->oPosY;
}
+ force_replicable_seed(TRUE);
for (i = 0; i < numCoins; i++) {
if (obj->oNumLootCoins <= 0) {
break;
@@ -1657,6 +1658,7 @@ void obj_spawn_loot_yellow_coins(struct Object *obj, s32 numCoins, f32 sp28) {
}
void cur_obj_spawn_loot_coin_at_mario_pos(void) {
+ force_replicable_seed(TRUE);
struct Object *coin;
if (o->oNumLootCoins <= 0) {
return;
@@ -2940,6 +2942,7 @@ s32 cur_obj_check_interacted(void) {
}
void cur_obj_spawn_loot_blue_coin(void) {
+ force_replicable_seed(TRUE);
if (o->oNumLootCoins >= 5) {
spawn_object(o, MODEL_BLUE_COIN, bhvMrIBlueCoin);
o->oNumLootCoins -= 5;
diff --git a/src/pc/network/network.c b/src/pc/network/network.c
index ad38690fc..99a7b5ef5 100644
--- a/src/pc/network/network.c
+++ b/src/pc/network/network.c
@@ -111,6 +111,7 @@ void network_update(void) {
case PACKET_LEVEL_WARP: network_receive_level_warp(&p); break;
case PACKET_INSIDE_PAINTING: network_receive_inside_painting(&p); break;
case PACKET_COLLECT_STAR: network_receive_collect_star(&p); break;
+ case PACKET_COLLECT_COIN: network_receive_collect_coin(&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 0260742d0..b49ce109d 100644
--- a/src/pc/network/network.h
+++ b/src/pc/network/network.h
@@ -21,6 +21,7 @@ enum PacketType {
PACKET_LEVEL_WARP,
PACKET_INSIDE_PAINTING,
PACKET_COLLECT_STAR,
+ PACKET_COLLECT_COIN,
};
struct Packet {
@@ -84,4 +85,7 @@ void network_receive_inside_painting(struct Packet* p);
void network_send_collect_star(s16 coinScore, s16 starIndex);
void network_receive_collect_star(struct Packet* p);
+
+void network_send_collect_coin(struct Object* o);
+void network_receive_collect_coin(struct Packet* p);
#endif
diff --git a/src/pc/network/packets/packet_collect_coin.c b/src/pc/network/packets/packet_collect_coin.c
new file mode 100644
index 000000000..66d7f80a6
--- /dev/null
+++ b/src/pc/network/packets/packet_collect_coin.c
@@ -0,0 +1,121 @@
+#include
+#include "../network.h"
+#include "object_fields.h"
+#include "object_constants.h"
+#include "course_table.h"
+#include "src/game/interaction.h"
+#include "src/engine/math_util.h"
+
+static u8 localCoinId = 1;
+
+// the remoteCoinId stuff is only valid for 'luigi' aka the one remote player
+// will need to be extended if MAX_PLAYERS is ever increased
+#define MAX_REMOTE_COIN_IDS 16
+static u8 remoteCoinIds[MAX_REMOTE_COIN_IDS] = { 0 };
+static u8 onRemoteCoinId = 0;
+
+static f32 dist_to_pos(struct Object* o, f32* pos) {
+ f32 x = (f32)o->oPosX - pos[0]; x *= x;
+ f32 y = (f32)o->oPosY - pos[1]; y *= y;
+ f32 z = (f32)o->oPosZ - pos[2]; z *= z;
+ return (f32)sqrt(x + y + z);
+}
+
+static struct Object* find_nearest_coin(const BehaviorScript *behavior, f32* pos, s32 coinValue, float minDist) {
+ uintptr_t *behaviorAddr = segmented_to_virtual(behavior);
+ struct Object *closestObj = NULL;
+ struct Object *obj;
+ struct ObjectNode *listHead;
+
+ extern struct ObjectNode *gObjectLists;
+ listHead = &gObjectLists[get_object_list_from_behavior(behaviorAddr)];
+ obj = (struct Object *) listHead->next;
+
+ while (obj != (struct Object *) listHead) {
+ if (obj->behavior == behaviorAddr && obj->activeFlags != ACTIVE_FLAG_DEACTIVATED && obj->oDamageOrCoinValue == coinValue) {
+ f32 objDist = dist_to_pos(obj, pos);
+ if (objDist < minDist) {
+ closestObj = obj;
+ minDist = objDist;
+ }
+ }
+ obj = (struct Object *) obj->header.next;
+ }
+
+ return closestObj;
+}
+
+void network_send_collect_coin(struct Object* o) {
+ struct Packet p;
+ packet_init(&p, PACKET_COLLECT_COIN, true);
+
+ packet_write(&p, &localCoinId, sizeof(u8));
+ packet_write(&p, &o->behavior, sizeof(void*));
+ packet_write(&p, &o->oPosX, sizeof(f32) * 3);
+ packet_write(&p, &gMarioStates[0].numCoins, sizeof(s16));
+ packet_write(&p, &o->oDamageOrCoinValue, sizeof(s32));
+
+ network_send(&p);
+ localCoinId++;
+}
+
+void network_receive_collect_coin(struct Packet* p) {
+ u8 remoteCoinId = 0;
+ void* behavior = NULL;
+ f32 pos[3] = { 0 };
+ s16 numCoins = 0;
+ s32 coinValue = 0;
+
+ packet_read(p, &remoteCoinId, sizeof(u8));
+ packet_read(p, &behavior, sizeof(void*));
+ packet_read(p, &pos, sizeof(f32) * 3);
+ packet_read(p, &numCoins, sizeof(s16));
+ packet_read(p, &coinValue, sizeof(s32));
+
+ // check if remote coin id has already been seen
+ for (int i = 0; i < MAX_REMOTE_COIN_IDS; i++) {
+ if (remoteCoinIds[i] == remoteCoinId) {
+ // we already saw this coin!
+ goto SANITY_CHECK_COINS;
+ }
+ }
+ // cache the seen id
+ remoteCoinIds[onRemoteCoinId] = remoteCoinId;
+ onRemoteCoinId = (onRemoteCoinId + 1) % MAX_REMOTE_COIN_IDS;
+
+ // make sure it's valid
+ if (behavior == NULL) { goto SANITY_CHECK_COINS; }
+
+ // find the coin
+ struct Object* coin = find_nearest_coin(behavior, pos, coinValue, 1000);
+ if (coin == NULL) { goto SANITY_CHECK_COINS; }
+
+ // destroy coin
+ coin->oInteractStatus = INT_STATUS_INTERACTED;
+
+ // add to local mario's coin count
+ gMarioStates[0].numCoins += coinValue;
+
+ // check for 100-coin star
+ extern s16 gCurrCourseNum;
+ if (COURSE_IS_MAIN_COURSE(gCurrCourseNum)
+ && gMarioStates[0].numCoins - coin->oDamageOrCoinValue < 100
+ && gMarioStates[0].numCoins >= 100) {
+ bhv_spawn_star_no_level_exit(6);
+ }
+
+ return;
+
+SANITY_CHECK_COINS:;
+ // make sure we're at least at the same coin count
+ s16 oldCoinCount = gMarioStates[0].numCoins;
+ gMarioStates[0].numCoins = max(numCoins, gMarioStates[0].numCoins);
+
+ // check for 100-coin star
+ if (COURSE_IS_MAIN_COURSE(gCurrCourseNum)
+ && oldCoinCount < 100
+ && gMarioStates[0].numCoins >= 100) {
+ bhv_spawn_star_no_level_exit(6);
+ }
+
+}