diff --git a/lib/netplay/netplay.cpp b/lib/netplay/netplay.cpp index bed221203b6..419cb4310e6 100644 --- a/lib/netplay/netplay.cpp +++ b/lib/netplay/netplay.cpp @@ -737,9 +737,8 @@ static void NETSendNPlayerInfoTo(uint32_t *index, uint32_t indexLen, unsigned to NETbool(&NetPlay.players[index[n]].allocated); NETbool(&NetPlay.players[index[n]].heartbeat); NETbool(&NetPlay.players[index[n]].kick); - if (game.blindMode != BLIND_MODE::NONE // if in blind mode - && index[n] < MAX_PLAYER_SLOTS // and an actual player slot (not a spectator slot) - && !ingame.endTime.has_value()) // and game has not ended + if (isBlindPlayerInfoState() // if in blind player info state + && index[n] < MAX_PLAYER_SLOTS) // and an actual player slot (not a spectator slot) { // send a generic player name const char* genericName = getPlayerGenericName(index[n]); @@ -1018,7 +1017,7 @@ static void NETplayerLeaving(UDWORD index, bool quietSocketClose) NET_DestroyPlayer(index); // sets index player's array to false if (!wasSpectator) { - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); // reset ready status for all players + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); // reset ready status for all players resetReadyCalled = true; } @@ -1059,7 +1058,7 @@ static void NETplayerDropped(UDWORD index) NET_DestroyPlayer(id); // just clears array if (!wasSpectator) { - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); // reset ready status for all players + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); // reset ready status for all players resetReadyCalled = true; } diff --git a/src/loop.cpp b/src/loop.cpp index 2eb64c8968e..422139195e5 100644 --- a/src/loop.cpp +++ b/src/loop.cpp @@ -501,7 +501,7 @@ static void gameStateUpdate() NetPlay.players[0].allocated, NetPlay.players[1].allocated, NetPlay.players[2].allocated, NetPlay.players[3].allocated, NetPlay.players[4].allocated, NetPlay.players[5].allocated, NetPlay.players[6].allocated, NetPlay.players[7].allocated, NetPlay.players[8].allocated, NetPlay.players[9].allocated, NetPlay.players[0].position, NetPlay.players[1].position, NetPlay.players[2].position, NetPlay.players[3].position, NetPlay.players[4].position, NetPlay.players[5].position, NetPlay.players[6].position, NetPlay.players[7].position, NetPlay.players[8].position, NetPlay.players[9].position ); - bool overrideHandleClientBlindNames = (game.blindMode != BLIND_MODE::NONE) && (NetPlay.isHost || NETisReplay()) && !ingame.endTime.has_value(); + bool overrideHandleClientBlindNames = (game.blindMode >= BLIND_MODE::BLIND_GAME) && (NetPlay.isHost || NETisReplay()) && !ingame.endTime.has_value(); for (unsigned n = 0; n < MAX_PLAYERS; ++n) { syncDebug("Player %d = \"%s\"", n, (!overrideHandleClientBlindNames) ? NetPlay.players[n].name : getPlayerGenericName(n)); diff --git a/src/multiint.cpp b/src/multiint.cpp index 383e9be1d80..e5092fa5aec 100644 --- a/src/multiint.cpp +++ b/src/multiint.cpp @@ -1540,7 +1540,7 @@ int allPlayersOnSameTeam(int except) int WzMultiplayerOptionsTitleUI::playerRowY0(uint32_t row) const { - if (game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY && !NetPlay.isHost) + if (isBlindSimpleLobby(game.blindMode) && !NetPlay.isHost) { // Get Y position of virtual player row in WzPlayerBlindWaitingRoom widget auto pBlindWaitingRoom = widgFormGetFromID(psWScreen->psForm, MULTIOP_BLIND_WAITING_ROOM); @@ -1671,7 +1671,7 @@ void WzMultiplayerOptionsTitleUI::openDifficultyChooser(uint32_t player) ASSERT_OR_RETURN(, pStrongPtr.operator bool(), "WzMultiplayerOptionsTitleUI no longer exists"); NetPlay.players[player].difficulty = difficultyValue[difficultyIdx]; NETBroadcastPlayerInfo(player); - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); widgScheduleTask([pStrongPtr] { pStrongPtr->closeDifficultyChooser(); pStrongPtr->addPlayerBox(true); @@ -1780,7 +1780,7 @@ void WzMultiplayerOptionsTitleUI::openAiChooser(uint32_t player) // common code NetPlay.players[player].difficulty = AIDifficulty::DISABLED; // disable AI for this slot NETBroadcastPlayerInfo(player); - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); } else { @@ -1930,7 +1930,7 @@ class WzPlayerSelectionChangePositionRow : public W_BUTTON auto strongTitleUI = titleUI.lock(); ASSERT_OR_RETURN(, strongTitleUI != nullptr, "Title UI is gone?"); // Switch player - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); // will reset only locally if not a host + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); // will reset only locally if not a host SendPositionRequest(switcherPlayerIdx, NetPlay.players[selectPositionRow->targetPlayerIdx].position); widgScheduleTask([strongTitleUI] { strongTitleUI->closePositionChooser(); @@ -1997,7 +1997,7 @@ class WzPlayerIndexSwapPositionRow : public W_BUTTON std::string playerName = getPlayerName(switcherPlayerIdx, true); NETmoveSpectatorToPlayerSlot(switcherPlayerIdx, selectPositionRow->targetPlayerIdx, true); - resetReadyStatus(true, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); //reset and send notification to all clients + resetReadyStatus(true, isBlindSimpleLobby(game.blindMode)); //reset and send notification to all clients widgScheduleTask([strongTitleUI] { strongTitleUI->closePlayerSlotSwapChooser(); strongTitleUI->updatePlayers(); @@ -2236,7 +2236,7 @@ void WzMultiplayerOptionsTitleUI::openTeamChooser(uint32_t player) ASSERT(id >= MULTIOP_TEAMCHOOSER && (id - MULTIOP_TEAMCHOOSER) < MAX_PLAYERS, "processMultiopWidgets: wrong id - MULTIOP_TEAMCHOOSER value (%d)", id - MULTIOP_TEAMCHOOSER); - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); // will reset only locally if not a host + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); // will reset only locally if not a host SendTeamRequest(player, (UBYTE)id - MULTIOP_TEAMCHOOSER); @@ -4309,7 +4309,7 @@ void WzMultiplayerOptionsTitleUI::addPlayerBox(bool players) auto titleUI = std::dynamic_pointer_cast(shared_from_this()); - if (game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY && !NetPlay.isHost) + if (isBlindSimpleLobby(game.blindMode) && !NetPlay.isHost) { // Display custom "waiting room" that completely blinds the current player @@ -5407,6 +5407,27 @@ static void updateMapWidgets(LEVEL_DATASET *mapData) widgSetString(psWScreen, MULTIOP_MAP + 1, name.toUtf8().c_str()); //What a horrible, horrible way to do this! FIX ME! (See addBlueForm) } +bool blindModeFromStr(const WzString& str, BLIND_MODE& mode_output) +{ + const std::unordered_map mappings = { + {"none", BLIND_MODE::NONE}, + {"blind_lobby", BLIND_MODE::BLIND_LOBBY}, + {"blind_lobby_simple_lobby", BLIND_MODE::BLIND_LOBBY_SIMPLE_LOBBY}, + {"blind_game", BLIND_MODE::BLIND_GAME}, + {"blind_game_simple_lobby", BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY}, + }; + + auto it = mappings.find(str); + if (it != mappings.end()) + { + mode_output = it->second; + return true; + } + + mode_output = BLIND_MODE::NONE; + return false; +} + static bool loadMapChallengeSettings(WzConfig& ini) { ini.beginGroup("locked"); // GUI lockdown @@ -5474,14 +5495,10 @@ static bool loadMapChallengeSettings(WzConfig& ini) defaultOpenSpectatorSlots = static_cast(std::min(openSpectatorSlots_uint, MAX_SPECTATOR_SLOTS)); // Allow setting "blind mode" - unsigned int blindMode_uint = ini.value("blindMode", 0).toUInt(); - if (blindMode_uint <= static_cast(BLIND_MODE_MAX)) - { - game.blindMode = static_cast(blindMode_uint); - } - else + WzString blindMode_str = ini.value("blindMode", "none").toWzString(); + if (!blindModeFromStr(blindMode_str, game.blindMode)) { - debug(LOG_ERROR, "Invalid blindMode (%u) specified in config .json - ignoring", blindMode_uint); + debug(LOG_ERROR, "Invalid blindMode (%s) specified in config .json - ignoring", blindMode_str.toUtf8().c_str()); } // DEPRECATED: This seems to have been odd workaround for not having the locked group handled. @@ -6392,7 +6409,7 @@ class WzHostLobbyOperationsInterface : public HostLobbyOperationsInterface return true; } ::changeTeam(player, team, responsibleIdx); - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); return true; } virtual bool changePosition(uint32_t player, uint8_t position, uint32_t responsibleIdx) override @@ -6414,7 +6431,7 @@ class WzHostLobbyOperationsInterface : public HostLobbyOperationsInterface { return false; } - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); return true; } virtual bool changeBase(uint8_t baseValue) override @@ -6477,7 +6494,7 @@ class WzHostLobbyOperationsInterface : public HostLobbyOperationsInterface std::string slotType = (NetPlay.players[player].isSpectator) ? "spectator" : "player"; sendRoomSystemMessage((std::string("Kicking ")+slotType+": "+std::string(getPlayerName(player, true))).c_str()); ::kickPlayer(player, reason, ERROR_KICKED, ban); - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); return true; } virtual bool changeHostChatPermissions(uint32_t player, bool freeChatEnabled) override @@ -6881,7 +6898,7 @@ void WzMultiplayerOptionsTitleUI::frontendMultiMessages(bool running) { uint32_t player_id = MAX_CONNECTED_PLAYERS; - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); NETbeginDecode(queue, NET_PLAYER_DROPPED); { diff --git a/src/multiplay.cpp b/src/multiplay.cpp index 24f5cb6d810..7b9bc4d16c0 100644 --- a/src/multiplay.cpp +++ b/src/multiplay.cpp @@ -462,6 +462,24 @@ bool multiPlayerLoop() ingame.lastPlayerDataCheck2 = std::chrono::steady_clock::now(); wz_command_interface_output("WZEVENT: allPlayersJoined\n"); wz_command_interface_output_room_status_json(); + + // If in blind *lobby* mode, send data on who the players are + if (game.blindMode != BLIND_MODE::NONE && game.blindMode < BLIND_MODE::BLIND_GAME) + { + if (NetPlay.isHost) + { + debug(LOG_INFO, "Revealing actual player names and identities to all players"); + + // Send updated player info (which will include real player names) to all players + NETSendAllPlayerInfoTo(NET_ALL_PLAYERS); + + // Send the verified player identity from initial join for each player + for (uint32_t idx = 0; idx < MAX_CONNECTED_PLAYERS; ++idx) + { + sendMultiStatsHostVerifiedIdentities(idx); + } + } + } } if (NetPlay.bComms) { @@ -674,7 +692,7 @@ BASE_OBJECT *IdToPointer(UDWORD id, UDWORD player) return nullptr; } -static inline bool isBlindPlayerInfoState() +bool isBlindPlayerInfoState() { if (game.blindMode == BLIND_MODE::NONE) { diff --git a/src/multiplay.h b/src/multiplay.h index 65df370a5ec..6ec5335a910 100644 --- a/src/multiplay.h +++ b/src/multiplay.h @@ -238,6 +238,7 @@ Vector3i cameraToHome(UDWORD player, bool scroll, bool fromSave); bool multiPlayerLoop(); // for loop.c +bool isBlindPlayerInfoState(); // return a "generic" player name that is fixed based on the player idx (useful for blind mode games) const char *getPlayerGenericName(int player); diff --git a/src/multiplaydefs.h b/src/multiplaydefs.h index 4d9f7265d91..712f9fb6a15 100644 --- a/src/multiplaydefs.h +++ b/src/multiplaydefs.h @@ -37,10 +37,14 @@ constexpr PLAYER_LEAVE_MODE PLAYER_LEAVE_MODE_DEFAULT = PLAYER_LEAVE_MODE::SPLIT enum class BLIND_MODE : uint8_t { NONE, + BLIND_LOBBY, // standard blind mode lobby (players' true identities are hidden from everyone except a spectator host until the game starts) + BLIND_LOBBY_SIMPLE_LOBBY, // BLIND_LOBBY, but not showing any other players in lobby (players will just be informed that they are waiting for other players) BLIND_GAME, // standard blind mode game (players' true identities are hidden from everyone except a spectator host until the game is over) BLIND_GAME_SIMPLE_LOBBY // BLIND_GAME, but not showing any other players in lobby (players will just be informed that they are waiting for other players) }; constexpr BLIND_MODE BLIND_MODE_MAX = BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY; +static inline bool isBlindSimpleLobby(BLIND_MODE mode) { return mode == BLIND_MODE::BLIND_LOBBY_SIMPLE_LOBBY || mode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY; } + #endif // __INCLUDED_SRC_MULTIPLAYDEFS_H__ diff --git a/src/multistat.cpp b/src/multistat.cpp index fe0c268393e..e4c473f809d 100644 --- a/src/multistat.cpp +++ b/src/multistat.cpp @@ -327,7 +327,7 @@ bool swapPlayerMultiStatsLocal(uint32_t playerIndexA, uint32_t playerIndexB) return true; } -bool sendMultiStats(uint32_t playerIndex, optional recipientPlayerIndex /*= nullopt*/) +static bool sendMultiStatsInternal(uint32_t playerIndex, optional recipientPlayerIndex = nullopt, bool sendHostVerifiedJoinIdentity = false) { ASSERT(NetPlay.isHost || playerIndex == realSelectedPlayer, "Hah"); @@ -363,7 +363,7 @@ bool sendMultiStats(uint32_t playerIndex, optional recipientPlayerInde EcKey::Key identityPublicKey; bool isHostVerifiedIdentity = false; // Choose the identity to send - if (!ingame.endTime.has_value() || !NetPlay.isHost) + if (!sendHostVerifiedJoinIdentity || !NetPlay.isHost) { if (playerIndex == realSelectedPlayer) { @@ -385,7 +385,8 @@ bool sendMultiStats(uint32_t playerIndex, optional recipientPlayerInde } else { - // Once game has ended, if we're the host, send the hostVerifiedJoinIdentity + // Once game has begun or ended (depending on settings), if we're the host, send the hostVerifiedJoinIdentity + ASSERT(NetPlay.isHost && !isBlindPlayerInfoState(), "Not time to send host verified identity yet?"); isHostVerifiedIdentity = true; if (!hostVerifiedJoinIdentities[playerIndex].empty()) { @@ -400,11 +401,16 @@ bool sendMultiStats(uint32_t playerIndex, optional recipientPlayerInde return true; } +bool sendMultiStats(uint32_t playerIndex, optional recipientPlayerIndex /*= nullopt*/) +{ + return sendMultiStatsInternal(playerIndex, recipientPlayerIndex, false); +} + bool sendMultiStatsHostVerifiedIdentities(uint32_t playerIndex) { ASSERT_HOST_ONLY(return false); - ASSERT_OR_RETURN(false, ingame.endTime.has_value(), "Game hasn't ended yet"); - return sendMultiStats(playerIndex); // rely on sendMultiStats behavior when game has ended and this is the host to send the host-verified join identity + ASSERT_OR_RETURN(false, !isBlindPlayerInfoState(), "Not time to send host verified identities yet"); + return sendMultiStatsInternal(playerIndex, nullopt, true); } // //////////////////////////////////////////////////////////////////////////// diff --git a/src/titleui/widgets/lobbyplayerrow.cpp b/src/titleui/widgets/lobbyplayerrow.cpp index 64ee9d2c7d5..6bd4b2bac67 100644 --- a/src/titleui/widgets/lobbyplayerrow.cpp +++ b/src/titleui/widgets/lobbyplayerrow.cpp @@ -492,7 +492,7 @@ void displayPlayer(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) static bool canChooseTeamFor(int i) { - if (game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY && !NetPlay.isHost) + if (isBlindSimpleLobby(game.blindMode) && !NetPlay.isHost) { return false; } @@ -603,7 +603,7 @@ std::shared_ptr WzPlayerRow::make(uint32_t playerIdx, const std::sh && !locked.position && player < MAX_PLAYERS && !isSpectatorOnlySlot(player) - && ((game.blindMode != BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY) || NetPlay.isHost)) + && (!isBlindSimpleLobby(game.blindMode) || NetPlay.isHost)) { widgScheduleTask([strongTitleUI, player] { strongTitleUI->openPositionChooser(player); @@ -628,7 +628,7 @@ std::shared_ptr WzPlayerRow::make(uint32_t playerIdx, const std::sh } widgScheduleTask([strongTitleUI] { strongTitleUI->updatePlayers(); - resetReadyStatus(false, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); + resetReadyStatus(false, isBlindSimpleLobby(game.blindMode)); }); } else @@ -709,7 +709,7 @@ void WzPlayerRow::updateState() && NetPlay.players[playerIdx].allocated && !locked.position && !isSpectatorOnlySlot(playerIdx) - && ((game.blindMode != BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY) || NetPlay.isHost)) + && (!isBlindSimpleLobby(game.blindMode) || NetPlay.isHost)) { playerInfoTooltip = _("Click to change player position"); } @@ -813,7 +813,7 @@ void WzPlayerRow::updateState() void WzPlayerRow::updateReadyButton() { - bool disallow = (allPlayersOnSameTeam(-1) != -1) && (game.blindMode != BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); + bool disallow = (allPlayersOnSameTeam(-1) != -1) && !isBlindSimpleLobby(game.blindMode); const auto& aidata = getAIData(); const auto& locked = getLockedOptions(); @@ -969,7 +969,7 @@ void WzPlayerRow::updateReadyButton() std::string msg = astringf(_("The host has kicked %s from the game!"), getPlayerName(player, true)); sendRoomSystemMessage(msg.c_str()); kickPlayer(player, _("The host has kicked you from the game."), ERROR_KICKED, false); - resetReadyStatus(true, game.blindMode == BLIND_MODE::BLIND_GAME_SIMPLE_LOBBY); //reset and send notification to all clients + resetReadyStatus(true, isBlindSimpleLobby(game.blindMode)); //reset and send notification to all clients } } });