Synchronized coin collection

Reimplemented how randomness is synchronized
This commit is contained in:
MysterD 2020-08-07 18:01:58 -07:00
parent 5ec9ab9ec0
commit 444c1fdd3b
16 changed files with 182 additions and 43 deletions

View file

@ -3944,6 +3944,8 @@
<ClCompile Include="..\src\pc\ini.c" />
<ClCompile Include="..\src\pc\mixer.c" />
<ClCompile Include="..\src\pc\network\network.c" />
<ClCompile Include="..\src\pc\network\packets\packet_collect_coin.c" />
<ClCompile Include="..\src\pc\network\packets\packet_collect_star.c" />
<ClCompile Include="..\src\pc\network\packets\packet_inside_painting.c" />
<ClCompile Include="..\src\pc\network\packets\packet_level_warp.c" />
<ClCompile Include="..\src\pc\network\packets\packet_object.c" />

View file

@ -14961,6 +14961,12 @@
<ClCompile Include="..\src\pc\network\packets\packet_inside_painting.c">
<Filter>Source Files\src\pc\network\packets</Filter>
</ClCompile>
<ClCompile Include="..\src\pc\network\packets\packet_collect_star.c">
<Filter>Source Files\src\pc\network\packets</Filter>
</ClCompile>
<ClCompile Include="..\src\pc\network\packets\packet_collect_coin.c">
<Filter>Source Files\src\pc\network\packets</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\actors\common0.h">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -285,7 +285,6 @@ void huge_goomba_weakly_attacked(void) {
*/
void bhv_goomba_update(void) {
// PARTIAL_UPDATE
random_sync_reset();
f32 animSpeed;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,121 @@
#include <stdio.h>
#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);
}
}