Added player connected/disconnected events

Changed synchronizing text to be more descriptive
Added 'player connected', 'player disconnected', 'network shutdown' chat messages
Prevented someone from joining through Discord while in another lobby
Added the distinction of sending a packet to all vs to a specific player
Enforced lobby size of 2, multiple joiners in a direct connection will be booted
Stored network destination for each player
Detected network drops
This commit is contained in:
MysterD 2020-09-18 23:06:26 -07:00
parent 6cf5e5da68
commit f8bffd3b2a
25 changed files with 564 additions and 79 deletions

View file

@ -3958,12 +3958,18 @@
<ClCompile Include="..\src\pc\network\discord\discord_network.c" />
<ClCompile Include="..\src\pc\network\discord\user.c" />
<ClCompile Include="..\src\pc\network\network.c" />
<ClCompile Include="..\src\pc\network\network_player.c" />
<ClCompile Include="..\src\pc\network\packets\packet.c" />
<ClCompile Include="..\src\pc\network\packets\packet_chat.c" />
<ClCompile Include="..\src\pc\network\packets\packet_collect_coin.c" />
<ClCompile Include="..\src\pc\network\packets\packet_collect_item.c" />
<ClCompile Include="..\src\pc\network\packets\packet_collect_star.c" />
<ClCompile Include="..\src\pc\network\packets\packet_custom.c" />
<ClCompile Include="..\src\pc\network\packets\packet_kick.c" />
<ClCompile Include="..\src\pc\network\packets\packet_inside_painting.c" />
<ClCompile Include="..\src\pc\network\packets\packet_join.c" />
<ClCompile Include="..\src\pc\network\packets\packet_keep_alive.c" />
<ClCompile Include="..\src\pc\network\packets\packet_leaving.c" />
<ClCompile Include="..\src\pc\network\packets\packet_level_warp.c" />
<ClCompile Include="..\src\pc\network\packets\packet_object.c" />
<ClCompile Include="..\src\pc\network\packets\packet_player.c" />
@ -4327,6 +4333,7 @@
<ClInclude Include="..\src\pc\network\discord\discord_network.h" />
<ClInclude Include="..\src\pc\network\discord\user.h" />
<ClInclude Include="..\src\pc\network\network.h" />
<ClInclude Include="..\src\pc\network\network_player.h" />
<ClInclude Include="..\src\pc\network\socket\socket.h" />
<ClInclude Include="..\src\pc\network\socket\socket_linux.h" />
<ClInclude Include="..\src\pc\network\socket\socket_windows.h" />

View file

