8c2943b854f742d9b27bdbf5ffe8fc278a3fe318
c891220772e1258e6e9192ef8ec195c227e8f10b
This commit is contained in:
Nikhil Narayana 2021-11-22 22:00:13 -08:00
commit 246ba8397e
20 changed files with 1065 additions and 402 deletions

Binary file not shown.

Binary file not shown.

View file

@ -258,6 +258,7 @@ void SConfig::SaveSlippiSettings(IniFile& ini)
slippi->Set("OnlineDelay", m_slippiOnlineDelay); slippi->Set("OnlineDelay", m_slippiOnlineDelay);
slippi->Set("SaveReplays", m_slippiSaveReplays); slippi->Set("SaveReplays", m_slippiSaveReplays);
slippi->Set("EnableQuickChat", m_slippiEnableQuickChat);
slippi->Set("ReplayMonthFolders", m_slippiReplayMonthFolders); slippi->Set("ReplayMonthFolders", m_slippiReplayMonthFolders);
slippi->Set("ReplayDir", m_strSlippiReplayDir); slippi->Set("ReplayDir", m_strSlippiReplayDir);
slippi->Set("PlaybackControls", m_slippiEnableSeek); slippi->Set("PlaybackControls", m_slippiEnableSeek);
@ -543,6 +544,7 @@ void SConfig::LoadSlippiSettings(IniFile& ini)
slippi->Get("PlaybackControls", &m_slippiEnableSeek, true); slippi->Get("PlaybackControls", &m_slippiEnableSeek, true);
slippi->Get("OnlineDelay", &m_slippiOnlineDelay, 2); slippi->Get("OnlineDelay", &m_slippiOnlineDelay, 2);
slippi->Get("SaveReplays", &m_slippiSaveReplays, true); slippi->Get("SaveReplays", &m_slippiSaveReplays, true);
slippi->Get("EnableQuickChat", &m_slippiEnableQuickChat, true);
slippi->Get("ReplayMonthFolders", &m_slippiReplayMonthFolders, false); slippi->Get("ReplayMonthFolders", &m_slippiReplayMonthFolders, false);
std::string default_replay_dir = File::GetHomeDirectory() + DIR_SEP + "Slippi"; std::string default_replay_dir = File::GetHomeDirectory() + DIR_SEP + "Slippi";
slippi->Get("ReplayDir", &m_strSlippiReplayDir, default_replay_dir); slippi->Get("ReplayDir", &m_strSlippiReplayDir, default_replay_dir);

View file

@ -154,6 +154,7 @@ struct SConfig
int m_slippiOnlineDelay = 2; int m_slippiOnlineDelay = 2;
bool m_slippiEnableSeek = true; bool m_slippiEnableSeek = true;
bool m_slippiSaveReplays = true; bool m_slippiSaveReplays = true;
bool m_slippiEnableQuickChat = true;
bool m_slippiReplayMonthFolders = false; bool m_slippiReplayMonthFolders = false;
std::string m_strSlippiReplayDir; std::string m_strSlippiReplayDir;
bool m_blockingPipes = false; bool m_blockingPipes = false;

View file

