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