@ -15048,6 +15048,24 @@
<ClCompile Include="..\src\pc\network\packets\packet_chat.c">
<Filter>Source Files\src\pc\network\packets</Filter>
</ClCompile>
<ClCompile Include="..\src\pc\network\network_player.c">
<Filter>Source Files\src\pc\network</Filter>
</ClCompile>
<ClCompile Include="..\src\pc\network\packets\packet_join.c">
<Filter>Source Files\src\pc\network\packets</Filter>
</ClCompile>
<ClCompile Include="..\src\pc\network\packets\packet.c">
<Filter>Source Files\src\pc\network\packets</Filter>
</ClCompile>
<ClCompile Include="..\src\pc\network\packets\packet_keep_alive.c">
<Filter>Source Files\src\pc\network\packets</Filter>
</ClCompile>
<ClCompile Include="..\src\pc\network\packets\packet_kick.c">
<Filter>Source Files\src\pc\network\packets</Filter>
</ClCompile>
<ClCompile Include="..\src\pc\network\packets\packet_leaving.c">
<Filter>Source Files\src\pc\network\packets</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\actors\common0.h">
@ -15997,5 +16015,8 @@
<ClInclude Include="..\src\game\chat.h">
<Filter>Header Files\src\game</Filter>
</ClInclude>
<ClInclude Include="..\src\pc\network\network_player.h">
<Filter>Header Files\src\pc\network</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -13,6 +13,7 @@
#include "pc/network/network.h"
#include "audio_defines.h"
#include "audio/external.h"
#include "menu/file_select.h"
#define CHAT_DIALOG_MAX 96
#define CHAT_MESSAGES_MAX 16
@ -90,14 +91,21 @@ static void render_chat_message(struct ChatMessage* chatMessage, u8 index) {
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
}
void chat_add_message(char* ascii, u8 isLocal) {
message[onMessageIndex].dialog[0] = isLocal ? 0xFD : 0xFA;
message[onMessageIndex].dialog[1] = 0x9E;
str_ascii_to_dialog(ascii, &message[onMessageIndex].dialog[2], MIN(strlen(ascii), CHAT_DIALOG_MAX - 3));
message[onMessageIndex].life = CHAT_LIFE_MAX;
message[onMessageIndex].isLocal = isLocal ? 1 : 0;
void chat_add_message(char* ascii, enum ChatMessageType chatMessageType) {
u8 character = '?';
switch (chatMessageType) {
case CMT_LOCAL: character = 0xFD; break;
case CMT_REMOTE: character = 0xFA; break;
case CMT_SYSTEM: character = 0xF9; break;
}
struct ChatMessage* msg = &message[onMessageIndex];
msg->dialog[0] = character;
msg->dialog[1] = 0x9E;
str_ascii_to_dialog(ascii, &msg->dialog[2], MIN(strlen(ascii), CHAT_DIALOG_MAX - 3));
msg->life = (sSelectedFileNum != 0) ? CHAT_LIFE_MAX : CHAT_LIFE_MAX / 3;
msg->isLocal = (chatMessageType == CMT_LOCAL);
onMessageIndex = (onMessageIndex + 1) % CHAT_MESSAGES_MAX;
play_sound(isLocal ? SOUND_MENU_MESSAGE_DISAPPEAR : SOUND_MENU_MESSAGE_APPEAR, gDefaultSoundArgs);
play_sound(msg->isLocal ? SOUND_MENU_MESSAGE_DISAPPEAR : SOUND_MENU_MESSAGE_APPEAR, gDefaultSoundArgs);
}
static void chat_stop_input(void) {
@ -118,7 +126,6 @@ void chat_start_input(void) {
keyboard_start_text_input(TIM_SINGLE_LINE, CHAT_DIALOG_MAX - 3, chat_stop_input, chat_send_input);
}
void render_chat(void) {
u8 count = 0;
if (sInChatInput) {

View file

@ -1,8 +1,14 @@
#ifndef CHAT_H
#define CHAT_H
enum ChatMessageType {
CMT_LOCAL,
CMT_REMOTE,
CMT_SYSTEM,
};
void render_chat(void);
void chat_add_message(char* ascii, u8 isLocal);
void chat_add_message(char* ascii, enum ChatMessageType chatMessageType);
void chat_start_input(void);
#endif

View file

@ -25,6 +25,7 @@
#include "types.h"
#include "macros.h"
#include "pc/cheats.h"
#include "pc/network/network.h"
#ifdef BETTERCAMERA
#include "bettercamera.h"
#endif
@ -2823,25 +2824,45 @@ s16 render_pause_courses_and_castle(void) {
}
s16 render_sync_level_screen(void) {
char* message;
if (gNetworkType == NT_SERVER) {
message = network_player_any_connected() ? "Waiting for player..." : "Waiting for player to connect...";
} else {
message = network_player_any_connected() ? "Waiting for player..." : "Not connected to anyone.\nPlease restart the game.";
}
static f32 alphaScalar = 0.0f;
static clock_t lastDisplay = 0;
f32 elapsed = (clock() - lastDisplay) / (f32)CLOCKS_PER_SEC;
if (elapsed > 1.0f) {
alphaScalar = 0;
} else if (alphaScalar < 1.0f) {
alphaScalar += 0.3f;
if (alphaScalar > 1) { alphaScalar = 1; }
}
u8 alpha = (((f32)fabs(sin(gGlobalTimer / 20.0f)) * alphaScalar) * 255);
lastDisplay = clock();
// black screen
create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(0), 240.0f, 0);
create_dl_scale_matrix(MENU_MTX_NOPUSH,
GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT / 130.0f, 3.0f, 1.0f);
create_dl_scale_matrix(MENU_MTX_NOPUSH, GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT / 130.0f, 3.0f, 1.0f);
gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255);
gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box);
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
// print text
gSPDisplayList(gDisplayListHead++, dl_ia_text_begin);
f32 textWidth = get_generic_ascii_string_width(message);
f32 textHeight = get_generic_ascii_string_height(message);
// synchronizing text
u8 colorFade = sins(gDialogColorFadeTimer) * 50.0f + 200.0f;
gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin);
gDPSetEnvColor(gDisplayListHead++, colorFade, colorFade, colorFade, 255);
f32 xPos = (SCREEN_WIDTH - textWidth) / 2.0f;
f32 yPos = (SCREEN_HEIGHT + textHeight) / 2.0f;
#define TEXT_SYNCHRONIZING 0x1C,0x22,0x17,0x0C,0x11,0x1B,0x18,0x17,0x12,0x02,0x12,0x17,0x10,0xFF
u8 synchronizing[] = { TEXT_SYNCHRONIZING };
gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, alpha);
print_generic_ascii_string(xPos, yPos, message);
print_hud_lut_string(HUD_LUT_GLOBAL, 80, 200, synchronizing);
gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end);
gSPDisplayList(gDisplayListHead++, dl_ia_text_end);
return 0;
}

View file