@ -449,17 +449,13 @@ void CEXISlippi::writeToFile(std::unique_ptr<WriteMessage> msg)
// Get display names and connection codes from slippi netplay client // Get display names and connection codes from slippi netplay client
if (slippi_netplay) if (slippi_netplay)
{ {
auto userInfo = user->GetUserInfo(); auto playerInfo = matchmaking->GetPlayerInfo();
auto oppInfo = matchmaking->GetOpponent();
auto isDecider = slippi_netplay->IsDecider(); for (int i = 0; i < playerInfo.size(); i++)
int local_port = isDecider ? 0 : 1; {
int remote_port = isDecider ? 1 : 0; slippi_names[i] = playerInfo[i].display_name;
slippi_connect_codes[i] = playerInfo[i].connect_code;
slippi_names[local_port] = userInfo.display_name; }
slippi_connect_codes[local_port] = userInfo.connect_code;
slippi_names[remote_port] = oppInfo.display_name;
slippi_connect_codes[remote_port] = oppInfo.connect_code;
} }
} }
@ -1499,12 +1495,6 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3]; int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3];
if (isDisconnected())
{
m_read_queue.push_back(3); // Indicate we disconnected
return;
}
if (frame == 1) if (frame == 1)
{ {
availableSavestates.clear(); availableSavestates.clear();
@ -1525,6 +1515,13 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
slippi_netplay->StartSlippiGame(); slippi_netplay->StartSlippiGame();
} }
if (isDisconnected())
{
auto status = slippi_netplay->GetSlippiConnectStatus();
m_read_queue.push_back(3); // Indicate we disconnected
return;
}
if (shouldSkipOnlineFrame(frame)) if (shouldSkipOnlineFrame(frame))
{ {
// Send inputs that have not yet been acked // Send inputs that have not yet been acked
@ -1586,6 +1583,7 @@ bool CEXISlippi::shouldSkipOnlineFrame(s32 frame)
auto offsetUs = slippi_netplay->CalcTimeOffsetUs(); auto offsetUs = slippi_netplay->CalcTimeOffsetUs();
INFO_LOG(SLIPPI_ONLINE, "[Frame %d] Offset is: %d us", frame, offsetUs); INFO_LOG(SLIPPI_ONLINE, "[Frame %d] Offset is: %d us", frame, offsetUs);
// TODO: figure out a better solution here for doubles?
if (offsetUs > 10000) if (offsetUs > 10000)
{ {
isCurrentlySkipping = true; isCurrentlySkipping = true;
@ -1642,27 +1640,56 @@ void CEXISlippi::prepareOpponentInputs(u8* payload)
m_read_queue.push_back(frameResult); // Indicate a continue frame m_read_queue.push_back(frameResult); // Indicate a continue frame
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
m_read_queue.push_back(remotePlayerCount); // Indicate the number of remote players
int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3]; int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3];
auto result = slippi_netplay->GetSlippiRemotePad(frame); std::unique_ptr<SlippiRemotePadOutput> results[SLIPPI_REMOTE_PLAYER_MAX];
int offset[SLIPPI_REMOTE_PLAYER_MAX];
INFO_LOG(SLIPPI_ONLINE, "Preparing pad data for frame %d", frame);
// Get pad data for each remote player and write each of their latest frame nums to the buf
for (int i = 0; i < remotePlayerCount; i++)
{
results[i] = slippi_netplay->GetSlippiRemotePad(frame, i);
// determine offset from which to copy data // determine offset from which to copy data
int offset = (result->latestFrame - frame) * SLIPPI_PAD_FULL_SIZE; offset[i] = (results[i]->latestFrame - frame) * SLIPPI_PAD_FULL_SIZE;
offset = offset < 0 ? 0 : offset; offset[i] = offset[i] < 0 ? 0 : offset[i];
// add latest frame we are transfering to begining of return buf // add latest frame we are transfering to begining of return buf
int32_t latestFrame = offset > 0 ? frame : result->latestFrame; int32_t latestFrame = results[i]->latestFrame;
appendWordToBuffer(&m_read_queue, *(u32*)&latestFrame); if (latestFrame > frame)
latestFrame = frame;
appendWordToBuffer(&m_read_queue, *(u32 *)&latestFrame);
// INFO_LOG(SLIPPI_ONLINE, "Sending frame num %d for pIdx %d (offset: %d)", latestFrame, i, offset[i]);
}
// Send the current frame for any unused player slots.
for (int i = remotePlayerCount; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
{
appendWordToBuffer(&m_read_queue, *(u32 *)&frame);
}
// copy pad data over // copy pad data over
auto txStart = result->data.begin() + offset; for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
auto txEnd = result->data.end(); {
std::vector<u8> tx; std::vector<u8> tx;
// Get pad data if this remote player exists
if (i < remotePlayerCount)
{
auto txStart = results[i]->data.begin() + offset[i];
auto txEnd = results[i]->data.end();
tx.insert(tx.end(), txStart, txEnd); tx.insert(tx.end(), txStart, txEnd);
}
tx.resize(SLIPPI_PAD_FULL_SIZE * ROLLBACK_MAX_FRAMES, 0); tx.resize(SLIPPI_PAD_FULL_SIZE * ROLLBACK_MAX_FRAMES, 0);
m_read_queue.insert(m_read_queue.end(), tx.begin(), tx.end()); m_read_queue.insert(m_read_queue.end(), tx.begin(), tx.end());
}
slippi_netplay->DropOldRemoteInputs(frame);
// ERROR_LOG(SLIPPI_ONLINE, "EXI: [%d] %X %X %X %X %X %X %X %X", latestFrame, m_read_queue[5], // ERROR_LOG(SLIPPI_ONLINE, "EXI: [%d] %X %X %X %X %X %X %X %X", latestFrame, m_read_queue[5],
// m_read_queue[6], m_read_queue[7], m_read_queue[8], m_read_queue[9], m_read_queue[10], // m_read_queue[6], m_read_queue[7], m_read_queue[8], m_read_queue[9], m_read_queue[10],
@ -1771,7 +1798,7 @@ void CEXISlippi::startFindMatch(u8* payload)
// give someone an early error before they even queue so that they wont enter the queue and make // give someone an early error before they even queue so that they wont enter the queue and make
// someone else get force removed from queue and have to requeue // someone else get force removed from queue and have to requeue
auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT; auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT;
if (search.mode != directMode && localSelections.characterId >= 26) if (search.mode < directMode && localSelections.characterId >= 26)
{ {
forcedError = "The character you selected is not allowed in this mode"; forcedError = "The character you selected is not allowed in this mode";
return; return;
@ -1794,30 +1821,27 @@ void CEXISlippi::startFindMatch(u8* payload)
void CEXISlippi::prepareOnlineMatchState() void CEXISlippi::prepareOnlineMatchState()
{ {
// This match block is a VS match with P1 Red Falco vs P2 Red Bowser on Battlefield. The proper // This match block is a VS match with P1 Red Falco vs P2 Red Bowser vs P3 Young Link vs P4 Young Link
// values will be overwritten // on Battlefield. The proper values will be overwritten
static std::vector<u8> onlineMatchBlock = { static std::vector<u8> onlineMatchBlock = {
0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x6E, 0x00, 0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x6E, 0x00, 0x1F, 0x00, 0x00,
0x1F, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04, 0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x80, 0x00, 0x00, 0x1A, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0x40, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x1A, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x40, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x40, 0x00, 0x04, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09,
0x00, 0x78, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
}; };
m_read_queue.clear(); m_read_queue.clear();
@ -1838,12 +1862,10 @@ void CEXISlippi::prepareOnlineMatchState()
m_read_queue.push_back(mmState); // Matchmaking State m_read_queue.push_back(mmState); // Matchmaking State
u8 localPlayerReady = localSelections.isCharacterSelected; u8 localPlayerReady = localSelections.isCharacterSelected;
u8 remotePlayerReady = 0; u8 remotePlayersReady = 0;
u8 localPlayerIndex = 0; u8 localPlayerIndex = matchmaking->LocalPlayerIndex();
u8 remotePlayerIndex = 1; u8 remotePlayerIndex = 1;
auto opponent = matchmaking->GetOpponent();
std::string oppName = opponent.display_name;
auto userInfo = user->GetUserInfo(); auto userInfo = user->GetUserInfo();
if (mmState == SlippiMatchmaking::ProcessState::CONNECTION_SUCCESS) if (mmState == SlippiMatchmaking::ProcessState::CONNECTION_SUCCESS)
@ -1871,9 +1893,24 @@ void CEXISlippi::prepareOnlineMatchState()
{ {
auto matchInfo = slippi_netplay->GetMatchInfo(); auto matchInfo = slippi_netplay->GetMatchInfo();
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
remotePlayerReady = true; remotePlayersReady = true;
#else #else
remotePlayerReady = matchInfo->remotePlayerSelections.isCharacterSelected; remotePlayersReady = 1;
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
for (int i = 0; i < remotePlayerCount; i++)
{
if (!matchInfo->remotePlayerSelections[i].isCharacterSelected)
{
remotePlayersReady = 0;
}
}
if (remotePlayerCount == 1)
{
auto isDecider = slippi_netplay->IsDecider();
localPlayerIndex = isDecider ? 0 : 1;
remotePlayerIndex = isDecider ? 1 : 0;
}
#endif #endif
auto isDecider = slippi_netplay->IsDecider(); auto isDecider = slippi_netplay->IsDecider();
@ -1892,9 +1929,13 @@ void CEXISlippi::prepareOnlineMatchState()
// Here we are connected, check to see if we should init play session // Here we are connected, check to see if we should init play session
if (!is_play_session_active) if (!is_play_session_active)
{ {
std::vector<std::string> uids{"", ""}; std::vector<std::string> uids;
uids[localPlayerIndex] = userInfo.uid;
uids[remotePlayerIndex] = opponent.uid; auto mmPlayers = matchmaking->GetPlayerInfo();
for (auto mmp : mmPlayers)
{
uids.push_back(mmp.uid);
}
game_reporter->StartNewSession(uids); game_reporter->StartNewSession(uids);
@ -1906,55 +1947,93 @@ void CEXISlippi::prepareOnlineMatchState()
slippi_netplay = nullptr; slippi_netplay = nullptr;
} }
m_read_queue.push_back(localPlayerReady); // Local player ready
m_read_queue.push_back(remotePlayerReady); // Remote player ready
m_read_queue.push_back(localPlayerIndex); // Local player index
m_read_queue.push_back(remotePlayerIndex); // Remote player index
u32 rngOffset = 0; u32 rngOffset = 0;
std::string localPlayerName = "";
std::string oppName = "";
std::string p1Name = ""; std::string p1Name = "";
std::string p2Name = ""; std::string p2Name = "";
u8 chatMessageId = 0; u8 chatMessageId = 0;
u8 chatMessagePlayerIdx = 0;
u8 sentChatMessageId = 0; u8 sentChatMessageId = 0;
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
localPlayerIndex = 0;
chatMessageId = localChatMessageId; chatMessageId = localChatMessageId;
chatMessagePlayerIdx = 0;
localChatMessageId = 0; localChatMessageId = 0;
// in CSS p1 is always current player and p2 is opponent // in CSS p1 is always current player and p2 is opponent
p1Name = "Player 1"; localPlayerName = p1Name = "Player 1";
p2Name = "Player 2"; oppName = p2Name = "Player 2";
#endif #endif
m_read_queue.push_back(localPlayerReady); // Local player ready
m_read_queue.push_back(remotePlayersReady); // Remote players ready
m_read_queue.push_back(localPlayerIndex); // Local player index
m_read_queue.push_back(remotePlayerIndex); // Remote player index
// Set chat message if any // Set chat message if any
if (slippi_netplay) if (slippi_netplay)
{ {
chatMessageId = slippi_netplay->GetSlippiRemoteChatMessage(); auto remoteMessageSelection = slippi_netplay->GetSlippiRemoteChatMessage();
chatMessageId = remoteMessageSelection.messageId;
chatMessagePlayerIdx = remoteMessageSelection.playerIdx;
sentChatMessageId = slippi_netplay->GetSlippiRemoteSentChatMessage(); sentChatMessageId = slippi_netplay->GetSlippiRemoteSentChatMessage();
// in CSS p1 is always current player and p2 is opponent // in CSS p1 is always current player and p2 is opponent
p1Name = userInfo.display_name; localPlayerName = p1Name = userInfo.display_name;
p2Name = oppName;
} }
auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT; auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT;
if (localPlayerReady && remotePlayerReady) std::vector<u8> leftTeamPlayers = {};
std::vector<u8> rightTeamPlayers = {};
if (localPlayerReady && remotePlayersReady)
{ {
auto isDecider = slippi_netplay->IsDecider(); auto isDecider = slippi_netplay->IsDecider();
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
auto matchInfo = slippi_netplay->GetMatchInfo(); auto matchInfo = slippi_netplay->GetMatchInfo();
SlippiPlayerSelections lps = matchInfo->localPlayerSelections; SlippiPlayerSelections lps = matchInfo->localPlayerSelections;
SlippiPlayerSelections rps = matchInfo->remotePlayerSelections; auto rps = matchInfo->remotePlayerSelections;
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
rps.characterId = 0x2; lps.playerIdx = 0;
rps.characterColor = 2;
for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
{
if (i == 0)
{
rps[i].characterColor = 1;
rps[i].teamId = 1;
}
else
{
rps[i].characterColor = 2;
rps[i].teamId = 2;
}
rps[i].characterId = 0x2 + i;
rps[i].playerIdx = i + 1;
rps[i].isCharacterSelected = true;
}
if (lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS)
{
remotePlayerCount = 4;
}
oppName = std::string("Player"); oppName = std::string("Player");
#endif #endif
// Check if someone is picking dumb characters in non-direct // Check if someone is picking dumb characters in non-direct
auto localCharOk = lps.characterId < 26; auto localCharOk = lps.characterId < 26;
auto remoteCharOk = rps.characterId < 26; auto remoteCharOk = true;
if (lastSearch.mode != directMode && (!localCharOk || !remoteCharOk)) INFO_LOG(SLIPPI_ONLINE, "remotePlayerCount: %d", remotePlayerCount);
for (int i = 0; i < remotePlayerCount; i++)
{
if (rps[i].characterId >= 26)
remoteCharOk = false;
}
if (lastSearch.mode < directMode && (!localCharOk || !remoteCharOk))
{ {
// If we get here, someone is doing something bad, clear the lobby // If we get here, someone is doing something bad, clear the lobby
handleConnectionCleanup(); handleConnectionCleanup();
@ -1965,52 +2044,99 @@ void CEXISlippi::prepareOnlineMatchState()
} }
// Overwrite local player character // Overwrite local player character
onlineMatchBlock[0x60 + localPlayerIndex * 0x24] = lps.characterId; onlineMatchBlock[0x60 + (lps.playerIdx) * 0x24] = lps.characterId;
onlineMatchBlock[0x63 + localPlayerIndex * 0x24] = lps.characterColor; onlineMatchBlock[0x63 + (lps.playerIdx) * 0x24] = lps.characterColor;
onlineMatchBlock[0x67 + (lps.playerIdx) * 0x24] = 0;
onlineMatchBlock[0x69 + (lps.playerIdx) * 0x24] = lps.teamId;
// Overwrite remote player character // Overwrite remote player character
onlineMatchBlock[0x60 + remotePlayerIndex * 0x24] = rps.characterId; for (int i = 0; i < remotePlayerCount; i++)
onlineMatchBlock[0x63 + remotePlayerIndex * 0x24] = rps.characterColor; {
u8 idx = matchInfo->remotePlayerSelections[i].playerIdx;
onlineMatchBlock[0x60 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterId;
// Set Char Colors
onlineMatchBlock[0x63 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterColor;
// Set Team Ids
onlineMatchBlock[0x69 + idx * 0x24] = matchInfo->remotePlayerSelections[i].teamId;
}
// Handle Singles/Teams specific logic
if (remotePlayerCount < 3)
{
onlineMatchBlock[0x8] = 0; // is Teams = false
// Set p3/p4 player type to none
onlineMatchBlock[0x61 + 2 * 0x24] = 3;
onlineMatchBlock[0x61 + 3 * 0x24] = 3;
// Make one character lighter if same character, same color // Make one character lighter if same character, same color
bool isSheikVsZelda = lps.characterId == 0x12 && rps.characterId == 0x13 || bool isSheikVsZelda = lps.characterId == 0x12 && rps[0].characterId == 0x13 ||
lps.characterId == 0x13 && rps.characterId == 0x12; lps.characterId == 0x13 && rps[0].characterId == 0x12;
bool charMatch = lps.characterId == rps.characterId || isSheikVsZelda; bool charMatch = lps.characterId == rps[0].characterId || isSheikVsZelda;
bool colMatch = lps.characterColor == rps.characterColor; bool colMatch = lps.characterColor == rps[0].characterColor;
onlineMatchBlock[0x67 + 0x24] = charMatch && colMatch ? 1 : 0; onlineMatchBlock[0x67 + 0x24] = charMatch && colMatch ? 1 : 0;
}
else
{
onlineMatchBlock[0x8] = 1; // is Teams = true
// Set p3/p4 player type to human
onlineMatchBlock[0x61 + 2 * 0x24] = 0;
onlineMatchBlock[0x61 + 3 * 0x24] = 0;
// Set alt color to light/dark costume for multiples of the same character on a team
int characterCount[26][3] = {0};
for (int i = 0; i < 4; i++)
{
int charId = onlineMatchBlock[0x60 + i * 0x24];
int teamId = onlineMatchBlock[0x69 + i * 0x24];
onlineMatchBlock[0x67 + i * 0x24] = characterCount[charId][teamId];
characterCount[charId][teamId]++;
}
}
// Overwrite stage // Overwrite stage
u16 stageId; u16 stageId;
if (isDecider) if (isDecider)
{ {
stageId = lps.isStageSelected ? lps.stageId : rps.stageId; stageId = lps.isStageSelected ? lps.stageId : rps[0].stageId;
} }
else else
{ {
stageId = rps.isStageSelected ? rps.stageId : lps.stageId; stageId = rps[0].isStageSelected ? rps[0].stageId : lps.stageId;
} }
// int seconds = 0; u16 *stage = (u16 *)&onlineMatchBlock[0xE];
// u32 *timer = (u32 *)&onlineMatchBlock[0x10];
//*timer = Common::swap32(seconds * 60);
u16* stage = (u16*)&onlineMatchBlock[0xE];
*stage = Common::swap16(stageId); *stage = Common::swap16(stageId);
// Set rng offset // Set rng offset
rngOffset = isDecider ? lps.rngOffset : rps.rngOffset; rngOffset = isDecider ? lps.rngOffset : rps[0].rngOffset;
WARN_LOG(SLIPPI_ONLINE, "Rng Offset: 0x%x", rngOffset); WARN_LOG(SLIPPI_ONLINE, "Rng Offset: 0x%x", rngOffset);
WARN_LOG(SLIPPI_ONLINE, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60], WARN_LOG(SLIPPI_ONLINE, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60], onlineMatchBlock[0x84]);
onlineMatchBlock[0x84]);
// Set player names
p1Name = isDecider ? userInfo.display_name : oppName;
p2Name = isDecider ? oppName : userInfo.display_name;
// Turn pause on in direct, off in everything else // Turn pause on in direct, off in everything else
u8* gameBitField3 = (u8*)&onlineMatchBlock[2]; u8 *gameBitField3 = (u8 *)&onlineMatchBlock[2];
*gameBitField3 = lastSearch.mode == directMode ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8; *gameBitField3 = lastSearch.mode >= directMode ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8;
//*gameBitField3 = *gameBitField3 | 0x8;
// Group players into left/right side for team splash screen display
for (int i = 0; i < 4; i++)
{
int teamId = onlineMatchBlock[0x69 + i * 0x24];
if (teamId == lps.teamId)
leftTeamPlayers.push_back(i);
else
rightTeamPlayers.push_back(i);
}
int leftTeamSize = leftTeamPlayers.size();
int rightTeamSize = rightTeamPlayers.size();
leftTeamPlayers.resize(4, 0);
rightTeamPlayers.resize(4, 0);
leftTeamPlayers[3] = leftTeamSize;
rightTeamPlayers[3] = rightTeamSize;
} }
// Add rng offset to output // Add rng offset to output
@ -2022,13 +2148,52 @@ void CEXISlippi::prepareOnlineMatchState()
// Add chat messages id // Add chat messages id
m_read_queue.push_back((u8)sentChatMessageId); m_read_queue.push_back((u8)sentChatMessageId);
m_read_queue.push_back((u8)chatMessageId); m_read_queue.push_back((u8)chatMessageId);
m_read_queue.push_back((u8)chatMessagePlayerIdx);
// Add player groupings for VS splash screen
leftTeamPlayers.resize(4, 0);
rightTeamPlayers.resize(4, 0);
m_read_queue.insert(m_read_queue.end(), leftTeamPlayers.begin(), leftTeamPlayers.end());
m_read_queue.insert(m_read_queue.end(), rightTeamPlayers.begin(), rightTeamPlayers.end());
// Add names to output // Add names to output
p1Name = ConvertStringForGame(p1Name, MAX_NAME_LENGTH); // Always send static local player name
m_read_queue.insert(m_read_queue.end(), p1Name.begin(), p1Name.end()); localPlayerName = ConvertStringForGame(localPlayerName, MAX_NAME_LENGTH);
p2Name = ConvertStringForGame(p2Name, MAX_NAME_LENGTH); m_read_queue.insert(m_read_queue.end(), localPlayerName.begin(), localPlayerName.end());
m_read_queue.insert(m_read_queue.end(), p2Name.begin(), p2Name.end());
oppName = ConvertStringForGame(oppName, MAX_NAME_LENGTH); #ifdef LOCAL_TESTING
std::string defaultNames[] = {"Player 1", "Player 2", "Player 3", "Player 4"};
#endif
for (int i = 0; i < 4; i++)
{
std::string name = matchmaking->GetPlayerName(i);
#ifdef LOCAL_TESTING
name = defaultNames[i];
#endif
name = ConvertStringForGame(name, MAX_NAME_LENGTH);
m_read_queue.insert(m_read_queue.end(), name.begin(), name.end());
}
// Create the opponent string using the names of all players on opposing teams
int teamIdx = onlineMatchBlock[0x69 + localPlayerIndex * 0x24];
std::string oppText = "";
for (int i = 0; i < 4; i++)
{
if (i == localPlayerIndex)
continue;
if (onlineMatchBlock[0x69 + i * 0x24] != teamIdx)
{
if (oppText != "")
oppText += "/";
oppText += matchmaking->GetPlayerName(i);
}
}
if (matchmaking->RemotePlayerCount() == 1)
oppText = matchmaking->GetPlayerName(remotePlayerIndex);
oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH * 2 + 1);
m_read_queue.insert(m_read_queue.end(), oppName.begin(), oppName.end()); m_read_queue.insert(m_read_queue.end(), oppName.begin(), oppName.end());
// Add error message if there is one // Add error message if there is one
@ -2071,12 +2236,13 @@ void CEXISlippi::setMatchSelections(u8* payload)
{ {
SlippiPlayerSelections s; SlippiPlayerSelections s;
s.characterId = payload[0]; s.teamId = payload[0];
s.characterColor = payload[1]; s.characterId = payload[1];
s.isCharacterSelected = payload[2]; s.characterColor = payload[2];
s.isCharacterSelected = payload[3];
s.stageId = Common::swap16(&payload[3]); s.stageId = Common::swap16(&payload[4]);
u8 stageSelectOption = payload[5]; u8 stageSelectOption = payload[6];
s.isStageSelected = stageSelectOption == 1 || stageSelectOption == 3; s.isStageSelected = stageSelectOption == 1 || stageSelectOption == 3;
if (stageSelectOption == 3) if (stageSelectOption == 3)
@ -2085,8 +2251,17 @@ void CEXISlippi::setMatchSelections(u8* payload)
s.stageId = getRandomStage(); s.stageId = getRandomStage();
} }
INFO_LOG(SLIPPI, "LPS set char: %d, iSS: %d, %d, stage: %d, team: %d", s.isCharacterSelected, stageSelectOption,
s.isStageSelected, s.stageId, s.teamId);
s.rngOffset = generator() % 0xFFFF; s.rngOffset = generator() % 0xFFFF;
if (matchmaking->LocalPlayerIndex() == 1 && firstMatch)
{
firstMatch = false;
s.stageId = getRandomStage();
}
// Merge these selections // Merge these selections
localSelections.Merge(s); localSelections.Merge(s);
@ -2142,7 +2317,7 @@ void CEXISlippi::handleChatMessage(u8* payload)
auto packet = std::make_unique<sf::Packet>(); auto packet = std::make_unique<sf::Packet>();
// OSD::AddMessage("[Me]: "+ msg, OSD::Duration::VERY_LONG, OSD::Color::YELLOW); // OSD::AddMessage("[Me]: "+ msg, OSD::Duration::VERY_LONG, OSD::Color::YELLOW);
slippi_netplay->remoteSentChatMessageId = messageId; slippi_netplay->remoteSentChatMessageId = messageId;
slippi_netplay->WriteChatMessageToPacket(*packet, messageId); slippi_netplay->WriteChatMessageToPacket(*packet, messageId, slippi_netplay->LocalPlayerPort());
slippi_netplay->SendAsync(std::move(packet)); slippi_netplay->SendAsync(std::move(packet));
} }
} }
@ -2258,6 +2433,7 @@ void CEXISlippi::handleConnectionCleanup()
// Reset play session // Reset play session
is_play_session_active = false; is_play_session_active = false;
firstMatch = true;
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
isLocalConnected = false; isLocalConnected = false;

View file

@ -189,6 +189,7 @@ private:
void logMessageFromGame(u8* payload); void logMessageFromGame(u8* payload);
void prepareFileLength(u8* payload); void prepareFileLength(u8* payload);
void prepareFileLoad(u8* payload); void prepareFileLoad(u8* payload);
int getCharColor(u8 charId, u8 teamId);
void FileWriteThread(void); void FileWriteThread(void);
@ -214,6 +215,7 @@ private:
u32 frameSeqIdx = 0; u32 frameSeqIdx = 0;
bool isEnetInitialized = false; bool isEnetInitialized = false;
bool firstMatch = true;
std::default_random_engine generator; std::default_random_engine generator;

View file

@ -7,6 +7,13 @@
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Common/Version.h" #include "Common/Version.h"
#if defined __linux__ && HAVE_ALSA
#elif defined __APPLE__
#include <arpa/inet.h>
#include <netdb.h>
#elif defined _WIN32
#endif
class MmMessageType class MmMessageType
{ {
public: public:
@ -68,11 +75,6 @@ std::string SlippiMatchmaking::GetErrorMessage()
return m_errorMsg; return m_errorMsg;
} }
SlippiUser::UserInfo SlippiMatchmaking::GetOpponent()
{
return m_oppUser;
}
bool SlippiMatchmaking::IsSearching() bool SlippiMatchmaking::IsSearching()
{ {
return searchingStates.count(m_state) != 0; return searchingStates.count(m_state) != 0;
@ -208,8 +210,12 @@ void SlippiMatchmaking::startMatchmaking()
m_client = nullptr; m_client = nullptr;
int retryCount = 0; int retryCount = 0;
auto userInfo = m_user->GetUserInfo();
while (m_client == nullptr && retryCount < 15) while (m_client == nullptr && retryCount < 15)
{ {
if (userInfo.port > 0)
m_hostPort = userInfo.port;
else
m_hostPort = 49000 + (generator() % 2000); m_hostPort = 49000 + (generator() % 2000);
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Port to use: %d...", m_hostPort); ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Port to use: %d...", m_hostPort);
@ -269,6 +275,7 @@ void SlippiMatchmaking::startMatchmaking()
continue; continue;
} }
netEvent.peer->data = &userInfo.display_name;
m_client->intercept = ENetUtil::InterceptCallback; m_client->intercept = ENetUtil::InterceptCallback;
isMmConnected = true; isMmConnected = true;
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Connected to mm server..."); ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Connected to mm server...");
@ -276,15 +283,40 @@ void SlippiMatchmaking::startMatchmaking()
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Trying to find match..."); ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Trying to find match...");
if (!m_user->IsLoggedIn()) // if (!m_user->IsLoggedIn())
{ // {
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Must be logged in to queue"); // ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Must be logged in to queue");
m_state = ProcessState::ERROR_ENCOUNTERED; // m_state = ProcessState::ERROR_ENCOUNTERED;
m_errorMsg = "Must be logged in to queue. Go back to menu"; // m_errorMsg = "Must be logged in to queue. Go back to menu";
return; // return;
} // }
auto userInfo = m_user->GetUserInfo(); // Compute LAN IP, in case 2 people are connecting from one IP we can send them each other's local
// IP instead of public. Experimental to allow people from behind one router to connect.
char host[256];
char lan_addr[30];
char* ip;
struct hostent* host_entry;
int hostname;
hostname = gethostname(host, sizeof(host)); // find the host name
if (hostname == -1)
{
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Error finding LAN address");
}
else
{
host_entry = gethostbyname(host); // find host information
if (host_entry == NULL)
{
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Error finding LAN host");
}
else
{
ip = inet_ntoa(*((struct in_addr*)host_entry->h_addr_list[0])); // Convert into IP string
INFO_LOG(SLIPPI_ONLINE, "[Matchmaking] LAN IP: %s", ip);
sprintf(lan_addr, "%s:%d", ip, m_hostPort);
}
}
std::vector<u8> connectCodeBuf; std::vector<u8> connectCodeBuf;
connectCodeBuf.insert(connectCodeBuf.end(), m_searchSettings.connectCode.begin(), connectCodeBuf.insert(connectCodeBuf.end(), m_searchSettings.connectCode.begin(),
@ -296,6 +328,7 @@ void SlippiMatchmaking::startMatchmaking()
request["user"] = {{"uid", userInfo.uid}, {"playKey", userInfo.play_key}}; request["user"] = {{"uid", userInfo.uid}, {"playKey", userInfo.play_key}};
request["search"] = {{"mode", m_searchSettings.mode}, {"connectCode", connectCodeBuf}}; request["search"] = {{"mode", m_searchSettings.mode}, {"connectCode", connectCodeBuf}};
request["appVersion"] = Common::scm_slippi_semver_str; request["appVersion"] = Common::scm_slippi_semver_str;
request["ipAddressLan"] = lan_addr;
sendMessage(request); sendMessage(request);
// Get response from server // Get response from server
@ -385,35 +418,62 @@ void SlippiMatchmaking::handleMatchmaking()
m_isSwapAttempt = false; m_isSwapAttempt = false;
m_netplayClient = nullptr; m_netplayClient = nullptr;
// Clear old user // Clear old users
SlippiUser::UserInfo emptyInfo; m_remoteIps.clear();
m_oppUser = emptyInfo; m_playerInfo.clear();
auto queue = getResp["players"]; auto queue = getResp["players"];
if (queue.is_array()) if (queue.is_array())
{ {
std::string localExternalIp = "";
for (json::iterator it = queue.begin(); it != queue.end(); ++it) for (json::iterator it = queue.begin(); it != queue.end(); ++it)
{ {
json el = *it; json el = *it;
//SlippiUser::UserInfo playerInfo; SlippiUser::UserInfo playerInfo;
bool isLocal = el.value("isLocalPlayer", false); bool isLocal = el.value("isLocalPlayer", false);
//playerInfo.uid = el.value("uid", ""); playerInfo.uid = el.value("uid", "");
//playerInfo.displayName = el.value("displayName", ""); playerInfo.display_name = el.value("displayName", "");
//playerInfo.connectCode = el.value("connectCode", ""); playerInfo.connect_code = el.value("connectCode", "");
//playerInfo.port = el.value("port", 0); playerInfo.port = el.value("port", 0);
m_playerInfo.push_back(playerInfo);
if (!isLocal) if (isLocal)
{ {
m_oppIp = el.value("ipAddress", "1.1.1.1:123"); std::vector<std::string> localIpParts;
m_oppUser.uid = el.value("uid", ""); localIpParts = SplitString(el.value("ipAddress", "1.1.1.1:123"), ':');
m_oppUser.display_name = el.value("displayName", ""); localExternalIp = localIpParts[0];
m_oppUser.connect_code = el.value("connectCode", ""); m_localPlayerIndex = playerInfo.port - 1;
}
};
// Loop a second time to get the correct remote IPs
for (json::iterator it = queue.begin(); it != queue.end(); ++it)
{
json el = *it;
if (el.value("port", 0) - 1 == m_localPlayerIndex)
continue;
auto extIp = el.value("ipAddress", "1.1.1.1:123");
std::vector<std::string> exIpParts;
exIpParts = SplitString(extIp, ':');
auto lanIp = el.value("ipAddressLan", "1.1.1.1:123");
if (exIpParts[0] != localExternalIp || lanIp.empty())
{
// If external IPs are different, just use that address
m_remoteIps.push_back(extIp);
continue;
} }
//else // TODO: Instead of using one or the other, it might be better to try both
// m_localPlayerPort = playerInfo.port - 1;
}; // If external IPs are the same, try using LAN IPs
m_remoteIps.push_back(lanIp);
}
} }
m_isHost = getResp.value("isHost", false); m_isHost = getResp.value("isHost", false);
@ -425,13 +485,62 @@ void SlippiMatchmaking::handleMatchmaking()
m_isHost ? "true" : "false"); m_isHost ? "true" : "false");
} }
int SlippiMatchmaking::LocalPlayerIndex()
{
return m_localPlayerIndex;
}
std::vector<SlippiUser::UserInfo> SlippiMatchmaking::GetPlayerInfo()
{
return m_playerInfo;
}
std::string SlippiMatchmaking::GetPlayerName(u8 port)
{
if (port >= m_playerInfo.size())
{
return "";
}
return m_playerInfo[port].display_name;
}
u8 SlippiMatchmaking::RemotePlayerCount()
{
if (m_playerInfo.size() == 0)
return 0;
return (u8)m_playerInfo.size() - 1;
}
void SlippiMatchmaking::handleConnecting() void SlippiMatchmaking::handleConnecting()
{ {
std::vector<std::string> ipParts = SplitString(m_oppIp, ':'); auto userInfo = m_user->GetUserInfo();
m_isSwapAttempt = false;
m_netplayClient = nullptr;
u8 remotePlayerCount = (u8)m_remoteIps.size();
std::vector<std::string> remoteParts;
std::vector<std::string> addrs;
std::vector<u16> ports;
for (int i = 0; i < m_remoteIps.size(); i++)
{
remoteParts.clear();
remoteParts = SplitString(m_remoteIps[i], ':');
addrs.push_back(remoteParts[0]);
ports.push_back(std::stoi(remoteParts[1]));
}
std::stringstream ipLog;
ipLog << "Remote player IPs: ";
for (int i = 0; i < m_remoteIps.size(); i++)
{
ipLog << m_remoteIps[i] << ", ";
}
// Is host is now used to specify who the decider is // Is host is now used to specify who the decider is
auto client = std::make_unique<SlippiNetplayClient>(ipParts[0], std::stoi(ipParts[1]), m_hostPort, auto client = std::make_unique<SlippiNetplayClient>(addrs, ports, remotePlayerCount, m_hostPort,
m_isHost); m_isHost, m_localPlayerIndex);
while (!m_netplayClient) while (!m_netplayClient)
{ {
@ -447,6 +556,32 @@ void SlippiMatchmaking::handleConnecting()
continue; continue;
} }
else if (status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_FAILED &&
m_searchSettings.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS)
{
// If we failed setting up a connection in teams mode, show a detailed error about who we had
// issues connecting to.
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Failed to connect to players");
m_state = ProcessState::ERROR_ENCOUNTERED;
m_errorMsg = "Timed out waiting for other players to connect";
auto failedConns = client->GetFailedConnections();
if (!failedConns.empty())
{
std::stringstream err;
err << "Could not connect to players: ";
for (int i = 0; i < failedConns.size(); i++)
{
int p = failedConns[i];
if (p >= m_localPlayerIndex)
p++;
err << m_playerInfo[p].display_name << " ";
}
m_errorMsg = err.str();
}
return;
}
else if (status != SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED) else if (status != SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED)
{ {
ERROR_LOG(SLIPPI_ONLINE, ERROR_LOG(SLIPPI_ONLINE,

View file

@ -5,11 +5,15 @@
#include "Core/Slippi/SlippiNetplay.h" #include "Core/Slippi/SlippiNetplay.h"
#include "Core/Slippi/SlippiUser.h" #include "Core/Slippi/SlippiUser.h"
#include <enet/enet.h>
#include <random> #include <random>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#ifndef _WIN32
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using json = nlohmann::json; using json = nlohmann::json;
@ -24,6 +28,7 @@ public:
RANKED = 0, RANKED = 0,
UNRANKED = 1, UNRANKED = 1,
DIRECT = 2, DIRECT = 2,
TEAMS = 3,
}; };
enum ProcessState enum ProcessState
@ -48,7 +53,10 @@ public:
bool IsSearching(); bool IsSearching();
std::unique_ptr<SlippiNetplayClient> GetNetplayClient(); std::unique_ptr<SlippiNetplayClient> GetNetplayClient();
std::string GetErrorMessage(); std::string GetErrorMessage();
SlippiUser::UserInfo GetOpponent(); int LocalPlayerIndex();
std::vector<SlippiUser::UserInfo> GetPlayerInfo();
std::string GetPlayerName(u8 port);
u8 RemotePlayerCount();
protected: protected:
const std::string MM_HOST_DEV = "35.197.121.196"; // Dev host const std::string MM_HOST_DEV = "35.197.121.196"; // Dev host
@ -76,9 +84,11 @@ protected:
int m_isSwapAttempt = false; int m_isSwapAttempt = false;
int m_hostPort; int m_hostPort;
std::string m_oppIp; int m_localPlayerIndex;
std::vector<std::string> m_remoteIps;
std::vector<SlippiUser::UserInfo> m_playerInfo;
bool m_joinedLobby;
bool m_isHost; bool m_isHost;
SlippiUser::UserInfo m_oppUser;
std::unique_ptr<SlippiNetplayClient> m_netplayClient; std::unique_ptr<SlippiNetplayClient> m_netplayClient;

View file

@ -34,7 +34,7 @@ SlippiNetplayClient::~SlippiNetplayClient()
if (m_thread.joinable()) if (m_thread.joinable())
m_thread.join(); m_thread.join();
if (m_server) if (!m_server.empty())
{ {
Disconnect(); Disconnect();
} }
@ -55,8 +55,9 @@ SlippiNetplayClient::~SlippiNetplayClient()
} }
// called from ---SLIPPI EXI--- thread // called from ---SLIPPI EXI--- thread
SlippiNetplayClient::SlippiNetplayClient(const std::string& address, const u16 remotePort, SlippiNetplayClient::SlippiNetplayClient(std::vector<std::string> addrs, std::vector<u16> ports,
const u16 localPort, bool isDecider) const u8 remotePlayerCount, const u16 localPort,
bool isDecider, u8 playerIdx)
#ifdef _WIN32 #ifdef _WIN32
: m_qos_handle(nullptr), m_qos_flow_id(0) : m_qos_handle(nullptr), m_qos_flow_id(0)
#endif #endif
@ -65,6 +66,25 @@ SlippiNetplayClient::SlippiNetplayClient(const std::string& address, const u16 r
isDecider ? "true" : "false"); isDecider ? "true" : "false");
this->isDecider = isDecider; this->isDecider = isDecider;
this->m_remotePlayerCount = remotePlayerCount;
this->playerIdx = playerIdx;
// Set up remote player data structures.
int j = 0;
for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++, j++)
{
if (j == playerIdx)
j++;
this->matchInfo.remotePlayerSelections[i] = SlippiPlayerSelections();
this->matchInfo.remotePlayerSelections[i].playerIdx = j;
this->remotePadQueue[i] = std::deque<std::unique_ptr<SlippiPad>>();
this->frameOffsetData[i] = FrameOffsetData();
this->lastFrameTiming[i] = FrameTiming();
this->pingUs[i] = 0;
this->lastFrameAcked[i] = 0;
}
SLIPPI_NETPLAY = std::move(this); SLIPPI_NETPLAY = std::move(this);
// Local address // Local address
@ -87,23 +107,33 @@ SlippiNetplayClient::SlippiNetplayClient(const std::string& address, const u16 r
// TODO: Figure out how to use a local port when not hosting without accepting incoming // TODO: Figure out how to use a local port when not hosting without accepting incoming
// connections // connections
m_client = enet_host_create(localAddr, 2, 3, 0, 0); m_client = enet_host_create(localAddr, 10, 3, 0, 0);
if (m_client == nullptr) if (m_client == nullptr)
{ {
PanicAlertT("Couldn't Create Client"); PanicAlertT("Couldn't Create Client");
} }
for (int i = 0; i < remotePlayerCount; i++)
{
ENetAddress addr; ENetAddress addr;
enet_address_set_host(&addr, address.c_str()); enet_address_set_host(&addr, addrs[i].c_str());
addr.port = remotePort; addr.port = ports[i];
INFO_LOG(SLIPPI_ONLINE, "Set ENet host, addr = %x, port = %d", addr.host, addr.port);
m_server = enet_host_connect(m_client, &addr, 3, 0); ENetPeer* peer = enet_host_connect(m_client, &addr, 3, 0);
m_server.push_back(peer);
if (m_server == nullptr) if (peer == nullptr)
{ {
PanicAlertT("Couldn't create peer."); PanicAlertT("Couldn't create peer.");
} }
else
{
INFO_LOG(SLIPPI_ONLINE, "Connecting to ENet host, addr = %x, port = %d", peer->address.host,
peer->address.port);
}
}
slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_INITIATED; slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_INITIATED;
@ -118,8 +148,23 @@ SlippiNetplayClient::SlippiNetplayClient(bool isDecider)
slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_FAILED; slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_FAILED;
} }
u8 SlippiNetplayClient::PlayerIdxFromPort(u8 port)
{
u8 p = port;
if (port > playerIdx)
{
p--;
}
return p;
}
u8 SlippiNetplayClient::LocalPlayerPort()
{
return this->playerIdx;
}
// called from ---NETPLAY--- thread // called from ---NETPLAY--- thread
unsigned int SlippiNetplayClient::OnData(sf::Packet& packet) unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
{ {
NetPlay::MessageId mid = 0; NetPlay::MessageId mid = 0;
if (!(packet >> mid)) if (!(packet >> mid))
@ -139,6 +184,19 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
break; break;
} }
u8 packetPlayerPort;
if (!(packet >> packetPlayerPort))
{
ERROR_LOG(SLIPPI_ONLINE, "Netplay packet too small to read player index");
break;
}
u8 pIdx = PlayerIdxFromPort(packetPlayerPort);
if (pIdx >= m_remotePlayerCount)
{
ERROR_LOG(SLIPPI_ONLINE, "Got packet with invalid player idx %d", pIdx);
break;
}
// Pad received, try to guess what our local time was when the frame was sent by our opponent // Pad received, try to guess what our local time was when the frame was sent by our opponent
// before we initialized // before we initialized
// We can compare this to when we sent a pad for last frame to figure out how far/behind we // We can compare this to when we sent a pad for last frame to figure out how far/behind we
@ -146,7 +204,7 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
u64 curTime = Common::Timer::GetTimeUs(); u64 curTime = Common::Timer::GetTimeUs();
auto timing = lastFrameTiming; auto timing = lastFrameTiming[pIdx];
if (!hasGameStarted) if (!hasGameStarted)
{ {
// Handle case where opponent starts sending inputs before our game has reached frame 1. This // Handle case where opponent starts sending inputs before our game has reached frame 1. This
@ -155,7 +213,7 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
timing.timeUs = curTime; timing.timeUs = curTime;
} }
s64 opponentSendTimeUs = curTime - (pingUs / 2); s64 opponentSendTimeUs = curTime - (pingUs[pIdx] / 2);
s64 frameDiffOffsetUs = 16683 * (timing.frame - frame); s64 frameDiffOffsetUs = 16683 * (timing.frame - frame);
s64 timeOffsetUs = opponentSendTimeUs - timing.timeUs + frameDiffOffsetUs; s64 timeOffsetUs = opponentSendTimeUs - timing.timeUs + frameDiffOffsetUs;
@ -163,12 +221,12 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
timing.frame, timeOffsetUs); timing.frame, timeOffsetUs);
// Add this offset to circular buffer for use later // Add this offset to circular buffer for use later
if (frameOffsetData.buf.size() < SLIPPI_ONLINE_LOCKSTEP_INTERVAL) if (frameOffsetData[pIdx].buf.size() < SLIPPI_ONLINE_LOCKSTEP_INTERVAL)
frameOffsetData.buf.push_back((s32)timeOffsetUs); frameOffsetData[pIdx].buf.push_back((s32)timeOffsetUs);
else else
frameOffsetData.buf[frameOffsetData.idx] = (s32)timeOffsetUs; frameOffsetData[pIdx].buf[frameOffsetData[pIdx].idx] = (s32)timeOffsetUs;
frameOffsetData.idx = (frameOffsetData.idx + 1) % SLIPPI_ONLINE_LOCKSTEP_INTERVAL; frameOffsetData[pIdx].idx = (frameOffsetData[pIdx].idx + 1) % SLIPPI_ONLINE_LOCKSTEP_INTERVAL;
{ {
std::lock_guard<std::mutex> lk(pad_mutex); // TODO: Is this the correct lock? std::lock_guard<std::mutex> lk(pad_mutex); // TODO: Is this the correct lock?
@ -177,7 +235,10 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
INFO_LOG(SLIPPI_ONLINE, "Receiving a packet of inputs [%d]...", frame); INFO_LOG(SLIPPI_ONLINE, "Receiving a packet of inputs [%d]...", frame);
int32_t headFrame = remotePadQueue.empty() ? 0 : remotePadQueue.front()->frame; INFO_LOG(SLIPPI_ONLINE, "Receiving a packet of inputs from player %d(%d) [%d]...",
packetPlayerPort, pIdx, frame);
int32_t headFrame = remotePadQueue[pIdx].empty() ? 0 : remotePadQueue[pIdx].front()->frame;
int inputsToCopy = frame - headFrame; int inputsToCopy = frame - headFrame;
// Check that the packet actually contains the data it claims to // Check that the packet actually contains the data it claims to
@ -192,13 +253,12 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
for (int i = inputsToCopy - 1; i >= 0; i--) for (int i = inputsToCopy - 1; i >= 0; i--)
{ {
auto pad = auto pad =
std::make_unique<SlippiPad>(frame - i, &packetData[5 + i * SLIPPI_PAD_DATA_SIZE]); std::make_unique<SlippiPad>(frame - i, pIdx, &packetData[6 + i * SLIPPI_PAD_DATA_SIZE]);
INFO_LOG(SLIPPI_ONLINE, "Rcv [%d] -> %02X %02X %02X %02X %02X %02X %02X %02X", pad->frame, INFO_LOG(SLIPPI_ONLINE, "Rcv [%d] -> %02X %02X %02X %02X %02X %02X %02X %02X", pad->frame,
pad->padBuf[0], pad->padBuf[1], pad->padBuf[2], pad->padBuf[3], pad->padBuf[4], pad->padBuf[0], pad->padBuf[1], pad->padBuf[2], pad->padBuf[3], pad->padBuf[4],
pad->padBuf[5], pad->padBuf[6], pad->padBuf[7]); pad->padBuf[5], pad->padBuf[6], pad->padBuf[7]);
remotePadQueue.push_front(std::move(pad)); remotePadQueue[pIdx].push_front(std::move(pad));
} }
} }
@ -206,7 +266,13 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
sf::Packet spac; sf::Packet spac;
spac << (NetPlay::MessageId)NetPlay::NP_MSG_SLIPPI_PAD_ACK; spac << (NetPlay::MessageId)NetPlay::NP_MSG_SLIPPI_PAD_ACK;
spac << frame; spac << frame;
Send(spac); spac << playerIdx;
INFO_LOG(SLIPPI_ONLINE, "Sending ack packet for frame %d (player %d) to peer at %d:%d", frame,
packetPlayerPort, peer->address.host, peer->address.port);
ENetPacket* epac =
enet_packet_create(spac.getData(), spac.getDataSize(), ENET_PACKET_FLAG_UNSEQUENCED);
int sendResult = enet_peer_send(peer, 2, epac);
} }
break; break;
@ -222,28 +288,49 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
break; break;
} }
lastFrameAcked = frame > lastFrameAcked ? frame : lastFrameAcked; u8 packetPlayerPort;
if (!(packet >> packetPlayerPort))
{
ERROR_LOG(SLIPPI_ONLINE, "Netplay ack packet too small to read player index");
break;
}
u8 pIdx = PlayerIdxFromPort(packetPlayerPort);
if (pIdx >= m_remotePlayerCount)
{
ERROR_LOG(SLIPPI_ONLINE, "Got ack packet with invalid player idx %d", pIdx);
break;
}
INFO_LOG(SLIPPI_ONLINE, "Received ack packet from player %d(%d) [%d]...", packetPlayerPort,
pIdx, frame);
lastFrameAcked[pIdx] = frame > lastFrameAcked[pIdx] ? frame : lastFrameAcked[pIdx];
// Remove old timings // Remove old timings
while (!ackTimers.empty() && ackTimers.front().frame < frame) while (!ackTimers[pIdx].empty() && ackTimers[pIdx].front().frame < frame)
{ {
ackTimers.pop(); ackTimers[pIdx].pop();
} }
// Don't get a ping if we do not have the right ack frame // Don't get a ping if we do not have the right ack frame
if (ackTimers.empty() || ackTimers.front().frame != frame) if (ackTimers[pIdx].empty() || ackTimers[pIdx].front().frame != frame)
{ {
break; break;
} }
auto sendTime = ackTimers.front().timeUs; auto sendTime = ackTimers[pIdx].front().timeUs;
ackTimers.pop(); ackTimers[pIdx].pop();
pingUs = Common::Timer::GetTimeUs() - sendTime; pingUs[pIdx] = Common::Timer::GetTimeUs() - sendTime;
if (g_ActiveConfig.bShowNetPlayPing && frame % SLIPPI_PING_DISPLAY_INTERVAL == 0) if (g_ActiveConfig.bShowNetPlayPing && frame % SLIPPI_PING_DISPLAY_INTERVAL == 0 && pIdx == 0)
{ {
OSD::AddTypedMessage(OSD::MessageType::NetPlayPing, std::stringstream pingDisplay;
StringFromFormat("Ping: %u", pingUs / 1000), OSD::Duration::NORMAL, pingDisplay << "Ping: " << (pingUs[0] / 1000);
for (int i = 1; i < m_remotePlayerCount; i++)
{
pingDisplay << " | " << (pingUs[i] / 1000);
}
OSD::AddTypedMessage(OSD::MessageType::NetPlayPing, pingDisplay.str(), OSD::Duration::NORMAL,
OSD::Color::CYAN); OSD::Color::CYAN);
} }
} }
@ -252,8 +339,10 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
case NetPlay::NP_MSG_SLIPPI_MATCH_SELECTIONS: case NetPlay::NP_MSG_SLIPPI_MATCH_SELECTIONS:
{ {
auto s = readSelectionsFromPacket(packet); auto s = readSelectionsFromPacket(packet);
INFO_LOG(SLIPPI_ONLINE, "[Netplay] Received selections from opponent"); INFO_LOG(SLIPPI_ONLINE, "[Netplay] Received selections from opponent with player idx %d",
matchInfo.remotePlayerSelections.Merge(*s); s->playerIdx);
u8 idx = PlayerIdxFromPort(s->playerIdx);
matchInfo.remotePlayerSelections[idx].Merge(*s);
// This might be a good place to reset some logic? Game can't start until we receive this msg // This might be a good place to reset some logic? Game can't start until we receive this msg
// so this should ensure that everything is initialized before the game starts // so this should ensure that everything is initialized before the game starts
@ -261,19 +350,17 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
// Reset remote pad queue such that next inputs that we get are not compared to inputs from last // Reset remote pad queue such that next inputs that we get are not compared to inputs from last
// game // game
remotePadQueue.clear(); remotePadQueue[idx].clear();
} }
break; break;
case NetPlay::NP_MSG_SLIPPI_CHAT_MESSAGE: case NetPlay::NP_MSG_SLIPPI_CHAT_MESSAGE:
{ {
auto playerSelection = ReadChatMessageFromPacket(packet); auto playerSelection = ReadChatMessageFromPacket(packet);
auto messageId = playerSelection->messageId; INFO_LOG(SLIPPI_ONLINE, "[Netplay] Received chat message from opponent %d: %d",
playerSelection->playerIdx, playerSelection->messageId);
// set message id to netplay instance // set message id to netplay instance
remoteChatMessageId = messageId; remoteChatMessageSelection = std::move(playerSelection);
// Show chat message OSD
INFO_LOG(SLIPPI_ONLINE, "[Netplay] Received chat message from opponent %i", messageId);
} }
break; break;
@ -295,21 +382,19 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
void SlippiNetplayClient::writeToPacket(sf::Packet& packet, SlippiPlayerSelections& s) void SlippiNetplayClient::writeToPacket(sf::Packet& packet, SlippiPlayerSelections& s)
{ {
u8 playerIndex = isDecider ? 0 : 1;
u8 teamId = 0;
packet << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_MATCH_SELECTIONS); packet << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_MATCH_SELECTIONS);
packet << s.characterId << s.characterColor << s.isCharacterSelected; packet << s.characterId << s.characterColor << s.isCharacterSelected;
packet << playerIndex; packet << s.playerIdx;
packet << s.stageId << s.isStageSelected; packet << s.stageId << s.isStageSelected;
packet << s.rngOffset; packet << s.rngOffset;
packet << teamId; packet << s.teamId;
} }
void SlippiNetplayClient::WriteChatMessageToPacket(sf::Packet& packet, int messageId) void SlippiNetplayClient::WriteChatMessageToPacket(sf::Packet& packet, int messageId, u8 playerIdx)
{ {
packet << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_CHAT_MESSAGE); packet << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_CHAT_MESSAGE);
packet << messageId; packet << messageId;
packet << playerIdx;
} }
std::unique_ptr<SlippiPlayerSelections> std::unique_ptr<SlippiPlayerSelections>
@ -318,6 +403,7 @@ SlippiNetplayClient::ReadChatMessageFromPacket(sf::Packet& packet)
auto s = std::make_unique<SlippiPlayerSelections>(); auto s = std::make_unique<SlippiPlayerSelections>();
packet >> s->messageId; packet >> s->messageId;
packet >> s->playerIdx;
return std::move(s); return std::move(s);
} }
@ -327,20 +413,17 @@ SlippiNetplayClient::readSelectionsFromPacket(sf::Packet& packet)
{ {
auto s = std::make_unique<SlippiPlayerSelections>(); auto s = std::make_unique<SlippiPlayerSelections>();
u8 playerIndex;
u8 teamId;
packet >> s->characterId; packet >> s->characterId;
packet >> s->characterColor; packet >> s->characterColor;
packet >> s->isCharacterSelected; packet >> s->isCharacterSelected;
packet >> playerIndex; packet >> s->playerIdx;
packet >> s->stageId; packet >> s->stageId;
packet >> s->isStageSelected; packet >> s->isStageSelected;
packet >> s->rngOffset; packet >> s->rngOffset;
packet >> teamId; packet >> s->teamId;
return std::move(s); return std::move(s);
} }
@ -350,6 +433,8 @@ void SlippiNetplayClient::Send(sf::Packet& packet)
enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE; enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE;
u8 channelId = 0; u8 channelId = 0;
for (int i = 0; i < m_server.size(); i++)
{
NetPlay::MessageId mid = ((u8*)packet.getData())[0]; NetPlay::MessageId mid = ((u8*)packet.getData())[0];
if (mid == NetPlay::NP_MSG_SLIPPI_PAD || mid == NetPlay::NP_MSG_SLIPPI_PAD_ACK) if (mid == NetPlay::NP_MSG_SLIPPI_PAD || mid == NetPlay::NP_MSG_SLIPPI_PAD_ACK)
{ {
@ -361,15 +446,20 @@ void SlippiNetplayClient::Send(sf::Packet& packet)
} }
ENetPacket* epac = enet_packet_create(packet.getData(), packet.getDataSize(), flags); ENetPacket* epac = enet_packet_create(packet.getData(), packet.getDataSize(), flags);
enet_peer_send(m_server, channelId, epac); int sendResult = enet_peer_send(m_server[i], channelId, epac);
}
} }
void SlippiNetplayClient::Disconnect() void SlippiNetplayClient::Disconnect()
{ {
ENetEvent netEvent; ENetEvent netEvent;
slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_DISCONNECTED; slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_DISCONNECTED;
if (m_server) if (!m_server.empty())
enet_peer_disconnect(m_server, 0); for (int i = 0; i < m_server.size(); i++)
{
INFO_LOG(SLIPPI_ONLINE, "[Netplay] Disconnecting peer %d", m_server[i]->address.port);
enet_peer_disconnect(m_server[i], 0);
}
else else
return; return;
@ -381,15 +471,18 @@ void SlippiNetplayClient::Disconnect()
enet_packet_destroy(netEvent.packet); enet_packet_destroy(netEvent.packet);
break; break;
case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT:
m_server = nullptr; INFO_LOG(SLIPPI_ONLINE, "[Netplay] Got disconnect from peer %d", netEvent.peer->address.port);
return; break;
default: default:
break; break;
} }
} }
// didn't disconnect gracefully force disconnect // didn't disconnect gracefully force disconnect
enet_peer_reset(m_server); for (int i = 0; i < m_server.size(); i++)
m_server = nullptr; {
enet_peer_reset(m_server[i]);
}
m_server.clear();
SLIPPI_NETPLAY = nullptr; SLIPPI_NETPLAY = nullptr;
} }
@ -406,56 +499,143 @@ void SlippiNetplayClient::SendAsync(std::unique_ptr<sf::Packet> packet)
void SlippiNetplayClient::ThreadFunc() void SlippiNetplayClient::ThreadFunc()
{ {
// Let client die 1 second before host such that after a swap, the client won't be connected to // Let client die 1 second before host such that after a swap, the client won't be connected to
int attemptCountLimit = 16; u64 startTime = Common::Timer::GetTimeMs();
u64 timeout = 8000;
std::vector<bool> connections;
std::vector<ENetAddress> remoteAddrs;
for (int i = 0; i < m_remotePlayerCount; i++)
{
remoteAddrs.push_back(m_server[i]->address);
connections.push_back(false);
}
int attemptCount = 0;
while (slippiConnectStatus == SlippiConnectStatus::NET_CONNECT_STATUS_INITIATED) while (slippiConnectStatus == SlippiConnectStatus::NET_CONNECT_STATUS_INITIATED)
{ {
// This will confirm that connection went through successfully // This will confirm that connection went through successfully
ENetEvent netEvent; ENetEvent netEvent;
int net = enet_host_service(m_client, &netEvent, 500); int net = enet_host_service(m_client, &netEvent, 500);
if (net > 0 && netEvent.type == ENET_EVENT_TYPE_CONNECT) if (net > 0)
{ {
// TODO: Confirm gecko codes match? switch (netEvent.type)
if (netEvent.peer)
{ {
WARN_LOG(SLIPPI_ONLINE, "[Netplay] Overwritting server"); case ENET_EVENT_TYPE_RECEIVE:
m_server = netEvent.peer; if (!netEvent.peer)
{
INFO_LOG(SLIPPI_ONLINE, "[Netplay] got receive event with nil peer");
continue;
}
INFO_LOG(SLIPPI_ONLINE, "[Netplay] got receive event with peer addr %x:%d",
netEvent.peer->address.host, netEvent.peer->address.port);
break;
case ENET_EVENT_TYPE_DISCONNECT:
if (!netEvent.peer)
{
INFO_LOG(SLIPPI_ONLINE, "[Netplay] got disconnect event with nil peer");
continue;
}
INFO_LOG(SLIPPI_ONLINE, "[Netplay] got disconnect event with peer addr %x:%d",
netEvent.peer->address.host, netEvent.peer->address.port);
break;
case ENET_EVENT_TYPE_CONNECT:
{
if (!netEvent.peer)
{
INFO_LOG(SLIPPI_ONLINE, "[Netplay] got connect event with nil peer");
continue;
} }
INFO_LOG(SLIPPI_ONLINE, "[Netplay] got connect event with peer addr %x:%d",
netEvent.peer->address.host, netEvent.peer->address.port);
for (int i = 0; i < m_server.size(); i++)
{
INFO_LOG(SLIPPI_ONLINE, "[Netplay] Comparing connection address: %x:%d - %x:%d",
remoteAddrs[i].host, remoteAddrs[i].port, netEvent.peer->address.host,
netEvent.peer->address.port);
if (remoteAddrs[i].host == netEvent.peer->address.host &&
remoteAddrs[i].port == netEvent.peer->address.port)
{
INFO_LOG(SLIPPI_ONLINE, "[Netplay] Overwriting ENetPeer for address: %x:%d",
netEvent.peer->address.host, netEvent.peer->address.port);
INFO_LOG(SLIPPI_ONLINE,
"[Netplay] Overwriting ENetPeer with id (%d) with new peer of id %d",
m_server[i]->connectID, netEvent.peer->connectID);
m_server[i] = netEvent.peer;
connections[i] = true;
break;
}
}
break;
}
}
}
bool allConnected = true;
for (int i = 0; i < m_remotePlayerCount; i++)
{
if (!connections[i])
allConnected = false;
}
if (allConnected)
{
m_client->intercept = ENetUtil::InterceptCallback; m_client->intercept = ENetUtil::InterceptCallback;
slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED;
INFO_LOG(SLIPPI_ONLINE, "Slippi online connection successful!"); INFO_LOG(SLIPPI_ONLINE, "Slippi online connection successful!");
slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED;
break; break;
} }
for (int i = 0; i < m_remotePlayerCount; i++)
{
INFO_LOG(SLIPPI_ONLINE, "m_client peer %d state: %d", i, m_client->peers[i].state);
}
WARN_LOG_FMT(SLIPPI_ONLINE, "[Netplay] Not yet connected. Res: {}, Type: {}", net, WARN_LOG_FMT(SLIPPI_ONLINE, "[Netplay] Not yet connected. Res: {}, Type: {}", net,
netEvent.type); netEvent.type);
// Time out after enough time has passed // Time out after enough time has passed
attemptCount++; u64 curTime = Common::Timer::GetTimeMs();
if (attemptCount >= attemptCountLimit || !m_do_loop.IsSet()) if ((curTime - startTime) >= timeout || !m_do_loop.IsSet())
{ {
for (int i = 0; i < m_remotePlayerCount; i++)
{
if (!connections[i])
{
failedConnections.push_back(i);
}
}
slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_FAILED; slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_FAILED;
INFO_LOG(SLIPPI_ONLINE, "Slippi online connection failed"); INFO_LOG(SLIPPI_ONLINE, "Slippi online connection failed");
return; return;
} }
} }
INFO_LOG(SLIPPI_ONLINE, "Successfully initialized %d connections", m_server.size());
for (int i = 0; i < m_server.size(); i++)
{
INFO_LOG(SLIPPI_ONLINE, "Connection %d: %d, %d", i, m_server[i]->address.host,
m_server[i]->address.port);
}
bool qos_success = false; bool qos_success = false;
#ifdef _WIN32 #ifdef _WIN32
QOS_VERSION ver = {1, 0}; QOS_VERSION ver = {1, 0};
if (Config::Get(Config::NETPLAY_ENABLE_QOS) && QOSCreateHandle(&ver, &m_qos_handle)) if (QOSCreateHandle(&ver, &m_qos_handle))
{
for (int i = 0; i < m_server.size(); i++)
{ {
// from win32.c // from win32.c
struct sockaddr_in sin = {0}; struct sockaddr_in sin = {0};
sin.sin_family = AF_INET; sin.sin_family = AF_INET;
sin.sin_port = ENET_HOST_TO_NET_16(m_server->host->address.port); sin.sin_port = ENET_HOST_TO_NET_16(m_server[i]->host->address.port);
sin.sin_addr.s_addr = m_server->host->address.host; sin.sin_addr.s_addr = m_server[i]->host->address.host;
if (QOSAddSocketToFlow(m_qos_handle, m_server->host->socket, reinterpret_cast<PSOCKADDR>(&sin), if (QOSAddSocketToFlow(m_qos_handle, m_server[i]->host->socket,
reinterpret_cast<PSOCKADDR>(&sin),
// this is 0x38 // this is 0x38
QOSTrafficTypeControl, QOS_NON_ADAPTIVE_FLOW, &m_qos_flow_id)) QOSTrafficTypeControl, QOS_NON_ADAPTIVE_FLOW, &m_qos_flow_id))
{ {
@ -469,20 +649,24 @@ void SlippiNetplayClient::ThreadFunc()
qos_success = true; qos_success = true;
} }
} }
}
#else #else
if (Config::Get(Config::NETPLAY_ENABLE_QOS)) if (SConfig::GetInstance().bQoSEnabled)
{
for (int i = 0; i < m_server.size(); i++)
{ {
#ifdef __linux__ #ifdef __linux__
// highest priority // highest priority
int priority = 7; int priority = 7;
setsockopt(m_server->host->socket, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); setsockopt(m_server[i]->host->socket, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority));
#endif #endif
// https://www.tucny.com/Home/dscp-tos // https://www.tucny.com/Home/dscp-tos
// ef is better than cs7 // ef is better than cs7
int tos_val = 0xb8; int tos_val = 0xb8;
qos_success = qos_success =
setsockopt(m_server->host->socket, IPPROTO_IP, IP_TOS, &tos_val, sizeof(tos_val)) == 0; setsockopt(m_server[i]->host->socket, IPPROTO_IP, IP_TOS, &tos_val, sizeof(tos_val)) == 0;
}
} }
#endif #endif
@ -499,21 +683,31 @@ void SlippiNetplayClient::ThreadFunc()
if (net > 0) if (net > 0)
{ {
sf::Packet rpac; sf::Packet rpac;
bool sameClient = false;
switch (netEvent.type) switch (netEvent.type)
{ {
case ENET_EVENT_TYPE_RECEIVE: case ENET_EVENT_TYPE_RECEIVE:
rpac.append(netEvent.packet->data, netEvent.packet->dataLength); rpac.append(netEvent.packet->data, netEvent.packet->dataLength);
OnData(rpac); OnData(rpac, netEvent.peer);
enet_packet_destroy(netEvent.packet); enet_packet_destroy(netEvent.packet);
break; break;
case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT:
for (int i = 0; i < m_remotePlayerCount; i++)
{
if (remoteAddrs[i].host == netEvent.peer->address.host &&
remoteAddrs[i].port == netEvent.peer->address.port)
{
sameClient = true;
break;
}
}
ERROR_LOG(SLIPPI_ONLINE, "[Netplay] Disconnected Event detected: %s", ERROR_LOG(SLIPPI_ONLINE, "[Netplay] Disconnected Event detected: %s",
netEvent.peer == m_server ? "same client" : "diff client"); sameClient ? "same client" : "diff client");
// If the disconnect event doesn't come from the client we are actually listening to, // If the disconnect event doesn't come from the client we are actually listening to,
// it can be safely ignored // it can be safely ignored
if (netEvent.peer == m_server) if (sameClient)
{ {
m_do_loop.Clear(); // Stop the loop, will trigger a disconnect m_do_loop.Clear(); // Stop the loop, will trigger a disconnect
} }
@ -528,7 +722,12 @@ void SlippiNetplayClient::ThreadFunc()
if (m_qos_handle != 0) if (m_qos_handle != 0)
{ {
if (m_qos_flow_id != 0) if (m_qos_flow_id != 0)
QOSRemoveSocketFromFlow(m_qos_handle, m_server->host->socket, m_qos_flow_id, 0); {
for (int i = 0; i < m_server.size(); i++)
{
QOSRemoveSocketFromFlow(m_qos_handle, m_server[i]->host->socket, m_qos_flow_id, 0);
}
}
QOSCloseHandle(m_qos_handle); QOSCloseHandle(m_qos_handle);
} }
#endif #endif
@ -552,37 +751,42 @@ SlippiNetplayClient::SlippiConnectStatus SlippiNetplayClient::GetSlippiConnectSt
return slippiConnectStatus; return slippiConnectStatus;
} }
std::vector<int> SlippiNetplayClient::GetFailedConnections()
{
return failedConnections;
}
void SlippiNetplayClient::StartSlippiGame() void SlippiNetplayClient::StartSlippiGame()
{ {
// Reset variables to start a new game // Reset variables to start a new game
lastFrameAcked = 0;
FrameTiming timing;
timing.frame = 0;
timing.timeUs = Common::Timer::GetTimeUs();
lastFrameTiming = timing;
hasGameStarted = false; hasGameStarted = false;
localPadQueue.clear(); localPadQueue.clear();
// Remote pad should have been cleared when receiving the match selections for (int i = 0; i < m_remotePlayerCount; i++)
{
FrameTiming timing;
timing.frame = 0;
timing.timeUs = Common::Timer::GetTimeUs();
lastFrameTiming[i] = timing;
lastFrameAcked[i] = 0;
// Reset ack timers
std::queue<SlippiNetplayClient::FrameTiming> empty;
std::swap(ackTimers[i], empty);
}
// Reset match info for next game // Reset match info for next game
matchInfo.Reset(); matchInfo.Reset();
// Reset ack timers
ackTimers = {};
} }
void SlippiNetplayClient::SendConnectionSelected() void SlippiNetplayClient::SendConnectionSelected()
{ {
isConnectionSelected = true; isConnectionSelected = true;
auto spac = std::make_unique<sf::Packet>(); auto spac = std::make_unique<sf::Packet>();
*spac << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_CONN_SELECTED); *spac << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_CONN_SELECTED);
SendAsync(std::move(spac)); SendAsync(std::move(spac));
} }
void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad) void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
{ {
auto status = slippiConnectStatus; auto status = slippiConnectStatus;
@ -594,14 +798,12 @@ void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
{ {
return; return;
} }
// if (pad && isDecider) // if (pad && isDecider)
//{ //{
// ERROR_LOG(SLIPPI_ONLINE, "[%d] %X %X %X %X %X %X %X %X", pad->frame, pad->padBuf[0], // ERROR_LOG(SLIPPI_ONLINE, "[%d] %X %X %X %X %X %X %X %X", pad->frame, pad->padBuf[0],
// pad->padBuf[1], pad->padBuf[2], pad->padBuf[3], pad->padBuf[4], pad->padBuf[5], // pad->padBuf[1], pad->padBuf[2], pad->padBuf[3], pad->padBuf[4], pad->padBuf[5],
// pad->padBuf[6], pad->padBuf[7]); // pad->padBuf[6], pad->padBuf[7]);
//} //}
if (pad) if (pad)
{ {
// Add latest local pad report to queue // Add latest local pad report to queue
@ -609,8 +811,20 @@ void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
} }
// Remove pad reports that have been received and acked // Remove pad reports that have been received and acked
while (!localPadQueue.empty() && localPadQueue.back()->frame < lastFrameAcked) int minAckFrame = lastFrameAcked[0];
for (int i = 1; i < m_remotePlayerCount; i++)
{ {
if (lastFrameAcked[i] < minAckFrame)
minAckFrame = lastFrameAcked[i];
}
INFO_LOG(SLIPPI_ONLINE,
"Checking to drop local inputs, oldest frame: %d | minAckFrame: %d | %d, %d, %d",
localPadQueue.back()->frame, minAckFrame, lastFrameAcked[0], lastFrameAcked[1],
lastFrameAcked[2]);
while (!localPadQueue.empty() && localPadQueue.back()->frame < minAckFrame)
{
INFO_LOG(SLIPPI_ONLINE, "Dropping local input for frame %d from queue",
localPadQueue.back()->frame);
localPadQueue.pop_back(); localPadQueue.pop_back();
} }
@ -619,43 +833,45 @@ void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
// If pad queue is empty now, there's no reason to send anything // If pad queue is empty now, there's no reason to send anything
return; return;
} }
auto frame = localPadQueue.front()->frame; auto frame = localPadQueue.front()->frame;
auto spac = std::make_unique<sf::Packet>(); auto spac = std::make_unique<sf::Packet>();
*spac << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_PAD); *spac << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_PAD);
*spac << frame; *spac << frame;
*spac << this->playerIdx;
// INFO_LOG(SLIPPI_ONLINE, "Sending a packet of inputs [%d]...", frame); // INFO_LOG(SLIPPI_ONLINE, "Sending a packet of inputs [%d]...", frame);
for (auto it = localPadQueue.begin(); it != localPadQueue.end(); ++it) for (auto it = localPadQueue.begin(); it != localPadQueue.end(); ++it)
{ {
/*INFO_LOG(SLIPPI_ONLINE, "Send [%d] -> %02X %02X %02X %02X %02X %02X %02X %02X", (*it)->frame, // INFO_LOG(SLIPPI_ONLINE, "Send [%d] -> %02X %02X %02X %02X %02X %02X %02X %02X", (*it)->frame,
(*it)->padBuf[0], (*it)->padBuf[1], (*it)->padBuf[2], (*it)->padBuf[3], // (*it)->padBuf[0],
(*it)->padBuf[4], (*it)->padBuf[5], (*it)->padBuf[6], (*it)->padBuf[7]);*/ // (*it)->padBuf[1], (*it)->padBuf[2], (*it)->padBuf[3], (*it)->padBuf[4],
// (*it)->padBuf[5],
// (*it)->padBuf[6], (*it)->padBuf[7]);
spac->append((*it)->padBuf, SLIPPI_PAD_DATA_SIZE); // only transfer 8 bytes per pad spac->append((*it)->padBuf, SLIPPI_PAD_DATA_SIZE); // only transfer 8 bytes per pad
} }
SendAsync(std::move(spac)); SendAsync(std::move(spac));
u64 time = Common::Timer::GetTimeUs(); u64 time = Common::Timer::GetTimeUs();
hasGameStarted = true; hasGameStarted = true;
for (int i = 0; i < m_remotePlayerCount; i++)
{
FrameTiming timing; FrameTiming timing;
timing.frame = frame; timing.frame = frame;
timing.timeUs = time; timing.timeUs = time;
lastFrameTiming = timing; lastFrameTiming[i] = timing;
// Add send time to ack timers // Add send time to ack timers
FrameTiming sendTime; FrameTiming sendTime;
sendTime.frame = frame; sendTime.frame = frame;
sendTime.timeUs = time; sendTime.timeUs = time;
ackTimers.emplace(sendTime); ackTimers[i].emplace(sendTime);
}
} }
void SlippiNetplayClient::SetMatchSelections(SlippiPlayerSelections& s) void SlippiNetplayClient::SetMatchSelections(SlippiPlayerSelections& s)
{ {
matchInfo.localPlayerSelections.Merge(s); matchInfo.localPlayerSelections.Merge(s);
matchInfo.localPlayerSelections.playerIdx = playerIdx;
// Send packet containing selections // Send packet containing selections
auto spac = std::make_unique<sf::Packet>(); auto spac = std::make_unique<sf::Packet>();
@ -663,11 +879,26 @@ void SlippiNetplayClient::SetMatchSelections(SlippiPlayerSelections& s)
SendAsync(std::move(spac)); SendAsync(std::move(spac));
} }
u8 SlippiNetplayClient::GetSlippiRemoteChatMessage() SlippiPlayerSelections SlippiNetplayClient::GetSlippiRemoteChatMessage()
{ {
u8 copiedMessageId = remoteChatMessageId; SlippiPlayerSelections copiedSelection = SlippiPlayerSelections();
remoteChatMessageId = 0; // Clear it out
return copiedMessageId; if (remoteChatMessageSelection != nullptr && SConfig::GetInstance().m_slippiEnableQuickChat)
{
copiedSelection.messageId = remoteChatMessageSelection->messageId;
copiedSelection.playerIdx = remoteChatMessageSelection->playerIdx;
// Clear it out
remoteChatMessageSelection->messageId = 0;
remoteChatMessageSelection->playerIdx = 0;
}
else
{
copiedSelection.messageId = 0;
copiedSelection.playerIdx = 0;
}
return copiedSelection;
} }
u8 SlippiNetplayClient::GetSlippiRemoteSentChatMessage() u8 SlippiNetplayClient::GetSlippiRemoteSentChatMessage()
@ -677,13 +908,14 @@ u8 SlippiNetplayClient::GetSlippiRemoteSentChatMessage()
return copiedMessageId; return copiedMessageId;
} }
std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(int32_t curFrame) std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(int32_t curFrame,
int index)
{ {
std::lock_guard<std::mutex> lk(pad_mutex); // TODO: Is this the correct lock? std::lock_guard<std::mutex> lk(pad_mutex); // TODO: Is this the correct lock?
std::unique_ptr<SlippiRemotePadOutput> padOutput = std::make_unique<SlippiRemotePadOutput>(); std::unique_ptr<SlippiRemotePadOutput> padOutput = std::make_unique<SlippiRemotePadOutput>();
if (remotePadQueue.empty()) if (remotePadQueue[index].empty())
{ {
auto emptyPad = std::make_unique<SlippiPad>(0); auto emptyPad = std::make_unique<SlippiPad>(0);
@ -695,22 +927,54 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(i
return std::move(padOutput); return std::move(padOutput);
} }
padOutput->latestFrame = remotePadQueue.front()->frame; padOutput->latestFrame = 0;
// Copy the entire remaining remote buffer // Copy the entire remaining remote buffer
for (auto it = remotePadQueue.begin(); it != remotePadQueue.end(); ++it) for (auto it = remotePadQueue[index].begin(); it != remotePadQueue[index].end(); ++it)
{ {
if ((*it)->frame > padOutput->latestFrame)
padOutput->latestFrame = (*it)->frame;
auto padIt = std::begin((*it)->padBuf); auto padIt = std::begin((*it)->padBuf);
padOutput->data.insert(padOutput->data.end(), padIt, padIt + SLIPPI_PAD_FULL_SIZE); padOutput->data.insert(padOutput->data.end(), padIt, padIt + SLIPPI_PAD_FULL_SIZE);
} }
// Remove pad reports that should no longer be needed return std::move(padOutput);
while (remotePadQueue.size() > 1 && remotePadQueue.back()->frame < curFrame) }
void SlippiNetplayClient::DropOldRemoteInputs(int32_t curFrame)
{
std::lock_guard<std::mutex> lk(pad_mutex);
// Remove pad reports that should no longer be needed, compute the lowest frame recieved by
// all remote players that can be safely dropped.
int lowestCommonFrame = 0;
for (int i = 0; i < m_remotePlayerCount; i++)
{ {
remotePadQueue.pop_back(); int playerFrame = 0;
for (auto it = remotePadQueue[i].begin(); it != remotePadQueue[i].end(); ++it)
{
if (it->get()->frame > playerFrame)
playerFrame = it->get()->frame;
} }
return std::move(padOutput); if (lowestCommonFrame == 0 || playerFrame < lowestCommonFrame)
lowestCommonFrame = playerFrame;
}
// INFO_LOG(SLIPPI_ONLINE, "Checking for remotePadQueue inputs to drop, lowest common: %d, [0]:
// %d, [1]: %d, [2]: %d",
// lowestCommonFrame, playerFrame[0], playerFrame[1], playerFrame[2]);
for (int i = 0; i < m_remotePlayerCount; i++)
{
INFO_LOG(SLIPPI_ONLINE, "remotePadQueue[%d] size: %d", i, remotePadQueue[i].size());
while (remotePadQueue[i].size() > 1 && remotePadQueue[i].back()->frame < lowestCommonFrame &&
remotePadQueue[i].back()->frame < curFrame)
{
INFO_LOG(SLIPPI_ONLINE, "Popping inputs for frame %d from back of player %d queue",
remotePadQueue[i].back()->frame, i);
remotePadQueue[i].pop_back();
}
}
} }
SlippiMatchInfo* SlippiNetplayClient::GetMatchInfo() SlippiMatchInfo* SlippiNetplayClient::GetMatchInfo()
@ -718,32 +982,55 @@ SlippiMatchInfo* SlippiNetplayClient::GetMatchInfo()
return &matchInfo; return &matchInfo;
} }
u64 SlippiNetplayClient::GetSlippiPing()
{
return pingUs;
}
int32_t SlippiNetplayClient::GetSlippiLatestRemoteFrame() int32_t SlippiNetplayClient::GetSlippiLatestRemoteFrame()
{ {
std::lock_guard<std::mutex> lk(pad_mutex); // TODO: Is this the correct lock? std::lock_guard<std::mutex> lk(pad_mutex); // TODO: Is this the correct lock?
if (remotePadQueue.empty()) // Return the lowest frame among remote queues
int lowestFrame = 0;
for (int i = 0; i < m_remotePlayerCount; i++)
{
if (remotePadQueue[i].empty())
{ {
return 0; return 0;
} }
return remotePadQueue.front()->frame; int f = remotePadQueue[i].front()->frame;
if (f < lowestFrame || lowestFrame == 0)
{
lowestFrame = f;
}
}
return lowestFrame;
} }
// return the largest time offset among all remote players
s32 SlippiNetplayClient::CalcTimeOffsetUs() s32 SlippiNetplayClient::CalcTimeOffsetUs()
{ {
if (frameOffsetData.buf.empty()) bool empty = true;
for (int i = 0; i < m_remotePlayerCount; i++)
{
if (!frameOffsetData[i].buf.empty())
{
empty = false;
break;
}
}
if (empty)
{ {
return 0; return 0;
} }
std::vector<int> offsets;
for (int i = 0; i < m_remotePlayerCount; i++)
{
if (frameOffsetData[i].buf.empty())
continue;
std::vector<s32> buf; std::vector<s32> buf;
std::copy(frameOffsetData.buf.begin(), frameOffsetData.buf.end(), std::back_inserter(buf)); std::copy(frameOffsetData[i].buf.begin(), frameOffsetData[i].buf.end(),
std::back_inserter(buf));
// TODO: Does this work? // TODO: Does this work?
std::sort(buf.begin(), buf.end()); std::sort(buf.begin(), buf.end());
@ -764,5 +1051,18 @@ s32 SlippiNetplayClient::CalcTimeOffsetUs()
return 0; // What do I return here? return 0; // What do I return here?
} }
return sum / count; s32 result = sum / count;
offsets.push_back(result);
}
s32 maxOffset = offsets.front();
for (int i = 1; i < offsets.size(); i++)
{
if (offsets[i] > maxOffset)
maxOffset = offsets[i];
}
// INFO_LOG(SLIPPI_ONLINE, "Time offsets, [0]: %d, [1]: %d, [2]: %d", offsets[0], offsets[1],
// offsets[2]);
return maxOffset;
} }

