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

View file

@ -154,6 +154,7 @@ struct SConfig
int m_slippiOnlineDelay = 2;
bool m_slippiEnableSeek = true;
bool m_slippiSaveReplays = true;
bool m_slippiEnableQuickChat = true;
bool m_slippiReplayMonthFolders = false;
std::string m_strSlippiReplayDir;
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
if (slippi_netplay)
{
auto userInfo = user->GetUserInfo();
auto oppInfo = matchmaking->GetOpponent();
auto playerInfo = matchmaking->GetPlayerInfo();
auto isDecider = slippi_netplay->IsDecider();
int local_port = isDecider ? 0 : 1;
int remote_port = isDecider ? 1 : 0;
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;
for (int i = 0; i < playerInfo.size(); i++)
{
slippi_names[i] = playerInfo[i].display_name;
slippi_connect_codes[i] = playerInfo[i].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];
if (isDisconnected())
{
m_read_queue.push_back(3); // Indicate we disconnected
return;
}
if (frame == 1)
{
availableSavestates.clear();
@ -1525,6 +1515,13 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
slippi_netplay->StartSlippiGame();
}
if (isDisconnected())
{
auto status = slippi_netplay->GetSlippiConnectStatus();
m_read_queue.push_back(3); // Indicate we disconnected
return;
}
if (shouldSkipOnlineFrame(frame))
{
// Send inputs that have not yet been acked
@ -1586,6 +1583,7 @@ bool CEXISlippi::shouldSkipOnlineFrame(s32 frame)
auto offsetUs = slippi_netplay->CalcTimeOffsetUs();
INFO_LOG(SLIPPI_ONLINE, "[Frame %d] Offset is: %d us", frame, offsetUs);
// TODO: figure out a better solution here for doubles?
if (offsetUs > 10000)
{
isCurrentlySkipping = true;
@ -1642,27 +1640,56 @@ void CEXISlippi::prepareOpponentInputs(u8* payload)
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];
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
int offset = (result->latestFrame - frame) * SLIPPI_PAD_FULL_SIZE;
offset = offset < 0 ? 0 : offset;
offset[i] = (results[i]->latestFrame - frame) * SLIPPI_PAD_FULL_SIZE;
offset[i] = offset[i] < 0 ? 0 : offset[i];
// 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;
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
auto txStart = result->data.begin() + offset;
auto txEnd = result->data.end();
for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
{
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.resize(SLIPPI_PAD_FULL_SIZE * ROLLBACK_MAX_FRAMES, 0);
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],
// 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
// someone else get force removed from queue and have to requeue
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";
return;
@ -1794,30 +1821,27 @@ void CEXISlippi::startFindMatch(u8* payload)
void CEXISlippi::prepareOnlineMatchState()
{
// This match block is a VS match with P1 Red Falco vs P2 Red Bowser on Battlefield. The proper
// values will be overwritten
// This match block is a VS match with P1 Red Falco vs P2 Red Bowser vs P3 Young Link vs P4 Young Link
// on Battlefield. The proper values will be overwritten
static std::vector<u8> onlineMatchBlock = {
0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x6E, 0x00,
0x1F, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F,
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, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09,
0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04,
0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F,
0x80, 0x00, 0x00, 0x1A, 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,
0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x1A, 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, 0x00, 0x3F, 0x80, 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, 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,
0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x6E, 0x00, 0x1F, 0x00, 0x00,
0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 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, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04, 0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
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, 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();
@ -1838,12 +1862,10 @@ void CEXISlippi::prepareOnlineMatchState()
m_read_queue.push_back(mmState); // Matchmaking State
u8 localPlayerReady = localSelections.isCharacterSelected;
u8 remotePlayerReady = 0;
u8 localPlayerIndex = 0;
u8 remotePlayersReady = 0;
u8 localPlayerIndex = matchmaking->LocalPlayerIndex();
u8 remotePlayerIndex = 1;
auto opponent = matchmaking->GetOpponent();
std::string oppName = opponent.display_name;
auto userInfo = user->GetUserInfo();
if (mmState == SlippiMatchmaking::ProcessState::CONNECTION_SUCCESS)
@ -1871,9 +1893,24 @@ void CEXISlippi::prepareOnlineMatchState()
{
auto matchInfo = slippi_netplay->GetMatchInfo();
#ifdef LOCAL_TESTING
remotePlayerReady = true;
remotePlayersReady = true;
#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
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
if (!is_play_session_active)
{
std::vector<std::string> uids{"", ""};
uids[localPlayerIndex] = userInfo.uid;
uids[remotePlayerIndex] = opponent.uid;
std::vector<std::string> uids;
auto mmPlayers = matchmaking->GetPlayerInfo();
for (auto mmp : mmPlayers)
{
uids.push_back(mmp.uid);
}
game_reporter->StartNewSession(uids);
@ -1906,55 +1947,93 @@ void CEXISlippi::prepareOnlineMatchState()
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;
std::string localPlayerName = "";
std::string oppName = "";
std::string p1Name = "";
std::string p2Name = "";
u8 chatMessageId = 0;
u8 chatMessagePlayerIdx = 0;
u8 sentChatMessageId = 0;
#ifdef LOCAL_TESTING
localPlayerIndex = 0;
chatMessageId = localChatMessageId;
chatMessagePlayerIdx = 0;
localChatMessageId = 0;
// in CSS p1 is always current player and p2 is opponent
p1Name = "Player 1";
p2Name = "Player 2";
localPlayerName = p1Name = "Player 1";
oppName = p2Name = "Player 2";
#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
if (slippi_netplay)
{
chatMessageId = slippi_netplay->GetSlippiRemoteChatMessage();
auto remoteMessageSelection = slippi_netplay->GetSlippiRemoteChatMessage();
chatMessageId = remoteMessageSelection.messageId;
chatMessagePlayerIdx = remoteMessageSelection.playerIdx;
sentChatMessageId = slippi_netplay->GetSlippiRemoteSentChatMessage();
// in CSS p1 is always current player and p2 is opponent
p1Name = userInfo.display_name;
p2Name = oppName;
localPlayerName = p1Name = userInfo.display_name;
}
auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT;
if (localPlayerReady && remotePlayerReady)
std::vector<u8> leftTeamPlayers = {};
std::vector<u8> rightTeamPlayers = {};
if (localPlayerReady && remotePlayersReady)
{
auto isDecider = slippi_netplay->IsDecider();
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
auto matchInfo = slippi_netplay->GetMatchInfo();
SlippiPlayerSelections lps = matchInfo->localPlayerSelections;
SlippiPlayerSelections rps = matchInfo->remotePlayerSelections;
auto rps = matchInfo->remotePlayerSelections;
#ifdef LOCAL_TESTING
rps.characterId = 0x2;
rps.characterColor = 2;
lps.playerIdx = 0;
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");
#endif
// Check if someone is picking dumb characters in non-direct
auto localCharOk = lps.characterId < 26;
auto remoteCharOk = rps.characterId < 26;
if (lastSearch.mode != directMode && (!localCharOk || !remoteCharOk))
auto remoteCharOk = true;
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
handleConnectionCleanup();
@ -1965,52 +2044,99 @@ void CEXISlippi::prepareOnlineMatchState()
}
// Overwrite local player character
onlineMatchBlock[0x60 + localPlayerIndex * 0x24] = lps.characterId;
onlineMatchBlock[0x63 + localPlayerIndex * 0x24] = lps.characterColor;
onlineMatchBlock[0x60 + (lps.playerIdx) * 0x24] = lps.characterId;
onlineMatchBlock[0x63 + (lps.playerIdx) * 0x24] = lps.characterColor;
onlineMatchBlock[0x67 + (lps.playerIdx) * 0x24] = 0;
onlineMatchBlock[0x69 + (lps.playerIdx) * 0x24] = lps.teamId;
// Overwrite remote player character
onlineMatchBlock[0x60 + remotePlayerIndex * 0x24] = rps.characterId;
onlineMatchBlock[0x63 + remotePlayerIndex * 0x24] = rps.characterColor;
for (int i = 0; i < remotePlayerCount; i++)
{
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
bool isSheikVsZelda = lps.characterId == 0x12 && rps.characterId == 0x13 ||
lps.characterId == 0x13 && rps.characterId == 0x12;
bool charMatch = lps.characterId == rps.characterId || isSheikVsZelda;
bool colMatch = lps.characterColor == rps.characterColor;
bool isSheikVsZelda = lps.characterId == 0x12 && rps[0].characterId == 0x13 ||
lps.characterId == 0x13 && rps[0].characterId == 0x12;
bool charMatch = lps.characterId == rps[0].characterId || isSheikVsZelda;
bool colMatch = lps.characterColor == rps[0].characterColor;
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
u16 stageId;
if (isDecider)
{
stageId = lps.isStageSelected ? lps.stageId : rps.stageId;
stageId = lps.isStageSelected ? lps.stageId : rps[0].stageId;
}
else
{
stageId = rps.isStageSelected ? rps.stageId : lps.stageId;
stageId = rps[0].isStageSelected ? rps[0].stageId : lps.stageId;
}
// int seconds = 0;
// u32 *timer = (u32 *)&onlineMatchBlock[0x10];
//*timer = Common::swap32(seconds * 60);
u16 *stage = (u16 *)&onlineMatchBlock[0xE];
*stage = Common::swap16(stageId);
// 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, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60],
onlineMatchBlock[0x84]);
// Set player names
p1Name = isDecider ? userInfo.display_name : oppName;
p2Name = isDecider ? oppName : userInfo.display_name;
WARN_LOG(SLIPPI_ONLINE, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60], onlineMatchBlock[0x84]);
// Turn pause on in direct, off in everything else
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
@ -2022,13 +2148,52 @@ void CEXISlippi::prepareOnlineMatchState()
// Add chat messages id
m_read_queue.push_back((u8)sentChatMessageId);
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
p1Name = ConvertStringForGame(p1Name, MAX_NAME_LENGTH);
m_read_queue.insert(m_read_queue.end(), p1Name.begin(), p1Name.end());
p2Name = ConvertStringForGame(p2Name, MAX_NAME_LENGTH);
m_read_queue.insert(m_read_queue.end(), p2Name.begin(), p2Name.end());
oppName = ConvertStringForGame(oppName, MAX_NAME_LENGTH);
// Always send static local player name
localPlayerName = ConvertStringForGame(localPlayerName, MAX_NAME_LENGTH);
m_read_queue.insert(m_read_queue.end(), localPlayerName.begin(), localPlayerName.end());
#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());
// Add error message if there is one
@ -2071,12 +2236,13 @@ void CEXISlippi::setMatchSelections(u8* payload)
{
SlippiPlayerSelections s;
s.characterId = payload[0];
s.characterColor = payload[1];
s.isCharacterSelected = payload[2];
s.teamId = payload[0];
s.characterId = payload[1];
s.characterColor = payload[2];
s.isCharacterSelected = payload[3];
s.stageId = Common::swap16(&payload[3]);
u8 stageSelectOption = payload[5];
s.stageId = Common::swap16(&payload[4]);
u8 stageSelectOption = payload[6];
s.isStageSelected = stageSelectOption == 1 || stageSelectOption == 3;
if (stageSelectOption == 3)
@ -2085,8 +2251,17 @@ void CEXISlippi::setMatchSelections(u8* payload)
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;
if (matchmaking->LocalPlayerIndex() == 1 && firstMatch)
{
firstMatch = false;
s.stageId = getRandomStage();
}
// Merge these selections
localSelections.Merge(s);
@ -2142,7 +2317,7 @@ void CEXISlippi::handleChatMessage(u8* payload)
auto packet = std::make_unique<sf::Packet>();
// OSD::AddMessage("[Me]: "+ msg, OSD::Duration::VERY_LONG, OSD::Color::YELLOW);
slippi_netplay->remoteSentChatMessageId = messageId;
slippi_netplay->WriteChatMessageToPacket(*packet, messageId);
slippi_netplay->WriteChatMessageToPacket(*packet, messageId, slippi_netplay->LocalPlayerPort());
slippi_netplay->SendAsync(std::move(packet));
}
}
@ -2258,6 +2433,7 @@ void CEXISlippi::handleConnectionCleanup()
// Reset play session
is_play_session_active = false;
firstMatch = true;
#ifdef LOCAL_TESTING
isLocalConnected = false;

View file

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

View file

@ -7,6 +7,13 @@
#include "Common/StringUtil.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
{
public:
@ -68,11 +75,6 @@ std::string SlippiMatchmaking::GetErrorMessage()
return m_errorMsg;
}
SlippiUser::UserInfo SlippiMatchmaking::GetOpponent()
{
return m_oppUser;
}
bool SlippiMatchmaking::IsSearching()
{
return searchingStates.count(m_state) != 0;
@ -208,8 +210,12 @@ void SlippiMatchmaking::startMatchmaking()
m_client = nullptr;
int retryCount = 0;
auto userInfo = m_user->GetUserInfo();
while (m_client == nullptr && retryCount < 15)
{
if (userInfo.port > 0)
m_hostPort = userInfo.port;
else
m_hostPort = 49000 + (generator() % 2000);
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Port to use: %d...", m_hostPort);
@ -269,6 +275,7 @@ void SlippiMatchmaking::startMatchmaking()
continue;
}
netEvent.peer->data = &userInfo.display_name;
m_client->intercept = ENetUtil::InterceptCallback;
isMmConnected = true;
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...");
if (!m_user->IsLoggedIn())
{
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Must be logged in to queue");
m_state = ProcessState::ERROR_ENCOUNTERED;
m_errorMsg = "Must be logged in to queue. Go back to menu";
return;
}
// if (!m_user->IsLoggedIn())
// {
// ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Must be logged in to queue");
// m_state = ProcessState::ERROR_ENCOUNTERED;
// m_errorMsg = "Must be logged in to queue. Go back to menu";
// 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;
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["search"] = {{"mode", m_searchSettings.mode}, {"connectCode", connectCodeBuf}};
request["appVersion"] = Common::scm_slippi_semver_str;
request["ipAddressLan"] = lan_addr;
sendMessage(request);
// Get response from server
@ -385,35 +418,62 @@ void SlippiMatchmaking::handleMatchmaking()
m_isSwapAttempt = false;
m_netplayClient = nullptr;
// Clear old user
SlippiUser::UserInfo emptyInfo;
m_oppUser = emptyInfo;
// Clear old users
m_remoteIps.clear();
m_playerInfo.clear();
auto queue = getResp["players"];
if (queue.is_array())
{
std::string localExternalIp = "";
for (json::iterator it = queue.begin(); it != queue.end(); ++it)
{
json el = *it;
//SlippiUser::UserInfo playerInfo;
SlippiUser::UserInfo playerInfo;
bool isLocal = el.value("isLocalPlayer", false);
//playerInfo.uid = el.value("uid", "");
//playerInfo.displayName = el.value("displayName", "");
//playerInfo.connectCode = el.value("connectCode", "");
//playerInfo.port = el.value("port", 0);
playerInfo.uid = el.value("uid", "");
playerInfo.display_name = el.value("displayName", "");
playerInfo.connect_code = el.value("connectCode", "");
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");
m_oppUser.uid = el.value("uid", "");
m_oppUser.display_name = el.value("displayName", "");
m_oppUser.connect_code = el.value("connectCode", "");
std::vector<std::string> localIpParts;
localIpParts = SplitString(el.value("ipAddress", "1.1.1.1:123"), ':');
localExternalIp = localIpParts[0];
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
// m_localPlayerPort = playerInfo.port - 1;
};
// TODO: Instead of using one or the other, it might be better to try both
// If external IPs are the same, try using LAN IPs
m_remoteIps.push_back(lanIp);
}
}
m_isHost = getResp.value("isHost", false);
@ -425,13 +485,62 @@ void SlippiMatchmaking::handleMatchmaking()
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()
{
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
auto client = std::make_unique<SlippiNetplayClient>(ipParts[0], std::stoi(ipParts[1]), m_hostPort,
m_isHost);
auto client = std::make_unique<SlippiNetplayClient>(addrs, ports, remotePlayerCount, m_hostPort,
m_isHost, m_localPlayerIndex);
while (!m_netplayClient)
{
@ -447,6 +556,32 @@ void SlippiMatchmaking::handleConnecting()
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)
{
ERROR_LOG(SLIPPI_ONLINE,

View file

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

View file

@ -34,7 +34,7 @@ SlippiNetplayClient::~SlippiNetplayClient()
if (m_thread.joinable())
m_thread.join();
if (m_server)
if (!m_server.empty())
{
Disconnect();
}
@ -55,8 +55,9 @@ SlippiNetplayClient::~SlippiNetplayClient()
}
// called from ---SLIPPI EXI--- thread
SlippiNetplayClient::SlippiNetplayClient(const std::string& address, const u16 remotePort,
const u16 localPort, bool isDecider)
SlippiNetplayClient::SlippiNetplayClient(std::vector<std::string> addrs, std::vector<u16> ports,
const u8 remotePlayerCount, const u16 localPort,
bool isDecider, u8 playerIdx)
#ifdef _WIN32
: m_qos_handle(nullptr), m_qos_flow_id(0)
#endif
@ -65,6 +66,25 @@ SlippiNetplayClient::SlippiNetplayClient(const std::string& address, const u16 r
isDecider ? "true" : "false");
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);
// 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
// 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)
{
PanicAlertT("Couldn't Create Client");
}
for (int i = 0; i < remotePlayerCount; i++)
{
ENetAddress addr;
enet_address_set_host(&addr, address.c_str());
addr.port = remotePort;
enet_address_set_host(&addr, addrs[i].c_str());
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.");
}
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;
@ -118,8 +148,23 @@ SlippiNetplayClient::SlippiNetplayClient(bool isDecider)
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
unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
{
NetPlay::MessageId mid = 0;
if (!(packet >> mid))
@ -139,6 +184,19 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
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
// before we initialized
// 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();
auto timing = lastFrameTiming;
auto timing = lastFrameTiming[pIdx];
if (!hasGameStarted)
{
// 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;
}
s64 opponentSendTimeUs = curTime - (pingUs / 2);
s64 opponentSendTimeUs = curTime - (pingUs[pIdx] / 2);
s64 frameDiffOffsetUs = 16683 * (timing.frame - frame);
s64 timeOffsetUs = opponentSendTimeUs - timing.timeUs + frameDiffOffsetUs;
@ -163,12 +221,12 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
timing.frame, timeOffsetUs);
// Add this offset to circular buffer for use later
if (frameOffsetData.buf.size() < SLIPPI_ONLINE_LOCKSTEP_INTERVAL)
frameOffsetData.buf.push_back((s32)timeOffsetUs);
if (frameOffsetData[pIdx].buf.size() < SLIPPI_ONLINE_LOCKSTEP_INTERVAL)
frameOffsetData[pIdx].buf.push_back((s32)timeOffsetUs);
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?
@ -177,7 +235,10 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
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;
// 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--)
{
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,
pad->padBuf[0], pad->padBuf[1], pad->padBuf[2], pad->padBuf[3], pad->padBuf[4],
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;
spac << (NetPlay::MessageId)NetPlay::NP_MSG_SLIPPI_PAD_ACK;
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;
@ -222,28 +288,49 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
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
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
if (ackTimers.empty() || ackTimers.front().frame != frame)
if (ackTimers[pIdx].empty() || ackTimers[pIdx].front().frame != frame)
{
break;
}
auto sendTime = ackTimers.front().timeUs;
ackTimers.pop();
auto sendTime = ackTimers[pIdx].front().timeUs;
ackTimers[pIdx].pop();
pingUs = Common::Timer::GetTimeUs() - sendTime;
if (g_ActiveConfig.bShowNetPlayPing && frame % SLIPPI_PING_DISPLAY_INTERVAL == 0)
pingUs[pIdx] = Common::Timer::GetTimeUs() - sendTime;
if (g_ActiveConfig.bShowNetPlayPing && frame % SLIPPI_PING_DISPLAY_INTERVAL == 0 && pIdx == 0)
{
OSD::AddTypedMessage(OSD::MessageType::NetPlayPing,
StringFromFormat("Ping: %u", pingUs / 1000), OSD::Duration::NORMAL,
std::stringstream pingDisplay;
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);
}
}
@ -252,8 +339,10 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
case NetPlay::NP_MSG_SLIPPI_MATCH_SELECTIONS:
{
auto s = readSelectionsFromPacket(packet);
INFO_LOG(SLIPPI_ONLINE, "[Netplay] Received selections from opponent");
matchInfo.remotePlayerSelections.Merge(*s);
INFO_LOG(SLIPPI_ONLINE, "[Netplay] Received selections from opponent with player idx %d",
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
// 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
// game
remotePadQueue.clear();
remotePadQueue[idx].clear();
}
break;
case NetPlay::NP_MSG_SLIPPI_CHAT_MESSAGE:
{
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
remoteChatMessageId = messageId;
// Show chat message OSD
INFO_LOG(SLIPPI_ONLINE, "[Netplay] Received chat message from opponent %i", messageId);
remoteChatMessageSelection = std::move(playerSelection);
}
break;
@ -295,21 +382,19 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
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 << s.characterId << s.characterColor << s.isCharacterSelected;
packet << playerIndex;
packet << s.playerIdx;
packet << s.stageId << s.isStageSelected;
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 << messageId;
packet << playerIdx;
}
std::unique_ptr<SlippiPlayerSelections>
@ -318,6 +403,7 @@ SlippiNetplayClient::ReadChatMessageFromPacket(sf::Packet& packet)
auto s = std::make_unique<SlippiPlayerSelections>();
packet >> s->messageId;
packet >> s->playerIdx;
return std::move(s);
}
@ -327,20 +413,17 @@ SlippiNetplayClient::readSelectionsFromPacket(sf::Packet& packet)
{
auto s = std::make_unique<SlippiPlayerSelections>();
u8 playerIndex;
u8 teamId;
packet >> s->characterId;
packet >> s->characterColor;
packet >> s->isCharacterSelected;
packet >> playerIndex;
packet >> s->playerIdx;
packet >> s->stageId;
packet >> s->isStageSelected;
packet >> s->rngOffset;
packet >> teamId;
packet >> s->teamId;
return std::move(s);
}
@ -350,6 +433,8 @@ void SlippiNetplayClient::Send(sf::Packet& packet)
enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE;
u8 channelId = 0;
for (int i = 0; i < m_server.size(); i++)
{
NetPlay::MessageId mid = ((u8*)packet.getData())[0];
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);
enet_peer_send(m_server, channelId, epac);
int sendResult = enet_peer_send(m_server[i], channelId, epac);
}
}
void SlippiNetplayClient::Disconnect()
{
ENetEvent netEvent;
slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_DISCONNECTED;
if (m_server)
enet_peer_disconnect(m_server, 0);
if (!m_server.empty())
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
return;
@ -381,15 +471,18 @@ void SlippiNetplayClient::Disconnect()
enet_packet_destroy(netEvent.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
m_server = nullptr;
return;
INFO_LOG(SLIPPI_ONLINE, "[Netplay] Got disconnect from peer %d", netEvent.peer->address.port);
break;
default:
break;
}
}
// didn't disconnect gracefully force disconnect
enet_peer_reset(m_server);
m_server = nullptr;
for (int i = 0; i < m_server.size(); i++)
{
enet_peer_reset(m_server[i]);
}
m_server.clear();
SLIPPI_NETPLAY = nullptr;
}
@ -406,56 +499,143 @@ void SlippiNetplayClient::SendAsync(std::unique_ptr<sf::Packet> packet)
void SlippiNetplayClient::ThreadFunc()
{
// 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)
{
// This will confirm that connection went through successfully
ENetEvent netEvent;
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?
if (netEvent.peer)
switch (netEvent.type)
{
WARN_LOG(SLIPPI_ONLINE, "[Netplay] Overwritting server");
m_server = netEvent.peer;
case ENET_EVENT_TYPE_RECEIVE:
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;
slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED;
INFO_LOG(SLIPPI_ONLINE, "Slippi online connection successful!");
slippiConnectStatus = SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED;
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,
netEvent.type);
// Time out after enough time has passed
attemptCount++;
if (attemptCount >= attemptCountLimit || !m_do_loop.IsSet())
u64 curTime = Common::Timer::GetTimeMs();
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;
INFO_LOG(SLIPPI_ONLINE, "Slippi online connection failed");
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;
#ifdef _WIN32
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
struct sockaddr_in sin = {0};
sin.sin_family = AF_INET;
sin.sin_port = ENET_HOST_TO_NET_16(m_server->host->address.port);
sin.sin_addr.s_addr = m_server->host->address.host;
sin.sin_port = ENET_HOST_TO_NET_16(m_server[i]->host->address.port);
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
QOSTrafficTypeControl, QOS_NON_ADAPTIVE_FLOW, &m_qos_flow_id))
{
@ -469,20 +649,24 @@ void SlippiNetplayClient::ThreadFunc()
qos_success = true;
}
}
}
#else
if (Config::Get(Config::NETPLAY_ENABLE_QOS))
if (SConfig::GetInstance().bQoSEnabled)
{
for (int i = 0; i < m_server.size(); i++)
{
#ifdef __linux__
// highest priority
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
// https://www.tucny.com/Home/dscp-tos
// ef is better than cs7
int tos_val = 0xb8;
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
@ -499,21 +683,31 @@ void SlippiNetplayClient::ThreadFunc()
if (net > 0)
{
sf::Packet rpac;
bool sameClient = false;
switch (netEvent.type)
{
case ENET_EVENT_TYPE_RECEIVE:
rpac.append(netEvent.packet->data, netEvent.packet->dataLength);
OnData(rpac);
OnData(rpac, netEvent.peer);
enet_packet_destroy(netEvent.packet);
break;
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",
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,
// it can be safely ignored
if (netEvent.peer == m_server)
if (sameClient)
{
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_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);
}
#endif
@ -552,37 +751,42 @@ SlippiNetplayClient::SlippiConnectStatus SlippiNetplayClient::GetSlippiConnectSt
return slippiConnectStatus;
}
std::vector<int> SlippiNetplayClient::GetFailedConnections()
{
return failedConnections;
}
void SlippiNetplayClient::StartSlippiGame()
{
// Reset variables to start a new game
lastFrameAcked = 0;
FrameTiming timing;
timing.frame = 0;
timing.timeUs = Common::Timer::GetTimeUs();
lastFrameTiming = timing;
hasGameStarted = false;
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
matchInfo.Reset();
// Reset ack timers
ackTimers = {};
}
void SlippiNetplayClient::SendConnectionSelected()
{
isConnectionSelected = true;
auto spac = std::make_unique<sf::Packet>();
*spac << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_CONN_SELECTED);
SendAsync(std::move(spac));
}
void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
{
auto status = slippiConnectStatus;
@ -594,14 +798,12 @@ void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
{
return;
}
// if (pad && isDecider)
//{
// 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[6], pad->padBuf[7]);
//}
if (pad)
{
// 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
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();
}
@ -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
return;
}
auto frame = localPadQueue.front()->frame;
auto spac = std::make_unique<sf::Packet>();
*spac << static_cast<NetPlay::MessageId>(NetPlay::NP_MSG_SLIPPI_PAD);
*spac << frame;
*spac << this->playerIdx;
// INFO_LOG(SLIPPI_ONLINE, "Sending a packet of inputs [%d]...", frame);
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,
(*it)->padBuf[0], (*it)->padBuf[1], (*it)->padBuf[2], (*it)->padBuf[3],
(*it)->padBuf[4], (*it)->padBuf[5], (*it)->padBuf[6], (*it)->padBuf[7]);*/
// 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[4],
// (*it)->padBuf[5],
// (*it)->padBuf[6], (*it)->padBuf[7]);
spac->append((*it)->padBuf, SLIPPI_PAD_DATA_SIZE); // only transfer 8 bytes per pad
}
SendAsync(std::move(spac));
u64 time = Common::Timer::GetTimeUs();
hasGameStarted = true;
for (int i = 0; i < m_remotePlayerCount; i++)
{
FrameTiming timing;
timing.frame = frame;
timing.timeUs = time;
lastFrameTiming = timing;
lastFrameTiming[i] = timing;
// Add send time to ack timers
FrameTiming sendTime;
sendTime.frame = frame;
sendTime.timeUs = time;
ackTimers.emplace(sendTime);
ackTimers[i].emplace(sendTime);
}
}
void SlippiNetplayClient::SetMatchSelections(SlippiPlayerSelections& s)
{
matchInfo.localPlayerSelections.Merge(s);
matchInfo.localPlayerSelections.playerIdx = playerIdx;
// Send packet containing selections
auto spac = std::make_unique<sf::Packet>();
@ -663,11 +879,26 @@ void SlippiNetplayClient::SetMatchSelections(SlippiPlayerSelections& s)
SendAsync(std::move(spac));
}
u8 SlippiNetplayClient::GetSlippiRemoteChatMessage()
SlippiPlayerSelections SlippiNetplayClient::GetSlippiRemoteChatMessage()
{
u8 copiedMessageId = remoteChatMessageId;
remoteChatMessageId = 0; // Clear it out
return copiedMessageId;
SlippiPlayerSelections copiedSelection = SlippiPlayerSelections();
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()
@ -677,13 +908,14 @@ u8 SlippiNetplayClient::GetSlippiRemoteSentChatMessage()
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::unique_ptr<SlippiRemotePadOutput> padOutput = std::make_unique<SlippiRemotePadOutput>();
if (remotePadQueue.empty())
if (remotePadQueue[index].empty())
{
auto emptyPad = std::make_unique<SlippiPad>(0);
@ -695,22 +927,54 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(i
return std::move(padOutput);
}
padOutput->latestFrame = remotePadQueue.front()->frame;
padOutput->latestFrame = 0;
// 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);
padOutput->data.insert(padOutput->data.end(), padIt, padIt + SLIPPI_PAD_FULL_SIZE);
}
// Remove pad reports that should no longer be needed
while (remotePadQueue.size() > 1 && remotePadQueue.back()->frame < curFrame)
{
remotePadQueue.pop_back();
return std::move(padOutput);
}
return std::move(padOutput);
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++)
{
int playerFrame = 0;
for (auto it = remotePadQueue[i].begin(); it != remotePadQueue[i].end(); ++it)
{
if (it->get()->frame > playerFrame)
playerFrame = it->get()->frame;
}
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()
@ -718,32 +982,55 @@ SlippiMatchInfo* SlippiNetplayClient::GetMatchInfo()
return &matchInfo;
}
u64 SlippiNetplayClient::GetSlippiPing()
{
return pingUs;
}
int32_t SlippiNetplayClient::GetSlippiLatestRemoteFrame()
{
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 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()
{
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;
}
std::vector<int> offsets;
for (int i = 0; i < m_remotePlayerCount; i++)
{
if (frameOffsetData[i].buf.empty())
continue;
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?
std::sort(buf.begin(), buf.end());
@ -764,5 +1051,18 @@ s32 SlippiNetplayClient::CalcTimeOffsetUs()
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 <string>
#include <thread>
#include <unordered_map>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/Event.h"
@ -21,7 +22,6 @@
#include "Core/NetPlayProto.h"
#include "Core/Slippi/SlippiPad.h"
#include "InputCommon/GCPadStatus.h"
#ifdef _WIN32
#include <Qos2.h>
#endif
@ -29,18 +29,24 @@
#define SLIPPI_ONLINE_LOCKSTEP_INTERVAL \
30 // Number of frames to wait before attempting to time-sync
#define SLIPPI_PING_DISPLAY_INTERVAL 60
#define SLIPPI_REMOTE_PLAYER_MAX 3
#define SLIPPI_REMOTE_PLAYER_COUNT 3
struct SlippiRemotePadOutput
{
int32_t latestFrame;
u8 playerIdx;
std::vector<u8> data;
};
class SlippiPlayerSelections
{
public:
u8 playerIdx = 0;
u8 characterId = 0;
u8 characterColor = 0;
u8 teamId = 0;
bool isCharacterSelected = false;
u16 stageId = 0;
@ -64,6 +70,7 @@ public:
{
this->characterId = s.characterId;
this->characterColor = s.characterColor;
this->teamId = s.teamId;
this->isCharacterSelected = true;
}
}
@ -73,6 +80,7 @@ public:
characterId = 0;
characterColor = 0;
isCharacterSelected = false;
teamId = 0;
stageId = 0;
isStageSelected = false;
@ -85,12 +93,15 @@ class SlippiMatchInfo
{
public:
SlippiPlayerSelections localPlayerSelections;
SlippiPlayerSelections remotePlayerSelections;
SlippiPlayerSelections remotePlayerSelections[SLIPPI_REMOTE_PLAYER_MAX];
void 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);
SlippiNetplayClient(bool isDecider); // Make a dummy client
SlippiNetplayClient(const std::string& address, const u16 remotePort, const u16 localPort,
bool isDecider);
SlippiNetplayClient(std::vector<std::string> addrs, std::vector<u16> ports,
const u8 remotePlayerCount, const u16 localPort, bool isDecider,
u8 playerIdx);
~SlippiNetplayClient();
// Slippi Online
@ -117,23 +129,26 @@ public:
bool IsDecider();
bool IsConnectionSelected();
u8 LocalPlayerPort();
SlippiConnectStatus GetSlippiConnectStatus();
std::vector<int> GetFailedConnections();
void StartSlippiGame();
void SendConnectionSelected();
void SendSlippiPad(std::unique_ptr<SlippiPad> pad);
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();
u64 GetSlippiPing();
s32 GetSlippiLatestRemoteFrame();
u8 GetSlippiRemoteChatMessage();
int32_t GetSlippiLatestRemoteFrame();
SlippiPlayerSelections GetSlippiRemoteChatMessage();
u8 GetSlippiRemoteSentChatMessage();
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);
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
protected:
@ -148,8 +163,9 @@ protected:
std::queue<std::unique_ptr<sf::Packet>> m_async_queue;
ENetHost* m_client = nullptr;
ENetPeer* m_server = nullptr;
std::vector<ENetPeer*> m_server;
std::thread m_thread;
u8 m_remotePlayerCount = 0;
std::string m_selected_game;
Common::Flag m_is_running{false};
@ -166,23 +182,30 @@ protected:
u64 timeUs;
};
struct
struct FrameOffsetData
{
// TODO: Should the buffer size be dynamic based on time sync interval or not?
int idx;
std::vector<s32> buf;
} frameOffsetData;
};
bool isConnectionSelected = false;
bool isDecider = false;
int32_t lastFrameAcked;
bool hasGameStarted = false;
FrameTiming lastFrameTiming;
u64 pingUs;
u8 playerIdx = 0;
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::queue<FrameTiming> ackTimers;
std::deque<std::unique_ptr<SlippiPad>>
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;
std::vector<int> failedConnections;
SlippiMatchInfo matchInfo;
bool m_is_recording = false;
@ -191,7 +214,8 @@ protected:
std::unique_ptr<SlippiPlayerSelections> readSelectionsFromPacket(sf::Packet& packet);
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 Disconnect();
@ -207,7 +231,7 @@ private:
extern SlippiNetplayClient* SLIPPI_NETPLAY; // singleton static pointer
inline bool IsOnline()
static bool IsOnline()
{
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);
}
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()
{
// Do nothing?

View file

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

View file

@ -106,7 +106,7 @@ bool SlippiUser::AttemptLogin()
{
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
@ -312,6 +312,7 @@ SlippiUser::UserInfo SlippiUser::parseFile(std::string file_contents)
info.play_key = readString(res, "playKey");
info.connect_code = readString(res, "connectCode");
info.latest_version = readString(res, "latestVersion");
info.port = res.value("port", -1);
return info;
}

View file

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