/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2013 Warzone 2100 Project Warzone 2100 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Warzone 2100 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Multijoin.c * * Alex Lee, pumpkin studios, bath, * * Stuff to handle the comings and goings of players. */ #include #include "lib/framework/frame.h" #include "lib/framework/strres.h" #include "lib/framework/math_ext.h" #include "lib/gamelib/gtime.h" #include "lib/ivis_opengl/textdraw.h" #include "lib/netplay/netplay.h" #include "lib/sound/audio.h" #include "lib/sound/audio_id.h" #include "lib/script/script.h" #include "multijoin.h" #include "objmem.h" #include "statsdef.h" #include "droiddef.h" #include "game.h" #include "projectile.h" #include "droid.h" #include "map.h" #include "power.h" #include "game.h" // for loading maps #include "message.h" // for clearing game messages #include "order.h" #include "console.h" #include "orderdef.h" // for droid_order_data #include "hci.h" #include "component.h" #include "research.h" #include "wrappers.h" #include "intimage.h" #include "data.h" #include "scripttabs.h" #include "multimenu.h" #include "multiplay.h" #include "multirecv.h" #include "multiint.h" #include "multistat.h" #include "multigifts.h" #include "qtscript.h" #include "scriptcb.h" // //////////////////////////////////////////////////////////////////////////// // Local Functions static void resetMultiVisibility(UDWORD player); // //////////////////////////////////////////////////////////////////////////// // Wait For Players bool intDisplayMultiJoiningStatus(UBYTE joinCount) { UDWORD x,y,w,h; char sTmp[6]; w = RET_FORMWIDTH; h = RET_FORMHEIGHT; x = RET_X; y = RET_Y; // cameraToHome(selectedPlayer); // home the camera to the player. RenderWindowFrame(FRAME_NORMAL, x, y ,w, h); // draw a wee blu box. // display how far done.. iV_SetFont(font_regular); iV_DrawText(_("Players Still Joining"), x+(w/2)-(iV_GetTextWidth(_("Players Still Joining"))/2), y+(h/2)-8 ); unsigned playerCount = 0; // Calculate what NetPlay.playercount should be, which is apparently only non-zero for the host. for (unsigned player = 0; player < game.maxPlayers; ++player) { if (isHumanPlayer(player)) { ++playerCount; } } if (!playerCount) { return true; } iV_SetFont(font_large); sprintf(sTmp, "%d%%", PERCENT(playerCount - joinCount, playerCount)); iV_DrawText(sTmp ,x + (w / 2) - 10, y + (h / 2) + 10); iV_SetFont(font_small); int yStep = iV_GetTextLineSize(); int yPos = RET_Y - yStep*game.maxPlayers; static const std::string statusStrings[3] = {"☐ ", "☑ ", "☒ "}; for (unsigned player = 0; player < game.maxPlayers; ++player) { int status = -1; if (isHumanPlayer(player)) { status = ingame.JoiningInProgress[player]? 0 : 1; // Human player, still joining or joined. } else if (NetPlay.players[player].ai >= 0) { status = 2; // AI player (automatically joined). } if (status >= 0) { iV_DrawText((statusStrings[status] + getPlayerName(player)).c_str(), x + 5, yPos + yStep*NetPlay.players[player].position); } } return true; } ////////////////////////////////////////////////////////////////////////////// /* ** when a remote player leaves an arena game do this! ** ** @param player -- the one we need to clear ** @param quietly -- true means without any visible effects */ void clearPlayer(UDWORD player,bool quietly) { UDWORD i; STRUCTURE *psStruct,*psNext; debug(LOG_NET, "R.I.P. %s (%u). quietly is %s", getPlayerName(player), player, quietly ? "true":"false"); ingame.JoiningInProgress[player] = false; // if they never joined, reset the flag ingame.DataIntegrity[player] = false; (void)setPlayerName(player,""); //clear custom player name (will use default instead) for(i = 0;ipsNext; // FIXME: look why destroyStruct() doesn't put back the feature like removeStruct() does if(quietly || psStruct->pStructureType->type == REF_RESOURCE_EXTRACTOR) // don't show effects { removeStruct(psStruct, true); } else // show effects { destroyStruct(psStruct, gameTime); } psStruct = psNext; } return; } // Reset visibilty, so a new player can't see the old stuff!! static void resetMultiVisibility(UDWORD player) { UDWORD owned; DROID *pDroid; STRUCTURE *pStruct; for(owned = 0 ; owned psNext) { pDroid->visible[player] = false; } //structures for(pStruct= apsStructLists[owned];pStruct;pStruct=pStruct->psNext) { pStruct->visible[player] = false; } } } return; } static void sendPlayerLeft(uint32_t playerIndex) { ASSERT(NetPlay.isHost, "Only host should call this."); uint32_t forcedPlayerIndex = whosResponsible(playerIndex); NETQUEUE (*netQueueType)(unsigned) = forcedPlayerIndex != selectedPlayer? NETgameQueueForced : NETgameQueue; NETbeginEncode(netQueueType(forcedPlayerIndex), GAME_PLAYER_LEFT); NETuint32_t(&playerIndex); NETend(); } void recvPlayerLeft(NETQUEUE queue) { uint32_t playerIndex = 0; NETbeginDecode(queue, GAME_PLAYER_LEFT); NETuint32_t(&playerIndex); NETend(); if (whosResponsible(playerIndex) != queue.index) { return; } turnOffMultiMsg(true); clearPlayer(playerIndex, false); // don't do it quietly turnOffMultiMsg(false); NetPlay.players[playerIndex].allocated = false; char buf[256]; ssprintf(buf, _("%s has Left the Game"), getPlayerName(playerIndex)); addConsoleMessage(buf, DEFAULT_JUSTIFY, SYSTEM_MESSAGE); NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_DROPPED, playerIndex); debug(LOG_INFO, "** player %u has dropped, in-game!", playerIndex); } // //////////////////////////////////////////////////////////////////////////// // A remote player has left the game bool MultiPlayerLeave(UDWORD playerIndex) { if (playerIndex >= MAX_PLAYERS) { ASSERT(false, "Bad player number"); return false; } NETlogEntry("Player leaving game", SYNC_FLAG, playerIndex); debug(LOG_NET,"** Player %u [%s], has left the game at game time %u.", playerIndex, getPlayerName(playerIndex), gameTime); if (ingame.localJoiningInProgress) { clearPlayer(playerIndex, false); } else if (NetPlay.isHost) // If hosting, and game has started (not in pre-game lobby screen, that is). { sendPlayerLeft(playerIndex); } game.skDiff[playerIndex] = 0; if (NetPlay.players[playerIndex].wzFile.isSending) { char buf[256]; ssprintf(buf, _("File transfer has been aborted for %d.") , playerIndex); addConsoleMessage(buf, DEFAULT_JUSTIFY, SYSTEM_MESSAGE); debug(LOG_INFO, "=== File has been aborted for %d ===", playerIndex); NetPlay.players[playerIndex].wzFile.isSending = false; NetPlay.players[playerIndex].needFile = false; } if (widgGetFromID(psWScreen, IDRET_FORM)) { audio_QueueTrack(ID_CLAN_EXIT); } // fire script callback to reassign skirmish players. CBPlayerLeft = playerIndex; eventFireCallbackTrigger((TRIGGER_TYPE)CALL_PLAYERLEFT); triggerEventPlayerLeft(playerIndex); netPlayersUpdated = true; return true; } // //////////////////////////////////////////////////////////////////////////// // A Remote Player has joined the game. bool MultiPlayerJoin(UDWORD playerIndex) { if(widgGetFromID(psWScreen,IDRET_FORM)) // if ingame. { audio_QueueTrack( ID_CLAN_ENTER ); } if(widgGetFromID(psWScreen,MULTIOP_PLAYERS)) // if in multimenu. { if (!multiRequestUp && (bHosted || ingame.localJoiningInProgress)) { addPlayerBox(true); // update the player box. } } if(NetPlay.isHost) // host responsible for welcoming this player. { // if we've already received a request from this player don't reallocate. if (ingame.JoiningInProgress[playerIndex]) { return true; } ASSERT(NetPlay.playercount <= MAX_PLAYERS, "Too many players!"); // setup data for this player, then broadcast it to the other players. setupNewPlayer(playerIndex); // setup all the guff for that player. if (bHosted) { sendOptions(); } // if skirmish and game full, then kick... if (NetPlay.playercount > game.maxPlayers) { kickPlayer(playerIndex, "the game is already full.", ERROR_FULL); } // send everyone's stats to the new guy { int i; for (i = 0; i < MAX_PLAYERS; i++) { if (NetPlay.players[i].allocated) { setMultiStats(i, getMultiStats(i), false); } } } } return true; } bool sendDataCheck(void) { int i = 0; NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_DATA_CHECK); // only need to send to HOST for(i = 0; i < DATA_MAXDATA; i++) { NETuint32_t(&DataHash[i]); } NETend(); debug(LOG_NET, "sent hash to host"); return true; } bool recvDataCheck(NETQUEUE queue) { int i = 0; uint32_t player = queue.index; uint32_t tempBuffer[DATA_MAXDATA] = {0}; if(!NetPlay.isHost) // only host should act { ASSERT(false, "Host only routine detected for client!"); return false; } NETbeginDecode(queue, NET_DATA_CHECK); for(i = 0; i < DATA_MAXDATA; i++) { NETuint32_t(&tempBuffer[i]); } NETend(); if (player >= MAX_PLAYERS) // invalid player number. { debug(LOG_ERROR, "invalid player number (%u) detected.", player); return false; } if (whosResponsible(player) != queue.index) { HandleBadParam("NET_DATA_CHECK given incorrect params.", player, queue.index); return false; } debug(LOG_NET, "** Received NET_DATA_CHECK from player %u", player); if (NetPlay.isHost) { if (memcmp(DataHash, tempBuffer, sizeof(DataHash))) { char msg[256] = {'\0'}; for (i=0; i