View file

@ -13,6 +13,7 @@
#include <queue> #include <queue>
#include <string> #include <string>
#include <thread> #include <thread>
#include <unordered_map>
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Event.h" #include "Common/Event.h"
@ -21,7 +22,6 @@
#include "Core/NetPlayProto.h" #include "Core/NetPlayProto.h"
#include "Core/Slippi/SlippiPad.h" #include "Core/Slippi/SlippiPad.h"
#include "InputCommon/GCPadStatus.h" #include "InputCommon/GCPadStatus.h"
#ifdef _WIN32 #ifdef _WIN32
#include <Qos2.h> #include <Qos2.h>
#endif #endif
@ -29,18 +29,24 @@
#define SLIPPI_ONLINE_LOCKSTEP_INTERVAL \ #define SLIPPI_ONLINE_LOCKSTEP_INTERVAL \
30 // Number of frames to wait before attempting to time-sync 30 // Number of frames to wait before attempting to time-sync
#define SLIPPI_PING_DISPLAY_INTERVAL 60 #define SLIPPI_PING_DISPLAY_INTERVAL 60
#define SLIPPI_REMOTE_PLAYER_MAX 3
#define SLIPPI_REMOTE_PLAYER_COUNT 3
struct SlippiRemotePadOutput struct SlippiRemotePadOutput
{ {
int32_t latestFrame; int32_t latestFrame;
u8 playerIdx;
std::vector<u8> data; std::vector<u8> data;
}; };
class SlippiPlayerSelections class SlippiPlayerSelections
{ {
public: public:
u8 playerIdx = 0;
u8 characterId = 0; u8 characterId = 0;
u8 characterColor = 0; u8 characterColor = 0;
u8 teamId = 0;
bool isCharacterSelected = false; bool isCharacterSelected = false;
u16 stageId = 0; u16 stageId = 0;
@ -64,6 +70,7 @@ public:
{ {
this->characterId = s.characterId; this->characterId = s.characterId;
this->characterColor = s.characterColor; this->characterColor = s.characterColor;
this->teamId = s.teamId;
this->isCharacterSelected = true; this->isCharacterSelected = true;
} }
} }
@ -73,6 +80,7 @@ public:
characterId = 0; characterId = 0;
characterColor = 0; characterColor = 0;
isCharacterSelected = false; isCharacterSelected = false;
teamId = 0;
stageId = 0; stageId = 0;
isStageSelected = false; isStageSelected = false;
@ -85,12 +93,15 @@ class SlippiMatchInfo
{ {
public: public:
SlippiPlayerSelections localPlayerSelections; SlippiPlayerSelections localPlayerSelections;
SlippiPlayerSelections remotePlayerSelections; SlippiPlayerSelections remotePlayerSelections[SLIPPI_REMOTE_PLAYER_MAX];
void Reset() void Reset()
{ {
localPlayerSelections.Reset(); localPlayerSelections.Reset();
remotePlayerSelections.Reset(); for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
{
remotePlayerSelections[i].Reset();
}
} }
}; };
@ -101,8 +112,9 @@ public:
void SendAsync(std::unique_ptr<sf::Packet> packet); void SendAsync(std::unique_ptr<sf::Packet> packet);
SlippiNetplayClient(bool isDecider); // Make a dummy client SlippiNetplayClient(bool isDecider); // Make a dummy client
SlippiNetplayClient(const std::string& address, const u16 remotePort, const u16 localPort, SlippiNetplayClient(std::vector<std::string> addrs, std::vector<u16> ports,
bool isDecider); const u8 remotePlayerCount, const u16 localPort, bool isDecider,
u8 playerIdx);
~SlippiNetplayClient(); ~SlippiNetplayClient();
// Slippi Online // Slippi Online
@ -117,23 +129,26 @@ public:
bool IsDecider(); bool IsDecider();
bool IsConnectionSelected(); bool IsConnectionSelected();
u8 LocalPlayerPort();
SlippiConnectStatus GetSlippiConnectStatus(); SlippiConnectStatus GetSlippiConnectStatus();
std::vector<int> GetFailedConnections();
void StartSlippiGame(); void StartSlippiGame();
void SendConnectionSelected(); void SendConnectionSelected();
void SendSlippiPad(std::unique_ptr<SlippiPad> pad); void SendSlippiPad(std::unique_ptr<SlippiPad> pad);
void SetMatchSelections(SlippiPlayerSelections& s); void SetMatchSelections(SlippiPlayerSelections& s);
std::unique_ptr<SlippiRemotePadOutput> GetSlippiRemotePad(int32_t curFrame); std::unique_ptr<SlippiRemotePadOutput> GetSlippiRemotePad(int32_t curFrame, int index);
void DropOldRemoteInputs(int32_t curFrame);
SlippiMatchInfo* GetMatchInfo(); SlippiMatchInfo* GetMatchInfo();
u64 GetSlippiPing(); int32_t GetSlippiLatestRemoteFrame();
s32 GetSlippiLatestRemoteFrame(); SlippiPlayerSelections GetSlippiRemoteChatMessage();
u8 GetSlippiRemoteChatMessage();
u8 GetSlippiRemoteSentChatMessage(); u8 GetSlippiRemoteSentChatMessage();
s32 CalcTimeOffsetUs(); s32 CalcTimeOffsetUs();
void WriteChatMessageToPacket(sf::Packet& packet, int messageId); void WriteChatMessageToPacket(sf::Packet& packet, int messageId, u8 playerIdx);
std::unique_ptr<SlippiPlayerSelections> ReadChatMessageFromPacket(sf::Packet& packet); std::unique_ptr<SlippiPlayerSelections> ReadChatMessageFromPacket(sf::Packet& packet);
u8 remoteChatMessageId = 0; // most recent chat message id from opponent std::unique_ptr<SlippiPlayerSelections> remoteChatMessageSelection =
nullptr; // most recent chat message player selection (message + player index)
u8 remoteSentChatMessageId = 0; // most recent chat message id that current player sent u8 remoteSentChatMessageId = 0; // most recent chat message id that current player sent
protected: protected:
@ -148,8 +163,9 @@ protected:
std::queue<std::unique_ptr<sf::Packet>> m_async_queue; std::queue<std::unique_ptr<sf::Packet>> m_async_queue;
ENetHost* m_client = nullptr; ENetHost* m_client = nullptr;
ENetPeer* m_server = nullptr; std::vector<ENetPeer*> m_server;
std::thread m_thread; std::thread m_thread;
u8 m_remotePlayerCount = 0;
std::string m_selected_game; std::string m_selected_game;
Common::Flag m_is_running{false}; Common::Flag m_is_running{false};
@ -166,23 +182,30 @@ protected:
u64 timeUs; u64 timeUs;
}; };
struct struct FrameOffsetData
{ {
// TODO: Should the buffer size be dynamic based on time sync interval or not? // TODO: Should the buffer size be dynamic based on time sync interval or not?
int idx; int idx;
std::vector<s32> buf; std::vector<s32> buf;
} frameOffsetData; };
bool isConnectionSelected = false; bool isConnectionSelected = false;
bool isDecider = false; bool isDecider = false;
int32_t lastFrameAcked;
bool hasGameStarted = false; bool hasGameStarted = false;
FrameTiming lastFrameTiming; u8 playerIdx = 0;
u64 pingUs;
std::deque<std::unique_ptr<SlippiPad>> localPadQueue; // most recent inputs at start of deque std::deque<std::unique_ptr<SlippiPad>> localPadQueue; // most recent inputs at start of deque
std::deque<std::unique_ptr<SlippiPad>> remotePadQueue; // most recent inputs at start of deque std::deque<std::unique_ptr<SlippiPad>>
std::queue<FrameTiming> ackTimers; remotePadQueue[SLIPPI_REMOTE_PLAYER_MAX]; // most recent inputs at start of deque
u64 pingUs[SLIPPI_REMOTE_PLAYER_MAX];
int32_t lastFrameAcked[SLIPPI_REMOTE_PLAYER_MAX];
FrameOffsetData frameOffsetData[SLIPPI_REMOTE_PLAYER_MAX];
FrameTiming lastFrameTiming[SLIPPI_REMOTE_PLAYER_MAX];
std::array<std::queue<FrameTiming>, SLIPPI_REMOTE_PLAYER_MAX> ackTimers;
SlippiConnectStatus slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_UNSET; SlippiConnectStatus slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_UNSET;
std::vector<int> failedConnections;
SlippiMatchInfo matchInfo; SlippiMatchInfo matchInfo;
bool m_is_recording = false; bool m_is_recording = false;
@ -191,7 +214,8 @@ protected:
std::unique_ptr<SlippiPlayerSelections> readSelectionsFromPacket(sf::Packet& packet); std::unique_ptr<SlippiPlayerSelections> readSelectionsFromPacket(sf::Packet& packet);
private: private:
unsigned int OnData(sf::Packet& packet); u8 PlayerIdxFromPort(u8 port);
unsigned int OnData(sf::Packet& packet, ENetPeer* peer);
void Send(sf::Packet& packet); void Send(sf::Packet& packet);
void Disconnect(); void Disconnect();
@ -207,7 +231,7 @@ private:
extern SlippiNetplayClient* SLIPPI_NETPLAY; // singleton static pointer extern SlippiNetplayClient* SLIPPI_NETPLAY; // singleton static pointer
inline bool IsOnline() static bool IsOnline()
{ {
return SLIPPI_NETPLAY != nullptr; return SLIPPI_NETPLAY != nullptr;
} }

