mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-22 10:19:01 +00:00
pull in enormous ranked commit e7adf9f79ef2c3d60066bbb36d672da841560dab. add missing data/sys files from current head, f68f6f6cbf56b3e4e43f27475763f1d15b6d7c67
This commit is contained in:
parent
69f1182777
commit
69b72a86de
24 changed files with 735 additions and 163 deletions
BIN
Data/Sys/GameFiles/GALE01/GameSetup.dat
Normal file
BIN
Data/Sys/GameFiles/GALE01/GameSetup.dat
Normal file
Binary file not shown.
BIN
Data/Sys/GameFiles/GALE01/GameSetup_gui.dat
Normal file
BIN
Data/Sys/GameFiles/GALE01/GameSetup_gui.dat
Normal file
Binary file not shown.
BIN
Data/Sys/GameFiles/GALE01/MxDb.dat
Normal file
BIN
Data/Sys/GameFiles/GALE01/MxDb.dat
Normal file
Binary file not shown.
BIN
Data/Sys/GameFiles/GALE01/MxScn.dat
Normal file
BIN
Data/Sys/GameFiles/GALE01/MxScn.dat
Normal file
Binary file not shown.
BIN
Data/Sys/GameFiles/GALE01/SlippiCSS.dat
Normal file
BIN
Data/Sys/GameFiles/GALE01/SlippiCSS.dat
Normal file
Binary file not shown.
Binary file not shown.
|
@ -19,8 +19,8 @@
|
|||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
|
@ -441,17 +441,20 @@ void StringPopBackIf(std::string* s, char c)
|
|||
// SLIPPITODO: look into boost locale maybe
|
||||
void ConvertNarrowSpecialSHIFTJIS(std::string& input)
|
||||
{
|
||||
// Melee doesn't correctly display special characters in narrow form We need to convert them to wide form.
|
||||
// I couldn't find a library to do this so for now let's just do it manually
|
||||
// Melee doesn't correctly display special characters in narrow form We need to convert them to
|
||||
// wide form. I couldn't find a library to do this so for now let's just do it manually
|
||||
static std::unordered_map<char, char16_t> specialCharConvert = {
|
||||
{'!', (char16_t)0x8149}, {'"', (char16_t)0x8168}, {'#', (char16_t)0x8194}, {'$', (char16_t)0x8190},
|
||||
{'%', (char16_t)0x8193}, {'&', (char16_t)0x8195}, {'\'', (char16_t)0x8166}, {'(', (char16_t)0x8169},
|
||||
{')', (char16_t)0x816a}, {'*', (char16_t)0x8196}, {'+', (char16_t)0x817b}, {',', (char16_t)0x8143},
|
||||
{'-', (char16_t)0x817c}, {'.', (char16_t)0x8144}, {'/', (char16_t)0x815e}, {':', (char16_t)0x8146},
|
||||
{';', (char16_t)0x8147}, {'<', (char16_t)0x8183}, {'=', (char16_t)0x8181}, {'>', (char16_t)0x8184},
|
||||
{'?', (char16_t)0x8148}, {'@', (char16_t)0x8197}, {'[', (char16_t)0x816d}, {'\\', (char16_t)0x815f},
|
||||
{']', (char16_t)0x816e}, {'^', (char16_t)0x814f}, {'_', (char16_t)0x8151}, {'`', (char16_t)0x814d},
|
||||
{'{', (char16_t)0x816f}, {'|', (char16_t)0x8162}, {'}', (char16_t)0x8170}, {'~', (char16_t)0x8160},
|
||||
{'!', (char16_t)0x8149}, {'"', (char16_t)0x8168}, {'#', (char16_t)0x8194},
|
||||
{'$', (char16_t)0x8190}, {'%', (char16_t)0x8193}, {'&', (char16_t)0x8195},
|
||||
{'\'', (char16_t)0x8166}, {'(', (char16_t)0x8169}, {')', (char16_t)0x816a},
|
||||
{'*', (char16_t)0x8196}, {'+', (char16_t)0x817b}, {',', (char16_t)0x8143},
|
||||
{'-', (char16_t)0x817c}, {'.', (char16_t)0x8144}, {'/', (char16_t)0x815e},
|
||||
{':', (char16_t)0x8146}, {';', (char16_t)0x8147}, {'<', (char16_t)0x8183},
|
||||
{'=', (char16_t)0x8181}, {'>', (char16_t)0x8184}, {'?', (char16_t)0x8148},
|
||||
{'@', (char16_t)0x8197}, {'[', (char16_t)0x816d}, {'\\', (char16_t)0x815f},
|
||||
{']', (char16_t)0x816e}, {'^', (char16_t)0x814f}, {'_', (char16_t)0x8151},
|
||||
{'`', (char16_t)0x814d}, {'{', (char16_t)0x816f}, {'|', (char16_t)0x8162},
|
||||
{'}', (char16_t)0x8170}, {'~', (char16_t)0x8160},
|
||||
};
|
||||
|
||||
int pos = 0;
|
||||
|
@ -476,13 +479,13 @@ void ConvertNarrowSpecialSHIFTJIS(std::string& input)
|
|||
input.erase(pos, 1);
|
||||
|
||||
// Add new chars to pos to replace
|
||||
auto newChars = (char*)& specialCharConvert[c];
|
||||
auto newChars = (char*)&specialCharConvert[c];
|
||||
input.insert(input.begin() + pos, 1, newChars[0]);
|
||||
input.insert(input.begin() + pos, 1, newChars[1]);
|
||||
}
|
||||
}
|
||||
|
||||
std::string ConvertStringForGame(const std::string& input, int length)
|
||||
std::string TruncateLengthChar(const std::string& input, int length)
|
||||
{
|
||||
auto utf32 = UTF8ToUTF32(input);
|
||||
|
||||
|
@ -492,7 +495,12 @@ std::string ConvertStringForGame(const std::string& input, int length)
|
|||
utf32.resize(length);
|
||||
}
|
||||
|
||||
auto utf8 = UTF32toUTF8(utf32);
|
||||
return UTF32toUTF8(utf32);
|
||||
}
|
||||
|
||||
std::string ConvertStringForGame(const std::string& input, int length)
|
||||
{
|
||||
auto utf8 = TruncateLengthChar(input, length);
|
||||
auto shiftJis = UTF8ToSHIFTJIS(utf8);
|
||||
ConvertNarrowSpecialSHIFTJIS(shiftJis);
|
||||
|
||||
|
|
|
@ -178,6 +178,7 @@ std::u32string UTF8ToUTF32(const std::string& input);
|
|||
std::u32string UTF8ToUTF32(const std::string& input);
|
||||
#endif
|
||||
std::string UTF32toUTF8(const std::u32string& input);
|
||||
std::string TruncateLengthChar(const std::string& input, int length);
|
||||
std::string ConvertStringForGame(const std::string& input, int length);
|
||||
std::string CP1252ToUTF8(std::string_view str);
|
||||
std::string SHIFTJISToUTF8(std::string_view str);
|
||||
|
|
|
@ -175,12 +175,21 @@ const Info<SerialInterface::SIDevices>& GetInfoForSIDevice(int channel)
|
|||
|
||||
const Info<bool>& GetInfoForAdapterRumble(int channel)
|
||||
{
|
||||
#ifndef IS_PLAYBACK
|
||||
static const std::array<const Info<bool>, 4> infos{
|
||||
Info<bool>{{System::Main, "Core", "AdapterRumble0"}, true},
|
||||
Info<bool>{{System::Main, "Core", "AdapterRumble1"}, true},
|
||||
Info<bool>{{System::Main, "Core", "AdapterRumble2"}, true},
|
||||
Info<bool>{{System::Main, "Core", "AdapterRumble3"}, true},
|
||||
};
|
||||
#else
|
||||
static const std::array<const Info<bool>, 4> infos{
|
||||
Info<bool>{{System::Main, "Core", "AdapterRumble0"}, false},
|
||||
Info<bool>{{System::Main, "Core", "AdapterRumble1"}, false},
|
||||
Info<bool>{{System::Main, "Core", "AdapterRumble2"}, false},
|
||||
Info<bool>{{System::Main, "Core", "AdapterRumble3"}, false},
|
||||
};
|
||||
#endif
|
||||
return infos[channel];
|
||||
}
|
||||
|
||||
|
|
|
@ -170,6 +170,13 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
|
|||
if (SConfig::GetSlippiConfig().melee_version == Melee::Version::NTSC ||
|
||||
SConfig::GetSlippiConfig().melee_version == Melee::Version::MEX)
|
||||
{
|
||||
// Here we are replacing a line in the codehandler with a blr.
|
||||
// The reason for this is that this is the section of the codehandler
|
||||
// that attempts to read/write commands for the USB Gecko. These calls
|
||||
// were sometimes interfering with the Slippi EXI calls and causing
|
||||
// the game to loop infinitely in EXISync.
|
||||
PowerPC::HostWrite_U32(guard, 0x4E800020, 0x80001D6C);
|
||||
|
||||
// Write GCT loader into memory which will eventually load the real GCT into the heap
|
||||
std::string bootloaderData;
|
||||
std::string _bootloaderFilename = File::GetSysDirectory() + GCT_BOOTLOADER;
|
||||
|
@ -218,7 +225,7 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
|
|||
"not write: \"{}\". Need {} bytes, only {} remain.",
|
||||
active_code.name, active_code.codes.size() * CODE_SIZE,
|
||||
end_address - next_address);
|
||||
continue;
|
||||
return Installation::Failed;
|
||||
}
|
||||
|
||||
for (const GeckoCode::Code& code : active_code.codes)
|
||||
|
|
|
@ -255,6 +255,17 @@ CEXISlippi::~CEXISlippi()
|
|||
|
||||
SlippiSpectateServer::getInstance().endGame(true);
|
||||
|
||||
// Try to determine whether we were playing an in-progress ranked match, if so
|
||||
// indicate to server that this client has abandoned. Anyone trying to modify
|
||||
// this behavior to game their rating is subject to get banned.
|
||||
auto active_match_id = matchmaking->GetMatchmakeResult().id;
|
||||
if (activeMatchId.find("mode.ranked") != std::string::npos)
|
||||
{
|
||||
ERROR_LOG(SLIPPI_ONLINE, "Exit during in-progress ranked game: %s", activeMatchId.c_str());
|
||||
gameReporter->ReportAbandonment(activeMatchId);
|
||||
}
|
||||
handleConnectionCleanup();
|
||||
|
||||
localSelections.Reset();
|
||||
|
||||
g_playbackStatus->resetPlayback();
|
||||
|
@ -1538,7 +1549,10 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
|
|||
m_read_queue.clear();
|
||||
|
||||
s32 frame = Common::swap32(&payload[0]);
|
||||
s32 finalizedFrame = Common::swap32(&payload[4]);
|
||||
s32 finalized_frame = Common::swap32(&payload[4]);
|
||||
u32 finalized_frame_checksum = Common::swap32(&payload[8]);
|
||||
u8 delay = payload[12];
|
||||
u8* inputs = &payload[13];
|
||||
|
||||
if (frame == 1)
|
||||
{
|
||||
|
@ -1565,7 +1579,7 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
|
|||
fallBehindCounter = 0;
|
||||
fallFarBehindCounter = 0;
|
||||
|
||||
// Reset character selections as they are no longer needed
|
||||
// Reset character selections such that they are cleared for next game
|
||||
localSelections.Reset();
|
||||
if (slippi_netplay)
|
||||
slippi_netplay->StartSlippiGame();
|
||||
|
@ -1578,9 +1592,9 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
|
|||
}
|
||||
|
||||
// Drop inputs that we no longer need (inputs older than the finalized frame passed in)
|
||||
slippi_netplay->DropOldRemoteInputs(finalizedFrame);
|
||||
slippi_netplay->DropOldRemoteInputs(finalized_frame);
|
||||
|
||||
bool shouldSkip = shouldSkipOnlineFrame(frame, finalizedFrame);
|
||||
bool shouldSkip = shouldSkipOnlineFrame(frame, finalized_frame);
|
||||
if (shouldSkip)
|
||||
{
|
||||
// Send inputs that have not yet been acked
|
||||
|
@ -1589,7 +1603,7 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
|
|||
else
|
||||
{
|
||||
// Send the input for this frame along with everything that has yet to be acked
|
||||
handleSendInputs(payload);
|
||||
handleSendInputs(frame, delay, finalized_frame, finalized_frame_checksum, inputs);
|
||||
}
|
||||
|
||||
prepareOpponentInputs(frame, shouldSkip);
|
||||
|
@ -1764,8 +1778,9 @@ bool CEXISlippi::shouldAdvanceOnlineFrame(s32 frame)
|
|||
// don't understand atm.
|
||||
OSD::AddTypedMessage(
|
||||
OSD::MessageType::PerformanceWarning,
|
||||
"Your computer is running slow and is impacting the performance of the match.", 10000,
|
||||
OSD::Color::RED);
|
||||
"Possible poor match performance detected.\nIf this message appears with most opponents, "
|
||||
"your computer or network is likely impacting match performance for the other players.",
|
||||
10000, OSD::Color::RED);
|
||||
}
|
||||
|
||||
if (offsetUs < -t2 && !isCurrentlyAdvancing)
|
||||
|
@ -1800,14 +1815,11 @@ bool CEXISlippi::shouldAdvanceOnlineFrame(s32 frame)
|
|||
return false;
|
||||
}
|
||||
|
||||
void CEXISlippi::handleSendInputs(u8* payload)
|
||||
void CEXISlippi::handleSendInputs(s32 frame, u8 delay, s32 checksum_frame, u32 checksum, u8* inputs)
|
||||
{
|
||||
if (isConnectionStalled)
|
||||
return;
|
||||
|
||||
s32 frame = Common::swap32(&payload[0]);
|
||||
u8 delay = payload[8];
|
||||
|
||||
// On the first frame sent, we need to queue up empty dummy pads for as many
|
||||
// frames as we have delay
|
||||
if (frame == 1)
|
||||
|
@ -1819,7 +1831,7 @@ void CEXISlippi::handleSendInputs(u8* payload)
|
|||
}
|
||||
}
|
||||
|
||||
auto pad = std::make_unique<SlippiPad>(frame + delay, &payload[9]);
|
||||
auto pad = std::make_unique<SlippiPad>(frame + delay, checksum_frame, checksum, inputs);
|
||||
|
||||
slippi_netplay->SendSlippiPad(std::move(pad));
|
||||
}
|
||||
|
@ -1854,6 +1866,24 @@ void CEXISlippi::prepareOpponentInputs(s32 frame, bool shouldSkip)
|
|||
m_read_queue.push_back(remotePlayerCount); // Indicate the number of remote players
|
||||
|
||||
std::unique_ptr<SlippiRemotePadOutput> results[SLIPPI_REMOTE_PLAYER_MAX];
|
||||
|
||||
for (int i = 0; i < remotePlayerCount; i++)
|
||||
{
|
||||
results[i] = slippi_netplay->GetSlippiRemotePad(i, ROLLBACK_MAX_FRAMES);
|
||||
// results[i] = slippi_netplay->GetFakePadOutput(frame);
|
||||
|
||||
// INFO_LOG(SLIPPI_ONLINE, "Sending checksum values: [%d] %08x", results[i]->checksumFrame,
|
||||
// results[i]->checksum);
|
||||
appendWordToBuffer(&m_read_queue, static_cast<u32>(results[i]->checksumFrame));
|
||||
appendWordToBuffer(&m_read_queue, results[i]->checksum);
|
||||
}
|
||||
for (int i = remotePlayerCount; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
|
||||
{
|
||||
// Send dummy data for unused players
|
||||
appendWordToBuffer(&m_read_queue, 0);
|
||||
appendWordToBuffer(&m_read_queue, 0);
|
||||
}
|
||||
|
||||
int offset[SLIPPI_REMOTE_PLAYER_MAX];
|
||||
|
||||
int32_t latestFrameRead[SLIPPI_REMOTE_PLAYER_MAX]{};
|
||||
|
@ -1893,7 +1923,7 @@ void CEXISlippi::prepareOpponentInputs(s32 frame, bool shouldSkip)
|
|||
std::vector<u8> tx;
|
||||
|
||||
// Get pad data if this remote player exists
|
||||
if (i < remotePlayerCount)
|
||||
if (i < remotePlayerCount && offset[i] < results[i]->data.size())
|
||||
{
|
||||
auto txStart = results[i]->data.begin() + offset[i];
|
||||
auto txEnd = results[i]->data.end();
|
||||
|
@ -2185,12 +2215,71 @@ void CEXISlippi::handleNameEntryLoad(u8* payload)
|
|||
appendWordToBuffer(&m_read_queue, curIndex);
|
||||
}
|
||||
|
||||
// teamId 0 = red, 1 = blue, 2 = green
|
||||
int CEXISlippi::getCharColor(u8 charId, u8 teamId)
|
||||
{
|
||||
switch (charId)
|
||||
{
|
||||
case 0x0: // Falcon
|
||||
switch (teamId)
|
||||
{
|
||||
case 0:
|
||||
return 2;
|
||||
case 1:
|
||||
return 5;
|
||||
case 2:
|
||||
return 4;
|
||||
}
|
||||
case 0x2: // Fox
|
||||
switch (teamId)
|
||||
{
|
||||
case 0:
|
||||
return 1;
|
||||
case 1:
|
||||
return 2;
|
||||
case 2:
|
||||
return 3;
|
||||
}
|
||||
case 0xC: // Peach
|
||||
switch (teamId)
|
||||
{
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
return 3;
|
||||
case 2:
|
||||
return 4;
|
||||
}
|
||||
case 0x13: // Sheik
|
||||
switch (teamId)
|
||||
{
|
||||
case 0:
|
||||
return 1;
|
||||
case 1:
|
||||
return 2;
|
||||
case 2:
|
||||
return 3;
|
||||
}
|
||||
case 0x14: // Falco
|
||||
switch (teamId)
|
||||
{
|
||||
case 0:
|
||||
return 1;
|
||||
case 1:
|
||||
return 2;
|
||||
case 2:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CEXISlippi::prepareOnlineMatchState()
|
||||
{
|
||||
// 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,
|
||||
0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x01, 0x01, 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,
|
||||
|
@ -2247,10 +2336,11 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
slippi_netplay = matchmaking->GetNetplayClient();
|
||||
#endif
|
||||
|
||||
// This happens on the initial connection to a player. Let's now grab the allowed stages
|
||||
// returned to us from the matchmaking service and pick a new random stage before sending
|
||||
// the selections to the opponent
|
||||
allowedStages = matchmaking->GetStages();
|
||||
// This happens on the initial connection to a player. The matchmaking object is ephemeral, it
|
||||
// gets re-created when a connection is terminated, that said, it can still be useful to know
|
||||
// who we were connected to after they disconnect from us, for example in the case of
|
||||
// reporting a match. So let's copy the results.
|
||||
allowedStages = recentMmResult.stages;
|
||||
// Clear stage pool so that when we call getRandomStage it will use full list
|
||||
stagePool.clear();
|
||||
localSelections.stageId = getRandomStage();
|
||||
|
@ -2305,16 +2395,7 @@ 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;
|
||||
|
||||
auto mmPlayers = matchmaking->GetPlayerInfo();
|
||||
for (auto mmp : mmPlayers)
|
||||
{
|
||||
uids.push_back(mmp.uid);
|
||||
}
|
||||
|
||||
game_reporter->StartNewSession(uids);
|
||||
|
||||
game_reporter->StartNewSession();
|
||||
is_play_session_active = true;
|
||||
}
|
||||
}
|
||||
|
@ -2415,10 +2496,7 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
rps[i].isCharacterSelected = true;
|
||||
}
|
||||
|
||||
if (lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS)
|
||||
{
|
||||
remotePlayerCount = 3;
|
||||
}
|
||||
remotePlayerCount = lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS ? 3 : 1;
|
||||
|
||||
oppName = std::string("Player");
|
||||
#endif
|
||||
|
@ -2433,25 +2511,39 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
remoteCharOk = false;
|
||||
}
|
||||
|
||||
// TODO: This is annoying, ideally remotePlayerSelections would just include everyone including
|
||||
// TODO: Ideally remotePlayerSelections would just include everyone including the local player
|
||||
// the local player
|
||||
// TODO: Would also simplify some logic in the Netplay class
|
||||
std::vector<SlippiPlayerSelections> orderedSelections(4);
|
||||
orderedSelections[lps.playerIdx] = lps;
|
||||
// Here we are storing pointers to the player selections. That means that we can technically
|
||||
// modify the values from here, which is probably not the cleanest thing since they're coming
|
||||
// from the netplay class. Unfortunately, I think it might be required for the overwrite stuff
|
||||
// to work correctly though, maybe on a tiebreak in ranked?
|
||||
std::vector<SlippiPlayerSelections*> orderedSelections(remotePlayerCount + 1);
|
||||
orderedSelections[lps.playerIdx] = &lps;
|
||||
for (int i = 0; i < remotePlayerCount; i++)
|
||||
{
|
||||
orderedSelections[rps[i].playerIdx] = rps[i];
|
||||
orderedSelections[rps[i].playerIdx] = &rps[i];
|
||||
}
|
||||
|
||||
// Overwrite selections
|
||||
for (int i = 0; i < overwrite_selections.size(); i++)
|
||||
{
|
||||
const auto& ow = overwrite_selections[i];
|
||||
|
||||
orderedSelections[i]->characterId = ow.characterId;
|
||||
orderedSelections[i]->characterColor = ow.characterColor;
|
||||
orderedSelections[i]->stageId = ow.stageId;
|
||||
}
|
||||
|
||||
// Overwrite stage information. Make sure everyone loads the same stage
|
||||
u16 stageId = 0x1F; // Default to battlefield if there was no selection
|
||||
for (const auto& selections : orderedSelections)
|
||||
{
|
||||
if (!selections.isStageSelected)
|
||||
if (!selections->isStageSelected)
|
||||
continue;
|
||||
|
||||
// Stage selected by this player, use that selection
|
||||
stageId = selections.stageId;
|
||||
stageId = selections->stageId;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2505,11 +2597,11 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
INFO_LOG_FMT(SLIPPI_ONLINE, "Rng Offset: {:#x}", rngOffset);
|
||||
|
||||
// Check if everyone is the same color
|
||||
auto color = orderedSelections[0].teamId;
|
||||
auto color = orderedSelections[0]->teamId;
|
||||
bool areAllSameTeam = true;
|
||||
for (const auto& s : orderedSelections)
|
||||
{
|
||||
if (s.teamId != color)
|
||||
if (s->teamId != color)
|
||||
areAllSameTeam = false;
|
||||
}
|
||||
|
||||
|
@ -2526,21 +2618,21 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
// Overwrite player character choices
|
||||
for (auto& s : orderedSelections)
|
||||
{
|
||||
if (!s.isCharacterSelected)
|
||||
if (!s->isCharacterSelected)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (areAllSameTeam)
|
||||
{
|
||||
// Overwrite teamId. Color is overwritten by ASM
|
||||
s.teamId = teamAssignments[s.playerIdx];
|
||||
s->teamId = teamAssignments[s->playerIdx];
|
||||
}
|
||||
|
||||
// Overwrite player character
|
||||
onlineMatchBlock[0x60 + (s.playerIdx) * 0x24] = s.characterId;
|
||||
onlineMatchBlock[0x63 + (s.playerIdx) * 0x24] = s.characterColor;
|
||||
onlineMatchBlock[0x67 + (s.playerIdx) * 0x24] = 0;
|
||||
onlineMatchBlock[0x69 + (s.playerIdx) * 0x24] = s.teamId;
|
||||
onlineMatchBlock[0x60 + (s->playerIdx) * 0x24] = s->characterId;
|
||||
onlineMatchBlock[0x63 + (s->playerIdx) * 0x24] = s->characterColor;
|
||||
onlineMatchBlock[0x67 + (s->playerIdx) * 0x24] = 0;
|
||||
onlineMatchBlock[0x69 + (s->playerIdx) * 0x24] = s->teamId;
|
||||
}
|
||||
|
||||
// Handle Singles/Teams specific logic
|
||||
|
@ -2573,8 +2665,7 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
*stage = Common::swap16(stageId);
|
||||
|
||||
// Turn pause off in unranked/ranked, on in other modes
|
||||
auto pauseAllowed = !SlippiMatchmaking::IsFixedRulesMode(lastSearch.mode) &&
|
||||
lastSearch.mode != SlippiMatchmaking::OnlinePlayMode::TEAMS;
|
||||
auto pauseAllowed = lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::DIRECT;
|
||||
u8* gameBitField3 = static_cast<u8*>(&onlineMatchBlock[2]);
|
||||
*gameBitField3 = pauseAllowed ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8;
|
||||
//*gameBitField3 = *gameBitField3 | 0x8;
|
||||
|
@ -2633,24 +2724,35 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
}
|
||||
|
||||
// Create the opponent string using the names of all players on opposing teams
|
||||
int teamIdx = onlineMatchBlock[0x69 + m_local_player_index * 0x24];
|
||||
std::string oppText = "";
|
||||
std::vector<std::string> opponentNames = {};
|
||||
if (matchmaking->RemotePlayerCount() == 1)
|
||||
{
|
||||
opponentNames.push_back(matchmaking->GetPlayerName(remotePlayerIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
int teamIdx = onlineMatchBlock[0x69 + localPlayerIndex * 0x24];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (i == m_local_player_index)
|
||||
if (localPlayerIndex == i || onlineMatchBlock[0x69 + i * 0x24] == teamIdx)
|
||||
continue;
|
||||
|
||||
if (onlineMatchBlock[0x69 + i * 0x24] != teamIdx)
|
||||
opponentNames.push_back(matchmaking->GetPlayerName(i));
|
||||
}
|
||||
}
|
||||
|
||||
auto numOpponents = opponentNames.size() == 0 ? 1 : opponentNames.size();
|
||||
auto charsPerName = (MAX_NAME_LENGTH - (numOpponents - 1)) / numOpponents;
|
||||
std::string oppText = "";
|
||||
for (auto& name : opponentNames)
|
||||
{
|
||||
if (oppText != "")
|
||||
oppText += "/";
|
||||
|
||||
oppText += matchmaking->GetPlayerName(i);
|
||||
oppText += TruncateLengthChar(name, charsPerName);
|
||||
}
|
||||
}
|
||||
if (matchmaking->RemotePlayerCount() == 1)
|
||||
oppText = matchmaking->GetPlayerName(m_remote_player_index);
|
||||
oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH * 2 + 1);
|
||||
|
||||
oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH);
|
||||
m_read_queue.insert(m_read_queue.end(), oppName.begin(), oppName.end());
|
||||
|
||||
#ifdef LOCAL_TESTING
|
||||
|
@ -3003,6 +3105,9 @@ void CEXISlippi::handleConnectionCleanup()
|
|||
// Reset any forced errors
|
||||
forcedError.clear();
|
||||
|
||||
// Reset any selection overwrites
|
||||
overwrite_selections.clear();
|
||||
|
||||
// Reset play session
|
||||
is_play_session_active = false;
|
||||
|
||||
|
@ -3022,29 +3127,50 @@ void CEXISlippi::prepareNewSeed()
|
|||
appendWordToBuffer(&m_read_queue, newSeed);
|
||||
}
|
||||
|
||||
void CEXISlippi::handleReportGame(u8* payload)
|
||||
void CEXISlippi::handleReportGame(const SlippiExiTypes::ReportGameQuery& query)
|
||||
{
|
||||
#ifndef LOCAL_TESTING
|
||||
SlippiGameReporter::GameReport r;
|
||||
r.duration_frames = Common::swap32(&payload[0]);
|
||||
r.match_id = recentMmResult.id;
|
||||
r.mode = static_cast<SlippiMatchmaking::OnlinePlayMode>(query.mode);
|
||||
r.duration_frames = query.frame_length;
|
||||
r.game_index = query.game_index;
|
||||
r.tiebreak_index = query.tiebreak_index;
|
||||
r.winner_idx = query.winner_idx;
|
||||
r.stage_id = Common::FromBigEndian(*(u16*)&query.game_info_block[0xE]);
|
||||
r.game_end_method = query.game_end_method;
|
||||
r.lras_initiator = query.lras_initiator;
|
||||
|
||||
// ERROR_LOG_FMT(SLIPPI_ONLINE, "Frames: {}", r.duration_frames);
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE,
|
||||
"Mode: {} / {}, Frames: {}, GameIdx: {}, TiebreakIdx: {}, WinnerIdx: {}, "
|
||||
"StageId: {}, GameEndMethod: {}, LRASInitiator: {}",
|
||||
r.mode, query.mode, r.duration_frames, r.game_index, r.tiebreak_index, r.winner_idx,
|
||||
r.stage_id, r.game_end_method, r.lras_initiator);
|
||||
|
||||
for (auto i = 0; i < 2; ++i)
|
||||
auto mm_players = recentMmResult.players;
|
||||
|
||||
for (auto i = 0; i < 4; ++i)
|
||||
{
|
||||
SlippiGameReporter::PlayerReport p;
|
||||
auto offset = i * 6;
|
||||
p.stocks_remaining = payload[5 + offset];
|
||||
p.uid = mm_players.size() > i ? mm_players[i].uid : "";
|
||||
p.slot_type = query.players[i].slot_type;
|
||||
p.stocks_remaining = query.players[i].stocks_remaining;
|
||||
p.damage_done = query.players[i].damage_done;
|
||||
p.char_id = query.game_info_block[0x60 + 0x24 * i];
|
||||
p.color_id = query.game_info_block[0x63 + 0x24 * i];
|
||||
p.starting_stocks = query.game_info_block[0x62 + 0x24 * i];
|
||||
p.starting_percent = Common::FromBigEndian(*(u16*)&query.game_info_block[0x70 + 0x24 * i]);
|
||||
|
||||
auto swappedDamageDone = Common::swap32(&payload[6 + offset]);
|
||||
p.damage_done = *(float*)&swappedDamageDone;
|
||||
|
||||
// ERROR_LOG_FMT(SLIPPI_ONLINE, "Stocks: {}, DamageDone: %f", p.stocks_remaining,
|
||||
// p.damage_done);
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE,
|
||||
"UID: {}, Port Type: {}, Stocks: {}, DamageDone: {}, CharId: {}, ColorId: {}, "
|
||||
"StartStocks: {}, "
|
||||
"StartPercent: {}",
|
||||
p.uid, p.slot_type, p.stocks_remaining, p.damage_done, p.char_id, p.color_id,
|
||||
p.starting_stocks, p.starting_percent);
|
||||
|
||||
r.players.push_back(p);
|
||||
}
|
||||
|
||||
#ifndef LOCAL_TESTING
|
||||
game_reporter->StartReport(r);
|
||||
#endif
|
||||
}
|
||||
|
@ -3063,6 +3189,84 @@ void CEXISlippi::prepareDelayResponse()
|
|||
m_read_queue.push_back(Config::Get(Config::SLIPPI_ONLINE_DELAY));
|
||||
}
|
||||
|
||||
void CEXISlippi::handleOverwriteSelections(const SlippiExiTypes::OverwriteSelectionsQuery& query)
|
||||
{
|
||||
overwrite_selections.clear();
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
// TODO: I'm pretty sure this contine would cause bugs if we tried to overwrite only player 1
|
||||
// TODO: and not player 0. Right now though GamePrep always overwrites both p0 and p1 so it's
|
||||
// fine
|
||||
// TODO: The bug would likely happen in the prepareOnlineMatchState, it would overwrite the
|
||||
// TODO: wrong players I think
|
||||
if (!query.chars[i].is_set)
|
||||
continue;
|
||||
|
||||
SlippiPlayerSelections s;
|
||||
s.isCharacterSelected = true;
|
||||
s.characterId = query.chars[i].char_id;
|
||||
s.characterColor = query.chars[i].char_color_id;
|
||||
s.isStageSelected = true;
|
||||
s.stageId = query.stage_id;
|
||||
s.playerIdx = i;
|
||||
|
||||
overwrite_selections.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
void CEXISlippi::handleGamePrepStepComplete(const SlippiExiTypes::GpCompleteStepQuery& query)
|
||||
{
|
||||
if (!slippi_netplay)
|
||||
return;
|
||||
|
||||
SlippiGamePrepStepResults res;
|
||||
res.step_idx = query.step_idx;
|
||||
res.char_selection = query.char_selection;
|
||||
res.char_color_selection = query.char_color_selection;
|
||||
memcpy(res.stage_selections, query.stage_selections, 2);
|
||||
|
||||
slippi_netplay->SendGamePrepStep(res);
|
||||
}
|
||||
|
||||
void CEXISlippi::prepareGamePrepOppStep(const SlippiExiTypes::GpFetchStepQuery& query)
|
||||
{
|
||||
SlippiExiTypes::GpFetchStepResponse resp;
|
||||
|
||||
m_read_queue.clear();
|
||||
|
||||
// Start by indicating not found
|
||||
resp.is_found = false;
|
||||
|
||||
#ifdef LOCAL_TESTING
|
||||
static int delay_count = 0;
|
||||
|
||||
delay_count++;
|
||||
if (delay_count >= 90)
|
||||
{
|
||||
resp.is_found = true;
|
||||
resp.is_skip = true; // Will make client just pick the next available options
|
||||
|
||||
delay_count = 0;
|
||||
}
|
||||
#else
|
||||
SlippiGamePrepStepResults res;
|
||||
if (slippi_netplay && slippi_netplay->GetGamePrepResults(query.step_idx, res))
|
||||
{
|
||||
// If we have received a response from the opponent, prepare the values for response
|
||||
resp.is_found = true;
|
||||
resp.is_skip = false;
|
||||
resp.char_selection = res.char_selection;
|
||||
resp.char_color_selection = res.char_color_selection;
|
||||
memcpy(resp.stage_selections, res.stage_selections, 2);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto data_ptr = (u8*)&resp;
|
||||
m_read_queue.insert(m_read_queue.end(), data_ptr,
|
||||
data_ptr + sizeof(SlippiExiTypes::GpFetchStepResponse));
|
||||
}
|
||||
|
||||
void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
@ -3106,13 +3310,16 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
|||
memPtr[bufLoc], memPtr[bufLoc + 1], memPtr[bufLoc + 2], memPtr[bufLoc + 3],
|
||||
memPtr[bufLoc + 4]);
|
||||
|
||||
u8 prevCommandByte = 0;
|
||||
|
||||
while (bufLoc < _uSize)
|
||||
{
|
||||
byte = memPtr[bufLoc];
|
||||
if (!payloadSizes.count(byte))
|
||||
{
|
||||
// This should never happen. Do something else if it does?
|
||||
WARN_LOG_FMT(EXPANSIONINTERFACE, "EXI SLIPPI: Invalid command byte: {:#x}", byte);
|
||||
ERROR_LOG_FMT(SLIPPI, "EXI SLIPPI: Invalid command byte: {:#x}. Prev command: {:#x}", byte,
|
||||
prevCommandByte);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3204,7 +3411,7 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
|||
prepareNewSeed();
|
||||
break;
|
||||
case CMD_REPORT_GAME:
|
||||
handleReportGame(&memPtr[bufLoc + 1]);
|
||||
handleReportGame(SlippiExiTypes::Convert<SlippiExiTypes::ReportGameQuery>(&memPtr[bufLoc]));
|
||||
break;
|
||||
case CMD_GCT_LENGTH:
|
||||
prepareGctLength();
|
||||
|
@ -3215,12 +3422,25 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
|||
case CMD_GET_DELAY:
|
||||
prepareDelayResponse();
|
||||
break;
|
||||
case CMD_OVERWRITE_SELECTIONS:
|
||||
handleOverwriteSelections(
|
||||
SlippiExiTypes::Convert<SlippiExiTypes::OverwriteSelectionsQuery>(&memPtr[bufLoc]));
|
||||
break;
|
||||
case CMD_GP_FETCH_STEP:
|
||||
prepareGamePrepOppStep(
|
||||
SlippiExiTypes::Convert<SlippiExiTypes::GpFetchStepQuery>(&memPtr[bufLoc]));
|
||||
break;
|
||||
case CMD_GP_COMPLETE_STEP:
|
||||
handleGamePrepStepComplete(
|
||||
SlippiExiTypes::Convert<SlippiExiTypes::GpCompleteStepQuery>(&memPtr[bufLoc]));
|
||||
break;
|
||||
default:
|
||||
writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, "");
|
||||
SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
prevCommandByte = byte;
|
||||
bufLoc += payloadLen + 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Common/FileUtil.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Core/Slippi/SlippiDirectCodes.h"
|
||||
#include "Core/Slippi/SlippiExiTypes.h"
|
||||
#include "Core/Slippi/SlippiGame.h"
|
||||
#include "Core/Slippi/SlippiGameFileLoader.h"
|
||||
#include "Core/Slippi/SlippiGameReporter.h"
|
||||
|
@ -75,6 +76,9 @@ private:
|
|||
CMD_GET_NEW_SEED = 0xBC,
|
||||
CMD_REPORT_GAME = 0xBD,
|
||||
CMD_FETCH_CODE_SUGGESTION = 0xBE,
|
||||
CMD_OVERWRITE_SELECTIONS = 0xBF,
|
||||
CMD_GP_COMPLETE_STEP = 0xC0,
|
||||
CMD_GP_FETCH_STEP = 0xC1,
|
||||
|
||||
// Misc
|
||||
CMD_LOG_MESSAGE = 0xD0,
|
||||
|
@ -102,8 +106,8 @@ private:
|
|||
{CMD_RECEIVE_COMMANDS, 1},
|
||||
|
||||
// The following are all commands used to play back a replay and
|
||||
// have fixed sizes
|
||||
{CMD_PREPARE_REPLAY, 0},
|
||||
// have fixed sizes unless otherwise specified
|
||||
{CMD_PREPARE_REPLAY, 0xFFFF}, // Variable size... will only work if by itself
|
||||
{CMD_READ_FRAME, 4},
|
||||
{CMD_IS_STOCK_STEAL, 5},
|
||||
{CMD_GET_LOCATION, 6},
|
||||
|
@ -111,7 +115,7 @@ private:
|
|||
{CMD_GET_GECKO_CODES, 0},
|
||||
|
||||
// The following are used for Slippi online and also have fixed sizes
|
||||
{CMD_ONLINE_INPUTS, 21},
|
||||
{CMD_ONLINE_INPUTS, 25},
|
||||
{CMD_CAPTURE_SAVESTATE, 32},
|
||||
{CMD_LOAD_SAVESTATE, 32},
|
||||
{CMD_GET_MATCH_STATE, 0},
|
||||
|
@ -124,8 +128,12 @@ private:
|
|||
{CMD_GET_ONLINE_STATUS, 0},
|
||||
{CMD_CLEANUP_CONNECTION, 0},
|
||||
{CMD_GET_NEW_SEED, 0},
|
||||
{CMD_REPORT_GAME, 16},
|
||||
{CMD_REPORT_GAME, static_cast<u32>(sizeof(SlippiExiTypes::ReportGameQuery) - 1)},
|
||||
{CMD_FETCH_CODE_SUGGESTION, 31},
|
||||
{CMD_OVERWRITE_SELECTIONS,
|
||||
static_cast<u32>(sizeof(SlippiExiTypes::OverwriteSelectionsQuery) - 1)},
|
||||
{CMD_GP_COMPLETE_STEP, static_cast<u32>(sizeof(SlippiExiTypes::GpCompleteStepQuery) - 1)},
|
||||
{CMD_GP_FETCH_STEP, static_cast<u32>(sizeof(SlippiExiTypes::GpFetchStepQuery) - 1)},
|
||||
|
||||
// Misc
|
||||
{CMD_LOG_MESSAGE, 0xFFFF}, // Variable size... will only work if by itself
|
||||
|
@ -173,7 +181,7 @@ private:
|
|||
bool isDisconnected();
|
||||
void handleOnlineInputs(u8* payload);
|
||||
void prepareOpponentInputs(s32 frame, bool shouldSkip);
|
||||
void handleSendInputs(u8* payload);
|
||||
void handleSendInputs(s32 frame, u8 delay, s32 checksum_frame, u32 checksum, u8* inputs);
|
||||
void handleCaptureSavestate(u8* payload);
|
||||
void handleLoadSavestate(u8* payload);
|
||||
void handleNameEntryLoad(u8* payload);
|
||||
|
@ -187,7 +195,10 @@ private:
|
|||
void prepareOnlineStatus();
|
||||
void handleConnectionCleanup();
|
||||
void prepareNewSeed();
|
||||
void handleReportGame(u8* payload);
|
||||
void handleReportGame(const SlippiExiTypes::ReportGameQuery& query);
|
||||
void handleOverwriteSelections(const SlippiExiTypes::OverwriteSelectionsQuery& query);
|
||||
void handleGamePrepStepComplete(const SlippiExiTypes::GpCompleteStepQuery& query);
|
||||
void prepareGamePrepOppStep(const SlippiExiTypes::GpFetchStepQuery& query);
|
||||
|
||||
// replay playback stuff
|
||||
void prepareGameInfo(u8* payload);
|
||||
|
@ -231,9 +242,13 @@ private:
|
|||
std::unique_ptr<Slippi::SlippiGame> m_current_game = nullptr;
|
||||
SlippiSpectateServer* m_slippiserver = nullptr;
|
||||
SlippiMatchmaking::MatchSearchSettings lastSearch;
|
||||
SlippiMatchmaking::MatchmakeResult recentMmResult;
|
||||
|
||||
std::vector<u16> stagePool;
|
||||
|
||||
// Used by ranked to set game prep selections
|
||||
std::vector<SlippiPlayerSelections> overwrite_selections;
|
||||
|
||||
u32 frameSeqIdx = 0;
|
||||
|
||||
bool isEnetInitialized = false;
|
||||
|
|
|
@ -884,7 +884,8 @@ void VideoInterfaceManager::Update(u64 ticks)
|
|||
if (m_half_line_count == 0 || m_half_line_count == GetHalfLinesPerEvenField())
|
||||
Core::Callback_NewField(m_system);
|
||||
|
||||
// SLIPPINOTES: this section is disable because we would rather poll every chance we get to reduce lag
|
||||
// SLIPPINOTES: this section is disable because we would rather poll every chance we get to reduce
|
||||
// lag
|
||||
// // If an SI poll is scheduled to happen on this half-line, do it!
|
||||
|
||||
// if (m_half_line_of_next_si_poll == m_half_line_count)
|
||||
|
|
|
@ -157,6 +157,7 @@ enum class MessageID : u8
|
|||
SLIPPI_MATCH_SELECTIONS = 0x82,
|
||||
SLIPPI_CONN_SELECTED = 0x83,
|
||||
SLIPPI_CHAT_MESSAGE = 0x84,
|
||||
SLIPPI_COMPLETE_STEP = 0x85,
|
||||
|
||||
GolfRequest = 0x90,
|
||||
GolfSwitch = 0x91,
|
||||
|
|
107
Source/Core/Core/Slippi/SlippiExiTypes.h
Normal file
107
Source/Core/Core/Slippi/SlippiExiTypes.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#define REPORT_PLAYER_COUNT 4
|
||||
|
||||
namespace SlippiExiTypes
|
||||
{
|
||||
// Using pragma pack here will remove any structure padding which is what EXI comms expect
|
||||
// https://www.geeksforgeeks.org/how-to-avoid-structure-padding-in-c/
|
||||
// Note that none of these classes should be used outside of the handler function, pragma pack
|
||||
// is supposedly not very efficient?
|
||||
#pragma pack(1)
|
||||
|
||||
struct ReportGameQueryPlayer
|
||||
{
|
||||
u8 slot_type;
|
||||
u8 stocks_remaining;
|
||||
float damage_done;
|
||||
};
|
||||
|
||||
struct ReportGameQuery
|
||||
{
|
||||
u8 command;
|
||||
u8 mode;
|
||||
u32 frame_length;
|
||||
u32 game_index;
|
||||
u32 tiebreak_index;
|
||||
s8 winner_idx;
|
||||
u8 game_end_method;
|
||||
s8 lras_initiator;
|
||||
ReportGameQueryPlayer players[REPORT_PLAYER_COUNT];
|
||||
u8 game_info_block[312];
|
||||
};
|
||||
|
||||
struct GpCompleteStepQuery
|
||||
{
|
||||
u8 command;
|
||||
u8 step_idx;
|
||||
u8 char_selection;
|
||||
u8 char_color_selection;
|
||||
u8 stage_selections[2];
|
||||
};
|
||||
|
||||
struct GpFetchStepQuery
|
||||
{
|
||||
u8 command;
|
||||
u8 step_idx;
|
||||
};
|
||||
|
||||
struct GpFetchStepResponse
|
||||
{
|
||||
u8 is_found;
|
||||
u8 is_skip;
|
||||
u8 char_selection;
|
||||
u8 char_color_selection;
|
||||
u8 stage_selections[2];
|
||||
};
|
||||
|
||||
struct OverwriteCharSelections
|
||||
{
|
||||
u8 is_set;
|
||||
u8 char_id;
|
||||
u8 char_color_id;
|
||||
};
|
||||
struct OverwriteSelectionsQuery
|
||||
{
|
||||
u8 command;
|
||||
u16 stage_id;
|
||||
OverwriteCharSelections chars[4];
|
||||
};
|
||||
|
||||
// Not sure if resetting is strictly needed, might be contained to the file
|
||||
#pragma pack()
|
||||
|
||||
template <typename T>
|
||||
inline T Convert(u8* payload)
|
||||
{
|
||||
return *reinterpret_cast<T*>(payload);
|
||||
}
|
||||
|
||||
// Here we define custom convert functions for any type that larger than u8 sized fields to convert
|
||||
// from big-endian
|
||||
|
||||
template <>
|
||||
inline ReportGameQuery Convert(u8* payload)
|
||||
{
|
||||
auto q = *reinterpret_cast<ReportGameQuery*>(payload);
|
||||
q.frameLength = Common::FromBigEndian(q.frameLength);
|
||||
q.gameIndex = Common::FromBigEndian(q.gameIndex);
|
||||
q.tiebreakIndex = Common::FromBigEndian(q.tiebreakIndex);
|
||||
for (int i = 0; i < REPORT_PLAYER_COUNT; i++)
|
||||
{
|
||||
auto* p = &q.players[i];
|
||||
p->damageDone = Common::FromBigEndian(p->damageDone);
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline OverwriteSelectionsQuery Convert(u8* payload)
|
||||
{
|
||||
auto q = *reinterpret_cast<OverwriteSelectionsQuery*>(payload);
|
||||
q.stage_id = Common::FromBigEndian(q.stage_id);
|
||||
return q;
|
||||
}
|
||||
}; // namespace SlippiExiTypes
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "Common/Common.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Slippi/SlippiMatchmaking.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
|
@ -39,15 +40,15 @@ SlippiGameReporter::SlippiGameReporter(SlippiUser* user)
|
|||
m_user = user;
|
||||
|
||||
run_thread = true;
|
||||
reportingThread = std::thread(&SlippiGameReporter::ReportThreadHandler, this);
|
||||
reporting_thread = std::thread(&SlippiGameReporter::ReportThreadHandler, this);
|
||||
}
|
||||
|
||||
SlippiGameReporter::~SlippiGameReporter()
|
||||
{
|
||||
run_thread = false;
|
||||
cv.notify_one();
|
||||
if (reportingThread.joinable())
|
||||
reportingThread.join();
|
||||
if (reporting_thread.joinable())
|
||||
reporting_thread.join();
|
||||
|
||||
if (m_curl)
|
||||
{
|
||||
|
@ -62,10 +63,9 @@ void SlippiGameReporter::StartReport(GameReport report)
|
|||
cv.notify_one();
|
||||
}
|
||||
|
||||
void SlippiGameReporter::StartNewSession(std::vector<std::string> new_player_uids)
|
||||
void SlippiGameReporter::StartNewSession()
|
||||
{
|
||||
this->m_player_uids = new_player_uids;
|
||||
gameIndex = 1;
|
||||
game_index = 1;
|
||||
}
|
||||
|
||||
void SlippiGameReporter::ReportThreadHandler()
|
||||
|
@ -83,22 +83,45 @@ void SlippiGameReporter::ReportThreadHandler()
|
|||
auto report = game_report_queue.front();
|
||||
game_report_queue.pop();
|
||||
|
||||
auto userInfo = m_user->GetUserInfo();
|
||||
auto ranked = SlippiMatchmaking::OnlinePlayMode::RANKED;
|
||||
auto unranked = SlippiMatchmaking::OnlinePlayMode::UNRANKED;
|
||||
bool should_report = report.mode == ranked || report.mode == unranked;
|
||||
if (!should_report)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
auto user_info = m_user->GetUserInfo();
|
||||
WARN_LOG_FMT(SLIPPI_ONLINE, "Checking game report for game {}. Length: {}...", game_index,
|
||||
report.duration_frames);
|
||||
|
||||
// Prepare report
|
||||
json request;
|
||||
request["uid"] = userInfo.uid;
|
||||
request["playKey"] = userInfo.play_key;
|
||||
request["gameIndex"] = gameIndex;
|
||||
request["matchId"] = report.match_id;
|
||||
request["uid"] = user_info.uid;
|
||||
request["playKey"] = user_info.play_key;
|
||||
request["mode"] = report.mode;
|
||||
request["gameIndex"] = report.mode == ranked ? report.game_index : game_index;
|
||||
request["tiebreakIndex"] = report.mode == ranked ? report.tiebreak_index : 0;
|
||||
request["gameIndex"] = game_index;
|
||||
request["gameDurationFrames"] = report.duration_frames;
|
||||
request["winnerIdx"] = report.winner_idx;
|
||||
request["gameEndMethod"] = report.game_end_method;
|
||||
request["lrasInitiator"] = report.lras_initiator;
|
||||
request["stageId"] = report.stage_id;
|
||||
|
||||
json players = json::array();
|
||||
for (int i = 0; i < report.players.size(); i++)
|
||||
{
|
||||
json p;
|
||||
p["uid"] = m_player_uids[i];
|
||||
p["damage_done"] = report.players[i].damage_done;
|
||||
p["stocks_remaining"] = report.players[i].stocks_remaining;
|
||||
p["slotType"] = report.players[i].slot_type;
|
||||
p["damageDone"] = report.players[i].damage_done;
|
||||
p["stocksRemaining"] = report.players[i].stocks_remaining;
|
||||
p["characterId"] = report.players[i].char_id;
|
||||
p["colorId"] = report.players[i].color_id;
|
||||
p["startingStocks"] = report.players[i].starting_stocks;
|
||||
p["startingPercent"] = report.players[i].starting_percent;
|
||||
|
||||
players[i] = p;
|
||||
}
|
||||
|
@ -120,8 +143,34 @@ void SlippiGameReporter::ReportThreadHandler()
|
|||
static_cast<u8>(res));
|
||||
}
|
||||
|
||||
gameIndex++;
|
||||
game_index++;
|
||||
Common::SleepCurrentThread(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SlippiGameReporter::ReportAbandonment(std::string match_id)
|
||||
{
|
||||
auto userInfo = m_user->GetUserInfo();
|
||||
|
||||
// Prepare report
|
||||
json request;
|
||||
request["matchId"] = match_id;
|
||||
request["uid"] = userInfo.uid;
|
||||
request["playKey"] = userInfo.play_key;
|
||||
|
||||
auto requestString = request.dump();
|
||||
|
||||
// Send report
|
||||
curl_easy_setopt(m_curl, CURLOPT_POST, true);
|
||||
curl_easy_setopt(m_curl, CURLOPT_URL, ABANDON_URL.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, requestString.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, requestString.length());
|
||||
CURLcode res = curl_easy_perform(m_curl);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE,
|
||||
"[GameReport] Got error executing abandonment request. Err code: {}", res);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <thread>
|
||||
#include <vector>
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/Slippi/SlippiMatchmaking.h"
|
||||
#include "Core/Slippi/SlippiUser.h"
|
||||
|
||||
class SlippiGameReporter
|
||||
|
@ -16,12 +17,27 @@ class SlippiGameReporter
|
|||
public:
|
||||
struct PlayerReport
|
||||
{
|
||||
std::string uid;
|
||||
u8 slot_type;
|
||||
float damage_done;
|
||||
u8 stocks_remaining;
|
||||
u8 char_id;
|
||||
u8 color_id;
|
||||
int starting_stocks;
|
||||
int starting_percent;
|
||||
};
|
||||
|
||||
struct GameReport
|
||||
{
|
||||
SlippiMatchmaking::OnlinePlayMode mode = SlippiMatchmaking::OnlinePlayMode::UNRANKED;
|
||||
std::string match_id;
|
||||
u32 duration_frames = 0;
|
||||
u32 game_index = 0;
|
||||
u32 tiebreak_index = 0;
|
||||
s8 winner_idx = 0;
|
||||
u8 game_end_method = 0;
|
||||
s8 lras_initiator = 0;
|
||||
int stage_id;
|
||||
std::vector<PlayerReport> players;
|
||||
};
|
||||
|
||||
|
@ -29,20 +45,22 @@ public:
|
|||
~SlippiGameReporter();
|
||||
|
||||
void StartReport(GameReport report);
|
||||
void StartNewSession(std::vector<std::string> player_uids);
|
||||
void ReportAbandonment(std::string match_id);
|
||||
void StartNewSession();
|
||||
void ReportThreadHandler();
|
||||
|
||||
protected:
|
||||
const std::string REPORT_URL = "https://rankings-dot-slippi.uc.r.appspot.com/report";
|
||||
const std::string ABANDON_URL = "https://rankings-dot-slippi.uc.r.appspot.com/abandon";
|
||||
CURL* m_curl = nullptr;
|
||||
struct curl_slist* m_curl_header_list = nullptr;
|
||||
|
||||
u32 gameIndex = 1;
|
||||
u32 game_index = 1;
|
||||
std::vector<std::string> m_player_uids;
|
||||
|
||||
SlippiUser* m_user;
|
||||
std::queue<GameReport> game_report_queue;
|
||||
std::thread reportingThread;
|
||||
std::thread reporting_thread;
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
std::atomic<bool> run_thread;
|
||||
|
|
|
@ -44,6 +44,7 @@ SlippiMatchmaking::SlippiMatchmaking(SlippiUser* user)
|
|||
|
||||
SlippiMatchmaking::~SlippiMatchmaking()
|
||||
{
|
||||
is_mm_terminated = true;
|
||||
m_state = ProcessState::ERROR_ENCOUNTERED;
|
||||
m_errorMsg = "Matchmaking shut down";
|
||||
|
||||
|
@ -148,6 +149,11 @@ void SlippiMatchmaking::MatchmakeThread()
|
|||
{
|
||||
while (IsSearching())
|
||||
{
|
||||
if (is_mm_terminated)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
switch (m_state)
|
||||
{
|
||||
case ProcessState::INITIALIZING:
|
||||
|
@ -380,7 +386,10 @@ void SlippiMatchmaking::startMatchmaking()
|
|||
// Send message to server to create ticket
|
||||
json request;
|
||||
request["type"] = MmMessageType::CREATE_TICKET;
|
||||
request["user"] = {{"uid", userInfo.uid}, {"playKey", userInfo.play_key}};
|
||||
request["user"] = {{"uid", userInfo.uid},
|
||||
{"playKey", userInfo.play_key},
|
||||
{"connectCode", userInfo.connect_code},
|
||||
{"displayName", userInfo.display_name}};
|
||||
request["search"] = {{"mode", m_searchSettings.mode}, {"connectCode", connectCodeBuf}};
|
||||
request["appVersion"] = Common::GetSemVerStr();
|
||||
request["ipAddressLan"] = lan_addr;
|
||||
|
@ -477,6 +486,12 @@ void SlippiMatchmaking::handleMatchmaking()
|
|||
m_remoteIps.clear();
|
||||
m_playerInfo.clear();
|
||||
|
||||
std::string matchId = getResp.value("matchId", "");
|
||||
WARN_LOG_FMT(SLIPPI_ONLINE, "Match ID: {}", matchId);
|
||||
|
||||
std::string match_id = getResp.value("matchId", "");
|
||||
WARN_LOG_FMT(SLIPPI_ONLINE, "Match ID: {}", match_id);
|
||||
|
||||
auto queue = getResp["players"];
|
||||
if (queue.is_array())
|
||||
{
|
||||
|
@ -564,6 +579,10 @@ void SlippiMatchmaking::handleMatchmaking()
|
|||
}
|
||||
}
|
||||
|
||||
m_mm_result.id = match_id;
|
||||
m_mm_result.players = m_playerInfo;
|
||||
m_mm_result.stages = m_allowedStages;
|
||||
|
||||
// Disconnect and destroy enet client to mm server
|
||||
terminateMmConnection();
|
||||
|
||||
|
@ -596,6 +615,11 @@ std::string SlippiMatchmaking::GetPlayerName(u8 port)
|
|||
return m_playerInfo[port].display_name;
|
||||
}
|
||||
|
||||
SlippiMatchmaking::MatchmakeResult SlippiMatchmaking::GetMatchmakeResult()
|
||||
{
|
||||
return m_mm_result;
|
||||
}
|
||||
|
||||
u8 SlippiMatchmaking::RemotePlayerCount()
|
||||
{
|
||||
if (m_playerInfo.size() == 0)
|
||||
|
|
|
@ -47,6 +47,13 @@ public:
|
|||
std::string connectCode = "";
|
||||
};
|
||||
|
||||
struct MatchmakeResult
|
||||
{
|
||||
std::string id = "";
|
||||
std::vector<SlippiUser::UserInfo> players;
|
||||
std::vector<u16> stages;
|
||||
};
|
||||
|
||||
void FindMatch(MatchSearchSettings settings);
|
||||
void MatchmakeThread();
|
||||
ProcessState GetMatchmakeState();
|
||||
|
@ -58,6 +65,7 @@ public:
|
|||
std::string GetPlayerName(u8 port);
|
||||
std::vector<u16> GetStages();
|
||||
u8 RemotePlayerCount();
|
||||
MatchmakeResult GetMatchmakeResult();
|
||||
static bool IsFixedRulesMode(OnlinePlayMode mode);
|
||||
|
||||
protected:
|
||||
|
@ -73,6 +81,7 @@ protected:
|
|||
std::default_random_engine generator;
|
||||
|
||||
bool isMmConnected = false;
|
||||
bool is_mm_terminated = false;
|
||||
|
||||
std::thread m_matchmakeThread;
|
||||
|
||||
|
@ -88,6 +97,7 @@ protected:
|
|||
int m_hostPort;
|
||||
int m_localPlayerIndex;
|
||||
std::vector<std::string> m_remoteIps;
|
||||
MatchmakeResult m_mm_result;
|
||||
std::vector<SlippiUser::UserInfo> m_playerInfo;
|
||||
std::vector<u16> m_allowedStages;
|
||||
bool m_joinedLobby;
|
||||
|
|
|
@ -188,7 +188,10 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
|
|||
// Fetch current time immediately for the most accurate timing calculations
|
||||
u64 curTime = Common::Timer::NowUs();
|
||||
|
||||
int32_t frame;
|
||||
s32 frame;
|
||||
s32 checksum_frame;
|
||||
u32 checksum;
|
||||
|
||||
if (!(packet >> frame))
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read frame count");
|
||||
|
@ -201,6 +204,16 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
|
|||
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read player index");
|
||||
break;
|
||||
}
|
||||
if (!(packet >> checksum_frame))
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read checksum frame");
|
||||
break;
|
||||
}
|
||||
if (!(packet >> checksum))
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read checksum value");
|
||||
break;
|
||||
}
|
||||
u8 pIdx = PlayerIdxFromPort(packetPlayerPort);
|
||||
if (pIdx >= m_remotePlayerCount)
|
||||
{
|
||||
|
@ -208,6 +221,9 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
|
|||
break;
|
||||
}
|
||||
|
||||
// This is the amount of bytes from the start of the packet where the pad data starts
|
||||
int pad_data_offset = 14;
|
||||
|
||||
// This fetches the m_server index that stores the connection we want to overwrite (if
|
||||
// necessary). Note that this index is not necessarily the same as the pIdx because if we have
|
||||
// users connecting with the same WAN, the m_server indices might not match
|
||||
|
@ -277,6 +293,7 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
|
|||
|
||||
frameOffsetData[pIdx].idx = (frameOffsetData[pIdx].idx + 1) % SLIPPI_ONLINE_LOCKSTEP_INTERVAL;
|
||||
|
||||
s64 inputs_to_copy;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(pad_mutex); // TODO: Is this the correct lock?
|
||||
|
||||
|
@ -289,45 +306,56 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
|
|||
|
||||
s64 frame64 = static_cast<s64>(frame);
|
||||
s32 headFrame = remotePadQueue[pIdx].empty() ? 0 : remotePadQueue[pIdx].front()->frame;
|
||||
s64 inputsToCopy = frame64 - static_cast<s64>(headFrame);
|
||||
s64 inputs_to_copy = frame64 - static_cast<s64>(headFrame);
|
||||
|
||||
// Check that the packet actually contains the data it claims to
|
||||
if ((6 + inputsToCopy * SLIPPI_PAD_DATA_SIZE) > static_cast<s64>(packet.getDataSize()))
|
||||
if ((pad_data_offset + inputs_to_copy * SLIPPI_PAD_DATA_SIZE) >
|
||||
static_cast<s64>(packet.getDataSize()))
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
SLIPPI_ONLINE,
|
||||
"Netplay packet too small to read pad buffer. Size: {}, Inputs: {}, MinSize: {}",
|
||||
static_cast<int>(packet.getDataSize()), inputsToCopy,
|
||||
6 + inputsToCopy * SLIPPI_PAD_DATA_SIZE);
|
||||
static_cast<int>(packet.getDataSize()), inputs_to_copy,
|
||||
pad_data_offset + inputs_to_copy * SLIPPI_PAD_DATA_SIZE);
|
||||
break;
|
||||
}
|
||||
|
||||
// Not sure what the max is here. If we never ack frames it could get big...
|
||||
if (inputsToCopy > 128)
|
||||
if (inputs_to_copy > 128)
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet contained too many frames: {}", inputsToCopy);
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet contained too many frames: {}",
|
||||
inputs_to_copy);
|
||||
break;
|
||||
}
|
||||
|
||||
for (s64 i = inputsToCopy - 1; i >= 0; i--)
|
||||
for (s64 i = inputs_to_copy - 1; i >= 0; i--)
|
||||
{
|
||||
auto pad = std::make_unique<SlippiPad>(static_cast<s32>(frame64 - i), pIdx,
|
||||
&packetData[6 + i * SLIPPI_PAD_DATA_SIZE]);
|
||||
auto pad =
|
||||
std::make_unique<SlippiPad>(static_cast<s32>(frame64 - i), pIdx,
|
||||
&packetData[pad_data_offset + i * SLIPPI_PAD_DATA_SIZE]);
|
||||
remotePadQueue[pIdx].push_front(std::move(pad));
|
||||
}
|
||||
}
|
||||
|
||||
// Only ack if inputsToCopy is greater than 0. Otherwise we are receiving an old input and
|
||||
// we should have already acked something in the future. This can also happen in the case
|
||||
// where a new game starts quickly before the remote queue is reset and if we ack the early
|
||||
// inputs we will never receive them
|
||||
if (inputs_to_copy > 0)
|
||||
{
|
||||
// Send Ack
|
||||
sf::Packet spac;
|
||||
spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_PAD_ACK);
|
||||
spac << frame;
|
||||
spac << m_player_idx;
|
||||
// INFO_LOG_FMT(SLIPPI_ONLINE, "Sending ack packet for frame {} (player {}) to peer at {}:{}",
|
||||
// frame, packetPlayerPort, peer->address.host, peer->address.port);
|
||||
// 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);
|
||||
enet_peer_send(peer, 2, epac);
|
||||
int sendResult = enet_peer_send(peer, 2, epac);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -440,6 +468,20 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
|
|||
}
|
||||
break;
|
||||
|
||||
case NetPlay::MessageID::SLIPPI_COMPLETE_STEP:
|
||||
{
|
||||
SlippiGamePrepStepResults results;
|
||||
|
||||
packet >> results.step_idx;
|
||||
packet >> results.char_selection;
|
||||
packet >> results.char_color_selection;
|
||||
packet >> results.stage_selections[0];
|
||||
packet >> results.stage_selections[1];
|
||||
|
||||
game_prep_step_queue.push_back(results);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
WARN_LOG_FMT(SLIPPI_ONLINE, "Unknown message received with id : {}", static_cast<u8>(mid));
|
||||
break;
|
||||
|
@ -989,6 +1031,9 @@ void SlippiNetplayClient::StartSlippiGame()
|
|||
std::swap(ackTimers[i], empty);
|
||||
}
|
||||
|
||||
// Clear game prep queue in case anything is still lingering
|
||||
game_prep_step_queue.clear();
|
||||
|
||||
// Reset match info for next game
|
||||
matchInfo.Reset();
|
||||
}
|
||||
|
@ -1046,9 +1091,11 @@ void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
|
|||
*spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_PAD);
|
||||
*spac << frame;
|
||||
*spac << this->m_player_idx;
|
||||
*spac << localPadQueue.front()->checksum_frame;
|
||||
*spac << localPadQueue.front()->checksum;
|
||||
|
||||
for (auto it = localPadQueue.begin(); it != localPadQueue.end(); ++it)
|
||||
spac->append((*it)->padBuf, SLIPPI_PAD_DATA_SIZE); // only transfer 8 bytes per pad
|
||||
spac->append((*it)->pad_buf, SLIPPI_PAD_DATA_SIZE); // only transfer 8 bytes per pad
|
||||
|
||||
SendAsync(std::move(spac));
|
||||
u64 time = Common::Timer::NowUs();
|
||||
|
@ -1081,6 +1128,35 @@ void SlippiNetplayClient::SetMatchSelections(SlippiPlayerSelections& s)
|
|||
SendAsync(std::move(spac));
|
||||
}
|
||||
|
||||
void SlippiNetplayClient::SendGamePrepStep(SlippiGamePrepStepResults& s)
|
||||
{
|
||||
auto spac = std::make_unique<sf::Packet>();
|
||||
*spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_COMPLETE_STEP);
|
||||
*spac << s.step_idx;
|
||||
*spac << s.char_selection;
|
||||
*spac << s.char_color_selection;
|
||||
*spac << s.stage_selections[0] << s.stage_selections[1];
|
||||
SendAsync(std::move(spac));
|
||||
}
|
||||
|
||||
bool SlippiNetplayClient::GetGamePrepResults(u8 step_idx, SlippiGamePrepStepResults& res)
|
||||
{
|
||||
// Just pull stuff off until we find something for the right step. I think that should be fine
|
||||
while (!game_prep_step_queue.empty())
|
||||
{
|
||||
auto front = game_prep_step_queue.front();
|
||||
if (front.step_idx == step_idx)
|
||||
{
|
||||
res = front;
|
||||
return true;
|
||||
}
|
||||
|
||||
game_prep_step_queue.pop_front();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SlippiPlayerSelections SlippiNetplayClient::GetSlippiRemoteChatMessage(bool isChatEnabled)
|
||||
{
|
||||
SlippiPlayerSelections copiedSelection = SlippiPlayerSelections();
|
||||
|
@ -1098,6 +1174,7 @@ SlippiPlayerSelections SlippiNetplayClient::GetSlippiRemoteChatMessage(bool isCh
|
|||
{
|
||||
copiedSelection.messageId = 0;
|
||||
copiedSelection.playerIdx = 0;
|
||||
|
||||
// if chat is not enabled, automatically send back a message saying so.
|
||||
if (remoteChatMessageSelection != nullptr && !isChatEnabled &&
|
||||
(remoteChatMessageSelection->messageId > 0 &&
|
||||
|
@ -1137,12 +1214,12 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetFakePadOutput(int
|
|||
if (frame % 60 < 5)
|
||||
{
|
||||
// Return old inputs for a bit
|
||||
padOutput->latestFrame = frame - (frame % 60);
|
||||
padOutput->latest_frame = frame - (frame % 60);
|
||||
padOutput->data.insert(padOutput->data.begin(), SLIPPI_PAD_FULL_SIZE, 0);
|
||||
}
|
||||
else if (frame % 60 == 5)
|
||||
{
|
||||
padOutput->latestFrame = frame;
|
||||
padOutput->latest_frame = frame;
|
||||
// Add 5 frames of 0'd inputs
|
||||
padOutput->data.insert(padOutput->data.begin(), 5 * SLIPPI_PAD_FULL_SIZE, 0);
|
||||
|
||||
|
@ -1151,7 +1228,7 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetFakePadOutput(int
|
|||
}
|
||||
else
|
||||
{
|
||||
padOutput->latestFrame = frame;
|
||||
padOutput->latest_frame = frame;
|
||||
padOutput->data.insert(padOutput->data.begin(), SLIPPI_PAD_FULL_SIZE, 0);
|
||||
}
|
||||
|
||||
|
@ -1169,9 +1246,9 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(i
|
|||
{
|
||||
auto emptyPad = std::make_unique<SlippiPad>(0);
|
||||
|
||||
padOutput->latestFrame = emptyPad->frame;
|
||||
padOutput->latest_frame = emptyPad->frame;
|
||||
|
||||
auto emptyIt = std::begin(emptyPad->padBuf);
|
||||
auto emptyIt = std::begin(emptyPad->pad_buf);
|
||||
padOutput->data.insert(padOutput->data.end(), emptyIt, emptyIt + SLIPPI_PAD_FULL_SIZE);
|
||||
|
||||
return std::move(padOutput);
|
||||
|
@ -1179,7 +1256,9 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(i
|
|||
|
||||
int inputCount = 0;
|
||||
|
||||
padOutput->latestFrame = 0;
|
||||
padOutput->latest_frame = 0;
|
||||
padOutput->checksum_frame = remoteChecksum[index].frame;
|
||||
padOutput->checksum = remoteChecksum[index].value;
|
||||
|
||||
// Copy inputs from the remote pad queue to the output. We iterate backwards because
|
||||
// we want to get the oldest frames possible (will have been cleared to contain the last
|
||||
|
@ -1188,14 +1267,14 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(i
|
|||
// game actually needed.
|
||||
for (auto it = remotePadQueue[index].rbegin(); it != remotePadQueue[index].rend(); ++it)
|
||||
{
|
||||
if ((*it)->frame > padOutput->latestFrame)
|
||||
padOutput->latestFrame = (*it)->frame;
|
||||
if ((*it)->frame > padOutput->latest_frame)
|
||||
padOutput->latest_frame = (*it)->frame;
|
||||
|
||||
// NOTICE_LOG(SLIPPI_ONLINE, "[%d] (Remote) P%d %08X %08X %08X", (*it)->frame,
|
||||
// index >= playerIdx ? index + 1 : index, Common::swap32(&(*it)->padBuf[0]),
|
||||
// Common::swap32(&(*it)->padBuf[4]), Common::swap32(&(*it)->padBuf[8]));
|
||||
|
||||
auto padIt = std::begin((*it)->padBuf);
|
||||
auto padIt = std::begin((*it)->pad_buf);
|
||||
padOutput->data.insert(padOutput->data.begin(), padIt, padIt + SLIPPI_PAD_FULL_SIZE);
|
||||
|
||||
// Limit max amount of inputs to send
|
||||
|
@ -1231,7 +1310,7 @@ int32_t SlippiNetplayClient::GetSlippiLatestRemoteFrame(int maxFrameCount)
|
|||
for (int i = 0; i < m_remotePlayerCount; i++)
|
||||
{
|
||||
auto rp = GetSlippiRemotePad(i, maxFrameCount);
|
||||
int f = rp->latestFrame;
|
||||
int f = rp->latest_frame;
|
||||
if (f < lowestFrame || !isFrameSet)
|
||||
{
|
||||
lowestFrame = f;
|
||||
|
|
|
@ -34,11 +34,21 @@
|
|||
|
||||
struct SlippiRemotePadOutput
|
||||
{
|
||||
int32_t latestFrame{};
|
||||
int32_t latest_frame{};
|
||||
s32 checksum_frame;
|
||||
u32 checksum;
|
||||
u8 playerIdx{};
|
||||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
struct SlippiGamePrepStepResults
|
||||
{
|
||||
u8 step_idx;
|
||||
u8 char_selection;
|
||||
u8 char_color_selection;
|
||||
u8 stage_selections[2];
|
||||
};
|
||||
|
||||
class SlippiPlayerSelections
|
||||
{
|
||||
public:
|
||||
|
@ -90,6 +100,12 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
struct ChecksumEntry
|
||||
{
|
||||
s32 frame;
|
||||
u32 value;
|
||||
};
|
||||
|
||||
class SlippiMatchInfo
|
||||
{
|
||||
public:
|
||||
|
@ -137,6 +153,8 @@ public:
|
|||
void SendConnectionSelected();
|
||||
void SendSlippiPad(std::unique_ptr<SlippiPad> pad);
|
||||
void SetMatchSelections(SlippiPlayerSelections& s);
|
||||
void SendGamePrepStep(SlippiGamePrepStepResults& s);
|
||||
bool GetGamePrepResults(u8 stepIdx, SlippiGamePrepStepResults& res);
|
||||
std::unique_ptr<SlippiRemotePadOutput> GetFakePadOutput(int frame);
|
||||
std::unique_ptr<SlippiRemotePadOutput> GetSlippiRemotePad(int index, int maxFrameCount);
|
||||
void DropOldRemoteInputs(int32_t finalizedFrame);
|
||||
|
@ -201,7 +219,8 @@ protected:
|
|||
std::deque<std::unique_ptr<SlippiPad>> localPadQueue; // most recent inputs at start of deque
|
||||
std::deque<std::unique_ptr<SlippiPad>>
|
||||
remotePadQueue[SLIPPI_REMOTE_PLAYER_MAX]; // most recent inputs at start of deque
|
||||
|
||||
ChecksumEntry remoteChecksum[SLIPPI_REMOTE_PLAYER_MAX];
|
||||
std::deque<SlippiGamePrepStepResults> game_prep_step_queue;
|
||||
u64 pingUs[SLIPPI_REMOTE_PLAYER_MAX];
|
||||
int32_t lastFrameAcked[SLIPPI_REMOTE_PLAYER_MAX];
|
||||
FrameOffsetData frameOffsetData[SLIPPI_REMOTE_PLAYER_MAX];
|
||||
|
|
|
@ -10,21 +10,24 @@ static u8 emptyPad[SLIPPI_PAD_FULL_SIZE] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|||
SlippiPad::SlippiPad(int32_t frame)
|
||||
{
|
||||
this->frame = frame;
|
||||
memcpy(this->padBuf, emptyPad, SLIPPI_PAD_FULL_SIZE);
|
||||
this->checksum = 0;
|
||||
this->checksum_frame = 0;
|
||||
memcpy(this->pad_buf, emptyPad, SLIPPI_PAD_FULL_SIZE);
|
||||
}
|
||||
|
||||
SlippiPad::SlippiPad(int32_t frame, u8* padBuf) : SlippiPad(frame)
|
||||
SlippiPad::SlippiPad(int32_t frame, u8* pad_buf) : SlippiPad(frame)
|
||||
{
|
||||
// Overwrite the data portion of the pad
|
||||
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
|
||||
memcpy(this->pad_buf, pad_buf, SLIPPI_PAD_DATA_SIZE);
|
||||
}
|
||||
|
||||
SlippiPad::SlippiPad(int32_t frame, u8 playerIdx, u8* padBuf) : SlippiPad(frame)
|
||||
SlippiPad::SlippiPad(int32_t frame, s32 checksum_frame, u32 checksum, u8* pad_buf)
|
||||
: SlippiPad(frame, pad_buf)
|
||||
{
|
||||
this->frame = frame;
|
||||
this->playerIdx = playerIdx;
|
||||
this->checksum_frame = checksum_frame;
|
||||
this->checksum = checksum;
|
||||
// Overwrite the data portion of the pad
|
||||
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
|
||||
memcpy(this->pad_buf, pad_buf, SLIPPI_PAD_DATA_SIZE);
|
||||
}
|
||||
|
||||
SlippiPad::~SlippiPad()
|
||||
|
|
|
@ -9,11 +9,12 @@ 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* pad_buf);
|
||||
SlippiPad(int32_t frame, s32 checksum_frame, u32 checksum, u8* pad_buf);
|
||||
~SlippiPad();
|
||||
|
||||
int32_t frame;
|
||||
u8 playerIdx;
|
||||
u8 padBuf[SLIPPI_PAD_FULL_SIZE];
|
||||
s32 frame;
|
||||
s32 checksum_frame;
|
||||
u32 checksum;
|
||||
u8 pad_buf[SLIPPI_PAD_FULL_SIZE];
|
||||
};
|
||||
|
|
|
@ -19,16 +19,16 @@
|
|||
#include <QTimer>
|
||||
#include <QWindow>
|
||||
|
||||
//test ui stuff
|
||||
#include <QVBoxLayout>
|
||||
// test ui stuff
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/State.h"
|
||||
#include "Core/Slippi/SlippiPlayback.h"
|
||||
#include "Core/State.h"
|
||||
|
||||
#include "DolphinQt/Host.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue