Rewrote how RNG is synchronized

The old implementation was hacky and often returned desynchronized
results. This implementation gives a seed to each sync object, and
synchronizes that seed. Also allows for a seed to be saved temporarily
based on position and recycled for multiple calls.
This commit is contained in:
MysterD 2020-10-05 19:15:13 -07:00
parent 87074ef098
commit b831acd59c
22 changed files with 116 additions and 56 deletions

View file

@ -3883,6 +3883,7 @@
<ClCompile Include="..\src\game\print.c" />
<ClCompile Include="..\src\game\profiler.c" />
<ClCompile Include="..\src\game\rendering_graph_node.c" />
<ClCompile Include="..\src\game\rng_position.c" />
<ClCompile Include="..\src\game\save_file.c" />
<ClCompile Include="..\src\game\screen_transition.c" />
<ClCompile Include="..\src\game\shadow.c" />
@ -4324,6 +4325,7 @@
<ItemGroup>
<ClInclude Include="..\include\behavior_table.h" />
<ClInclude Include="..\src\game\chat.h" />
<ClInclude Include="..\src\game\rng_position.h" />
<ClInclude Include="..\src\menu\custom_menu.h" />
<ClInclude Include="..\src\menu\custom_menu_system.h" />
<ClInclude Include="..\src\pc\controller\controller_keyboard_debug.h" />

View file

@ -15075,6 +15075,9 @@
<ClCompile Include="..\src\pc\utils\string_linked_list.c">
<Filter>Source Files\src\pc\utils</Filter>
</ClCompile>
<ClCompile Include="..\src\game\rng_position.c">
<Filter>Source Files\src\game</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\actors\common0.h">
@ -16030,5 +16033,8 @@
<ClInclude Include="..\src\pc\utils\string_linked_list.h">
<Filter>Source Files\src\pc\utils</Filter>
</ClInclude>
<ClInclude Include="..\src\game\rng_position.h">
<Filter>Source Files\src\game</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -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;
}

View file

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

View file

@ -45,6 +45,7 @@
#include "spawn_sound.h"
#include "thread6.h"
#include "area.h"
#include "game/rng_position.h"
#define o gCurrentObject

View file

@ -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) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)) {

View file

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

View file

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

View file

@ -20,6 +20,7 @@
#include "segment2.h"
#include "segment_symbols.h"
#include "thread6.h"
#include "rng_position.h"
#include <prevent_bss_reordering.h>
#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();
}

View file

@ -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();
}
/**

View file

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

View file

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

50
src/game/rng_position.c Normal file
View file

@ -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;
}
}
}

18
src/game/rng_position.h Normal file
View file

@ -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

View file

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

View file

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