diff --git a/developer/network.sh b/developer/network.sh index 0d095ced..b274aefe 100644 --- a/developer/network.sh +++ b/developer/network.sh @@ -18,10 +18,10 @@ fi #exit # no debug, direct -#$FILE --server 27015 --configfile sm64config_server.txt & -#sleep 7 -#$FILE --client 127.0.0.1 27015 --configfile sm64config_client.txt & -#exit +$FILE --server 27015 --configfile sm64config_server.txt & +sleep 7 +$FILE --client 127.0.0.1 27015 --configfile sm64config_client.txt & +exit # debug on server #$FILE --client 127.0.0.1 27015 --configfile sm64config_client.txt & diff --git a/src/game/object_list_processor.c b/src/game/object_list_processor.c index 1b3c33f7..7fe4d860 100644 --- a/src/game/object_list_processor.c +++ b/src/game/object_list_processor.c @@ -424,18 +424,28 @@ s32 unload_deactivated_objects_in_list(struct ObjectNode *objList) { void set_object_respawn_info_bits(struct Object *obj, u8 bits) { u32 *info32; u16 *info16; + u8 oldRespawnInfoBits = 0; + u8 newRespawnInfoBits = 0; switch (obj->respawnInfoType) { case RESPAWN_INFO_TYPE_32: info32 = (u32 *) obj->respawnInfo; + oldRespawnInfoBits = (u8)(*info32 >> 8); *info32 |= bits << 8; + newRespawnInfoBits = (u8)(*info32 >> 8); break; case RESPAWN_INFO_TYPE_16: info16 = (u16 *) obj->respawnInfo; + oldRespawnInfoBits = (u8)(*info16 >> 8); *info16 |= bits << 8; + newRespawnInfoBits = (u8)(*info16 >> 8); break; } + + if (newRespawnInfoBits != oldRespawnInfoBits) { + network_send_level_respawn_info(obj, newRespawnInfoBits); + } } /** diff --git a/src/pc/network/packets/packet.c b/src/pc/network/packets/packet.c index 5ec629aa..562cc693 100644 --- a/src/pc/network/packets/packet.c +++ b/src/pc/network/packets/packet.c @@ -72,6 +72,7 @@ void packet_receive(struct Packet* p) { case PACKET_LEVEL_SPAWN_INFO: network_receive_level_spawn_info(p); break; case PACKET_LEVEL_MACRO: network_receive_level_macro(p); break; case PACKET_LEVEL_AREA_INFORM: network_receive_level_area_inform(p); break; + case PACKET_LEVEL_RESPAWN_INFO: network_receive_level_respawn_info(p); break; // custom case PACKET_CUSTOM: network_receive_custom(p); break; diff --git a/src/pc/network/packets/packet.h b/src/pc/network/packets/packet.h index 455fa555..e4e3f8e3 100644 --- a/src/pc/network/packets/packet.h +++ b/src/pc/network/packets/packet.h @@ -45,6 +45,7 @@ enum PacketType { PACKET_LEVEL_SPAWN_INFO, PACKET_LEVEL_MACRO, PACKET_LEVEL_AREA_INFORM, + PACKET_LEVEL_RESPAWN_INFO, /// PACKET_CUSTOM = 255, @@ -224,4 +225,8 @@ void network_receive_level_macro(struct Packet* p); void network_send_level_area_inform(struct NetworkPlayer* np); void network_receive_level_area_inform(struct Packet* p); +// packet_level_respawn_info.c +void network_send_level_respawn_info(struct Object* o, u8 respawnInfoBits); +void network_receive_level_respawn_info(struct Packet* p); + #endif diff --git a/src/pc/network/packets/packet_level_macro.c b/src/pc/network/packets/packet_level_macro.c index 45455a12..0e28adeb 100644 --- a/src/pc/network/packets/packet_level_macro.c +++ b/src/pc/network/packets/packet_level_macro.c @@ -14,12 +14,12 @@ #define DISABLE_MODULE_LOG 1 #include "pc/debuglog.h" +// TODO: move to common utility location static struct Object* get_object_matching_respawn_info(s16* respawnInfo) { for (int i = 0; i < OBJECT_POOL_CAPACITY; i++) { struct Object* o = &gObjectPool[i]; if (o->respawnInfo == respawnInfo) { return o; } } - return NULL; } diff --git a/src/pc/network/packets/packet_level_respawn_info.c b/src/pc/network/packets/packet_level_respawn_info.c new file mode 100644 index 00000000..28cc50c4 --- /dev/null +++ b/src/pc/network/packets/packet_level_respawn_info.c @@ -0,0 +1,214 @@ +#include +#include "../network.h" +#include "game/interaction.h" +#include "game/object_list_processor.h" +#include "game/object_helpers.h" +#include "game/interaction.h" +#include "game/level_update.h" +#include "game/macro_special_objects.h" +#include "object_constants.h" +#include "object_fields.h" +#include "behavior_table.h" +#include "model_ids.h" +//#define DISABLE_MODULE_LOG 1 +#include "pc/debuglog.h" + +#define ERR_COULD_NOT_FIND_OBJECT ((u16)-1) + +// TODO: These respawn info changes REALLY need to be batched and sent on a timer instead of immediately. +// currently when collecting coins a flood of packets gets sent out when there is no immediate need +// to know when a coin is collected in a different area. +// Ideally this logic would be combined into packet_level_macro and packet_level_spawn_info without +// being bolted on top like this. + +static s16* get_respawn_info_from_macro_offset(u16 areaIndex, u16 macroOffset) { + // loop through macro objects for santiziation + u16 maxOffset = 0; + s16* macroObjList = gAreaData[areaIndex].macroObjects; + while (macroObjList != NULL && *macroObjList != -1) { + macroObjList += 4; + s16* respawnInfo = macroObjList++; + maxOffset = respawnInfo - gAreaData[areaIndex].macroObjects; + } + + // sanitize array + if (macroOffset > maxOffset) { return NULL; } + + return gAreaData[areaIndex].macroObjects + macroOffset; +} + +static u32* get_respawn_info_from_spawn_info_index(u16 areaIndex, u16 fromSpawnInfoIndex) { + struct SpawnInfo* spawnInfo = gAreaData[areaIndex].objectSpawnInfos; + u16 spawnInfoIndex = 0; + + while (spawnInfo != NULL) { + if (spawnInfoIndex == fromSpawnInfoIndex) { + return &spawnInfo->behaviorArg; + } + + spawnInfo = spawnInfo->next; + spawnInfoIndex++; + } + + return NULL; +} + +static u16 get_macro_offset_of_object(struct Object* o) { + // loop through macro objects to find object + s16* macroObjList = gCurrentArea->macroObjects; + while (macroObjList != NULL && *macroObjList != -1) { + // grab preset ID + s32 presetID = (*macroObjList & 0x1FF) - 31; // Preset identifier for MacroObjectPresets array + if (presetID < 0) { break; } + + // parse respawn info + macroObjList += 4; + s16* respawnInfo = macroObjList++; + + if (o->respawnInfo == respawnInfo) { + return (respawnInfo - gCurrentArea->macroObjects); + } + } + + return ERR_COULD_NOT_FIND_OBJECT; +} + +static u16 get_spawn_info_index_of_object(struct Object* o) { + // loop through spawn infos to find object + struct SpawnInfo* spawnInfo = gCurrentArea->objectSpawnInfos; + u16 spawnInfoIndex = 0; + + while (spawnInfo != NULL) { + // if a spawn info object was destroyed, send its spawn info index + if (&spawnInfo->behaviorArg == o->respawnInfo) { + return spawnInfoIndex; + } + spawnInfo = spawnInfo->next; + spawnInfoIndex++; + } + + return ERR_COULD_NOT_FIND_OBJECT; +} + +//// + +void network_send_level_respawn_info(struct Object* o, u8 respawnInfoBits) { + // make sure our area is valid + if (!gNetworkPlayerLocal->currAreaSyncValid) { + LOG_ERROR("my area is invalid"); + return; + } + + // make sure we can find the object + u16 macroOffset = get_macro_offset_of_object(o); + u16 spawnInfoIndex = get_spawn_info_index_of_object(o); + if (macroOffset == ERR_COULD_NOT_FIND_OBJECT && spawnInfoIndex == ERR_COULD_NOT_FIND_OBJECT) { + LOG_INFO("could not find object in macro or spawn info"); + return; + } + bool isMacroObject = (macroOffset != ERR_COULD_NOT_FIND_OBJECT); + + // write header + struct Packet p; + packet_init(&p, PACKET_LEVEL_RESPAWN_INFO, true, false); + packet_write(&p, &gCurrCourseNum, sizeof(s16)); + packet_write(&p, &gCurrActNum, sizeof(s16)); + packet_write(&p, &gCurrLevelNum, sizeof(s16)); + packet_write(&p, &gCurrAreaIndex, sizeof(s16)); + + // write object info + packet_write(&p, &isMacroObject, sizeof(u8)); + packet_write(&p, isMacroObject + ? ¯oOffset + : &spawnInfoIndex, sizeof(u16)); + packet_write(&p, &respawnInfoBits, sizeof(u8)); + + // send the packet + if (gNetworkType == NT_SERVER) { + // broadcast + for (int i = 0; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (!np->connected) { continue; } + if (!np->currLevelSyncValid) { continue; } + if (np->currCourseNum != gCurrCourseNum) { continue; } + if (np->currActNum != gCurrActNum) { continue; } + if (np->currLevelNum != gCurrLevelNum) { continue; } + struct Packet p2; + packet_duplicate(&p, &p2); + network_send_to(np->localIndex, &p2); + } + } else { + network_send_to(gNetworkPlayerServer->localIndex, &p); + } + + LOG_INFO("tx level respawn info"); +} + +void network_receive_level_respawn_info(struct Packet* p) { + LOG_INFO("rx level respawn info"); + + // read header + s16 courseNum, actNum, levelNum, areaIndex; + packet_read(p, &courseNum, sizeof(s16)); + packet_read(p, &actNum, sizeof(s16)); + packet_read(p, &levelNum, sizeof(s16)); + packet_read(p, &areaIndex, sizeof(s16)); + + // read object info + bool isMacroObject; + u16 offsetOrIndex; + u8 respawnInfoBits; + packet_read(p, &isMacroObject, sizeof(u8)); + packet_read(p, &offsetOrIndex, sizeof(u16)); + packet_read(p, &respawnInfoBits, sizeof(u8)); + + + bool levelMismatch = (courseNum != gCurrCourseNum || actNum != gCurrActNum || levelNum != gCurrLevelNum); + if (gNetworkType == NT_SERVER) { + // ensure we got the info from a valid player + struct NetworkPlayer* npFrom = &gNetworkPlayers[p->localIndex]; + if (npFrom == NULL || npFrom->localIndex == UNKNOWN_LOCAL_INDEX || !npFrom->connected) { + LOG_ERROR("Receiving 'level respawn info' from inactive player!"); + return; + } + + // broadcast this change to the other players in that level + for (int i = 0; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (!np->connected) { continue; } + if (!np->currLevelSyncValid) { continue; } + if (np->currCourseNum != courseNum) { continue; } + if (np->currActNum != actNum) { continue; } + if (np->currLevelNum != levelNum) { continue; } + if (np == npFrom) { continue; } + struct Packet p2; + packet_duplicate(p, &p2); + network_send_to(np->localIndex, &p2); + } + + // do not have the server apply the changes unless their level matches + if (levelMismatch) { return; } + } else if (levelMismatch) { + LOG_ERROR("Receiving 'level respawn info' with the wrong location!"); + return; + } + + if (isMacroObject) { + s16* respawnInfo = get_respawn_info_from_macro_offset(areaIndex, offsetOrIndex); + if (respawnInfo == NULL) { + LOG_ERROR("Could not find respawn info from macro offset"); + return; + } + // apply the change + *respawnInfo |= respawnInfoBits << 8; + } else { + u32* respawnInfo = get_respawn_info_from_spawn_info_index(areaIndex, offsetOrIndex); + if (respawnInfo == NULL) { + LOG_ERROR("Could not find respawn info from spawn info index"); + return; + } + // apply the change + *respawnInfo |= respawnInfoBits << 8; + } + LOG_INFO("rx level respawn info (success!)"); +} \ No newline at end of file diff --git a/src/pc/network/packets/packet_level_spawn_info.c b/src/pc/network/packets/packet_level_spawn_info.c index 17990156..fbd55451 100644 --- a/src/pc/network/packets/packet_level_spawn_info.c +++ b/src/pc/network/packets/packet_level_spawn_info.c @@ -13,12 +13,12 @@ #define DISABLE_MODULE_LOG 1 #include "pc/debuglog.h" +// TODO: move to common utility location static struct Object* get_object_matching_respawn_info(u32* respawnInfo) { for (int i = 0; i < OBJECT_POOL_CAPACITY; i++) { struct Object* o = &gObjectPool[i]; if (o->respawnInfo == respawnInfo) { return o; } } - return NULL; }