View file

@ -19,6 +19,14 @@ SlippiPad::SlippiPad(int32_t frame, u8* padBuf) : SlippiPad(frame)
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE); memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
} }
SlippiPad::SlippiPad(int32_t frame, u8 playerIdx, u8 *padBuf) : SlippiPad(frame)
{
this->frame = frame;
this->playerIdx = playerIdx;
// Overwrite the data portion of the pad
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
}
SlippiPad::~SlippiPad() SlippiPad::~SlippiPad()
{ {
// Do nothing? // Do nothing?

View file

@ -10,8 +10,10 @@ class SlippiPad
public: public:
SlippiPad(int32_t frame); SlippiPad(int32_t frame);
SlippiPad(int32_t frame, u8* padBuf); SlippiPad(int32_t frame, u8* padBuf);
SlippiPad(int32_t frame, u8 playerIdx, u8 *padBuf);
~SlippiPad(); ~SlippiPad();
int32_t frame; int32_t frame;
u8 playerIdx;
u8 padBuf[SLIPPI_PAD_FULL_SIZE]; u8 padBuf[SLIPPI_PAD_FULL_SIZE];
}; };

View file

@ -106,7 +106,7 @@ bool SlippiUser::AttemptLogin()
{ {
std::string user_file_path = getUserFilePath(); std::string user_file_path = getUserFilePath();
INFO_LOG(SLIPPI_ONLINE, "Looking for file at: %s", user_file_path.c_str()); // INFO_LOG(SLIPPI_ONLINE, "Looking for file at: %s", user_file_path.c_str());
{ {
// Put the filename here in its own scope because we don't really need it elsewhere // Put the filename here in its own scope because we don't really need it elsewhere
@ -312,6 +312,7 @@ SlippiUser::UserInfo SlippiUser::parseFile(std::string file_contents)
info.play_key = readString(res, "playKey"); info.play_key = readString(res, "playKey");
info.connect_code = readString(res, "connectCode"); info.connect_code = readString(res, "connectCode");
info.latest_version = readString(res, "latestVersion"); info.latest_version = readString(res, "latestVersion");
info.port = res.value("port", -1);
return info; return info;
} }

View file

@ -19,6 +19,8 @@ public:
std::string connect_code = ""; std::string connect_code = "";
std::string latest_version = ""; std::string latest_version = "";
std::string file_contents = ""; std::string file_contents = "";
int port;
}; };
SlippiUser(); SlippiUser();