diff --git a/src/include/net_message.h b/src/include/net_message.h index a7630c07a..d1edda490 100644 --- a/src/include/net_message.h +++ b/src/include/net_message.h @@ -65,6 +65,7 @@ public: uint32_t Host; /// Host address uint16_t Port; /// Port on host uint16_t PlyNr; /// Player number + int16_t PlayerIndex; /// Desired player index in game, or -1 for default char PlyName[NetPlayerNameSize]; /// Name of player }; diff --git a/src/network/net_message.cpp b/src/network/net_message.cpp index d63dc4fb5..0ca08aec4 100644 --- a/src/network/net_message.cpp +++ b/src/network/net_message.cpp @@ -224,6 +224,7 @@ void CNetworkHost::Clear() this->Host = 0; this->Port = 0; this->PlyNr = 0; + this->PlayerIndex = -1; memset(this->PlyName, 0, sizeof(this->PlyName)); } diff --git a/src/network/netconnect.cpp b/src/network/netconnect.cpp index 3242ed026..1a3b0a72a 100644 --- a/src/network/netconnect.cpp +++ b/src/network/netconnect.cpp @@ -37,6 +37,10 @@ #include <fstream> #include <set> #include <vector> +#include <functional> +#include <algorithm> +#include <array> +#include <utility> #if __has_include(<filesystem>) #include <filesystem> namespace fs = std::filesystem; @@ -1720,29 +1724,29 @@ void NetworkServerStartGame() LocalSetupState = ServerSetupState; // Make a list of the available player slots. - int num[PlayerMax]; - int rev[PlayerMax]; - int h = 0; + int humanPlayerIndexToMapPlayerIndex[PlayerMax]; + int mapPlayerIndexToHumanPlayerIndexAndBeyond[PlayerMax]; + int humanPlayerCount = 0; for (int i = 0; i < PlayerMax; ++i) { if (Map.Info.PlayerType[i] == PlayerTypes::PlayerPerson) { - rev[i] = h; - num[h++] = i; - DebugPrint("Slot %d is available for an interactive player (%d)\n" _C_ i _C_ rev[i]); + mapPlayerIndexToHumanPlayerIndexAndBeyond[i] = humanPlayerCount; + humanPlayerIndexToMapPlayerIndex[humanPlayerCount++] = i; + DebugPrint("Slot %d is available for an interactive player (%d)\n" _C_ i _C_ mapPlayerIndexToHumanPlayerIndexAndBeyond[i]); } } // Make a list of the available computer slots. - int n = h; + int n = humanPlayerCount; for (int i = 0; i < PlayerMax; ++i) { if (Map.Info.PlayerType[i] == PlayerTypes::PlayerComputer) { - rev[i] = n++; - DebugPrint("Slot %d is available for an ai computer player (%d)\n" _C_ i _C_ rev[i]); + mapPlayerIndexToHumanPlayerIndexAndBeyond[i] = n++; + DebugPrint("Slot %d is available for an ai computer player (%d)\n" _C_ i _C_ mapPlayerIndexToHumanPlayerIndexAndBeyond[i]); } } // Make a list of the remaining slots. for (int i = 0; i < PlayerMax; ++i) { if (Map.Info.PlayerType[i] != PlayerTypes::PlayerPerson && Map.Info.PlayerType[i] != PlayerTypes::PlayerComputer) { - rev[i] = n++; + mapPlayerIndexToHumanPlayerIndexAndBeyond[i] = n++; // PlayerTypes::PlayerNobody - not available to anything.. } } @@ -1757,25 +1761,27 @@ void NetworkServerStartGame() printf("\n"); } - int org[PlayerMax]; + int slotIndexToMapPlayerIndex[PlayerMax]; // Reverse to assign slots to menu setup state positions. for (int i = 0; i < PlayerMax; ++i) { - org[i] = -1; + slotIndexToMapPlayerIndex[i] = -1; for (int j = 0; j < PlayerMax; ++j) { - if (rev[j] == i) { - org[i] = j; + if (mapPlayerIndexToHumanPlayerIndexAndBeyond[j] == i) { + slotIndexToMapPlayerIndex[i] = j; break; } } } // Calculate NetPlayers - NetPlayers = h; + NetPlayers = humanPlayerCount; int compPlayers = ServerSetupState.ServerGameSettings.Opponents; - for (int i = 1; i < h; ++i) { + for (int i = 1; i < humanPlayerCount; ++i) { if (Hosts[i].PlyNr == 0 && ServerSetupState.CompOpt[i] != SlotOption::Available) { + // no host connectd and the slot is not available, not a net player NetPlayers--; } else if (Hosts[i].PlyName[0] == 0) { + // the slot is available, but no host connected to it NetPlayers--; if (--compPlayers >= 0) { // Unused slot gets a computer player @@ -1789,8 +1795,9 @@ void NetworkServerStartGame() } // Compact host list.. (account for computer/closed slots in the middle..) - for (int i = 1; i < h; ++i) { + for (int i = 1; i < humanPlayerCount; ++i) { if (Hosts[i].PlyNr == 0) { + // this host is not connected, so we can move some later host down int j; for (j = i + 1; j < PlayerMax - 1; ++j) { if (Hosts[j].PlyNr) { @@ -1802,6 +1809,7 @@ void NetworkServerStartGame() break; } } + // early return, in case there are no more hosts later if (j == PlayerMax - 1) { break; } @@ -1810,42 +1818,95 @@ void NetworkServerStartGame() // Randomize the position. // It can be disabled by writing NoRandomPlacementMultiplayer() in lua files. - // Players slots are then mapped to players numbers(and colors). + // Players slots are then mapped to players numbers(and colors) or by PlayerIndex preference + // (set in lua via Hosts[i].PlyNr, which is not really changing it, just "requesting" the change). if (NoRandomPlacementMultiplayer) { + std::array<std::pair<int, CNetworkHost>, PlayerMax> s; for (int i = 0; i < PlayerMax; ++i) { - if (Map.Info.PlayerType[i] != PlayerTypes::PlayerComputer) { - org[i] = Hosts[i].PlyNr; + s[i] = std::make_pair(i, Hosts[i]); + } + std::sort(s.begin(), s.end(), [](std::pair<int, CNetworkHost> &a, std::pair<int, CNetworkHost> &b) { + if (std::get<1>(a).PlayerIndex < 0 && std::get<1>(b).PlayerIndex < 0) { + // no preferences here, first slot first + return std::get<0>(a) < std::get<0>(b); + } else { + if (std::get<1>(b).PlayerIndex < 0) { + return true; // a has preference, b not, a first + } else if (std::get<1>(a).PlayerIndex < 0) { + return false; // b has preference, a not, b first + } else { + return std::get<1>(a).PlayerIndex < std::get<1>(b).PlayerIndex; + } + } + }); + int j = 0; + for (int i = 0; i < PlayerMax; ++i) { + if (Map.Info.PlayerType[i] == PlayerTypes::PlayerPerson) { + std::get<1>(s[j]).PlyNr = i; + int netPlayer = std::get<0>(s[j]); + int k = slotIndexToMapPlayerIndex[netPlayer]; + // k is the in-game PlayerIndex for the slot i. + // we need to swap it with the in-game PlayerIndex + // that we just assigned + if (k != i) { + for (int o = 0; o < PlayerMax; ++o) { + if (slotIndexToMapPlayerIndex[o] == i) { + slotIndexToMapPlayerIndex[o] = k; + break; + } + } + slotIndexToMapPlayerIndex[i] = i; + } + DebugPrint("Assigning player %d to slot %d (%d)\n" _C_ netPlayer _C_ i _C_ slotIndexToMapPlayerIndex[netPlayer]); + j++; } } } else { - int j = h; + // j is the number of human player slots the map info provides + int j = humanPlayerCount; for (int i = 0; i < NetPlayers; ++i) { Assert(j > 0); - int chosen = MyRand() % j; - - n = num[chosen]; + int chosenHumanPlayerNumber = MyRand() % j; + // we've chosen a slot on the map to assign + n = humanPlayerIndexToMapPlayerIndex[chosenHumanPlayerNumber]; + // n is now the in-game PlayerIndex for this slot Hosts[i].PlyNr = n; - int k = org[i]; + + int k = slotIndexToMapPlayerIndex[i]; + // k is the in-game PlayerIndex for the slot i. + // we need to swap it with the in-game PlayerIndex + // that we just assigned if (k != n) { for (int o = 0; o < PlayerMax; ++o) { - if (org[o] == n) { - org[o] = k; + if (slotIndexToMapPlayerIndex[o] == n) { + slotIndexToMapPlayerIndex[o] = k; break; } } - org[i] = n; + slotIndexToMapPlayerIndex[i] = n; } - DebugPrint("Assigning player %d to slot %d (%d)\n" _C_ i _C_ n _C_ org[i]); + DebugPrint("Assigning player %d to slot %d (%d)\n" _C_ i _C_ n _C_ slotIndexToMapPlayerIndex[i]); - num[chosen] = num[--j]; + // we replace the in-game PlayerIndex we have just assigned + // with the last one available to human players in our map + // for the next MyRand() % j call. We won't be needing this + // mapping to be correct anymore afterwards + humanPlayerIndexToMapPlayerIndex[chosenHumanPlayerNumber] = humanPlayerIndexToMapPlayerIndex[--j]; } } + int mapPlayerIndexToAckStatus[PlayerMax]; // Complete all setup states for the assigned slots. + // At this point, LocalSetupState has all the info + // assigned in-order, that is, LocalSetupState first has + // the settings for the human players, then computers, then + // nothing. We now go through the players and set it up through + // the mapping that the server assigns each slot index to the + // correct map player index for (int i = 0; i < PlayerMax; ++i) { - num[i] = 1; - n = org[i]; + mapPlayerIndexToAckStatus[i] = 1; + n = slotIndexToMapPlayerIndex[i]; ServerSetupState.CompOpt[n] = LocalSetupState.CompOpt[i]; ServerSetupState.ServerGameSettings.Presets[n].Race = LocalSetupState.ServerGameSettings.Presets[i].Race; } @@ -1892,10 +1953,10 @@ breakout: for (int i = 0; i < HostsCount; ++i) { const CHost host(message.hosts[i].Host, message.hosts[i].Port); - if (num[Hosts[i].PlyNr] == 1) { // not acknowledged yet + if (mapPlayerIndexToAckStatus[Hosts[i].PlyNr] == 1) { // not acknowledged yet message.clientIndex = i; NetworkSendICMessage_Log(NetworkFildes, host, message); - } else if (num[Hosts[i].PlyNr] == 2) { + } else if (mapPlayerIndexToAckStatus[Hosts[i].PlyNr] == 2) { NetworkSendICMessage_Log(NetworkFildes, host, statemsg); } } @@ -1922,8 +1983,8 @@ breakout: DebugPrint("Got ack for InitConfig from %s\n" _C_ hostStr.c_str()); const int index = FindHostIndexBy(host); if (index != -1) { - if (num[Hosts[index].PlyNr] == 1) { - num[Hosts[index].PlyNr]++; + if (mapPlayerIndexToAckStatus[Hosts[index].PlyNr] == 1) { + mapPlayerIndexToAckStatus[Hosts[index].PlyNr]++; } goto breakout; } @@ -1934,8 +1995,8 @@ breakout: DebugPrint("Got ack for InitState from %s\n" _C_ hostStr.c_str()); const int index = FindHostIndexBy(host); if (index != -1) { - if (num[Hosts[index].PlyNr] == 2) { - num[Hosts[index].PlyNr] = 0; + if (mapPlayerIndexToAckStatus[Hosts[index].PlyNr] == 2) { + mapPlayerIndexToAckStatus[Hosts[index].PlyNr] = 0; --j; DebugPrint("Removing host %d from waiting list\n" _C_ j); } diff --git a/src/tolua/network.pkg b/src/tolua/network.pkg index a6ed44171..46e3f9511 100644 --- a/src/tolua/network.pkg +++ b/src/tolua/network.pkg @@ -45,7 +45,7 @@ tolua_readonly int NetPlayerNameSize; struct CNetworkHost { unsigned long Host; /// Host address unsigned short Port; /// Port on host - unsigned short PlyNr; /// Player nummer + short PlayerIndex @ PlyNr; /// Desired player number when in game char PlyName[NetPlayerNameSize]; /// Name of player }; extern CNetworkHost Hosts[PlayerMax];