From 444c1fdd3ba1ebf6bb34389cfd44bd5cb6950be4 Mon Sep 17 00:00:00 2001 From: MysterD Date: Fri, 7 Aug 2020 18:01:58 -0700 Subject: [PATCH] Synchronized coin collection Reimplemented how randomness is synchronized --- build-windows-visual-studio/sm64ex.vcxproj | 2 + .../sm64ex.vcxproj.filters | 6 + include/types.h | 10 ++ src/engine/behavior_script.c | 61 +++------ src/engine/behavior_script.h | 1 + src/game/behaviors/bully.inc.c | 1 + src/game/behaviors/coin.inc.c | 1 + src/game/behaviors/goomba.inc.c | 1 - src/game/game_init.h | 4 +- src/game/interaction.c | 7 + src/game/obj_behaviors.c | 1 + src/game/obj_behaviors_2.c | 1 + src/game/object_helpers.c | 3 + src/pc/network/network.c | 1 + src/pc/network/network.h | 4 + src/pc/network/packets/packet_collect_coin.c | 121 ++++++++++++++++++ 16 files changed, 182 insertions(+), 43 deletions(-) create mode 100644 src/pc/network/packets/packet_collect_coin.c 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); + } + +}