@ -1046,11 +1046,11 @@ s32 play_mode_normal(void) {
if (sCurrPlayMode == PLAY_MODE_NORMAL) {
if (!gReceiveWarp) {
if (sWarpDest.type == WARP_TYPE_CHANGE_LEVEL) {
set_play_mode((gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL);
set_play_mode(PLAY_MODE_SYNC_LEVEL);
network_send_level_warp_begin();
} else if (sTransitionTimer != 0) {
if (sWarpDest.type == WARP_TYPE_CHANGE_AREA) {
set_play_mode((gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_AREA);
set_play_mode(PLAY_MODE_SYNC_LEVEL);
network_send_level_warp_begin();
} else {
set_play_mode(PLAY_MODE_CHANGE_AREA);
@ -1085,7 +1085,7 @@ s32 play_mode_paused(void) {
fade_into_special_warp(0, 0);
gSavedCourseNum = COURSE_NONE;
}
set_play_mode((gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL);
set_play_mode(PLAY_MODE_SYNC_LEVEL);
network_send_level_warp_begin();
} else if (gPauseScreenMode == 3) {
// We should only be getting "int 3" to here

View file

@ -31,7 +31,7 @@ static void debug_warp_level(u8 level) {
sWarpDest.nodeId = node->destNode;
sWarpDest.arg = 0;
sCurrPlayMode = (gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL;
sCurrPlayMode = PLAY_MODE_SYNC_LEVEL;
network_send_level_warp_begin();
return;
}
@ -47,7 +47,7 @@ static void debug_warp_level(u8 level) {
sWarpDest.nodeId = node->destNode;
sWarpDest.arg = 0;
sCurrPlayMode = (gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL;
sCurrPlayMode = PLAY_MODE_SYNC_LEVEL;
network_send_level_warp_begin();
return;
}
@ -82,7 +82,7 @@ static void debug_warp_area() {
sWarpDest.nodeId = node->destNode;
sWarpDest.arg = 0;
sCurrPlayMode = (gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL;
sCurrPlayMode = PLAY_MODE_SYNC_LEVEL;
network_send_level_warp_begin();
return;
}

View file

@ -13,8 +13,13 @@ static void on_activity_update_callback(UNUSED void* data, enum EDiscordResult r
}
static void on_activity_join_callback(UNUSED void* data, enum EDiscordResult result, struct DiscordLobby* lobby) {
LOG_INFO("> on_activity_join_callback returned %d, lobby %lld", result, lobby->id);
LOG_INFO("> on_activity_join_callback returned %d, lobby %lld, owner %lld", result, lobby->id, lobby->owner_id);
DISCORD_REQUIRE(result);
if (gNetworkType != NT_NONE) {
LOG_ERROR("Joined lobby when already connected somewhere!");
exit(0);
return;
}
network_init(NT_CLIENT);
gCurActivity.type = DiscordActivityType_Playing;
@ -27,6 +32,7 @@ static void on_activity_join_callback(UNUSED void* data, enum EDiscordResult res
discord_network_init(lobby->id);
discord_activity_update(false);
gNetworkUserIds[0] = lobby->owner_id;
network_send_join_request();
}

View file

@ -122,6 +122,8 @@ static void ns_discord_shutdown(void) {
struct NetworkSystem gNetworkSystemDiscord = {
.initialize = ns_discord_initialize,
.save_id = ns_discord_save_id,
.clear_id = ns_discord_clear_id,
.update = ns_discord_update,
.send = ns_discord_network_send,
.shutdown = ns_discord_shutdown,

View file

@ -2,33 +2,60 @@
#include "lobby.h"
#include "pc/debuglog.h"
int ns_discord_network_send(u8* data, u16 dataLength) {
int64_t gNetworkUserIds[MAX_PLAYERS] = { 0 };
u8 discord_user_id_to_local_index(int64_t userId) {
for (int i = 1; i < MAX_PLAYERS; i++) {
if (gNetworkPlayers[i].connected && gNetworkUserIds[i] == userId) {
return i;
}
}
return UNKNOWN_LOCAL_INDEX;
}
int ns_discord_network_send(u8 localIndex, u8* data, u16 dataLength) {
if (!gDiscordInitialized) { return 1; }
if (gCurLobbyId == 0) { return 2; }
int32_t memberCount = 0;
DISCORD_REQUIRE(app.lobbies->member_count(app.lobbies, gCurLobbyId, &memberCount));
if (memberCount <= 1) { return 3; }
for (int i = 0; i < memberCount; i++) {
DiscordUserId userId;
DISCORD_REQUIRE(app.lobbies->get_member_user_id(app.lobbies, gCurLobbyId, i, &userId));
if (userId == app.userId) { continue; }
DISCORD_REQUIRE(app.lobbies->send_network_message(app.lobbies, gCurLobbyId, userId, 0, data, dataLength));
}
DISCORD_REQUIRE(app.lobbies->send_network_message(app.lobbies, gCurLobbyId, gNetworkUserIds[localIndex], 0, data, dataLength));
return 0;
}
void discord_network_on_message(UNUSED void* eventData, int64_t lobbyId, int64_t userId, uint8_t channelId, uint8_t* data, uint32_t dataLength) {
network_receive((u8*)data, (u16)dataLength);
gNetworkUserIds[0] = userId;
u8 localIndex = UNKNOWN_LOCAL_INDEX;
for (int i = 1; i < MAX_PLAYERS; i++) {
if (gNetworkUserIds[i] == userId) {
localIndex = i;
break;
}
}
network_receive(localIndex, (u8*)data, (u16)dataLength);
}
void discord_network_flush(void) {
app.lobbies->flush_network(app.lobbies);
}
void ns_discord_save_id(u8 localId) {
assert(localId > 0);
assert(localId < MAX_PLAYERS);
gNetworkUserIds[localId] = gNetworkUserIds[0];
LOG_INFO("saved user id %d == %lld", localId, gNetworkUserIds[localId]);
}
void ns_discord_clear_id(u8 localId) {
assert(localId > 0);
assert(localId < MAX_PLAYERS);
gNetworkUserIds[localId] = 0;
LOG_INFO("cleared user id %d == %lld", localId, gNetworkUserIds[localId]);
}
void discord_network_init(int64_t lobbyId) {
DISCORD_REQUIRE(app.lobbies->connect_network(app.lobbies, lobbyId));
DISCORD_REQUIRE(app.lobbies->open_network_channel(app.lobbies, lobbyId, 0, false));
LOG_INFO("network initialized");
}
void discord_network_shutdown(void) {

View file

@ -2,9 +2,14 @@
#define DISCORD_NETWORK_H
#include "discord.h"
int ns_discord_network_send(u8* data, u16 dataLength);
extern int64_t gNetworkUserIds[MAX_PLAYERS];
u8 discord_user_id_to_local_index(int64_t userId);
int ns_discord_network_send(u8 localIndex, u8* data, u16 dataLength);
void discord_network_on_message(UNUSED void* eventData, int64_t lobbyId, int64_t userId, uint8_t channelId, uint8_t* data, uint32_t dataLength);
void discord_network_flush(void);
void ns_discord_save_id(u8 localId);
void ns_discord_clear_id(u8 localId);
void discord_network_init(int64_t lobbyId);
void discord_network_shutdown(void);

View file

@ -7,13 +7,13 @@ static bool isHosting = false;
DiscordLobbyId gCurLobbyId = 0;
static void on_lobby_create_callback(UNUSED void* data, enum EDiscordResult result, struct DiscordLobby* lobby) {
LOG_INFO("> on_lobby_update returned %d\n", (int)result);
LOG_INFO("Lobby id: %lld\n", lobby->id);
LOG_INFO("Lobby type: %u\n", lobby->type);
LOG_INFO("Lobby owner id: %lld\n", lobby->owner_id);
LOG_INFO("Lobby secret: %s\n", lobby->secret);
LOG_INFO("Lobby capacity: %u\n", lobby->capacity);
LOG_INFO("Lobby locked: %d\n", lobby->locked);
LOG_INFO("> on_lobby_update returned %d", (int)result);
LOG_INFO("Lobby id: %lld", lobby->id);
LOG_INFO("Lobby type: %u", lobby->type);
LOG_INFO("Lobby owner id: %lld", lobby->owner_id);
LOG_INFO("Lobby secret: %s", lobby->secret);
LOG_INFO("Lobby capacity: %u", lobby->capacity);
LOG_INFO("Lobby locked: %d", lobby->locked);
gCurActivity.type = DiscordActivityType_Playing;
snprintf(gCurActivity.party.id, 128, "%lld", lobby->id);
@ -47,6 +47,10 @@ static void on_member_update(UNUSED void* data, int64_t lobbyId, int64_t userId)
static void on_member_disconnect(UNUSED void* data, int64_t lobbyId, int64_t userId) {
LOG_INFO("> on_member_disconnect lobby: %lld, user: %lld", lobbyId, userId);
u8 localIndex = discord_user_id_to_local_index(userId);
if (localIndex != UNKNOWN_LOCAL_INDEX && gNetworkPlayers[localIndex].connected) {
network_player_disconnected(gNetworkPlayers[localIndex].globalIndex);
}
gCurActivity.party.size.current_size--;
discord_activity_update(isHosting);
}

View file

@ -16,6 +16,7 @@ struct NetworkSystem* gNetworkSystem = &gNetworkSystemDiscord;
#define LOADING_LEVEL_THRESHOLD 10
u8 networkLoadingLevel = 0;
bool gNetworkLevelLoaded = false;
clock_t gLastNetworkSend = 0;
struct ServerSettings gServerSettings = {
.playerInteractions = PLAYER_INTERACTIONS_SOLID,
@ -53,6 +54,12 @@ bool network_init(enum NetworkType inNetworkType) {
// set network type
gNetworkType = inNetworkType;
if (gNetworkType == NT_SERVER) {
network_player_connected(NPT_LOCAL, 0);
extern u8* gOverrideEeprom;
gOverrideEeprom = NULL;
}
LOG_INFO("initialized");
return true;
@ -71,12 +78,14 @@ void network_on_loaded_level(void) {
}
}
void network_send(struct Packet* p) {
void network_send_to(u8 localIndex, struct Packet* p) {
// sanity checks
if (gNetworkType == NT_NONE) { return; }
if (p->error) { LOG_ERROR("packet error!"); return; }
if (gNetworkSystem == NULL) { LOG_ERROR("no network system attached"); return; }
p->localIndex = localIndex;
// remember reliable packets
network_remember_reliable(p);
@ -85,20 +94,35 @@ void network_send(struct Packet* p) {
memcpy(&p->buffer[p->dataLength], &hash, sizeof(u32));
// send
int rc = gNetworkSystem->send(p->buffer, p->cursor + sizeof(u32));
int rc = gNetworkSystem->send(localIndex, p->buffer, p->cursor + sizeof(u32));
if (rc != NO_ERROR) { return; }
p->sent = true;
gLastNetworkSend = clock();
}
void network_receive(u8* data, u16 dataLength) {
void network_send(struct Packet* p) {
for (int i = 1; i < MAX_PLAYERS; i++) {
if (!gNetworkPlayers[i].connected) { continue; }
p->localIndex = i;
network_send_to(i, p);
}
}
void network_receive(u8 localIndex, u8* data, u16 dataLength) {
// receive packet
struct Packet p = {
.localIndex = localIndex,
.cursor = 3,
.buffer = { 0 },
.dataLength = dataLength,
};
memcpy(p.buffer, data, dataLength);
if (localIndex != UNKNOWN_LOCAL_INDEX && localIndex != 0) {
gNetworkPlayers[localIndex].lastReceived = clock();
}
// subtract and check hash
p.dataLength -= sizeof(u32);
if (!packet_check_hash(&p)) {
@ -121,7 +145,8 @@ void network_update(void) {
}
// send out update packets
if (gNetworkType != NT_NONE) {
if (gNetworkType != NT_NONE && network_player_any_connected()) {
network_player_update();
if (sCurrPlayMode == PLAY_MODE_NORMAL || sCurrPlayMode == PLAY_MODE_PAUSED) {
network_update_player();
network_update_objects();
@ -141,8 +166,10 @@ void network_update(void) {
void network_shutdown(void) {
if (gNetworkType == NT_NONE) { return; }
if (gNetworkSystem == NULL) { LOG_ERROR("no network system attached"); return; }
if (gNetworkPlayerLocal != NULL) { network_send_leaving(gNetworkPlayerLocal->globalIndex); }
network_player_shutdown();
gNetworkSystem->shutdown();
gNetworkType = NT_NONE;

View file

@ -5,6 +5,7 @@
#include <time.h>
#include <types.h>
#include <assert.h>
#include "network_player.h"
#include "packets/packet.h"
#include "../cliopts.h"
@ -28,8 +29,10 @@ enum NetworkSystemType {
struct NetworkSystem {
bool (*initialize)(enum NetworkType);
void (*save_id)(u8 localIndex);
void (*clear_id)(u8 localIndex);
void (*update)(void);
int (*send)(u8* data, u16 dataLength);
int (*send)(u8 localIndex, u8* data, u16 dataLength);
void (*shutdown)(void);
};
@ -66,18 +69,21 @@ struct ServerSettings {
};
// Networking-specific externs
extern bool gNetworkLevelLoaded;
extern struct NetworkSystem* gNetworkSystem;
extern enum NetworkType gNetworkType;
extern bool gNetworkLevelLoaded;
extern struct SyncObject gSyncObjects[];
extern struct ServerSettings gServerSettings;
extern clock_t gLastNetworkSend;
// network.c
void network_set_system(enum NetworkSystemType nsType);
bool network_init(enum NetworkType inNetworkType);
void network_on_init_level(void);
void network_on_loaded_level(void);
void network_send_to(u8 localIndex, struct Packet* p);
void network_send(struct Packet* p);
void network_receive(u8* data, u16 dataLength);
void network_receive(u8 localIndex, u8* data, u16 dataLength);
void network_update(void);
void network_shutdown(void);

View file

@ -0,0 +1,131 @@
#include <stdio.h>
#include "network_player.h"
#include "game/chat.h"
#include "pc/debuglog.h"
struct NetworkPlayer gNetworkPlayers[MAX_PLAYERS] = { 0 };
struct NetworkPlayer* gNetworkPlayerLocal = NULL;
struct NetworkPlayer* gNetworkPlayerServer = NULL;
bool network_player_any_connected(void) {
for (int i = 1; i < MAX_PLAYERS; i++) {
if (gNetworkPlayers[i].connected) { return true; }
}
return false;
}
void network_player_update(void) {
float elapsed = (clock() - gLastNetworkSend) / (float)CLOCKS_PER_SEC;
if (elapsed > NETWORK_PLAYER_TIMEOUT / 3.0f) {
network_send_keep_alive();
}
if (gNetworkType == NT_SERVER) {
for (int i = 1; i < MAX_PLAYERS; i++) {
struct NetworkPlayer* np = &gNetworkPlayers[i];
if (!np->connected) { continue; }
float elapsed = (clock() - np->lastReceived) / (float)CLOCKS_PER_SEC;
if (elapsed > NETWORK_PLAYER_TIMEOUT) {
network_player_disconnected(i);
}
}
} else if (gNetworkType == NT_CLIENT) {
bool connectionAlive = false;
for (int i = 1; i < MAX_PLAYERS; i++) {
struct NetworkPlayer* np = &gNetworkPlayers[i];
if (!np->connected) { continue; }
float elapsed = (clock() - np->lastReceived) / (float)CLOCKS_PER_SEC;
if (elapsed <= NETWORK_PLAYER_TIMEOUT * 1.5f) {
connectionAlive = true;
break;
}
}
if (!connectionAlive) {
network_shutdown();
}
}
}
u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex) {
if (type == NPT_LOCAL) {
gNetworkPlayers[0].connected = true;
gNetworkPlayers[0].type = type;
gNetworkPlayers[0].localIndex = 0;
gNetworkPlayers[0].globalIndex = globalIndex;
gNetworkPlayerLocal = &gNetworkPlayers[0];
return 0;
}
if (globalIndex != UNKNOWN_GLOBAL_INDEX) {
for (int i = 1; i < MAX_PLAYERS; i++) {
struct NetworkPlayer* np = &gNetworkPlayers[i];
if (!np->connected) { continue; }
if (np->globalIndex != globalIndex) { continue; }
np->localIndex = i;
np->lastReceived = clock();
gNetworkSystem->save_id(i);
LOG_ERROR("player connected, reusing local %d, global %d, duplicate event?", i, globalIndex);
return i;
}
}
for (int i = 1; i < MAX_PLAYERS; i++) {
struct NetworkPlayer* np = &gNetworkPlayers[i];
if (np->connected) { continue; }
np->connected = true;
np->localIndex = i;
np->globalIndex = (gNetworkType == NT_SERVER) ? i : globalIndex;
np->type = type;
np->lastReceived = clock();
gNetworkSystem->save_id(i);
if (type == NPT_SERVER) { gNetworkPlayerServer = np; }
chat_add_message("player connected", CMT_SYSTEM);
LOG_INFO("player connected, local %d, global %d", i, globalIndex);
return i;
}
LOG_ERROR("player connected, but unable to allocate!");
return UNKNOWN_GLOBAL_INDEX;
}
u8 network_player_disconnected(u8 globalIndex) {
if (globalIndex == 0) {
if (gNetworkType == NT_SERVER) {
LOG_ERROR("player disconnected, but it's local.. this shouldn't happen!");
return UNKNOWN_GLOBAL_INDEX;
} else {
network_shutdown();
}
}
if (globalIndex == UNKNOWN_GLOBAL_INDEX) {
LOG_ERROR("player disconnected, but unknown global index!");
return UNKNOWN_GLOBAL_INDEX;
}
for (int i = 1; i < MAX_PLAYERS; i++) {
struct NetworkPlayer* np = &gNetworkPlayers[i];
if (!np->connected) { continue; }
if (np->globalIndex != globalIndex) { continue; }
if (gNetworkType == NT_SERVER) { network_send_leaving(np->globalIndex); }
np->connected = false;
gNetworkSystem->clear_id(i);
LOG_INFO("player disconnected, local %d, global %d", i, globalIndex);
chat_add_message("player disconnected", CMT_SYSTEM);
return i;
}
return UNKNOWN_GLOBAL_INDEX;
}
void network_player_shutdown(void) {
gNetworkPlayerLocal = NULL;
gNetworkPlayerServer = NULL;
for (int i = 1; i < MAX_PLAYERS; i++) {
struct NetworkPlayer* networkPlayer = &gNetworkPlayers[i];
networkPlayer->connected = false;
gNetworkSystem->clear_id(i);
}
chat_add_message("network shutdown", CMT_SYSTEM);
LOG_INFO("cleared all network players");
}

View file

@ -0,0 +1,36 @@
#ifndef NETWORK_PLAYER_H
#define NETWORK_PLAYER_H
#include <stdbool.h>
#include "network.h"
#define UNKNOWN_LOCAL_INDEX ((u8)-1)
#define UNKNOWN_GLOBAL_INDEX ((u8)-1)
#define UNKNOWN_NETWORK_INDEX ((u64)-1)
#define NETWORK_PLAYER_TIMEOUT 10
enum NetworkPlayerType {
NPT_LOCAL,
NPT_SERVER,
NPT_CLIENT,
};
struct NetworkPlayer {
bool connected;
enum NetworkPlayerType type;
u8 localIndex;
u8 globalIndex;
clock_t lastReceived;
};
extern struct NetworkPlayer gNetworkPlayers[];
extern struct NetworkPlayer* gNetworkPlayerLocal;
extern struct NetworkPlayer* gNetworkPlayerServer;
bool network_player_any_connected(void);
void network_player_update(void);
u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex);
u8 network_player_disconnected(u8 globalIndex);
void network_player_shutdown(void);
#endif

View file

@ -3,7 +3,15 @@
#include "pc/debuglog.h"
void packet_receive(struct Packet* p) {
switch ((u8)p->buffer[0]) {
u8 packetType = (u8)p->buffer[0];
// refuse packets from unknown players other than join request
if (gNetworkType == NT_SERVER && p->localIndex == UNKNOWN_LOCAL_INDEX && packetType != PACKET_JOIN_REQUEST) {
network_send_kick(EKT_CLOSE_CONNECTION);
return;
}
switch (packetType) {
case PACKET_ACK: network_receive_ack(p); break;
case PACKET_PLAYER: network_receive_player(p); break;
case PACKET_OBJECT: network_receive_object(p); break;
@ -19,6 +27,10 @@ void packet_receive(struct Packet* p) {
case PACKET_JOIN_REQUEST: network_receive_join_request(p); break;
case PACKET_JOIN: network_receive_join(p); break;
case PACKET_CHAT: network_receive_chat(p); break;
case PACKET_KICK: network_receive_kick(p); break;
case PACKET_KEEP_ALIVE: network_receive_keep_alive(p); break;
case PACKET_LEAVING: network_receive_leaving(p); break;
///
case PACKET_CUSTOM: network_receive_custom(p); break;
default: LOG_ERROR("received unknown packet: %d", p->buffer[0]);
}

View file

@ -25,10 +25,15 @@ enum PacketType {
PACKET_JOIN_REQUEST,
PACKET_JOIN,
PACKET_CHAT,
PACKET_KICK,
PACKET_KEEP_ALIVE,
PACKET_LEAVING,
///
PACKET_CUSTOM = 255,
};
struct Packet {
u8 localIndex;
u16 dataLength;
u16 cursor;
bool error;
@ -38,6 +43,11 @@ struct Packet {
u8 buffer[PACKET_LENGTH];
};
enum KickReasonType {
EKT_CLOSE_CONNECTION,
EKT_FULL_PARTY,
};
// packet.c
void packet_receive(struct Packet* packet);
@ -107,8 +117,8 @@ void network_receive_reservation(struct Packet* p);
// packet_join.c
void network_send_join_request(void);
void network_receive_join_request(UNUSED struct Packet* p);
void network_send_join(void);
void network_receive_join_request(struct Packet* p);
void network_send_join(struct Packet* joinRequestPacket);
void network_receive_join(struct Packet* p);
// packet_custom.c
@ -120,4 +130,16 @@ void network_receive_custom(struct Packet* p);
void network_send_chat(char* message);
void network_receive_chat(struct Packet* p);
// packet_kick.c
void network_send_kick(enum KickReasonType kickReason);
void network_receive_kick(struct Packet* p);
// packet_keep_alive.c
void network_send_keep_alive(void);
void network_receive_keep_alive(struct Packet* p);
// packet_leaving.c
void network_send_leaving(u8 globalIndex);
void network_receive_leaving(struct Packet* p);
#endif

View file

@ -10,6 +10,7 @@
#include "src/menu/custom_menu.h"
#include "src/pc/fs/fs.h"
#include "PR/os_eeprom.h"
#include "pc/debuglog.h"
#define HASH_LENGTH 8
extern u8* gOverrideEeprom;
@ -22,15 +23,17 @@ void network_send_join_request(void) {
struct Packet p;
packet_init(&p, PACKET_JOIN_REQUEST, true);
network_send(&p);
network_send_to(0, &p);
LOG_INFO("sending join request");
}
void network_receive_join_request(UNUSED struct Packet* p) {
void network_receive_join_request(struct Packet* p) {
assert(gNetworkType == NT_SERVER);
network_send_join();
LOG_INFO("received join request");
network_send_join(p);
}
void network_send_join(void) {
void network_send_join(struct Packet* joinRequestPacket) {
assert(gNetworkType == NT_SERVER);
fs_file_t* fp = fs_open(SAVE_FILENAME);
@ -39,38 +42,60 @@ void network_send_join(void) {
fs_close(fp);
}
// do connection event
joinRequestPacket->localIndex = network_player_connected(NPT_CLIENT, joinRequestPacket->localIndex);
if (joinRequestPacket->localIndex == UNKNOWN_LOCAL_INDEX) {
network_send_kick(EKT_FULL_PARTY);
return;
}
char hash[HASH_LENGTH] = GIT_HASH;
struct Packet p;
packet_init(&p, PACKET_JOIN, true);
packet_write(&p, &hash, sizeof(u8) * HASH_LENGTH);
packet_write(&p, &joinRequestPacket->localIndex, sizeof(u8));
packet_write(&p, &gCurrSaveFileNum, sizeof(s16));
packet_write(&p, &gServerSettings.playerInteractions, sizeof(u8));
packet_write(&p, &gServerSettings.playerKnockbackStrength, sizeof(u8));
packet_write(&p, &gServerSettings.stayInLevelAfterStar, sizeof(u8));
packet_write(&p, eeprom, sizeof(u8) * 512);
network_send(&p);
network_send_to(joinRequestPacket->localIndex , &p);
LOG_INFO("sending join packet");
}
void network_receive_join(struct Packet* p) {
assert(gNetworkType == NT_CLIENT);
LOG_INFO("received join packet");
gOverrideEeprom = eeprom;
char hash[HASH_LENGTH] = GIT_HASH;
char remoteHash[HASH_LENGTH] = { 0 };
u8 myGlobalIndex = UNKNOWN_GLOBAL_INDEX;
// find all reserved objects
if (network_player_any_connected()) {
LOG_ERROR("Received join packet, but already in-game!");
return;
}
// verify version
packet_read(p, &remoteHash, sizeof(u8) * HASH_LENGTH);
if (memcmp(hash, remoteHash, HASH_LENGTH) != 0) {
custom_menu_version_mismatch();
return;
}
packet_read(p, &myGlobalIndex, sizeof(u8));
packet_read(p, &gCurrSaveFileNum, sizeof(s16));
packet_read(p, &gServerSettings.playerInteractions, sizeof(u8));
packet_read(p, &gServerSettings.playerKnockbackStrength, sizeof(u8));
packet_read(p, &gServerSettings.stayInLevelAfterStar, sizeof(u8));
packet_read(p, eeprom, sizeof(u8) * 512);
network_player_connected(NPT_SERVER, 0);
network_player_connected(NPT_LOCAL, myGlobalIndex);
save_file_load_all(TRUE);
if (memcmp(hash, remoteHash, HASH_LENGTH) != 0) {
custom_menu_version_mismatch();
return;
}
custom_menu_goto_game(gCurrSaveFileNum);
}

View file

@ -0,0 +1,15 @@
#include <stdio.h>
#include "../network.h"
#include "pc/debuglog.h"
void network_send_keep_alive(void) {
struct Packet p;
packet_init(&p, PACKET_KEEP_ALIVE, FALSE);
network_send(&p);
gLastNetworkSend = clock();
LOG_INFO("sending keep alive");
}
void network_receive_keep_alive(struct Packet* p) {
LOG_INFO("received keep alive");
}

View file

@ -0,0 +1,31 @@
#include <stdio.h>
#include "../network.h"
#include "menu/custom_menu_system.h"
#include "pc/debuglog.h"
void network_send_kick(enum KickReasonType kickReason) {
struct Packet p;
packet_init(&p, PACKET_KICK, FALSE);
packet_write(&p, &kickReason, sizeof(enum KickReasonType));
network_send_to(0, &p);
}
void network_receive_kick(struct Packet* p) {
if (gNetworkType != NT_CLIENT) {
LOG_ERROR("Kicking non-client... refuse!");
return;
}
if (network_player_any_connected() && gNetworkPlayers[p->localIndex].type != NPT_SERVER) {
LOG_ERROR("Kick came from non-server... refuse!");
return;
}
enum KickReasonType kickReason;
packet_read(p, &kickReason, sizeof(enum KickReasonType));
switch (kickReason) {
case EKT_FULL_PARTY: custom_menu_error("The party is full."); break;
default: custom_menu_error("Host has closed the connection."); break;
}
network_shutdown();
}

View file

@ -0,0 +1,43 @@
#include <stdio.h>
#include "../network.h"
#include "menu/custom_menu_system.h"
#include "pc/debuglog.h"
void network_send_leaving(u8 globalIndex) {
if (gNetworkPlayerLocal == NULL) {
LOG_ERROR("Local network player not initialized.");
return;
}
if (gNetworkType != NT_SERVER) {
if (gNetworkPlayerServer == NULL) {
LOG_ERROR("Server network player not initialized.");
return;
}
globalIndex = gNetworkPlayerLocal->globalIndex;
}
struct Packet p;
packet_init(&p, PACKET_LEAVING, TRUE);
packet_write(&p, &globalIndex, sizeof(u8));
if (gNetworkType == NT_SERVER) {
network_send(&p);
} else {
network_send_to(gNetworkPlayerServer->localIndex, &p);
}
LOG_INFO("Sending leaving event for %d", globalIndex);
}
void network_receive_leaving(struct Packet* p) {
if (gNetworkType != NT_SERVER && network_player_any_connected() && gNetworkPlayers[p->localIndex].type != NPT_SERVER) {
LOG_ERROR("Leaving came from non-server... refuse!");
return;
}
u8 globalIndex = 0;
packet_read(p, &globalIndex, sizeof(u8));
LOG_INFO("Received leaving event for %d", globalIndex);
network_player_disconnected(globalIndex);
}

View file

@ -2,6 +2,8 @@
#include "../network.h"
#include "pc/debuglog.h"
// two-player hack: the localIndex for resending packets can be 0... this means reply to last person received from. THIS WILL NOT WORK with more than two players
#define RELIABLE_RESEND_RATE 0.10f
#define MAX_RESEND_ATTEMPTS 20
@ -41,7 +43,7 @@ void network_send_ack(struct Packet* p) {
struct Packet ack = { 0 };
packet_init(&ack, PACKET_ACK, false);
packet_write(&ack, &seqId, sizeof(u16));
network_send(&ack);
network_send_to(0, &ack);
}
void network_receive_ack(struct Packet* p) {
@ -94,7 +96,7 @@ void network_update_reliable(void) {
float maxElapsed = (node->sendAttempts * node->sendAttempts * RELIABLE_RESEND_RATE) / ((float)MAX_RESEND_ATTEMPTS);
if (elapsed > maxElapsed) {
// resend
network_send(&node->p);
network_send_to(node->p.localIndex, &node->p);
node->lastSend = clock();
node->sendAttempts++;
if (node->sendAttempts >= MAX_RESEND_ATTEMPTS) {

View file

@ -5,7 +5,7 @@
#include "menu/custom_menu.h"
static SOCKET curSocket = INVALID_SOCKET;
struct sockaddr_in txAddr = { 0 };
static struct sockaddr_in addr[MAX_PLAYERS] = { 0 };
static int socket_bind(SOCKET socket, unsigned int port) {
struct sockaddr_in rxAddr;
@ -31,11 +31,19 @@ static int socket_send(SOCKET socket, struct sockaddr_in* addr, u8* buffer, u16
return rc;
}
static int socket_receive(SOCKET socket, struct sockaddr_in* rxAddr, u8* buffer, u16 bufferLength, u16* receiveLength) {
static int socket_receive(SOCKET socket, struct sockaddr_in* rxAddr, u8* buffer, u16 bufferLength, u16* receiveLength, u8* localIndex) {
*receiveLength = 0;
int rxAddrSize = sizeof(struct sockaddr_in);
int rc = recvfrom(socket, (char*)buffer, bufferLength, 0, (struct sockaddr*)rxAddr, &rxAddrSize);
for (int i = 1; i < MAX_PLAYERS; i++) {
if (memcmp(rxAddr, &addr[i], sizeof(struct sockaddr_in)) == 0) {
*localIndex = i;
break;
}
}
if (rc == SOCKET_ERROR) {
int error = SOCKET_LAST_ERROR;
if (error != SOCKET_EWOULDBLOCK && error != SOCKET_ECONNRESET) {
@ -49,6 +57,7 @@ static int socket_receive(SOCKET socket, struct sockaddr_in* rxAddr, u8* buffer,
}
static bool ns_socket_initialize(enum NetworkType networkType) {
// sanity check port
unsigned int port = (networkType == NT_CLIENT) ? configJoinPort : configHostPort;
if (port == 0) { port = DEFAULT_PORT; }
@ -65,9 +74,9 @@ static bool ns_socket_initialize(enum NetworkType networkType) {
LOG_INFO("bound to port %u", port);
} else {
// save the port to send to
txAddr.sin_family = AF_INET;
txAddr.sin_port = htons(port);
txAddr.sin_addr.s_addr = inet_addr(configJoinIp);
addr[0].sin_family = AF_INET;
addr[0].sin_port = htons(port);
addr[0].sin_addr.s_addr = inet_addr(configJoinIp);
LOG_INFO("connecting to %s %u", configJoinIp, port);
}
@ -87,20 +96,39 @@ static bool ns_socket_initialize(enum NetworkType networkType) {
return true;
}
static void ns_socket_save_id(u8 localId) {
assert(localId > 0);
assert(localId < MAX_PLAYERS);
addr[localId] = addr[0];
LOG_INFO("saved addr for id %d", localId);
}
static void ns_socket_clear_id(u8 localId) {
assert(localId > 0);
assert(localId < MAX_PLAYERS);
memset(&addr[localId], 0, sizeof(struct sockaddr_in));
LOG_INFO("cleared addr for id %d", localId);
}
static void ns_socket_update(void) {
if (gNetworkType == NT_NONE) { return; }
do {
// receive packet
u8 data[PACKET_LENGTH];
u16 dataLength = 0;
int rc = socket_receive(curSocket, &txAddr, data, PACKET_LENGTH, &dataLength);
u8 localIndex = UNKNOWN_LOCAL_INDEX;
int rc = socket_receive(curSocket, &addr[0], data, PACKET_LENGTH, &dataLength, &localIndex);
if (rc != NO_ERROR) { break; }
network_receive(data, dataLength);
network_receive(localIndex, data, dataLength);
} while (true);
}
static int ns_socket_send(u8* data, u16 dataLength) {
return socket_send(curSocket, &txAddr, data, dataLength);
static int ns_socket_send(u8 localIndex, u8* data, u16 dataLength) {
if (localIndex != 0) {
if (gNetworkType == NT_SERVER && gNetworkPlayers[localIndex].type != NPT_CLIENT) { return SOCKET_ERROR; }
if (gNetworkType == NT_CLIENT && gNetworkPlayers[localIndex].type != NPT_SERVER) { return SOCKET_ERROR; }
}
return socket_send(curSocket, &addr[localIndex], data, dataLength);
}
static void ns_socket_shutdown(void) {
@ -111,6 +139,8 @@ static void ns_socket_shutdown(void) {
struct NetworkSystem gNetworkSystemSocket = {
.initialize = ns_socket_initialize,
.save_id = ns_socket_save_id,
.clear_id = ns_socket_clear_id,
.update = ns_socket_update,
.send = ns_socket_send,
.shutdown = ns_socket_shutdown,

View file

@ -7,5 +7,4 @@
#define SOCKET_LAST_ERROR WSAGetLastError()
#define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK
#define SOCKET_ECONNRESET WSAECONNRESET
#endif