diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj
index e066021fe..c5052b8be 100644
--- a/build-windows-visual-studio/sm64ex.vcxproj
+++ b/build-windows-visual-studio/sm64ex.vcxproj
@@ -3883,6 +3883,7 @@
+
@@ -4324,6 +4325,7 @@
+
diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters
index 033a6c51f..b33547f45 100644
--- a/build-windows-visual-studio/sm64ex.vcxproj.filters
+++ b/build-windows-visual-studio/sm64ex.vcxproj.filters
@@ -15075,6 +15075,9 @@
Source Files\src\pc\utils
+
+ Source Files\src\game
+
@@ -16030,5 +16033,8 @@
Source Files\src\pc\utils
+
+ Source Files\src\game
+
\ No newline at end of file
diff --git a/src/engine/behavior_script.c b/src/engine/behavior_script.c
index 3226789be..929bc7987 100644
--- a/src/engine/behavior_script.c
+++ b/src/engine/behavior_script.c
@@ -14,6 +14,7 @@
#include "graph_node.h"
#include "surface_collision.h"
#include "pc/network/network.h"
+#include "game/rng_position.h"
// Macros for retrieving arguments from behavior scripts.
#define BHV_CMD_GET_1ST_U8(index) (u8)((gCurBhvCommand[index] >> 24) & 0xFF) // unused
@@ -30,7 +31,6 @@
#define BHV_CMD_GET_ADDR_OF_CMD(index) (uintptr_t)(&gCurBhvCommand[index])
static u16 gRandomSeed16;
-static u16 gSavedSeed16;
// Unused function that directly jumps to a behavior command and resets the object's stack index.
static void goto_behavior_unused(const BehaviorScript *bhvAddr) {
@@ -38,44 +38,21 @@ static void goto_behavior_unused(const BehaviorScript *bhvAddr) {
gCurrentObject->bhvStackIndex = 0;
}
-void force_replicable_seed(u8 always) {
- // force the seed to consistent values
- 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) {
- gSavedSeed16 = 0;
- return;
- }
- 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;
- }
-}
-
// Generate a pseudorandom integer from 0 to 65535 from the random seed, and update the seed.
u16 random_u16(void) {
- // restore random seed when applicable
- if (gSavedSeed16 != 0) {
- gRandomSeed16 = gSavedSeed16;
- gSavedSeed16 = 0;
- }
+ u16 savedSeed = gRandomSeed16;
+ struct SyncObject* so = NULL;
- // override this function for synchronized entities
- if (gCurrentObject->oSyncID != 0) {
- struct SyncObject* so = &gSyncObjects[gCurrentObject->oSyncID];
- if (so->o != NULL && !so->keepRandomSeed) {
- gSavedSeed16 = gRandomSeed16;
- force_replicable_seed(FALSE);
+ if (gOverrideRngPosition != NULL) {
+ // override this function for rng positions
+ gRandomSeed16 = gOverrideRngPosition->seed;
+ } else if (gCurrentObject->oSyncID != 0) {
+ // override this function for synchronized entities
+ so = &gSyncObjects[gCurrentObject->oSyncID];
+ if (so->o == gCurrentObject) {
+ gRandomSeed16 = so->randomSeed;
+ } else {
+ so = NULL;
}
}
@@ -103,6 +80,17 @@ u16 random_u16(void) {
gRandomSeed16 = temp2 ^ 0x8180;
}
+ // restore seed
+ if (gOverrideRngPosition != NULL) {
+ gOverrideRngPosition->seed = gRandomSeed16;
+ gRandomSeed16 = savedSeed;
+ return gOverrideRngPosition->seed;
+ } else if (so != NULL) {
+ so->randomSeed = gRandomSeed16;
+ gRandomSeed16 = savedSeed;
+ return so->randomSeed;
+ }
+
return gRandomSeed16;
}
diff --git a/src/engine/behavior_script.h b/src/engine/behavior_script.h
index e99e6abca..92ba10993 100644
--- a/src/engine/behavior_script.h
+++ b/src/engine/behavior_script.h
@@ -19,7 +19,6 @@
#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/behavior_actions.c b/src/game/behavior_actions.c
index 8a622a106..61fc8b953 100644
--- a/src/game/behavior_actions.c
+++ b/src/game/behavior_actions.c
@@ -45,6 +45,7 @@
#include "spawn_sound.h"
#include "thread6.h"
#include "area.h"
+#include "game/rng_position.h"
#define o gCurrentObject
diff --git a/src/game/behaviors/bowling_ball.inc.c b/src/game/behaviors/bowling_ball.inc.c
index 249a4bfed..3e44dc713 100644
--- a/src/game/behaviors/bowling_ball.inc.c
+++ b/src/game/behaviors/bowling_ball.inc.c
@@ -182,7 +182,6 @@ void bhv_generic_bowling_ball_spawner_init(void) {
void bhv_generic_bowling_ball_spawner_loop(void) {
if (!network_sync_object_initialized(o)) {
struct SyncObject* so = network_init_object(o, SYNC_DISTANCE_ONLY_EVENTS);
- so->keepRandomSeed = TRUE;
}
struct Object *bowlingBall;
@@ -219,7 +218,6 @@ void bhv_generic_bowling_ball_spawner_loop(void) {
void bhv_thi_bowling_ball_spawner_loop(void) {
if (!network_sync_object_initialized(o)) {
struct SyncObject* so = network_init_object(o, SYNC_DISTANCE_ONLY_EVENTS);
- so->keepRandomSeed = TRUE;
}
struct Object *bowlingBall;
@@ -255,7 +253,6 @@ void bhv_bob_pit_bowling_ball_init(void) {
struct SyncObject* so = network_init_object(o, 5000.0f);
so->maxUpdateRate = 5.0f;
- so->keepRandomSeed = TRUE;
}
void bhv_bob_pit_bowling_ball_loop(void) {
diff --git a/src/game/behaviors/bully.inc.c b/src/game/behaviors/bully.inc.c
index 80a737c9b..3909ffd14 100644
--- a/src/game/behaviors/bully.inc.c
+++ b/src/game/behaviors/bully.inc.c
@@ -204,7 +204,6 @@ 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/checkerboard_platform.inc.c b/src/game/behaviors/checkerboard_platform.inc.c
index bd1118693..c7e5f7e96 100644
--- a/src/game/behaviors/checkerboard_platform.inc.c
+++ b/src/game/behaviors/checkerboard_platform.inc.c
@@ -8,7 +8,6 @@ void bhv_checkerboard_elevator_group_init(void) {
struct SyncObject* so = network_init_object(o, 1000.0f);
so->hasStandardFields = FALSE;
so->maxUpdateRate = 5.0f;
- so->keepRandomSeed = TRUE;
s32 sp3C;
s32 sp38;
s32 sp34;
diff --git a/src/game/behaviors/coin.inc.c b/src/game/behaviors/coin.inc.c
index 80387b2ae..3cc8584e5 100644
--- a/src/game/behaviors/coin.inc.c
+++ b/src/game/behaviors/coin.inc.c
@@ -49,10 +49,11 @@ void bhv_temp_coin_loop(void) {
}
void bhv_coin_init(void) {
- force_replicable_seed(FALSE);
+ rng_position_init(o->oPosX, o->oPosY, o->oPosZ);
o->oVelY = random_float() * 10.0f + 30 + o->oCoinUnk110;
o->oForwardVel = random_float() * 10.0f;
o->oMoveAngleYaw = random_u16();
+ rng_position_finish();
cur_obj_set_behavior(bhvYellowCoin);
obj_set_hitbox(o, &sYellowCoinHitbox);
cur_obj_become_intangible();
diff --git a/src/game/behaviors/jrb_ship.inc.c b/src/game/behaviors/jrb_ship.inc.c
index 144915608..3011b32d1 100644
--- a/src/game/behaviors/jrb_ship.inc.c
+++ b/src/game/behaviors/jrb_ship.inc.c
@@ -24,7 +24,6 @@ void bhv_ship_part_3_loop(void) {
if (!network_sync_object_initialized(o)) {
struct SyncObject* so = network_init_object(o, 4000.0f);
so->maxUpdateRate = 5.0f;
- so->keepRandomSeed = TRUE;
network_init_object_field(o, &o->oFaceAnglePitch);
network_init_object_field(o, &o->oFaceAngleRoll);
network_init_object_field(o, &o->oShipPart3UnkF4);
@@ -46,7 +45,6 @@ void bhv_jrb_sliding_box_loop(void) {
if (!network_sync_object_initialized(o)) {
struct SyncObject* so = network_init_object(o, 4000.0f);
so->maxUpdateRate = 5.0f;
- so->keepRandomSeed = TRUE;
network_init_object_field(o, &o->oFaceAnglePitch);
network_init_object_field(o, &o->oFaceAngleRoll);
network_init_object_field(o, &o->oJrbSlidingBoxUnkF8);
diff --git a/src/game/behaviors/lll_hexagonal_ring.inc.c b/src/game/behaviors/lll_hexagonal_ring.inc.c
index 809d4eb6e..7b6388b12 100644
--- a/src/game/behaviors/lll_hexagonal_ring.inc.c
+++ b/src/game/behaviors/lll_hexagonal_ring.inc.c
@@ -17,7 +17,6 @@ void hexagonal_ring_spawn_flames(void) {
void bhv_lll_rotating_hexagonal_ring_loop(void) {
if (!network_sync_object_initialized(o)) {
struct SyncObject* so = network_init_object(o, 4000.0f);
- so->keepRandomSeed = FALSE;
network_init_object_field(o, &o->oAngleVelYaw);
}
UNUSED s32 unused;
diff --git a/src/game/behaviors/platform_on_track.inc.c b/src/game/behaviors/platform_on_track.inc.c
index d878dffe6..7508cb674 100644
--- a/src/game/behaviors/platform_on_track.inc.c
+++ b/src/game/behaviors/platform_on_track.inc.c
@@ -57,7 +57,6 @@ void bhv_platform_on_track_init(void) {
struct SyncObject* so = network_init_object(o, 1000.0f);
so->fullObjectSync = TRUE;
so->maxUpdateRate = 5.0f;
- so->keepRandomSeed = TRUE;
}
if (!(o->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM)) {
diff --git a/src/game/behaviors/water_bomb.inc.c b/src/game/behaviors/water_bomb.inc.c
index 6da769e17..73b639b41 100644
--- a/src/game/behaviors/water_bomb.inc.c
+++ b/src/game/behaviors/water_bomb.inc.c
@@ -33,7 +33,6 @@ void bhv_water_bomb_spawner_update(void) {
struct SyncObject* so = network_init_object(o, SYNC_DISTANCE_ONLY_EVENTS);
so->fullObjectSync = TRUE;
so->maxUpdateRate = 5.0f;
- so->keepRandomSeed = TRUE;
network_init_object_field(o, &o->oWaterBombSpawnerBombActive);
network_init_object_field(o, &o->oWaterBombSpawnerTimeToSpawn);
}
diff --git a/src/game/behaviors/yoshi.inc.c b/src/game/behaviors/yoshi.inc.c
index e8a9e1043..62e9e3575 100644
--- a/src/game/behaviors/yoshi.inc.c
+++ b/src/game/behaviors/yoshi.inc.c
@@ -29,7 +29,6 @@ void bhv_yoshi_init(void) {
struct SyncObject* so = network_init_object(o, 4000.0f);
so->ignore_if_true = bhv_yoshi_ignore_if_true;
so->override_ownership = bhv_yoshi_override_ownership;
- so->keepRandomSeed = TRUE;
network_init_object_field(o, &o->oYoshiBlinkTimer);
network_init_object_field(o, &o->oYoshiChosenHome);
network_init_object_field(o, &o->oYoshiTargetYaw);
diff --git a/src/game/game_init.c b/src/game/game_init.c
index 556180525..100df4976 100644
--- a/src/game/game_init.c
+++ b/src/game/game_init.c
@@ -20,6 +20,7 @@
#include "segment2.h"
#include "segment_symbols.h"
#include "thread6.h"
+#include "rng_position.h"
#include
#ifdef BETTERCAMERA
#include "bettercamera.h"
@@ -605,4 +606,7 @@ void game_loop_one_iteration(void) {
// amount of free space remaining.
print_text_fmt_int(180, 20, "BUF %d", gGfxPoolEnd - (u8 *) gDisplayListHead);
}
+
+ // custom coop hooks
+ rng_position_update();
}
diff --git a/src/game/obj_behaviors.c b/src/game/obj_behaviors.c
index 6574f6415..c50256b1a 100644
--- a/src/game/obj_behaviors.c
+++ b/src/game/obj_behaviors.c
@@ -32,6 +32,7 @@
#include "spawn_object.h"
#include "spawn_sound.h"
#include "pc/network/network.h"
+#include "game/rng_position.h"
/**
* @file obj_behaviors.c
@@ -668,14 +669,15 @@ 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);
+ rng_position_init(o->oPosX, o->oPosY, o->oPosZ);
for (count = 0; count < nCoins; count++) {
coin = spawn_object(obj, MODEL_YELLOW_COIN, bhvMovingYellowCoin);
coin->oForwardVel = random_float() * 20;
coin->oVelY = random_float() * 40 + 20;
coin->oMoveAngleYaw = random_u16();
}
+ rng_position_finish();
}
/**
diff --git a/src/game/obj_behaviors_2.c b/src/game/obj_behaviors_2.c
index ff34d39d7..0a68f7ee7 100644
--- a/src/game/obj_behaviors_2.c
+++ b/src/game/obj_behaviors_2.c
@@ -745,7 +745,6 @@ 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 47fd74902..894dec536 100644
--- a/src/game/object_helpers.c
+++ b/src/game/object_helpers.c
@@ -1705,7 +1705,6 @@ 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;
@@ -1729,7 +1728,6 @@ void obj_spawn_loot_yellow_coins(struct Object *obj, s32 numCoins, f32 sp28) {
}
void cur_obj_spawn_loot_coin_at_mario_pos(struct MarioState* m) {
- force_replicable_seed(TRUE);
struct Object *coin;
if (o->oNumLootCoins <= 0) {
return;
@@ -3031,7 +3029,6 @@ 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/game/rng_position.c b/src/game/rng_position.c
new file mode 100644
index 000000000..64d2ec038
--- /dev/null
+++ b/src/game/rng_position.c
@@ -0,0 +1,50 @@
+#include "rng_position.h"
+#include "engine/math_util.h"
+
+#define RNG_POSITION_MAX 16
+#define RNG_POSITION_MATCH_DIST 300
+#define RNG_POSITION_LIFE 30
+
+struct RngPosition sRngPosition[RNG_POSITION_MAX] = { 0 };
+struct RngPosition* gOverrideRngPosition = NULL;
+
+void rng_position_init(f32 x, f32 y, f32 z) {
+ Vec3f position = { x, y, z };
+ // try to find and update past rng position
+ for (u8 i = 0; i < RNG_POSITION_MAX; i++) {
+ if (sRngPosition[i].life == 0) { continue; }
+ Vec3f difference = { 0 };
+ vec3f_dif(difference, position, sRngPosition[i].position);
+ f32 length = vec3f_length(difference);
+ if (length >= RNG_POSITION_MATCH_DIST) { continue; }
+ sRngPosition[i].life = RNG_POSITION_LIFE;
+ gOverrideRngPosition = &sRngPosition[i];
+ return;
+ }
+
+ // try to store new rng position
+ for (u8 i = 0; i < RNG_POSITION_MAX; i++) {
+ if (sRngPosition[i].life != 0) { continue; }
+ sRngPosition[i].life = RNG_POSITION_LIFE;
+ sRngPosition[i].seed = (u16)((position[0] / (3 * RNG_POSITION_MATCH_DIST)) + (position[1] / (3 * RNG_POSITION_MATCH_DIST)));
+ vec3f_copy(sRngPosition[i].position, position);
+ gOverrideRngPosition = &sRngPosition[i];
+ return;
+ }
+}
+
+void rng_position_finish(void) {
+ gOverrideRngPosition = NULL;
+}
+
+void rng_position_update(void) {
+ // decrease life of all rng positions
+ for (u8 i = 0; i < RNG_POSITION_MAX; i++) {
+ if (sRngPosition[i].life == 0) { continue; }
+ sRngPosition[i].life--;
+ if (sRngPosition[i].life > 0) { continue; }
+ if (&sRngPosition[i] == gOverrideRngPosition) {
+ gOverrideRngPosition = NULL;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/game/rng_position.h b/src/game/rng_position.h
new file mode 100644
index 000000000..f98e8b472
--- /dev/null
+++ b/src/game/rng_position.h
@@ -0,0 +1,18 @@
+#ifndef RNG_POSITION_H
+#define RNG_POSITION_H
+
+#include "types.h"
+
+struct RngPosition {
+ Vec3f position;
+ u16 seed;
+ u8 life;
+};
+
+extern struct RngPosition* gOverrideRngPosition;
+
+void rng_position_init(f32 x, f32 y, f32 z);
+void rng_position_finish(void);
+void rng_position_update(void);
+
+#endif
\ No newline at end of file
diff --git a/src/pc/network/network.h b/src/pc/network/network.h
index 5ad600d4f..de287b240 100644
--- a/src/pc/network/network.h
+++ b/src/pc/network/network.h
@@ -49,9 +49,9 @@ struct SyncObject {
void* behavior;
u16 txEventId;
u16 rxEventId;
+ u16 randomSeed;
u8 extraFieldCount;
bool fullObjectSync;
- bool keepRandomSeed;
bool syncDeathEvent;
bool hasStandardFields;
float minUpdateRate;
diff --git a/src/pc/network/packets/packet_object.c b/src/pc/network/packets/packet_object.c
index 6b7b37e08..9659abe9a 100644
--- a/src/pc/network/packets/packet_object.c
+++ b/src/pc/network/packets/packet_object.c
@@ -63,7 +63,6 @@ struct SyncObject* network_init_object(struct Object *o, float maxSyncDistance)
so->rxEventId = 0;
so->txEventId = 0;
so->fullObjectSync = false;
- so->keepRandomSeed = false;
so->hasStandardFields = (maxSyncDistance >= 0);
so->minUpdateRate = 0.33f;
so->maxUpdateRate = 0.00f;
@@ -71,6 +70,7 @@ struct SyncObject* network_init_object(struct Object *o, float maxSyncDistance)
so->on_received_pre = NULL;
so->on_received_post = NULL;
so->syncDeathEvent = true;
+ so->randomSeed = (u16)(o->oSyncID * 7951);
memset(so->extraFields, 0, sizeof(void*) * MAX_SYNC_OBJECT_FIELDS);
return so;
@@ -143,6 +143,7 @@ static void packet_write_object_header(struct Packet* p, struct Object* o) {
packet_write(p, &o->oSyncID, sizeof(u32));
packet_write(p, &so->txEventId, sizeof(u16));
+ packet_write(p, &so->randomSeed, sizeof(u16));
packet_write(p, &behaviorId, sizeof(enum BehaviorId));
}
@@ -197,6 +198,9 @@ static struct SyncObject* packet_read_object_header(struct Packet* p) {
}
so->rxEventId = eventId;
+ // update the random seed
+ packet_read(p, &so->randomSeed, sizeof(u16));
+
// make sure the behaviors match
enum BehaviorId behaviorId;
packet_read(p, &behaviorId, sizeof(enum BehaviorId));