mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-27 12:45:50 +00:00
https://github.com/project-slippi/Ishiiruka/pull/225 (doubles) + extra commits detailed below
8c2943b854f742d9b27bdbf5ffe8fc278a3fe318 c891220772e1258e6e9192ef8ec195c227e8f10b
This commit is contained in:
parent
b5dff938a3
commit
246ba8397e
20 changed files with 1065 additions and 402 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Data/Sys/GameFiles/GALE01/SdSlChr.dat.diff
Normal file
BIN
Data/Sys/GameFiles/GALE01/SdSlChr.dat.diff
Normal file
Binary file not shown.
BIN
Data/Sys/GameFiles/GALE01/SdSlChr.usd.diff
Normal file
BIN
Data/Sys/GameFiles/GALE01/SdSlChr.usd.diff
Normal file
Binary file not shown.
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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];
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ public:
|
|||
std::string connect_code = "";
|
||||
std::string latest_version = "";
|
||||
std::string file_contents = "";
|
||||
|
||||
int port;
|
||||
};
|
||||
|
||||
SlippiUser();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue