void C4TeamList::ReassignAllTeams() { assert(::Control.isCtrlHost()); if (!::Control.isCtrlHost()) return; // go through all player infos; reset team in them int32_t idStart = -1; C4PlayerInfo *pNfo; while ((pNfo = Game.PlayerInfos.GetNextPlayerInfoByID(idStart))) { idStart = pNfo->GetID(); if (pNfo->HasJoinIssued()) continue; pNfo->SetTeam(0); // mark changed info as updated C4ClientPlayerInfos *pCltInfo = Game.PlayerInfos.GetClientInfoByPlayerID(idStart); assert(pCltInfo); if (pCltInfo) { pCltInfo->SetUpdated(); } } // clear players from team lists RecheckPlayers(); EnsureTeamCount(); // reassign them idStart = -1; while ((pNfo = Game.PlayerInfos.GetNextPlayerInfoByID(idStart))) { idStart = pNfo->GetID(); if (pNfo->HasJoinIssued()) continue; assert(!pNfo->GetTeam()); RecheckPlayerInfoTeams(*pNfo, true); } }
void C4Network2Players::HandlePlayerInfo(const class C4ClientPlayerInfos &rInfoPacket) { // network only assert(::Network.isEnabled()); // copy client player infos out of packet to be used in local list C4ClientPlayerInfos *pClientInfo = new C4ClientPlayerInfos(rInfoPacket); // add client info to local player info list - eventually deleting pClientInfo and // returning a pointer to the new info structure when multiple player infos are merged // may also replace existing info, if this is an update-call pClientInfo = rInfoList.AddInfo(pClientInfo); // make sure team list reflects teams set in player infos Game.Teams.RecheckPlayers(); Game.Teams.RecheckTeams(); // recheck random teams - if a player left, teams may need to be rebalanced // make sure resources are loaded for those players rInfoList.LoadResources(); // get associated client - note that pClientInfo might be NULL for empty packets that got discarded if (pClientInfo) { const C4Client *pClient = Game.Clients.getClientByID(pClientInfo->GetClientID()); // host, game running and client active already? if (::Network.isHost() && ::Network.isRunning() && pClient && pClient->isActivated()) { // then join the players immediately JoinUnjoinedPlayersInControlQueue(pClientInfo); } } // adding the player may have invalidated other players (through team settings). Send them. SendUpdatedPlayers(); // lobby: update players C4GameLobby::MainDlg *pLobby = ::Network.GetLobby(); if (pLobby) pLobby->OnPlayersChange(); // invalidate reference ::Network.InvalidateReference(); }
C4ClientPlayerInfos *C4Network2Players::GetLocalPlayerInfoPacket() const { // get local client ID int iLocalClientID = Game.Clients.getLocalID(); // check all packets for same client ID as local int i=0; C4ClientPlayerInfos *pkInfo; while ((pkInfo = rInfoList.GetIndexedInfo(i++))) if (pkInfo->GetClientID() == iLocalClientID) // found return pkInfo; // not found return NULL; }
void C4Network2Players::SendUpdatedPlayers() { // check all clients for update C4ClientPlayerInfos *pUpdInfo; int i=0; while ((pUpdInfo = rInfoList.GetIndexedInfo(i++))) if (pUpdInfo->IsUpdated()) { pUpdInfo->ResetUpdated(); C4ControlPlayerInfo *pkSend = new C4ControlPlayerInfo(*pUpdInfo); // send info to all ::Control.DoInput(CID_PlrInfo, pkSend, CDT_Direct); } }
void C4Network2Players::UpdateSavegameAssignments(C4ClientPlayerInfos *pNewInfo) { // safety if (!pNewInfo) return; // check all joins of new info; backwards so they can be deleted C4PlayerInfo *pInfo, *pInfo2, *pSaveInfo; int i=pNewInfo->GetPlayerCount(), j, id; while (i--) if ((pInfo = pNewInfo->GetPlayerInfo(i))) if ((id=pInfo->GetAssociatedSavegamePlayerID())) { // check for non-existant savegame players if (!(pSaveInfo=Game.RestorePlayerInfos.GetPlayerInfoByID(id))) { pInfo->SetAssociatedSavegamePlayer(id=0); pNewInfo->SetUpdated(); } // check for duplicates (can't really occur...) if (id) { j=i; while ((pInfo2 = pNewInfo->GetPlayerInfo(++j))) if (pInfo2->GetAssociatedSavegamePlayerID() == id) { // fix it by resetting the savegame info pInfo->SetAssociatedSavegamePlayer(id=0); pNewInfo->SetUpdated(); break; } } // check against all infos of other clients C4ClientPlayerInfos *pkClientInfo; int k=0; while ((pkClientInfo = rInfoList.GetIndexedInfo(k++)) && id) { // if it's not an add packet, don't check own client twice if (pkClientInfo->GetClientID() == pNewInfo->GetClientID() && !(pNewInfo->IsAddPacket())) continue; // check against all players j=0; while ((pInfo2 = pkClientInfo->GetPlayerInfo(j++))) if (pInfo2->GetAssociatedSavegamePlayerID() == id) { // fix it by resetting the savegame info pInfo->SetAssociatedSavegamePlayer(id=0); pNewInfo->SetUpdated(); break; } } // if the player joined just for the savegame assignment, and that failed, delete it if (!id && pInfo->IsJoinForSavegameOnly()) pNewInfo->RemoveIndexedInfo(i); // prev info } }
void C4TeamList::RecheckTeams() { // automatic team distributions only if (!IsRandomTeam()) return; // host decides random teams if (!::Control.isCtrlHost()) { // Still make sure that we have the right number of teams on the clients as well so that // GetTeamCount() does not report inconsistent values. EnsureTeamCount(); return; } // random teams in auto generate mode? Make sure there are exactly two teams if (IsAutoGenerateTeams() && GetTeamCount() != 2) { ReassignAllTeams(); return; } // redistribute players of largest team that has relocatable players left towards smaller teams for (;;) { C4Team *pLowestTeam = GetRandomSmallestTeam(); if (!pLowestTeam) break; // no teams: Nothing to re-distribute. // get largest team that has relocateable players C4Team *pLargestTeam = nullptr; C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount; for (; iCnt--; ++ppCheck) if (!pLargestTeam || pLargestTeam->GetPlayerCount() > (*ppCheck)->GetPlayerCount()) if ((*ppCheck)->GetFirstUnjoinedPlayerID()) pLargestTeam = *ppCheck; // no team can redistribute? if (!pLargestTeam) break; // redistribution won't help much? if (pLargestTeam->GetPlayerCount() - pLowestTeam->GetPlayerCount() <= 1) break; // okay; redistribute one player! int32_t idRedistPlayer = pLargestTeam->GetFirstUnjoinedPlayerID(); C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(idRedistPlayer); assert(pInfo); if (!pInfo) break; // umn...serious problems pLargestTeam->RemovePlayerByID(idRedistPlayer); pLowestTeam->AddPlayer(*pInfo, true); C4ClientPlayerInfos *pClrInfo = Game.PlayerInfos.GetClientInfoByPlayerID(idRedistPlayer); assert(pClrInfo); // player info change: mark updated to remote clients get information if (pClrInfo) { pClrInfo->SetUpdated(); } } }
void C4Network2Players::OnStatusGoReached() { // host only if (!::Network.isHost()) return; // check all player lists int i=0; C4ClientPlayerInfos *pkInfo; while ((pkInfo = rInfoList.GetIndexedInfo(i++))) // any unsent player joins? if (pkInfo->HasUnjoinedPlayers()) { // get client core const C4Client *pClient = Game.Clients.getClientByID(pkInfo->GetClientID()); // don't send if client is unknown or not activated yet if (!pClient || !pClient->isActivated()) continue; // send them w/o info packet // info packets are synced during pause mode JoinUnjoinedPlayersInControlQueue(pkInfo); } }
DWORD C4Network2Players::GetClientChatColor(int idForClient, bool fLobby) const { // return color of first joined player; force to white for unknown // deactivated always white const C4Client *pClient = Game.Clients.getClientByID(idForClient); if (pClient && pClient->isActivated()) { // get players for activated C4ClientPlayerInfos *pInfoPacket = rInfoList.GetInfoByClientID(idForClient); C4PlayerInfo *pPlrInfo; if (pInfoPacket && (pPlrInfo = pInfoPacket->GetPlayerInfo(0, C4PT_User))) { if (fLobby) return pPlrInfo->GetLobbyColor(); else return pPlrInfo->GetColor(); } } // default color return 0xffffff; }
void C4PlayerInfoListAttributeConflictResolver::ResolveInPacket() { // check all player infos fAnyChange = false; int32_t iCheck = 0; while ((pResolveInfo = pResolvePacket->GetPlayerInfo(iCheck++))) { // not already joined? Joined player must not change their attributes! if (pResolveInfo->HasJoined()) continue; DWORD dwPrevColor = pResolveInfo->GetColor(); StdStrBuf sPrevForcedName; sPrevForcedName.Copy(pResolveInfo->GetForcedName()); // check attributes: Name and color for (eAttr = C4PlayerInfo::PLRATT_Color; eAttr != C4PlayerInfo::PLRATT_Last; eAttr = (C4PlayerInfo::Attribute) (eAttr+1)) { if (eAttr == C4PlayerInfo::PLRATT_Color) { // no color change in savegame associations if (pResolveInfo->GetAssociatedSavegamePlayerID()) continue; // or forced team colors if (Game.Teams.IsTeamColors() && Game.Teams.GetTeamByID(pResolveInfo->GetTeam())) continue; } else if (eAttr == C4PlayerInfo::PLRATT_Name) { // no name change if a league name is used if (pResolveInfo->getLeagueAccount() && *pResolveInfo->getLeagueAccount()) continue; } // not if attributes are otherwise fixed (e.g., for script players) if (pResolveInfo->IsAttributesFixed()) continue; // resolve in this info ResolveInInfo(); } // mark change for return value if anything was changed if (pResolveInfo->GetColor() != dwPrevColor || (pResolveInfo->GetForcedName() != sPrevForcedName)) fAnyChange = true; // next player info check } // mark update if anything was changed if (fAnyChange) pResolvePacket->SetUpdated(); }
void C4TeamList::ReassignAllTeams() { assert(::Control.isCtrlHost()); if (!::Control.isCtrlHost()) return; // go through all player infos; reset team in them int32_t idStart = -1; C4PlayerInfo *pNfo; while ((pNfo = Game.PlayerInfos.GetNextPlayerInfoByID(idStart))) { idStart = pNfo->GetID(); if (pNfo->HasJoinIssued()) continue; pNfo->SetTeam(0); // mark changed info as updated C4ClientPlayerInfos *pCltInfo = Game.PlayerInfos.GetClientInfoByPlayerID(idStart); assert(pCltInfo); if (pCltInfo) { pCltInfo->SetUpdated(); } } // clear players from team lists RecheckPlayers(); // in random autogenerate mode, there must be exactly two teams if (IsRandomTeam()) if (IsAutoGenerateTeams() && GetTeamCount() != 2) { ClearTeams(); GenerateDefaultTeams(2); } // reassign them idStart = -1; while ((pNfo = Game.PlayerInfos.GetNextPlayerInfoByID(idStart))) { idStart = pNfo->GetID(); if (pNfo->HasJoinIssued()) continue; assert(!pNfo->GetTeam()); RecheckPlayerInfoTeams(*pNfo, true); } }
void C4PlayerInfoListAttributeConflictResolver::MarkConflicts(C4ClientPlayerInfos &rCheckPacket, bool fTestOriginal) { C4PlayerInfo *pCheckAgainstInfo; // check current and original attribute against all player infos for (int32_t j=0; (pCheckAgainstInfo = rCheckPacket.GetPlayerInfo(j)); ++j) { if (pCheckAgainstInfo->IsUsingAttribute(eAttr)) if (!pResolveInfo->GetID() || pResolveInfo->GetID() != pCheckAgainstInfo->GetID()) if (pResolveInfo != pCheckAgainstInfo) { // current conflict is marked only if the checked packet has same of lower priority than the one compared to // if the priority is higher, the attribute shall be changed in the other, low priority info instead! bool fHasHigherPrio = (GetAttributePriorityDifference(pResolveInfo, pResolvePacket, pCheckAgainstInfo, &rCheckPacket) > 0); if (!fHasHigherPrio) if (IsAttributeConflict(pCheckAgainstInfo, pResolveInfo, C4PlayerInfo::PLRAL_Current)) fCurrentConflict = true; if (fTestOriginal) { if (IsAttributeConflict(pCheckAgainstInfo, pResolveInfo, C4PlayerInfo::PLRAL_Original)) { if (fHasHigherPrio && !fOriginalConflict && !pLowPrioOriginalConflictPacket) { // original attribute is taken by a low prio packet - do not mark an original conflict, but remember the packet // that's blocking it pLowPrioOriginalConflictPacket = &rCheckPacket; } else { // original attribute is taken by either one higher/equal priority by packet, or by two low prio packets // in this case, don't revert to original pLowPrioOriginalConflictPacket = nullptr; fOriginalConflict = true; } } if (IsAttributeConflict(pCheckAgainstInfo, pResolveInfo, C4PlayerInfo::PLRAL_Alternate)) { if (fHasHigherPrio && !fAlternateConflict && !pLowPrioAlternateConflictPacket) pLowPrioAlternateConflictPacket = &rCheckPacket; else fAlternateConflict = true; } } } } }
void C4Network2Players::ResetUpdatedPlayers() { // mark all client packets as up-to-date C4ClientPlayerInfos *pUpdInfo; int i=0; while ((pUpdInfo = rInfoList.GetIndexedInfo(i++))) pUpdInfo->ResetUpdated(); }
void C4Network2Players::HandlePlayerInfoUpdRequest(const class C4ClientPlayerInfos *pInfoPacket, bool fByHost) { // network host only assert(::Network.isEnabled()); assert(::Network.isHost()); // copy client infos (need to be adjusted) C4ClientPlayerInfos OwnInfoPacket(*pInfoPacket); // safety: check any duplicate, unjoined players first // check those with unassigned IDs only, so update packets won't be rejected by this if (!OwnInfoPacket.IsInitialPacket()) { C4ClientPlayerInfos *pExistingClientInfo = rInfoList.GetInfoByClientID(OwnInfoPacket.GetClientID()); if (pExistingClientInfo) { int iCnt=OwnInfoPacket.GetPlayerCount(); C4PlayerInfo *pPlrInfo; C4Network2Res *pRes; while (iCnt--) if ((pPlrInfo=OwnInfoPacket.GetPlayerInfo(iCnt))) if (!pPlrInfo->GetID()) if ((pRes = pPlrInfo->GetRes())) if (pExistingClientInfo->GetPlayerInfoByRes(pRes->getResID())) { // double join: simply deny without message #ifdef _DEBUG Log("Network: Duplicate player join rejected!"); #endif OwnInfoPacket.RemoveIndexedInfo(iCnt); } } if (!OwnInfoPacket.GetPlayerCount()) { // player join request without players: probably all removed because doubled #ifdef _DEBUG Log("Network: Empty player join request ignored!"); #endif return; } } // assign player IDs if (!rInfoList.AssignPlayerIDs(&OwnInfoPacket) && OwnInfoPacket.IsAddPacket()) { // no players could be joined in an add request: probably because the maximum player limit has been reached return; } // check doubled savegame player usage UpdateSavegameAssignments(&OwnInfoPacket); // update teams rInfoList.AssignTeams(&OwnInfoPacket, fByHost); // update any other player colors and names // this may only change colors and names of all unjoined players (which is all players in lobby mode) // any affected players will get an updated-flag rInfoList.UpdatePlayerAttributes(&OwnInfoPacket, true); // league score gains may now be different rInfoList.ResetLeagueProjectedGain(true); int32_t iPlrInfo = 0; C4PlayerInfo *pPlrInfo; while ((pPlrInfo = OwnInfoPacket.GetPlayerInfo(iPlrInfo++))) pPlrInfo->ResetLeagueProjectedGain(); if (Game.Parameters.isLeague()) { // lobby only if (!::Network.isLobbyActive()) return; // check league authentication for new players for (int i = 0; i < OwnInfoPacket.GetPlayerCount(); i++) if (!rInfoList.GetPlayerInfoByID(OwnInfoPacket.GetPlayerInfo(i)->GetID())) { C4PlayerInfo *pInfo = OwnInfoPacket.GetPlayerInfo(i); // remove player infos without authentication if (!::Network.LeaguePlrAuthCheck(pInfo)) { OwnInfoPacket.RemoveIndexedInfo(i); i--; } else // always reset authentication ID after check - it's not needed anymore pInfo->SetAuthID(""); } } // send updates to all other clients and reset update flags SendUpdatedPlayers(); // finally, add new player join as direct input // this will add the player infos directly on host side (DirectExec as a subcall), // so future player join request will take the other joined clients into consideration // when assigning player colors, etc.; it will also start resource loading // in running mode, this call will also put the actual player joins into the queue ::Control.DoInput(CID_PlrInfo, new C4ControlPlayerInfo(OwnInfoPacket), CDT_Direct); // notify lobby of updates C4GameLobby::MainDlg *pLobby = ::Network.GetLobby(); if (pLobby) pLobby->OnPlayersChange(); }