#include "server/playermgr.h" namespace wi { #define CHECK_SYNC_ERRORS PlayerMgr::PlayerMgr(Server& server) : server_(server), tLagAcknowledge_(0) { } void PlayerMgr::Init(const LevelInfo& info) { // Create Players for all sides. Until we know otherwise, assume // the human players will be unfulfilled and need to be removed. // Map authors can set up the players incorrectly. For example, // a map may be set to 2 player, but there is 1 human and 3 // computer players. Fix that up. int cHuman = 0; for (Side side = kside1; side < kcSides; side++) { word wf = 0; int intelligence = info.GetIntelligence(side); switch (intelligence) { case knIntelligenceComputer: case knIntelligenceComputerNeutral: wf = kfPlrComputer | kfPlrReady; break; case knIntelligenceComputerOvermind: wf = kfPlrComputer | kfPlrComputerOvermind | kfPlrReady; break; case knIntelligenceHuman: cHuman++; wf = kfPlrUnfulfilled; break; } Player *player = AllocPlayer(wf); if (player == NULL) { continue; } player->SetSide(side); } // Convert some computer players to human if necessary int cHumansNeeded = info.maxplayers() - cHuman; if (cHumansNeeded > 0) { for (Side side = kside1; cHumansNeeded > 0 && side < kcSides; side++) { Player *pplr = GetPlayer(side); if (pplr == NULL || !(pplr->wf_ & kfPlrComputer)) { continue; } pplr->wf_ &= ~(kfPlrComputer | kfPlrComputerOvermind | kfPlrReady); pplr->wf_ |= kfPlrUnfulfilled; cHumansNeeded--; } } // Really only necessary for the computer players, but some players // are slotted for human, but then get converted to computer players // when the game starts (if there are unfulfilled slots), so name // them all AI int count = 1; for (Side side = kside1; side < kcSides; side++) { Player *pplr = GetPlayer(side); if (pplr == NULL) { continue; } pplr->SetName("AI"); } } Player *PlayerMgr::AllocPlayer(word wf) { Player *player = players_; for (Pid pid = 0; pid < ARRAYSIZE(players_); pid++, player++) { if (player->wf_ & kfPlrInUse) { continue; } player->Init(pid); player->wf_ |= kfPlrInUse | wf; return player; } RLOG() << "this shouldn't happen!"; return NULL; } void PlayerMgr::SwapPlayers(Player *playerA, Player *playerB) { Endpoint *endpointA = playerA->endpoint(); word flagsA = playerA->flags(); Endpoint *endpointB = playerB->endpoint(); word flagsB = playerB->flags(); playerA->SetEndpoint(endpointB); playerA->SetFlags(flagsB); playerB->SetEndpoint(endpointA); playerB->SetFlags(flagsA); BroadcastPlayersUpdate(); } int PlayerMgr::GetEndpointCount() { // This counts players with endpoints (that are receiving updates), // whether human or observer (player with endpoint marked as computer). int cEndpoints = 0; Player *pplr = players_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (pplr->endpoint_ != NULL) { cEndpoints++; } } return cEndpoints; } const PlayerName *PlayerMgr::GetEndpointNames() { // The creator always goes first Player *pplr = players_; PlayerName *name = names_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (pplr->endpoint_ != NULL && (pplr->wf_ & kfPlrCreator) != 0) { strncpyz(name->szPlayerName, pplr->name_, sizeof(name->szPlayerName)); name++; } } // Then the other players pplr = players_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (pplr->endpoint_ != NULL && (pplr->wf_ & kfPlrCreator) == 0) { strncpyz(name->szPlayerName, pplr->name_, sizeof(name->szPlayerName)); name++; } } return names_; } Player *PlayerMgr::GetPlayer(Side side) { Player *pplr = players_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (pplr->side_ == side) { return pplr; } } return NULL; } Player *PlayerMgr::GetPlayer(Endpoint *endpoint) { Player *pplr = players_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (pplr->endpoint() == endpoint) { return pplr; } } return NULL; } Player *PlayerMgr::GetPlayerFromPid(Pid pid) { // Pid is unsigned so this is a full range check if (pid != kpidNeutral && pid < kcPlayersMax) { return &players_[((int)pid)]; } return NULL; } Player *PlayerMgr::GetNextHumanPlayer(Player *pplr) { int i = pplr == NULL ? 0 : pplr->pid_ + 1; pplr = &players_[i]; for (; i < kcPlayersMax; i++, pplr++) { if ((pplr->wf_ & (kfPlrInUse | kfPlrComputer)) == kfPlrInUse) { return pplr; } } return NULL; } Player *PlayerMgr::GetNextPlayerWithEndpoint(Player *pplr) { // Players that resign but are observing have kfPlrComputer set // and endpoint_ != NULL. These players still receive updates, // participate in lag detection, can be kicked, etc. int i = pplr == NULL ? 0 : pplr->pid_ + 1; pplr = &players_[i]; for (; i < kcPlayersMax; i++, pplr++) { if ((pplr->wf_ & kfPlrInUse) && pplr->endpoint_ != NULL) { return pplr; } } return NULL; } Player *PlayerMgr::GetNextPlayer(Player *pplr) { int i = (pplr == NULL) ? 0 : pplr->pid_ + 1; pplr = &players_[i]; for (; i < kcPlayersMax; i++, pplr++) { if (pplr->wf_ & kfPlrInUse) { return pplr; } } return NULL; } void PlayerMgr::Broadcast(NetMessage *pnm, Pid pidIgnore) { for (int i = 0; i < ARRAYSIZE(players_); i++) { Player *pplr = &players_[i]; if (!(pplr->wf_ & kfPlrInUse)) { continue; } if (pplr->pid_ == pidIgnore) { continue; } Endpoint *endpoint = pplr->endpoint(); if (endpoint != NULL) { endpoint->xpump().Send(XMsgGameNetMessage::ToBuffer(pnm)); } } } void PlayerMgr::BroadcastPlayersUpdate() { // Allocate a properly sized PlayersUpdateNetMessage int cplrs = GetPlayerCount(); int cb = sizeof(PlayersUpdateNetMessage) + sizeof(PlayerRecord) * (cplrs - 1); PlayersUpdateNetMessage *ppunm = (PlayersUpdateNetMessage *)new byte[cb]; if (ppunm == NULL) return; // Fill the message in with all the juicy pplr data memset(ppunm, 0, cb); ppunm->nmid = knmidScPlayersUpdate; ppunm->cb = cb; ppunm->cplrr = cplrs; Player *pplr = players_; PlayerRecord *record = ppunm->aplrr; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (!(pplr->wf_ & kfPlrInUse)) { continue; } strncpyz(record->szName, pplr->name_, sizeof(record->szName)); record->side = pplr->side_; record->wf = 0; if (pplr->wf_ & kfPlrReady) record->wf |= kfPlrrReady; if (pplr->wf_ & kfPlrComputer) record->wf |= kfPlrrComputer; if (pplr->wf_ & kfPlrComputerOvermind) record->wf |= kfPlrrComputerOvermind; if (pplr->wf_ & kfPlrUnfulfilled) record->wf |= kfPlrrUnfulfilled; if (pplr->wf_ & kfPlrCreator) record->wf |= kfPlrrCreator; record->pid = pplr->pid_; record++; } // Send it off to all pplrs pplr = players_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (!(pplr->wf_ & kfPlrInUse)) { continue; } // Computer pplrs don't need to know about the other pplrs if (pplr->endpoint_ == NULL) { continue; } // Mark this Connection's PlayerRecord so it knows which // Player it is. PlayerRecord *record = ppunm->aplrr; for (int j = 0; j < cplrs; j++, record++) { if (pplr->pid() == record->pid) { record->wf |= kfPlrrLocal; } else { record->wf &= ~kfPlrrLocal; } } pplr->endpoint()->xpump().Send(XMsgGameNetMessage::ToBuffer(ppunm)); } delete ppunm; } void PlayerMgr::BroadcastPlayerDisconnect(Pid pid, int nReason) { Player *pplr = GetPlayerFromPid(pid); if (pplr == NULL) return; // If already notified of disconnection, do nothing else // This prevents knDisconnectReasonAbnormal after Kicked or Resigned. if (pplr->wf_ & kfPlrDisconnectBroadcasted) { return; } pplr->wf_ |= kfPlrDisconnectBroadcasted; // Tell the other clients about it PlayerDisconnectNetMessage pdnm; pdnm.pid = pid; pdnm.nReason = nReason; Broadcast(&pdnm); } bool PlayerMgr::HaveAllPlayersReachedUpdate(long cUpdates) { Player *pplr = players_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if ((pplr->wf_ & kfPlrInUse) && pplr->endpoint_ != NULL) { if (pplr->updates() < cUpdates) { return false; } } } return true; } Player *PlayerMgr::FindLaggingPlayer(long cUpdates) { // Update lag info for each player, return the most laggy long cUpdatesMostLagging = -999; Player *pplrMostLagging = NULL; Player *pplr = players_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (!(pplr->wf_ & kfPlrInUse) || pplr->endpoint_ == NULL) { continue; } int nLagState = pplr->UpdateLagState(cUpdates); if (nLagState == knLagGuilty || nLagState == knLagKill) { long cUpdatesLagging = cUpdates - pplr->updates(); if (cUpdatesLagging > cUpdatesMostLagging) { cUpdatesMostLagging = cUpdatesLagging; pplrMostLagging = pplr; } } } return pplrMostLagging; } bool PlayerMgr::AllPlayersLagging() { Player *pplr = players_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (!(pplr->wf_ & kfPlrInUse) || pplr->endpoint_ == NULL) { continue; } if (pplr->lag() != knLagKill) { return false; } } return true; } int PlayerMgr::GetPlayerCount() { int cplr = 0; Player *pplr = players_; for (int i = 0; i < kcPlayersMax; i++, pplr++) { if (pplr->wf_ & kfPlrInUse) { cplr++; } } return cplr; } bool PlayerMgr::CheckSyncError(Player *pplr, const UpdateResult& ur, long *pcUpdatesSync) { // Add to the end. It should always fit, but just in case, check for that. if (pplr->cur_ >= ARRAYSIZE(pplr->aur_)) { LOG() << "UpdateResult array filled up!"; return true; } pplr->aur_[pplr->cur_] = ur; pplr->cur_++; // Starting from the beginning, march forward through URs for all players. // If an UR has been received for all players, compare the hash. If there // is a mismatch, then a sync error has been discovered. If not, left // shift the UR arrays, and continue. long cUpdatesBlock = pplr->aur_[0].cUpdatesBlock; long hash = pplr->aur_[0].hash; bool syncerror = false; bool advance = true; while (true) { Player *pplrT = players_; for (int i = 0; i < kcPlayersMax; i++, pplrT++) { if (!(pplrT->wf_ & kfPlrInUse) || pplrT->endpoint_ == NULL) { continue; } if (pplrT->cur_ == 0) { advance = false; break; } if (pplrT->aur_[0].cUpdatesBlock != cUpdatesBlock) { advance = false; break; } if (server_.checksync() && pplrT->aur_[0].hash != hash) { advance = false; syncerror = true; break; } } if (!advance) { break; } // Update the caller's idea of where the players are *pcUpdatesSync = cUpdatesBlock; // All clients have reached this update result. Left shift. pplrT = players_; for (int i = 0; i < kcPlayersMax; i++, pplrT++) { if (!(pplrT->wf_ & kfPlrInUse) || pplrT->endpoint_ == NULL) { continue; } if (pplrT->cur_ == 0) { LOG() << "shouldn't happen"; // Shouldn't happen since this was checked already continue; } // Left shift by one entry memmove(&pplrT->aur_[0], &pplrT->aur_[1], ELEMENTSIZE(pplrT->aur_) * (pplrT->cur_ - 1)); pplrT->cur_--; } // Loop and try the next entry. If this is working correctly, it won't // find advancement. } return syncerror; } void PlayerMgr::BroadcastSyncError(const UpdateResult& ur) { // Prepare the net message SyncErrorNetMessage senm; Player *pplrT = players_; for (int i = 0; i < kcPlayersMax; i++, pplrT++) { if (pplrT->cur_ != 0) { senm.aur[i] = pplrT->aur_[0]; } else { memset(&senm.aur[i], 0xff, ELEMENTSIZE(senm.aur)); } } senm.urLastStraw = ur; pplrT = players_; for (int i = 0; i < kcPlayersMax; i++, pplrT++) { if (!(pplrT->wf_ & kfPlrInUse) || pplrT->endpoint_ == NULL) { continue; } pplrT->endpoint()->xpump().Send(XMsgGameNetMessage::ToBuffer(&senm)); } } } // namespace wi