/* 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 */ /* * MultiInt.c * * Alex Lee, 98. Pumpkin Studios, Bath. * Functions to display and handle the multiplayer interface screens, * along with connection and game options. */ #include "lib/framework/wzapp.h" #include "lib/framework/wzconfig.h" #include #include "lib/framework/frameresource.h" #include "lib/framework/file.h" #include "lib/framework/stdio_ext.h" /* Includes direct access to render library */ #include "lib/ivis_opengl/bitimage.h" #include "lib/ivis_opengl/pieblitfunc.h" #include "lib/ivis_opengl/piedef.h" #include "lib/ivis_opengl/piestate.h" #include "lib/ivis_opengl/pieclip.h" #include "lib/ivis_opengl/piemode.h" #include "lib/ivis_opengl/piepalette.h" #include "lib/ivis_opengl/screen.h" #include "lib/gamelib/gtime.h" #include "lib/netplay/netplay.h" #include "lib/script/script.h" #include "lib/widget/editbox.h" #include "lib/widget/button.h" #include "lib/widget/widget.h" #include "lib/widget/widgint.h" #include "lib/widget/label.h" #include "challenge.h" #include "main.h" #include "objects.h" #include "display.h"// pal stuff #include "display3d.h" #include "objmem.h" #include "gateway.h" #include "configuration.h" #include "intdisplay.h" #include "design.h" #include "hci.h" #include "power.h" #include "loadsave.h" // for blueboxes. #include "component.h" #include "map.h" #include "console.h" // chat box stuff #include "frend.h" #include "advvis.h" #include "frontend.h" #include "data.h" #include "keymap.h" #include "game.h" #include "warzoneconfig.h" #include "modding.h" #include "qtscript.h" #include "random.h" #include "multiplay.h" #include "multiint.h" #if defined(WZ_CC_MSVC) #include "multiint_moc.h" // this is generated on the pre-build event. #endif #include "multijoin.h" #include "multistat.h" #include "multirecv.h" #include "multimenu.h" #include "multilimit.h" #include "multigifts.h" #include "warzoneconfig.h" #include "init.h" #include "levels.h" #include "wrappers.h" #define MAP_PREVIEW_DISPLAY_TIME 2500 // number of milliseconds to show map in preview // //////////////////////////////////////////////////////////////////////////// // tertile dependant colors for map preview // C1 - Arizona type #define WZCOL_TERC1_CLIFF_LOW pal_Colour(0x68, 0x3C, 0x24) #define WZCOL_TERC1_CLIFF_HIGH pal_Colour(0xE8, 0x84, 0x5C) #define WZCOL_TERC1_WATER pal_Colour(0x3F, 0x68, 0x9A) #define WZCOL_TERC1_ROAD_LOW pal_Colour(0x24, 0x1F, 0x16) #define WZCOL_TERC1_ROAD_HIGH pal_Colour(0xB2, 0x9A, 0x66) #define WZCOL_TERC1_GROUND_LOW pal_Colour(0x24, 0x1F, 0x16) #define WZCOL_TERC1_GROUND_HIGH pal_Colour(0xCC, 0xB2, 0x80) // C2 - Urban type #define WZCOL_TERC2_CLIFF_LOW pal_Colour(0x3C, 0x3C, 0x3C) #define WZCOL_TERC2_CLIFF_HIGH pal_Colour(0x84, 0x84, 0x84) #define WZCOL_TERC2_WATER WZCOL_TERC1_WATER #define WZCOL_TERC2_ROAD_LOW pal_Colour(0x00, 0x00, 0x00) #define WZCOL_TERC2_ROAD_HIGH pal_Colour(0x24, 0x1F, 0x16) #define WZCOL_TERC2_GROUND_LOW pal_Colour(0x1F, 0x1F, 0x1F) #define WZCOL_TERC2_GROUND_HIGH pal_Colour(0xB2, 0xB2, 0xB2) // C3 - Rockies type #define WZCOL_TERC3_CLIFF_LOW pal_Colour(0x3C, 0x3C, 0x3C) #define WZCOL_TERC3_CLIFF_HIGH pal_Colour(0xFF, 0xFF, 0xFF) #define WZCOL_TERC3_WATER WZCOL_TERC1_WATER #define WZCOL_TERC3_ROAD_LOW pal_Colour(0x24, 0x1F, 0x16) #define WZCOL_TERC3_ROAD_HIGH pal_Colour(0x3D, 0x21, 0x0A) #define WZCOL_TERC3_GROUND_LOW pal_Colour(0x00, 0x1C, 0x0E) #define WZCOL_TERC3_GROUND_HIGH WZCOL_TERC3_CLIFF_HIGH static const unsigned gnImage[] = {IMAGE_GN_0, IMAGE_GN_1, IMAGE_GN_2, IMAGE_GN_3, IMAGE_GN_4, IMAGE_GN_5, IMAGE_GN_6, IMAGE_GN_7, IMAGE_GN_8, IMAGE_GN_9, IMAGE_GN_10, IMAGE_GN_11, IMAGE_GN_12, IMAGE_GN_13, IMAGE_GN_14, IMAGE_GN_15}; // //////////////////////////////////////////////////////////////////////////// // vars extern char MultiCustomMapsPath[PATH_MAX]; extern char MultiPlayersPath[PATH_MAX]; extern bool bSendingMap; // used to indicate we are sending a map bool bHosted = false; //we have set up a game char sPlayer[128]; // player name (to be used) static int colourChooserUp = -1; static int teamChooserUp = -1; static int aiChooserUp = -1; static int difficultyChooserUp = -1; static int positionChooserUp = -1; static bool SettingsUp = false; static UBYTE InitialProto = 0; static W_SCREEN *psConScreen; static SDWORD dwSelectedGame =0; //player[] and games[] indexes static UDWORD gameNumber; // index to games icons static bool safeSearch = false; // allow auto game finding. static bool disableLobbyRefresh = false; // if we allow lobby to be refreshed or not. static UDWORD hideTime=0; static bool EnablePasswordPrompt = false; // if we need the password prompt LOBBY_ERROR_TYPES LobbyError = ERROR_NOERROR; static char tooltipbuffer[MaxGames][256] = {{'\0'}}; static bool toggleFilter = true; // Used to show all games or only games that are of the same version /// end of globals. // //////////////////////////////////////////////////////////////////////////// // Function protos // widget functions static bool addMultiEditBox(UDWORD formid, UDWORD id, UDWORD x, UDWORD y, char const *tip, char const *tipres, UDWORD icon, UDWORD iconhi, UDWORD iconid); static void addBlueForm (UDWORD parent,UDWORD id, const char *txt,UDWORD x,UDWORD y,UDWORD w,UDWORD h); static void drawReadyButton(UDWORD player); // Drawing Functions static void displayChatEdit (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static void intDisplayFeBox (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static void displayRemoteGame (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static void displayPlayer (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static void displayPosition (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static void displayColour (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static void displayTeamChooser (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static void displayAi (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static void displayDifficulty (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static void displayMultiEditBox (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset); static Image getFrontHighlightImage(Image image); // find games static void addGames (void); static void removeGames (void); // password form functions static void hidePasswordForm(void); static void showPasswordForm(void); // Game option functions static void addGameOptions(); static void addChatBox (void); static void addConsoleBox(void); static void disableMultiButs (void); static void processMultiopWidgets(UDWORD); static void SendFireUp (void); static void decideWRF (void); static void closeColourChooser (void); static void closeTeamChooser (void); static void closePositionChooser (void); static void closeAiChooser (void); static void closeDifficultyChooser (void); static bool SendColourRequest (UBYTE player, UBYTE col); static bool SendPositionRequest (UBYTE player, UBYTE chosenPlayer); static bool safeToUseColour (UDWORD player,UDWORD col); static bool changeReadyStatus (UBYTE player, bool bReady); static void stopJoining(void); static int difficultyIcon(int difficulty); // //////////////////////////////////////////////////////////////////////////// // map previews.. static const char *difficultyList[] = { N_("Easy"), N_("Medium"), N_("Hard"), N_("Insane") }; static const int difficultyValue[] = { 1, 10, 15, 20 }; static struct { bool scavengers; bool alliances; bool teams; bool power; bool difficulty; bool ai; bool position; bool bases; } locked; struct AIDATA { AIDATA() : assigned(0) {} char name[MAX_LEN_AI_NAME]; char slo[MAX_LEN_AI_NAME]; char vlo[MAX_LEN_AI_NAME]; char js[MAX_LEN_AI_NAME]; char tip[255]; int assigned; ///< How many AIs have we assigned of this type }; static std::vector aidata; /// Player name overrides static char nameOverrides[MAX_PLAYERS][MAX_LEN_AI_NAME]; struct WzMultiButton : public W_BUTTON { WzMultiButton(WIDGET *parent) : W_BUTTON(parent) {} void display(int xOffset, int yOffset); Image imNormal; Image imDown; unsigned doHighlight; unsigned tc; }; const char *getAIName(int player) { if (NetPlay.players[player].ai >= 0) { return aidata[NetPlay.players[player].ai].name; } else { return ""; } } int getNextAIAssignment(const char *name) { int ai = matchAIbyName(name); int match = aidata[ai].assigned; for (int i = 0; i < game.maxPlayers; i++) { if (ai == NetPlay.players[i].ai) { if (match == 0) { aidata[ai].assigned++; debug(LOG_SAVE, "wzscript assigned to player %d", i); return i; // found right player } match--; // find next of this type } } return AI_NOT_FOUND; } void loadMultiScripts() { bool defaultRules = true; char aFileName[256]; char aPathName[256]; const char *ininame = challengeActive ? sRequestResult : aFileName; const char *path = challengeActive ? "challenges/" : aPathName; LEVEL_DATASET *psLevel = levFindDataSet(game.map, &game.hash); ASSERT_OR_RETURN(, psLevel, "No level found for %s", game.map); sstrcpy(aFileName, psLevel->apDataFiles[psLevel->game]); aFileName[strlen(aFileName) - 4] = '\0'; sstrcpy(aPathName, aFileName); sstrcat(aFileName, ".ini"); sstrcat(aPathName, "/"); // Reset assigned counter std::vector::iterator it; for (it = aidata.begin(); it < aidata.end(); it++) { (*it).assigned = 0; } // Load map scripts WzConfig ini(ininame, WzConfig::ReadOnly); if (PHYSFS_exists(ininame)) { debug(LOG_SAVE, "Loading map scripts"); ini.beginGroup("scripts"); if (ini.contains("extra")) { loadGlobalScript(path + ini.value("extra").toString()); } if (ini.contains("rules")) { loadGlobalScript(path + ini.value("rules").toString()); defaultRules = false; } ini.endGroup(); } // Load multiplayer rules resForceBaseDir("messages/"); resLoadFile("SMSG", "multiplay.txt"); if (defaultRules) { debug(LOG_SAVE, "Loading default rules"); loadGlobalScript("multiplay/skirmish/rules.js"); } // Backup data hashes, since AI and scavenger scripts aren't run on all clients. uint32_t oldHash1 = DataHash[DATA_SCRIPT]; uint32_t oldHash2 = DataHash[DATA_SCRIPTVAL]; // Load AI players resForceBaseDir("multiplay/skirmish/"); for (int i = 0; i < game.maxPlayers; i++) { if (NetPlay.players[i].ai < 0 && i == selectedPlayer) { NetPlay.players[i].ai = 0; // For autogames. } // The i == selectedPlayer hack is to enable autogames if (bMultiPlayer && game.type == SKIRMISH && (!NetPlay.players[i].allocated || i == selectedPlayer) && NetPlay.players[i].ai >= 0 && myResponsibility(i)) { if (PHYSFS_exists(ininame)) // challenge file may override AI { ini.beginGroup("player_" + QString::number(i + 1)); if (ini.contains("ai")) { QString val = ini.value("ai").toString(); ini.endGroup(); if (val.compare("null") == 0) { continue; // no AI } loadPlayerScript(val, i, NetPlay.players[i].difficulty); NetPlay.players[i].ai = AI_CUSTOM; continue; } ini.endGroup(); } if (aidata[NetPlay.players[i].ai].slo[0] != '\0') { debug(LOG_SAVE, "Loading wzscript AI for player %d", i); resLoadFile("SCRIPT", aidata[NetPlay.players[i].ai].slo); resLoadFile("SCRIPTVAL", aidata[NetPlay.players[i].ai].vlo); } // autogames are to be implemented differently for qtscript, do not start for human players yet if (!NetPlay.players[i].allocated && aidata[NetPlay.players[i].ai].js[0] != '\0') { debug(LOG_SAVE, "Loading javascript AI for player %d", i); loadPlayerScript(QString("multiplay/skirmish/") + aidata[NetPlay.players[i].ai].js, i, NetPlay.players[i].difficulty); } } } // Load scavengers if (game.scavengers && myResponsibility(scavengerPlayer())) { debug(LOG_SAVE, "Loading scavenger AI for player %d", scavengerPlayer()); loadPlayerScript("multiplay/script/scavfact.js", scavengerPlayer(), DIFFICULTY_EASY); } // Restore data hashes, since AI and scavenger scripts aren't run on all clients. DataHash[DATA_SCRIPT] = oldHash1; // Not all players load the same AI scripts. DataHash[DATA_SCRIPTVAL] = oldHash2; // Reset resource path, otherwise things break down the line resForceBaseDir(""); } static int guessMapTilesetType(LEVEL_DATASET *psLevel) { unsigned t = 0, c = 0; if (psLevel->psBaseData && psLevel->psBaseData->pName) { if (sscanf(psLevel->psBaseData->pName, "MULTI_CAM_%u", &c) != 1) { sscanf(psLevel->psBaseData->pName, "MULTI_T%u_C%u", &t, &c); } } switch (c) { case 1: return TILESET_ARIZONA; break; case 2: return TILESET_URBAN; break; case 3: return TILESET_ROCKIES; break; } debug(LOG_MAP, "Could not guess map tileset, using ARIZONA."); return TILESET_ARIZONA; } static void loadEmptyMapPreview() { // No map is available to preview, so improvise. char *imageData = (char *)malloc(BACKDROP_HACK_WIDTH * BACKDROP_HACK_HEIGHT * 3); memset(imageData, 0, BACKDROP_HACK_WIDTH * BACKDROP_HACK_HEIGHT * 3); //dunno about background color int const ex = 100, ey = 100, bx = 5, by = 8; for (unsigned n = 0; n < 125; ++n) { int sx = rand()%(ex - bx), sy = rand()%(ey - by); char col[3] = {char(rand()%256), char(rand()%256), char(rand()%256)}; for (unsigned y = 0; y < by; ++y) for (unsigned x = 0; x < bx; ++x) if (("\2\1\261\11\6"[x]>>y & 1) == 1) // ? memcpy(imageData + 3*(sx + x + BACKDROP_HACK_WIDTH*(sy + y)), col, 3); } // Slight hack to init array with a special value used to determine how many players on map Vector2i playerpos[MAX_PLAYERS]; memset(playerpos, 0x77, sizeof(playerpos)); screen_enableMapPreview(ex, ey, playerpos); screen_Upload(imageData); free(imageData); } /// Loads the entire map just to show a picture of it void loadMapPreview(bool hideInterface) { static char aFileName[256]; UDWORD fileSize; char *pFileData = NULL; LEVEL_DATASET *psLevel = NULL; PIELIGHT plCliffL, plCliffH, plWater, plRoadL, plRoadH, plGroundL, plGroundH; UDWORD x, y, height; UBYTE col; MAPTILE *psTile,*WTile; UDWORD oursize; Vector2i playerpos[MAX_PLAYERS]; // Will hold player positions char *ptr = NULL, *imageData = NULL; // absurd hack, since there is a problem with updating this crap piece of info, we're setting it to // true by default for now, like it used to be game.mapHasScavengers = true; // this is really the wrong place for it, but this is where it has to be if(psMapTiles) { mapShutdown(); } // load the terrain types psLevel = levFindDataSet(game.map, &game.hash); if (psLevel == NULL) { debug(LOG_INFO, "Could not find level dataset \"%s\" %s. We %s waiting for a download.", game.map, game.hash.toString().c_str(), NetPlay.players[selectedPlayer].needFile? "are":"aren't"); loadEmptyMapPreview(); return; } if (psLevel->realFileName == NULL) { debug(LOG_WZ, "Loading map preview: \"%s\" builtin t%d", psLevel->pName, psLevel->dataDir); } else { debug(LOG_WZ, "Loading map preview: \"%s\" in (%s)\"%s\" %s t%d", psLevel->pName, PHYSFS_getRealDir(psLevel->realFileName), psLevel->realFileName, psLevel->realFileHash.toString().c_str(), psLevel->dataDir); } rebuildSearchPath(psLevel->dataDir, false, psLevel->realFileName); sstrcpy(aFileName, psLevel->apDataFiles[psLevel->game]); aFileName[strlen(aFileName)-4] = '\0'; sstrcat(aFileName, "/ttypes.ttp"); pFileData = fileLoadBuffer; if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize)) { debug(LOG_ERROR, "Failed to load terrain types file: [%s]", aFileName); return; } if (pFileData) { if (!loadTerrainTypeMap(pFileData, fileSize)) { debug(LOG_ERROR, "Failed to load terrain types"); return; } } // load the map data ptr = strrchr(aFileName, '/'); ASSERT_OR_RETURN(, ptr, "this string was supposed to contain a /"); strcpy(ptr, "/game.map"); if (!mapLoad(aFileName, true)) { debug(LOG_ERROR, "Failed to load map"); return; } gwShutDown(); // set tileset colors switch (guessMapTilesetType(psLevel)) { case TILESET_ARIZONA: plCliffL = WZCOL_TERC1_CLIFF_LOW; plCliffH = WZCOL_TERC1_CLIFF_HIGH; plWater = WZCOL_TERC1_WATER; plRoadL = WZCOL_TERC1_ROAD_LOW; plRoadH = WZCOL_TERC1_ROAD_HIGH; plGroundL = WZCOL_TERC1_GROUND_LOW; plGroundH = WZCOL_TERC1_GROUND_HIGH; break; case TILESET_URBAN: plCliffL = WZCOL_TERC2_CLIFF_LOW; plCliffH = WZCOL_TERC2_CLIFF_HIGH; plWater = WZCOL_TERC2_WATER; plRoadL = WZCOL_TERC2_ROAD_LOW; plRoadH = WZCOL_TERC2_ROAD_HIGH; plGroundL = WZCOL_TERC2_GROUND_LOW; plGroundH = WZCOL_TERC2_GROUND_HIGH; break; case TILESET_ROCKIES: plCliffL = WZCOL_TERC3_CLIFF_LOW; plCliffH = WZCOL_TERC3_CLIFF_HIGH; plWater = WZCOL_TERC3_WATER; plRoadL = WZCOL_TERC3_ROAD_LOW; plRoadH = WZCOL_TERC3_ROAD_HIGH; plGroundL = WZCOL_TERC3_GROUND_LOW; plGroundH = WZCOL_TERC3_GROUND_HIGH; break; } oursize = sizeof(char) * BACKDROP_HACK_WIDTH * BACKDROP_HACK_HEIGHT; imageData = (char*)malloc(oursize * 3); // used for the texture if( !imageData ) { debug(LOG_FATAL,"Out of memory for texture!"); abort(); // should be a fatal error ? return; } ptr = imageData; memset(ptr, 0, sizeof(char) * BACKDROP_HACK_WIDTH * BACKDROP_HACK_HEIGHT * 3); //dunno about background color psTile = psMapTiles; for (y = 0; y < mapHeight; y++) { WTile = psTile; for (x = 0; x < mapWidth; x++) { char * const p = imageData + (3 * (y * BACKDROP_HACK_WIDTH + x)); height = WTile->height / ELEVATION_SCALE; col = height; switch (terrainType(WTile)) { case TER_CLIFFFACE: p[0] = plCliffL.byte.r + (plCliffH.byte.r-plCliffL.byte.r) * col / 256; p[1] = plCliffL.byte.g + (plCliffH.byte.g-plCliffL.byte.g) * col / 256; p[2] = plCliffL.byte.b + (plCliffH.byte.b-plCliffL.byte.b) * col / 256; break; case TER_WATER: p[0] = plWater.byte.r; p[1] = plWater.byte.g; p[2] = plWater.byte.b; break; case TER_ROAD: p[0] = plRoadL.byte.r + (plRoadH.byte.r-plRoadL.byte.r) * col / 256; p[1] = plRoadL.byte.g + (plRoadH.byte.g-plRoadL.byte.g) * col / 256; p[2] = plRoadL.byte.b + (plRoadH.byte.b-plRoadL.byte.b) * col / 256; break; default: p[0] = plGroundL.byte.r + (plGroundH.byte.r-plGroundL.byte.r) * col / 256; p[1] = plGroundL.byte.g + (plGroundH.byte.g-plGroundL.byte.g) * col / 256; p[2] = plGroundL.byte.b + (plGroundH.byte.b-plGroundL.byte.b) * col / 256; break; } WTile += 1; } psTile += mapWidth; } // Slight hack to init array with a special value used to determine how many players on map memset(playerpos,0x77,sizeof(playerpos)); // color our texture with clancolors @ correct position plotStructurePreview16(imageData, playerpos); screen_enableMapPreview(mapWidth, mapHeight, playerpos); screen_Upload(imageData); free(imageData); if (hideInterface) { hideTime = gameTime; } mapShutdown(); } // //////////////////////////////////////////////////////////////////////////// // helper func int matchAIbyName(const char *name) { std::vector::iterator it; int i = 0; if (name[0] == '\0') { return AI_CLOSED; } for (it = aidata.begin(); it < aidata.end(); it++, i++) { if (strncasecmp(name, (*it).name, MAX_LEN_AI_NAME) == 0) { return i; } } return AI_NOT_FOUND; } void readAIs() { char basepath[PATH_MAX]; const char *sSearchPath = "multiplay/skirmish/"; char **i, **files; aidata.clear(); strcpy(basepath, sSearchPath); files = PHYSFS_enumerateFiles(basepath); for (i = files; *i != NULL; ++i) { char path[PATH_MAX]; // See if this filename contains the extension we're looking for if (!strstr(*i, ".ai")) { continue; } sstrcpy(path, basepath); sstrcat(path, *i); WzConfig aiconf(path, WzConfig::ReadOnly); AIDATA ai; aiconf.beginGroup("AI"); sstrcpy(ai.name, aiconf.value("name", "error").toString().toUtf8().constData()); sstrcpy(ai.slo, aiconf.value("slo", "").toString().toUtf8().constData()); sstrcpy(ai.vlo, aiconf.value("vlo", "").toString().toUtf8().constData()); sstrcpy(ai.js, aiconf.value("js", "").toString().toUtf8().constData()); sstrcpy(ai.tip, aiconf.value("tip", "Click to choose this AI").toString().toUtf8().constData()); if (strcmp(ai.name, "Nexus") == 0) { std::vector::iterator it; it = aidata.begin(); aidata.insert(it, ai); } else { aidata.push_back(ai); } } PHYSFS_freeList(files); } //sets sWRFILE form game.map static void decideWRF(void) { // try and load it from the maps directory first, sstrcpy(aLevelName, MultiCustomMapsPath); sstrcat(aLevelName, game.map); sstrcat(aLevelName, ".wrf"); debug(LOG_WZ, "decideWRF: %s", aLevelName); //if the file exists in the downloaded maps dir then use that one instead. // FIXME: Try to incorporate this into physfs setup somehow for sane paths if ( !PHYSFS_exists(aLevelName) ) { sstrcpy(aLevelName, game.map); // doesn't exist, must be a predefined one. } } // //////////////////////////////////////////////////////////////////////////// // Connection Options Screen. static bool OptionsInet(void) //internet options { psConScreen = new W_SCREEN; W_FORMINIT sFormInit; //Connection Settings sFormInit.formID = 0; sFormInit.id = CON_SETTINGS; sFormInit.style = WFORM_PLAIN; sFormInit.x = CON_SETTINGSX; sFormInit.y = CON_SETTINGSY; sFormInit.width = CON_SETTINGSWIDTH; sFormInit.height = CON_SETTINGSHEIGHT; sFormInit.pDisplay = intDisplayFeBox; widgAddForm(psConScreen, &sFormInit); addMultiBut(psConScreen, CON_SETTINGS,CON_OK,CON_OKX,CON_OKY,MULTIOP_OKW,MULTIOP_OKH, _("Accept Settings"),IMAGE_OK,IMAGE_OK,true); addMultiBut(psConScreen, CON_SETTINGS,CON_IP_CANCEL,CON_OKX+MULTIOP_OKW+10,CON_OKY,MULTIOP_OKW,MULTIOP_OKH, _("Cancel"),IMAGE_NO,IMAGE_NO,true); //label. W_LABINIT sLabInit; sLabInit.formID = CON_SETTINGS; sLabInit.id = CON_SETTINGS_LABEL; sLabInit.style = WLAB_ALIGNCENTRE; sLabInit.x = 0; sLabInit.y = 10; sLabInit.width = CON_SETTINGSWIDTH; sLabInit.height = 20; sLabInit.pText = _("IP Address or Machine Name"); widgAddLabel(psConScreen, &sLabInit); W_EDBINIT sEdInit; // address sEdInit.formID = CON_SETTINGS; sEdInit.id = CON_IP; sEdInit.x = CON_IPX; sEdInit.y = CON_IPY; sEdInit.width = CON_NAMEBOXWIDTH; sEdInit.height = CON_NAMEBOXHEIGHT; sEdInit.pText = ""; //_("IP Address or Machine Name"); sEdInit.pBoxDisplay = intDisplayEditBox; if (!widgAddEditBox(psConScreen, &sEdInit)) { return false; } // auto click in the text box W_CONTEXT sContext; sContext.xOffset = 0; sContext.yOffset = 0; sContext.mx = 0; sContext.my = 0; widgGetFromID(psConScreen, CON_IP)->clicked(&sContext); SettingsUp = true; return true; } // //////////////////////////////////////////////////////////////////////////// // Draw the connections screen. bool startConnectionScreen(void) { addBackdrop(); //background addTopForm(); // logo addBottomForm(); SettingsUp = false; InitialProto = 0; safeSearch = false; // don't pretend we are running a network game. Really do it! NetPlay.bComms = true; // use network = true addSideText(FRONTEND_SIDETEXT, FRONTEND_SIDEX, FRONTEND_SIDEY,_("CONNECTION")); addMultiBut(psWScreen,FRONTEND_BOTFORM,CON_CANCEL,10,10,MULTIOP_OKW,MULTIOP_OKH, _("Return To Previous Screen"), IMAGE_RETURN, IMAGE_RETURN_HI, IMAGE_RETURN_HI); // goback buttpn levels addTextButton(CON_TYPESID_START+0,FRONTEND_POS2X,FRONTEND_POS2Y, _("Lobby"), WBUT_TXTCENTRE); addTextButton(CON_TYPESID_START+1,FRONTEND_POS3X,FRONTEND_POS3Y, _("IP"), WBUT_TXTCENTRE); return true; } void runConnectionScreen(void) { static char addr[128]; W_SCREEN *curScreen = SettingsUp? psConScreen : psWScreen; WidgetTriggers const &triggers = widgRunScreen(curScreen); unsigned id = triggers.empty()? 0 : triggers.front().widget->id; // Just use first click here, since the next click could be on another menu. switch(id) { case CON_CANCEL: //cancel changeTitleMode(MULTI); bMultiPlayer = false; bMultiMessages = false; break; case CON_TYPESID_START+0: // Lobby button if (LobbyError != ERROR_INVALID) { setLobbyError(ERROR_NOERROR); } changeTitleMode(GAMEFIND); break; case CON_TYPESID_START+1: // IP button OptionsInet(); break; case CON_OK: sstrcpy(addr, widgGetString(psConScreen, CON_IP)); if (addr[0] == '\0') { sstrcpy(addr, "127.0.0.1"); // Default to localhost. } if(SettingsUp == true) { delete psConScreen; psConScreen = NULL; SettingsUp = false; } joinGame(addr, 0); break; case CON_IP_CANCEL: if (SettingsUp == true) { delete psConScreen; psConScreen = NULL; SettingsUp = false; } break; } widgDisplayScreen(psWScreen); // show the widgets currently running if(SettingsUp == true) { widgDisplayScreen(psConScreen); // show the widgets currently running } if (CancelPressed()) { changeTitleMode(MULTI); } } // //////////////////////////////////////////////////////////////////////// // Lobby error reading LOBBY_ERROR_TYPES getLobbyError(void) { return LobbyError; } void setLobbyError (LOBBY_ERROR_TYPES error_type) { LobbyError = error_type; if (LobbyError <= ERROR_FULL) { disableLobbyRefresh = false; } else { disableLobbyRefresh = true; } } /** * Try connecting to the given host, show * the multiplayer screen or a error. */ bool joinGame(const char* host, uint32_t port) { PLAYERSTATS playerStats; if(ingame.localJoiningInProgress) { return false; } if (!NETjoinGame(host, port, (char*)sPlayer)) // join { switch (getLobbyError()) { case ERROR_HOSTDROPPED: setLobbyError(ERROR_NOERROR); break; case ERROR_WRONGPASSWORD: { // Change to GAMEFIND, screen with a password dialog. changeTitleMode(GAMEFIND); showPasswordForm(); WidgetTriggers const &triggers = widgRunScreen(psWScreen); unsigned id = triggers.empty()? 0 : triggers.front().widget->id; // Just use first click here, since the next click could be on another menu. if (id != CON_PASSWORD) // Run the current set of widgets { return false; } NETsetGamePassword(widgGetString(psWScreen, CON_PASSWORD)); break; } default: break; } // Hide a (maybe shown) password form. hidePasswordForm(); // Change to GAMEFIND, screen. changeTitleMode(GAMEFIND); // Shows the lobby error. addGames(); addConsoleBox(); return false; } ingame.localJoiningInProgress = true; loadMultiStats(sPlayer,&playerStats); setMultiStats(selectedPlayer, playerStats, false); setMultiStats(selectedPlayer, playerStats, true); changeTitleMode(MULTIOPTION); if (war_getMPcolour() >= 0) { SendColourRequest(selectedPlayer, war_getMPcolour()); } return true; } // //////////////////////////////////////////////////////////////////////////// // Game Chooser Screen. static void addGames(void) { int i, gcount = 0, added = 0; static const char *wrongVersionTip = "Your version of Warzone is incompatible with this game."; static const char *badModTip = "Your loaded mods are incompatible with this game. (Check mods/autoload/?)"; //count games to see if need two columns. for (i=0; i < MaxGames; i++) { if (NetPlay.games[i].desc.dwSize !=0) { gcount++; } } W_BUTINIT sButInit; sButInit.formID = FRONTEND_BOTFORM; sButInit.width = GAMES_GAMEWIDTH; sButInit.height = GAMES_GAMEHEIGHT; sButInit.pDisplay = displayRemoteGame; // we want the old games deleted, and only list games when we should if (getLobbyError() || !gcount) { for(i = 0; i gameTime) lastupdate = 0; if (gameTime-lastupdate > 6000 && !EnablePasswordPrompt) { lastupdate = gameTime; if(safeSearch) { if (!NETfindGame()) // find games synchronously { pie_LoadBackDrop(SCREEN_RANDOMBDROP); } } addGames(); //redraw list addConsoleBox(); } WidgetTriggers const &triggers = widgRunScreen(psWScreen); unsigned id = triggers.empty()? 0 : triggers.front().widget->id; // Just use first click here, since the next click could be on another menu. if(id == CON_CANCEL) // ok { changeTitleMode(PROTOCOL); } if(id == MULTIOP_REFRESH || id == MULTIOP_FILTER_TOGGLE) { if (id == MULTIOP_FILTER_TOGGLE) { toggleFilter = !toggleFilter; toggleFilter ? widgSetButtonState(psWScreen, MULTIOP_FILTER_TOGGLE, WBUT_CLICKLOCK) : widgSetButtonState(psWScreen, MULTIOP_FILTER_TOGGLE, 0); } else { widgSetButtonState(psWScreen, MULTIOP_FILTER_TOGGLE, 0); } ingame.localOptionsReceived = true; if (!NETfindGame()) // find games synchronously { pie_LoadBackDrop(SCREEN_RANDOMBDROP); } addGames(); //redraw list. addConsoleBox(); } if (id == CON_PASSWORD) { sstrcpy(game_password, widgGetString(psWScreen, CON_PASSWORD)); NETsetGamePassword(game_password); } // below is when they hit a game box to connect to--ideally this would be where // we would want a modal password entry box. if (id >= GAMES_GAMESTART && id <= GAMES_GAMEEND) { gameNumber = id - GAMES_GAMESTART; if (NetPlay.games[gameNumber].privateGame) { showPasswordForm(); } else { ingame.localOptionsReceived = false; // note, we are awaiting options sstrcpy(game.name, NetPlay.games[gameNumber].name); // store name joinGame(NetPlay.games[gameNumber].desc.host, 0); } } else if (id == CON_PASSWORDYES) { ingame.localOptionsReceived = false; // note, we are awaiting options sstrcpy(game.name, NetPlay.games[gameNumber].name); // store name joinGame(NetPlay.games[gameNumber].desc.host, 0); } else if (id == CON_PASSWORDNO) { hidePasswordForm(); } widgDisplayScreen(psWScreen); // show the widgets currently running if(safeSearch) { iV_SetFont(font_large); iV_DrawText(_("Searching"), D_W+260, D_H+460); } if (CancelPressed()) { changeTitleMode(PROTOCOL); } // console box handling iV_SetFont(font_small); if(widgGetFromID(psWScreen, MULTIOP_CONSOLEBOX)) { while(getNumberConsoleMessages() > getConsoleLineInfo()) { removeTopConsoleMessage(); } updateConsoleMessages(); } displayConsoleMessages(); } // This is what starts the lobby screen void startGameFind(void) { addBackdrop(); //background image WIDGET *parent = widgGetFromID(psWScreen, FRONTEND_BACKDROP); // draws the background of the games listed IntFormAnimated *botForm = new IntFormAnimated(parent); botForm->id = FRONTEND_BOTFORM; botForm->setGeometry(MULTIOP_OPTIONSX, MULTIOP_OPTIONSY, MULTIOP_CHATBOXW, 415); // FIXME: Add box at bottom for server messages addSideText(FRONTEND_SIDETEXT, MULTIOP_OPTIONSX-3, MULTIOP_OPTIONSY,_("GAMES")); // cancel addMultiBut(psWScreen,FRONTEND_BOTFORM,CON_CANCEL,10,5,MULTIOP_OKW,MULTIOP_OKH,_("Return To Previous Screen"), IMAGE_RETURN, IMAGE_RETURN_HI, IMAGE_RETURN_HI); //refresh addMultiBut(psWScreen, FRONTEND_BOTFORM, MULTIOP_REFRESH, MULTIOP_CHATBOXW-MULTIOP_OKW-5, 5, MULTIOP_OKW, MULTIOP_OKH, _("Refresh Games List"), IMAGE_RELOAD_HI, IMAGE_RELOAD, IMAGE_RELOAD); //filter toggle addMultiBut(psWScreen, FRONTEND_BOTFORM, MULTIOP_FILTER_TOGGLE, MULTIOP_CHATBOXW-MULTIOP_OKW-45, 5, MULTIOP_OKW, MULTIOP_OKH, _("Filter Games List"),IMAGE_FILTER, IMAGE_FILTER_ON, IMAGE_FILTER_ON); if (safeSearch || disableLobbyRefresh) { widgHide(psWScreen, MULTIOP_REFRESH); widgHide(psWScreen, MULTIOP_FILTER_TOGGLE); } if (!NETfindGame()) { pie_LoadBackDrop(SCREEN_RANDOMBDROP); } addGames(); // now add games. addConsoleBox(); displayConsoleMessages(); // Password stuff. Hidden by default. // draws the background of the password box IntFormAnimated *passwordForm = new IntFormAnimated(parent); passwordForm->id = FRONTEND_PASSWORDFORM; passwordForm->setGeometry(FRONTEND_BOTFORMX, 160, FRONTEND_TOPFORMW, FRONTEND_TOPFORMH - 40); // password label. W_LABEL *enterPasswordLabel = new W_LABEL(passwordForm); enterPasswordLabel->setTextAlignment(WLAB_ALIGNCENTRE); enterPasswordLabel->setGeometry(130, 0, 280, 40); enterPasswordLabel->setFont(font_large, WZCOL_FORM_TEXT); enterPasswordLabel->setString(_("Enter Password:")); // and finally draw the password entry box W_EDITBOX *passwordBox = new W_EDITBOX(passwordForm); passwordBox->id = CON_PASSWORD; passwordBox->setGeometry(130, 40, 280, 20); passwordBox->setBoxColours(WZCOL_MENU_BORDER, WZCOL_MENU_BORDER, WZCOL_MENU_BACKGROUND); W_BUTTON *buttonYes = new W_BUTTON(passwordForm); buttonYes->id = CON_PASSWORDYES; buttonYes->setImages(Image(FrontImages, IMAGE_OK), Image(FrontImages, IMAGE_OK), getFrontHighlightImage(Image(FrontImages, IMAGE_OK))); buttonYes->move(180, 65); buttonYes->setTip(_("OK")); W_BUTTON *buttonNo = new W_BUTTON(passwordForm); buttonNo->id = CON_PASSWORDNO; buttonNo->setImages(Image(FrontImages, IMAGE_NO), Image(FrontImages, IMAGE_NO), getFrontHighlightImage(Image(FrontImages, IMAGE_NO))); buttonNo->move(230, 65); buttonNo->setTip(_("Cancel")); passwordForm->hide(); EnablePasswordPrompt = false; } static void hidePasswordForm(void) { EnablePasswordPrompt = false; if (widgGetFromID(psWScreen, FRONTEND_PASSWORDFORM)) widgHide(psWScreen, FRONTEND_PASSWORDFORM); widgReveal(psWScreen, FRONTEND_SIDETEXT); widgReveal(psWScreen, FRONTEND_BOTFORM); widgReveal(psWScreen, CON_CANCEL); if (!safeSearch && (!disableLobbyRefresh)) { if (widgGetFromID(psWScreen, MULTIOP_REFRESH)) widgReveal(psWScreen, MULTIOP_REFRESH); if (widgGetFromID(psWScreen, MULTIOP_FILTER_TOGGLE)) widgReveal(psWScreen,MULTIOP_FILTER_TOGGLE); } addGames(); addConsoleBox(); } static void showPasswordForm(void) { W_CONTEXT sContext; EnablePasswordPrompt = true; widgHide(psWScreen, FRONTEND_SIDETEXT); widgHide(psWScreen, FRONTEND_BOTFORM); widgHide(psWScreen, CON_CANCEL); widgHide(psWScreen, MULTIOP_REFRESH); widgHide(psWScreen, MULTIOP_FILTER_TOGGLE); removeGames(); widgReveal(psWScreen, FRONTEND_PASSWORDFORM); // auto click in the password box sContext.xOffset = 0; sContext.yOffset = 0; sContext.mx = 0; sContext.my = 0; widgGetFromID(psWScreen, CON_PASSWORD)->clicked(&sContext); } // //////////////////////////////////////////////////////////////////////////// // Game Options Screen. // //////////////////////////////////////////////////////////////////////////// MultibuttonWidget::MultibuttonWidget(WIDGET* parent, int value) : W_FORM(parent) , label(nullptr) , mapper(new QSignalMapper(this)) , currentValue_(value) , disabled(false) , gap_(3) , lockCurrent(false) { connect(mapper, SIGNAL(mapped(int)), this, SLOT(choose(int))); } void MultibuttonWidget::display(int xOffset, int yOffset) { iV_ShadowBox(xOffset + x(), yOffset + y(), xOffset + x() + width() - 1, yOffset + y() + height() - 1, 0, WZCOL_MENU_BORDER, WZCOL_MENU_BORDER, WZCOL_MENU_BACKGROUND); } void MultibuttonWidget::geometryChanged() { int s = width() - gap_; for (std::vector >::const_reverse_iterator i = buttons.rbegin(); i != buttons.rend(); ++i) { i->first->move(s - i->first->width(), (height() - i->first->height())/2); s -= i->first->width() + gap_; } if (label != nullptr) { label->setGeometry(gap_, 0, s - gap_, height()); } } void MultibuttonWidget::setLabel(char const *text) { delete label; label = new W_LABEL(this); label->setString(text); geometryChanged(); } void MultibuttonWidget::addButton(int value, Image image, Image imageDown, char const *tip) { W_BUTTON *button = new W_BUTTON(this); button->setImages(image, imageDown, getFrontHighlightImage(image)); button->setTip(tip); button->setState(value == currentValue_ && lockCurrent? WBUT_LOCK : disabled? WBUT_DISABLE : 0); buttons.push_back(std::make_pair(button, value)); mapper->setMapping(button, value); connect(button, SIGNAL(clicked()), mapper, SLOT(map())); geometryChanged(); } void MultibuttonWidget::enable(bool enabled) { if (!enabled == disabled) { return; } disabled = !enabled; stateChanged(); } void MultibuttonWidget::setGap(int gap) { if (gap == gap_) { return; } gap_ = gap; geometryChanged(); } void MultibuttonWidget::choose(int value) { if (value == currentValue_ && lockCurrent) { return; } currentValue_ = value; stateChanged(); emit chosen(currentValue_); screenPointer->setReturn(this); } void MultibuttonWidget::stateChanged() { for (std::vector >::const_iterator i = buttons.begin(); i != buttons.end(); ++i) { i->first->setState(i->second == currentValue_ && lockCurrent? WBUT_LOCK : disabled? WBUT_DISABLE : 0); } } MultichoiceWidget::MultichoiceWidget(WIDGET *parent, int value) : MultibuttonWidget(parent, value) { lockCurrent = true; } static void addBlueForm(UDWORD parent,UDWORD id, const char *txt,UDWORD x,UDWORD y,UDWORD w,UDWORD h) { W_FORMINIT sFormInit; // draw options box. sFormInit.formID= parent; sFormInit.id = id; sFormInit.x =(UWORD) x; sFormInit.y =(UWORD) y; sFormInit.style = WFORM_PLAIN; sFormInit.width = (UWORD)w;//190; sFormInit.height= (UWORD)h;//27; sFormInit.pDisplay = intDisplayFeBox; widgAddForm(psWScreen, &sFormInit); if(strlen(txt)>0) { W_LABINIT sLabInit; sLabInit.formID = id; sLabInit.id = id+1; sLabInit.x = 3; sLabInit.y = 4; sLabInit.width = 80; sLabInit.height = 20; sLabInit.pText = txt; widgAddLabel(psWScreen, &sLabInit); } return; } struct LimitIcon { char const *stat; char const *desc; int icon; }; static const LimitIcon limitIcons[] = { {"A0LightFactory", N_("Tanks disabled!!"), IMAGE_NO_TANK}, {"A0CyborgFactory", N_("Cyborgs disabled."), IMAGE_NO_CYBORG}, {"A0VTolFactory1", N_("VTOLs disabled."), IMAGE_NO_VTOL}, {"A0Sat-linkCentre", N_("Satellite Uplink disabled."), IMAGE_NO_UPLINK}, {"A0LasSatCommand", N_("Laser Satellite disabled."), IMAGE_NO_LASSAT}, }; void updateLimitFlags() { unsigned i; unsigned flags = 0; if (!ingame.bHostSetup) { return; // The host works out the flags. } for (i = 0; i < ARRAY_SIZE(limitIcons); ++i) { int stat = getStructStatFromName(limitIcons[i].stat); bool disabled = asStructLimits[0] != NULL && stat >= 0 && asStructLimits[0][stat].limit == 0; flags |= disabled<id = MULTIOP_OPTIONS; optionsForm->setGeometry(MULTIOP_OPTIONSX, MULTIOP_OPTIONSY, MULTIOP_OPTIONSW, MULTIOP_OPTIONSH); addSideText(FRONTEND_SIDETEXT3, MULTIOP_OPTIONSX-3 , MULTIOP_OPTIONSY,_("OPTIONS")); // game name box if (!NetPlay.bComms) { addMultiEditBox(MULTIOP_OPTIONS, MULTIOP_GNAME, MCOL0, MROW2, _("Game Name"), challengeActive ? game.name : _("One-Player Skirmish"), IMAGE_EDIT_GAME, IMAGE_EDIT_GAME_HI, MULTIOP_GNAME_ICON); // disable for one-player skirmish widgSetButtonState(psWScreen, MULTIOP_GNAME, WEDBS_DISABLE); } else { addMultiEditBox(MULTIOP_OPTIONS, MULTIOP_GNAME, MCOL0, MROW2, _("Select Game Name"), game.name, IMAGE_EDIT_GAME, IMAGE_EDIT_GAME_HI, MULTIOP_GNAME_ICON); } widgSetButtonState(psWScreen, MULTIOP_GNAME_ICON, WBUT_DISABLE); // map chooser addBlueForm(MULTIOP_OPTIONS, MULTIOP_MAP, game.map, MCOL0, MROW3, MULTIOP_BLUEFORMW, 29); addMultiBut(psWScreen, MULTIOP_MAP, MULTIOP_MAP_ICON, MCOL4, 2, 20, MULTIOP_BUTH, _("Select Map"), IMAGE_EDIT_MAP, IMAGE_EDIT_MAP_HI, true); addMultiBut(psWScreen, MULTIOP_MAP, MULTIOP_MAP_MOD, MCOL3+11, 10, 12, 12, _("Map-Mod!"), IMAGE_LAMP_RED, IMAGE_LAMP_AMBER, false); if (!game.isMapMod) { widgHide(psWScreen, MULTIOP_MAP_MOD); } // disable for challenges if (challengeActive) { widgSetButtonState(psWScreen, MULTIOP_MAP_ICON, WBUT_DISABLE); } // password box if (ingame.bHostSetup && NetPlay.bComms) { addMultiEditBox(MULTIOP_OPTIONS, MULTIOP_PASSWORD_EDIT, MCOL0, MROW4, _("Click to set Password"), NetPlay.gamePassword, IMAGE_UNLOCK_BLUE, IMAGE_LOCK_BLUE, MULTIOP_PASSWORD_BUT); if (NetPlay.GamePassworded) { widgSetButtonState(psWScreen, MULTIOP_PASSWORD_BUT, WBUT_CLICKLOCK); widgSetButtonState(psWScreen, MULTIOP_PASSWORD_EDIT, WEDBS_DISABLE); } } //just display the game options. addMultiEditBox(MULTIOP_OPTIONS, MULTIOP_PNAME, MCOL0, MROW1, _("Select Player Name"), (char*) sPlayer, IMAGE_EDIT_PLAYER, IMAGE_EDIT_PLAYER_HI, MULTIOP_PNAME_ICON); ListWidget *optionsList = new ListWidget(optionsForm); optionsList->setChildSize(MULTIOP_BLUEFORMW, 29); optionsList->setChildSpacing(2, 2); optionsList->setGeometry(MCOL0, MROW5, MULTIOP_BLUEFORMW, optionsForm->height() - MROW5); MultichoiceWidget *scavengerChoice = new MultichoiceWidget(optionsList, game.scavengers); scavengerChoice->id = MULTIOP_GAMETYPE; scavengerChoice->setLabel(_("Scavengers")); if (game.mapHasScavengers) { scavengerChoice->addButton(true, Image(FrontImages, IMAGE_SCAVENGERS_ON), Image(FrontImages, IMAGE_SCAVENGERS_ON_HI), _("Scavengers")); } scavengerChoice->addButton(false, Image(FrontImages, IMAGE_SCAVENGERS_OFF), Image(FrontImages, IMAGE_SCAVENGERS_OFF_HI), _("No Scavengers")); scavengerChoice->enable(!locked.scavengers); optionsList->addWidgetToLayout(scavengerChoice); MultichoiceWidget *allianceChoice = new MultichoiceWidget(optionsList, game.alliance); allianceChoice->id = MULTIOP_ALLIANCES; allianceChoice->setLabel(_("Alliances")); allianceChoice->addButton(NO_ALLIANCES, Image(FrontImages, IMAGE_NOALLI), Image(FrontImages, IMAGE_NOALLI_HI), _("No Alliances")); allianceChoice->addButton(ALLIANCES, Image(FrontImages, IMAGE_ALLI), Image(FrontImages, IMAGE_ALLI_HI), _("Allow Alliances")); allianceChoice->addButton(ALLIANCES_UNSHARED, Image(FrontImages, IMAGE_ALLI_UNSHARED), Image(FrontImages, IMAGE_ALLI_UNSHARED_HI), _("Locked Teams, No Shared Research")); allianceChoice->addButton(ALLIANCES_TEAMS, Image(FrontImages, IMAGE_ALLI_TEAMS), Image(FrontImages, IMAGE_ALLI_TEAMS_HI), _("Locked Teams")); allianceChoice->enable(!locked.alliances); optionsList->addWidgetToLayout(allianceChoice); MultichoiceWidget *powerChoice = new MultichoiceWidget(optionsList, game.power); powerChoice->id = MULTIOP_POWER; powerChoice->setLabel(_("Power")); powerChoice->addButton(LEV_LOW, Image(FrontImages, IMAGE_POWLO), Image(FrontImages, IMAGE_POWLO_HI), _("Low Power Levels")); powerChoice->addButton(LEV_MED, Image(FrontImages, IMAGE_POWMED), Image(FrontImages, IMAGE_POWMED_HI), _("Medium Power Levels")); powerChoice->addButton(LEV_HI, Image(FrontImages, IMAGE_POWHI), Image(FrontImages, IMAGE_POWHI_HI), _("High Power Levels")); powerChoice->enable(!locked.power); optionsList->addWidgetToLayout(powerChoice); MultichoiceWidget *baseTypeChoice = new MultichoiceWidget(optionsList, game.base); baseTypeChoice->id = MULTIOP_BASETYPE; baseTypeChoice->setLabel(_("Base")); baseTypeChoice->addButton(CAMP_CLEAN, Image(FrontImages, IMAGE_NOBASE), Image(FrontImages, IMAGE_NOBASE_HI), _("Start with No Bases")); baseTypeChoice->addButton(CAMP_BASE, Image(FrontImages, IMAGE_SBASE), Image(FrontImages, IMAGE_SBASE_HI), _("Start with Bases")); baseTypeChoice->addButton(CAMP_WALLS, Image(FrontImages, IMAGE_LBASE), Image(FrontImages, IMAGE_LBASE_HI), _("Start with Advanced Bases")); baseTypeChoice->enable(!locked.bases); optionsList->addWidgetToLayout(baseTypeChoice); MultibuttonWidget *mapPreviewButton = new MultibuttonWidget(optionsList); mapPreviewButton->id = MULTIOP_MAP_PREVIEW; mapPreviewButton->setLabel(_("Map Preview")); mapPreviewButton->addButton(0, Image(FrontImages, IMAGE_FOG_OFF), Image(FrontImages, IMAGE_FOG_OFF_HI), _("Click to see Map")); optionsList->addWidgetToLayout(mapPreviewButton); if (ingame.bHostSetup) { MultibuttonWidget *structLimitsButton = new MultibuttonWidget(optionsList); structLimitsButton->id = MULTIOP_STRUCTLIMITS; structLimitsButton->setLabel(challengeActive ? _("Show Structure Limits") : _("Set Structure Limits")); structLimitsButton->addButton(0, Image(FrontImages, IMAGE_SLIM), Image(FrontImages, IMAGE_SLIM_HI), challengeActive ? _("Show Structure Limits") : _("Set Structure Limits")); optionsList->addWidgetToLayout(structLimitsButton); } if (ingame.bHostSetup && !bHosted && !challengeActive) { MultibuttonWidget *hostButton = new MultibuttonWidget(optionsList); hostButton->id = MULTIOP_HOST; hostButton->setLabel(_("Start Hosting Game")); hostButton->addButton(0, Image(FrontImages, IMAGE_HOST), Image(FrontImages, IMAGE_HOST_HI), _("Start Hosting Game")); optionsList->addWidgetToLayout(hostButton); } // cancel addMultiBut(psWScreen,MULTIOP_OPTIONS,CON_CANCEL, MULTIOP_CANCELX,MULTIOP_CANCELY, iV_GetImageWidth(FrontImages,IMAGE_RETURN), iV_GetImageHeight(FrontImages,IMAGE_RETURN), _("Return To Previous Screen"), IMAGE_RETURN, IMAGE_RETURN_HI, IMAGE_RETURN_HI); // Add any relevant factory disabled icons. updateLimitFlags(); int y = 2; bool skip = false; for (int i = 0; i < ARRAY_SIZE(limitIcons); ++i) { if ((ingame.flags & 1<id = MULTIOP_AI_FORM; aiForm->setGeometry(MULTIOP_PLAYERSX, MULTIOP_PLAYERSY, MULTIOP_PLAYERSW, MULTIOP_PLAYERSH); addSideText(FRONTEND_SIDETEXT2, MULTIOP_PLAYERSX - 3, MULTIOP_PLAYERSY, _("DIFFICULTY")); for (int i = 0; i < 4; i++) { W_BUTINIT sButInit; sButInit.formID = MULTIOP_AI_FORM; sButInit.id = MULTIOP_DIFFICULTY_CHOOSE_START + i; sButInit.x = 7; sButInit.y = (MULTIOP_PLAYERHEIGHT + 5) * i + 4; sButInit.width = MULTIOP_PLAYERWIDTH + 1; sButInit.height = MULTIOP_PLAYERHEIGHT; switch (i) { case 0: sButInit.pTip = _("Starts disadvantaged"); break; case 1: sButInit.pTip = _("Plays nice"); break; case 2: sButInit.pTip = _("No holds barred"); break; case 3: sButInit.pTip = _("Starts with advantages"); break; } sButInit.pDisplay = displayDifficulty; sButInit.UserData = i; widgAddButton(psWScreen, &sButInit); } } static void addAiChooser(int player) { closeColourChooser(); closeTeamChooser(); widgDelete(psWScreen, MULTIOP_AI_FORM); widgDelete(psWScreen, MULTIOP_PLAYERS); widgDelete(psWScreen, FRONTEND_SIDETEXT2); aiChooserUp = player; WIDGET *parent = widgGetFromID(psWScreen, FRONTEND_BACKDROP); IntFormAnimated *aiForm = new IntFormAnimated(parent, false); aiForm->id = MULTIOP_AI_FORM; aiForm->setGeometry(MULTIOP_PLAYERSX, MULTIOP_PLAYERSY, MULTIOP_PLAYERSW, MULTIOP_PLAYERSH); addSideText(FRONTEND_SIDETEXT2, MULTIOP_PLAYERSX - 3, MULTIOP_PLAYERSY, _("CHOOSE AI")); W_BUTINIT sButInit; sButInit.formID = MULTIOP_AI_FORM; sButInit.x = 7; sButInit.width = MULTIOP_PLAYERWIDTH + 1; // Try to fit as many as possible, just got to make sure text fits in the 'box'. // NOTE: Correct way would be to get the actual font size, render the text, and see what fits. if (aidata.size() > 8) { sButInit.height = MULTIOP_PLAYERHEIGHT - 7; } else { sButInit.height = MULTIOP_PLAYERHEIGHT; } sButInit.pDisplay = displayAi; // only need this button in (true) mp games int mpbutton = NetPlay.bComms ? 1 : 0; // cap AI's that are shown, since it looks a bit ugly. *FIXME* int capAIs = aidata.size(); if (aidata.size() > 9) { debug(LOG_INFO, "You have too many AI's loaded for the GUI to handle. Only the first 10 will be shown."); addConsoleMessage("You have too many AI's loaded for the GUI to handle. Only the first 10 will be shown.", DEFAULT_JUSTIFY, NOTIFY_MESSAGE); capAIs = 10; } // button height * how many AI + possible buttons (openclosed) int gap = MULTIOP_PLAYERSH - ((sButInit.height)* (capAIs + 1 + mpbutton)); int gapDiv = capAIs - 1; gap = std::min(gap, 5*gapDiv); // Open button if (mpbutton) { sButInit.id = MULTIOP_AI_OPEN; sButInit.pTip = _("Allow human players to join in this slot"); sButInit.UserData = (UDWORD)AI_OPEN; sButInit.y = 3; //Top most position widgAddButton(psWScreen, &sButInit); } // Closed button sButInit.pTip = _("Leave this slot unused"); sButInit.id = MULTIOP_AI_CLOSED; sButInit.UserData = (UDWORD)AI_CLOSED; if (mpbutton) { sButInit.y = sButInit.height; } else { sButInit.y = 0; //since we don't have the lone mpbutton, we can start at position 0 } widgAddButton(psWScreen, &sButInit); for (int i = 0; i < capAIs; i++) { sButInit.y = (sButInit.height*gapDiv + gap)*(i+1+mpbutton)/gapDiv; // +1 for 'closed', and possible +1 more for 'open' for MP games) sButInit.pTip = aidata[i].tip; sButInit.id = MULTIOP_AI_START + i; sButInit.UserData = i; widgAddButton(psWScreen, &sButInit); } } static void closeAiChooser() { widgDelete(psWScreen, MULTIOP_AI_FORM); widgDelete(psWScreen, FRONTEND_SIDETEXT2); aiChooserUp = -1; } static void closeDifficultyChooser() { widgDelete(psWScreen, MULTIOP_AI_FORM); widgDelete(psWScreen, FRONTEND_SIDETEXT2); difficultyChooserUp = -1; } static void closePositionChooser() { positionChooserUp = -1; } static int playerBoxHeight(int player) { int gap = MULTIOP_PLAYERSH - MULTIOP_TEAMSHEIGHT * game.maxPlayers; int gapDiv = game.maxPlayers - 1; gap = std::min(gap, 5*gapDiv); STATIC_ASSERT(MULTIOP_TEAMSHEIGHT == MULTIOP_PLAYERHEIGHT); // Why are these different defines? return (MULTIOP_TEAMSHEIGHT*gapDiv + gap)*NetPlay.players[player].position/gapDiv; } static void addPositionChooser(int player) { closeColourChooser(); closeTeamChooser(); closePositionChooser(); closeAiChooser(); closeDifficultyChooser(); positionChooserUp = player; addPlayerBox(true); } static void addColourChooser(UDWORD player) { UDWORD i; initChooser(player); // add form. addBlueForm(MULTIOP_PLAYERS,MULTIOP_COLCHOOSER_FORM,"", 8, playerBoxHeight(player), MULTIOP_ROW_WIDTH,MULTIOP_PLAYERHEIGHT); // add the flags int flagW = iV_GetImageWidth(FrontImages, IMAGE_PLAYERN); int flagH = iV_GetImageHeight(FrontImages, IMAGE_PLAYERN); int space = MULTIOP_ROW_WIDTH - 0 - flagW*MAX_PLAYERS_IN_GUI; int spaceDiv = MAX_PLAYERS_IN_GUI; space = std::min(space, 5*spaceDiv); for (i = 0; i < MAX_PLAYERS_IN_GUI; i++) { addMultiBut(psWScreen, MULTIOP_COLCHOOSER_FORM, MULTIOP_COLCHOOSER + getPlayerColour(i), i*(flagW*spaceDiv + space)/spaceDiv + 7, 4, // x, y flagW, flagH, // w, h getPlayerColourName(i), IMAGE_PLAYERN, IMAGE_PLAYERN_HI, IMAGE_PLAYERN_HI, getPlayerColour(i)); if( !safeToUseColour(selectedPlayer,i)) { widgSetButtonState(psWScreen, MULTIOP_COLCHOOSER + getPlayerColour(i), WBUT_DISABLE); } } colourChooserUp = player; } static void closeColourChooser(void) { colourChooserUp = -1; widgDelete(psWScreen,MULTIOP_COLCHOOSER_FORM); } static void changeTeam(UBYTE player, UBYTE team) { NetPlay.players[player].team = team; debug(LOG_WZ, "set %d as new team for player %d", team, player); NETBroadcastPlayerInfo(player); netPlayersUpdated = true; } static bool SendTeamRequest(UBYTE player, UBYTE chosenTeam) { if(NetPlay.isHost) // do or request the change. { changeTeam(player, chosenTeam); // do the change, remember only the host can do this to avoid confusion. } else { NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_TEAMREQUEST); NETuint8_t(&player); NETuint8_t(&chosenTeam); NETend(); } return true; } bool recvTeamRequest(NETQUEUE queue) { UBYTE player, team; if (!NetPlay.isHost || !bHosted) // Only host should act, and only if the game hasn't started yet. { ASSERT(false, "Host only routine detected for client!"); return true; } NETbeginDecode(queue, NET_TEAMREQUEST); NETuint8_t(&player); NETuint8_t(&team); NETend(); if (player >= MAX_PLAYERS || team >= MAX_PLAYERS) { debug(LOG_NET, "NET_TEAMREQUEST invalid, player %d team, %d", (int) player, (int) team); debug(LOG_ERROR, "Invalid NET_TEAMREQUEST from player %d: Tried to change player %d (team %d)", queue.index, (int)player, (int)team); return false; } if (whosResponsible(player) != queue.index) { HandleBadParam("NET_TEAMREQUEST given incorrect params.", player, queue.index); return false; } if (NetPlay.players[player].team != team) { resetReadyStatus(false); } debug(LOG_NET, "%s is now part of team: %d", NetPlay.players[player].name, (int) team); changeTeam(player, team); // we do this regardless, in case of sync issues return true; } static bool SendReadyRequest(UBYTE player, bool bReady) { if(NetPlay.isHost) // do or request the change. { return changeReadyStatus(player, bReady); } else { NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_READY_REQUEST); NETuint8_t(&player); NETbool(&bReady); NETend(); } return true; } bool recvReadyRequest(NETQUEUE queue) { UBYTE player; bool bReady; if (!NetPlay.isHost || !bHosted) // Only host should act, and only if the game hasn't started yet. { ASSERT(false, "Host only routine detected for client!"); return true; } NETbeginDecode(queue, NET_READY_REQUEST); NETuint8_t(&player); NETbool(&bReady); NETend(); if (player >= MAX_PLAYERS) { debug(LOG_ERROR, "Invalid NET_READY_REQUEST from player %d: player id = %d", queue.index, (int)player); return false; } if (whosResponsible(player) != queue.index) { HandleBadParam("NET_READY_REQUEST given incorrect params.", player, queue.index); return false; } // do not allow players to select 'ready' if we are sending a map too them! // TODO: make a new icon to show this state? if (NetPlay.players[player].wzFile.isSending) { return false; } return changeReadyStatus((UBYTE)player, bReady); } static bool changeReadyStatus(UBYTE player, bool bReady) { drawReadyButton(player); NetPlay.players[player].ready = bReady; NETBroadcastPlayerInfo(player); netPlayersUpdated = true; return true; } static bool changePosition(UBYTE player, UBYTE position) { int i; for (i = 0; i < MAX_PLAYERS; i++) { if (NetPlay.players[i].position == position) { debug(LOG_NET, "Swapping positions between players %d(%d) and %d(%d)", player, NetPlay.players[player].position, i, NetPlay.players[i].position); NetPlay.players[i].position = NetPlay.players[player].position; NetPlay.players[player].position = position; NETBroadcastTwoPlayerInfo(player, i); netPlayersUpdated = true; return true; } } debug(LOG_ERROR, "Failed to swap positions for player %d, position %d", (int)player, (int)position); if (player < game.maxPlayers && position < game.maxPlayers) { debug(LOG_NET, "corrupted positions: player (%u) new position (%u) old position (%d)", player, position, NetPlay.players[player].position); // Positions were corrupted. Attempt to fix. NetPlay.players[player].position = position; NETBroadcastPlayerInfo(player); netPlayersUpdated = true; return true; } return false; } bool changeColour(unsigned player, int col, bool isHost) { if (col < 0 || col >= MAX_PLAYERS_IN_GUI) { return true; } if (getPlayerColour(player) == col) { return true; // Nothing to do. } for (unsigned i = 0; i < MAX_PLAYERS; ++i) { if (getPlayerColour(i) == col) { if (!isHost && NetPlay.players[i].allocated) { return true; // May not swap. } debug(LOG_NET, "Swapping colours between players %d(%d) and %d(%d)", player, getPlayerColour(player), i, getPlayerColour(i)); setPlayerColour(i, getPlayerColour(player)); NetPlay.players[i].colour = getPlayerColour(player); setPlayerColour(player, col); NetPlay.players[player].colour = col; NETBroadcastTwoPlayerInfo(player, i); netPlayersUpdated = true; return true; } } debug(LOG_ERROR, "Failed to swap colours for player %d, colour %d", (int)player, (int)col); if (player < game.maxPlayers && col < MAX_PLAYERS) { // Colours were corrupted. Attempt to fix. debug(LOG_NET, "corrupted colours: player (%u) new colour (%u) old colour (%d)", player, col, NetPlay.players[player].colour); setPlayerColour(player, col); NetPlay.players[player].colour = col; NETBroadcastPlayerInfo(player); netPlayersUpdated = true; return true; } return false; } static bool SendColourRequest(UBYTE player, UBYTE col) { if(NetPlay.isHost) // do or request the change { return changeColour(player, col, true); } else { // clients tell the host which color they want NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_COLOURREQUEST); NETuint8_t(&player); NETuint8_t(&col); NETend(); } return true; } static bool SendPositionRequest(UBYTE player, UBYTE position) { if(NetPlay.isHost) // do or request the change { return changePosition(player, position); } else { debug(LOG_NET, "Requesting the host to change our position. From %d to %d", player, position); // clients tell the host which position they want NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_POSITIONREQUEST); NETuint8_t(&player); NETuint8_t(&position); NETend(); } return true; } bool recvColourRequest(NETQUEUE queue) { UBYTE player, col; if (!NetPlay.isHost || !bHosted) // Only host should act, and only if the game hasn't started yet. { ASSERT(false, "Host only routine detected for client!"); return true; } NETbeginDecode(queue, NET_COLOURREQUEST); NETuint8_t(&player); NETuint8_t(&col); NETend(); if (player >= MAX_PLAYERS) { debug(LOG_ERROR, "Invalid NET_COLOURREQUEST from player %d: Tried to change player %d to colour %d", queue.index, (int)player, (int)col); return false; } if (whosResponsible(player) != queue.index) { HandleBadParam("NET_COLOURREQUEST given incorrect params.", player, queue.index); return false; } resetReadyStatus(false); return changeColour(player, col, false); } bool recvPositionRequest(NETQUEUE queue) { UBYTE player, position; if (!NetPlay.isHost || !bHosted) // Only host should act, and only if the game hasn't started yet. { ASSERT(false, "Host only routine detected for client!"); return true; } NETbeginDecode(queue, NET_POSITIONREQUEST); NETuint8_t(&player); NETuint8_t(&position); NETend(); debug(LOG_NET, "Host received position request from player %d to %d", player, position); if (player >= MAX_PLAYERS || position >= MAX_PLAYERS) { debug(LOG_ERROR, "Invalid NET_POSITIONREQUEST from player %d: Tried to change player %d to %d", queue.index, (int)player, (int)position); return false; } if (whosResponsible(player) != queue.index) { HandleBadParam("NET_POSITIONREQUEST given incorrect params.", player, queue.index); return false; } resetReadyStatus(false); return changePosition(player, position); } // If so, return that team; if not, return -1; if there are no players, return team MAX_PLAYERS. int allPlayersOnSameTeam(int except) { int minTeam = MAX_PLAYERS, maxTeam = 0; for (int i = 0; i < game.maxPlayers; ++i) { if (i != except && (NetPlay.players[i].allocated || NetPlay.players[i].ai >= 0)) { minTeam = std::min(minTeam, NetPlay.players[i].team); maxTeam = std::max(maxTeam, NetPlay.players[i].team); } } if (minTeam == MAX_PLAYERS || minTeam == maxTeam) { return minTeam; // Players all on same team. } return -1; // Players not all on same team. } /* * Opens a menu for a player to choose a team * 'player' is a player id of the player who will get a new team assigned */ static void addTeamChooser(UDWORD player) { UDWORD i; int disallow = allPlayersOnSameTeam(player); debug(LOG_NET, "Opened team chooser for %d, current team: %d", player, NetPlay.players[player].team); initChooser(player); // add form. addBlueForm(MULTIOP_PLAYERS, MULTIOP_TEAMCHOOSER_FORM, "", 8, playerBoxHeight(player), MULTIOP_ROW_WIDTH, MULTIOP_TEAMSHEIGHT); int teamW = iV_GetImageWidth(FrontImages, IMAGE_TEAM0); int teamH = iV_GetImageHeight(FrontImages, IMAGE_TEAM0); int space = MULTIOP_ROW_WIDTH - 4 - teamW*(game.maxPlayers + 1); int spaceDiv = game.maxPlayers; space = std::min(space, 3*spaceDiv); // add the teams, skipping the one we CAN'T be on (if applicable) for (i = 0; i < game.maxPlayers; i++) { if (i != disallow) { addMultiBut(psWScreen, MULTIOP_TEAMCHOOSER_FORM, MULTIOP_TEAMCHOOSER + i, i * (teamW*spaceDiv + space)/spaceDiv + 3, 6, teamW, teamH, _("Team"), IMAGE_TEAM0 + i , IMAGE_TEAM0_HI + i, IMAGE_TEAM0_HI + i); } // may want to add some kind of 'can't do' icon instead of being blank? } // add a kick button if (player != selectedPlayer && NetPlay.bComms && NetPlay.isHost && NetPlay.players[player].allocated) { const int imgwidth = iV_GetImageWidth(FrontImages, IMAGE_NOJOIN); const int imgheight = iV_GetImageHeight(FrontImages, IMAGE_NOJOIN); addMultiBut(psWScreen, MULTIOP_TEAMCHOOSER_FORM, MULTIOP_TEAMCHOOSER_KICK, MULTIOP_ROW_WIDTH - imgwidth - 4, 8, imgwidth, imgheight, ("Kick player"), IMAGE_NOJOIN, IMAGE_NOJOIN, IMAGE_NOJOIN); } teamChooserUp = player; } /* * Closes Team Chooser dialog box, if there was any open */ static void closeTeamChooser(void) { teamChooserUp = -1; widgDelete(psWScreen,MULTIOP_TEAMCHOOSER_FORM); //only once! } static void drawReadyButton(UDWORD player) { int disallow = allPlayersOnSameTeam(-1); // delete 'ready' botton form widgDelete(psWScreen, MULTIOP_READY_FORM_ID + player); // add form to hold 'ready' botton addBlueForm(MULTIOP_PLAYERS,MULTIOP_READY_FORM_ID + player,"", 7 + MULTIOP_PLAYERWIDTH - MULTIOP_READY_WIDTH, playerBoxHeight(player), MULTIOP_READY_WIDTH,MULTIOP_READY_HEIGHT); WIDGET *parent = widgGetFromID(psWScreen, MULTIOP_READY_FORM_ID + player); if (!NetPlay.players[player].allocated && NetPlay.players[player].ai >= 0) { int icon = difficultyIcon(NetPlay.players[player].difficulty); addMultiBut(psWScreen, MULTIOP_READY_FORM_ID + player, MULTIOP_DIFFICULTY_INIT_START + player, 6, 4, MULTIOP_READY_WIDTH, MULTIOP_READY_HEIGHT, locked.difficulty ? _("Difficulty locked") : (NetPlay.isHost ? _("Click to change difficulty") : NULL), icon, icon, icon); return; } else if (!NetPlay.players[player].allocated) { return; // closed or open } if (disallow != -1) { return; } bool isMe = player == selectedPlayer; bool isReady = NetPlay.players[player].ready; char const *const toolTips[2][2] = {{_("Waiting for player"), _("Player is ready")}, {_("Click when ready"), _("Waiting for other players")}}; unsigned images[2][2] = {{IMAGE_CHECK_OFF, IMAGE_CHECK_ON}, {IMAGE_CHECK_OFF_HI, IMAGE_CHECK_ON_HI}}; // draw 'ready' button addMultiBut(psWScreen, MULTIOP_READY_FORM_ID + player, MULTIOP_READY_START + player, 3, 10, MULTIOP_READY_WIDTH, MULTIOP_READY_HEIGHT, toolTips[isMe][isReady], images[0][isReady], images[0][isReady], images[isMe][isReady]); W_LABEL *label = new W_LABEL(parent); label->id = MULTIOP_READY_START + MAX_PLAYERS + player; label->setGeometry(0, 0, MULTIOP_READY_WIDTH, 17); label->setTextAlignment(WLAB_ALIGNBOTTOM); label->setFont(font_small, WZCOL_TEXT_BRIGHT); label->setString(_("READY?")); } static bool canChooseTeamFor(int i) { return (i == selectedPlayer || NetPlay.isHost); } // //////////////////////////////////////////////////////////////////////////// // box for players. void addPlayerBox(bool players) { // if background isn't there, then return since were not ready to draw the box yet! if(widgGetFromID(psWScreen,FRONTEND_BACKDROP) == NULL) { return; } widgDelete(psWScreen,MULTIOP_PLAYERS); // del player window widgDelete(psWScreen,FRONTEND_SIDETEXT2); // del text too, if (aiChooserUp >= 0) { addAiChooser(aiChooserUp); return; } else if (difficultyChooserUp >= 0) { addDifficultyChooser(difficultyChooserUp); return; } // draw player window WIDGET *parent = widgGetFromID(psWScreen, FRONTEND_BACKDROP); IntFormAnimated *playersForm = new IntFormAnimated(parent, false); playersForm->id = MULTIOP_PLAYERS; playersForm->setGeometry(MULTIOP_PLAYERSX, MULTIOP_PLAYERSY, MULTIOP_PLAYERSW, MULTIOP_PLAYERSH); addSideText(FRONTEND_SIDETEXT2, MULTIOP_PLAYERSX-3, MULTIOP_PLAYERSY,_("PLAYERS")); if(players) { int team = -1; bool allOnSameTeam = true; for (int i = 0; i < game.maxPlayers; i++) { if (game.skDiff[i] || isHumanPlayer(i)) { int myTeam = alliancesSetTeamsBeforeGame(game.alliance)? NetPlay.players[i].team : i; if (team == -1) { team = myTeam; } else if (myTeam != team) { allOnSameTeam = false; break; // We just need to know if we have enough to start a game } } } for (int i = 0; i < game.maxPlayers; i++) { if (positionChooserUp >= 0 && positionChooserUp != i && (NetPlay.isHost || !isHumanPlayer(i))) { W_BUTINIT sButInit; sButInit.formID = MULTIOP_PLAYERS; sButInit.id = MULTIOP_PLAYER_START + i; sButInit.x = 7; sButInit.y = playerBoxHeight(i); sButInit.width = MULTIOP_PLAYERWIDTH + 1; sButInit.height = MULTIOP_PLAYERHEIGHT; sButInit.pTip = _("Click to change to this slot"); sButInit.pDisplay = displayPosition; sButInit.UserData = i; widgAddButton(psWScreen, &sButInit); continue; } else if (i == colourChooserUp) { addColourChooser(i); continue; } else if (i == teamChooserUp) { addTeamChooser(i); continue; } else if (ingame.localOptionsReceived) { //add team chooser W_BUTINIT sButInit; sButInit.formID = MULTIOP_PLAYERS; sButInit.id = MULTIOP_TEAMS_START+i; sButInit.x = 7; sButInit.y = playerBoxHeight(i); sButInit.width = MULTIOP_TEAMSWIDTH; sButInit.height = MULTIOP_TEAMSHEIGHT; if (canChooseTeamFor(i) && !locked.teams) { sButInit.pTip = _("Choose Team"); } else if (locked.teams) { sButInit.pTip = _("Teams locked"); } sButInit.pDisplay = displayTeamChooser; sButInit.UserData = i; if (teamChooserUp == i && colourChooserUp < 0) { addTeamChooser(i); } else if (alliancesSetTeamsBeforeGame(game.alliance)) { // only if not disabled and in locked teams mode widgAddButton(psWScreen, &sButInit); } } // draw player colour W_BUTINIT sColInit; sColInit.formID = MULTIOP_PLAYERS; sColInit.id = MULTIOP_COLOUR_START + i; sColInit.x = 7 + MULTIOP_TEAMSWIDTH; sColInit.y = playerBoxHeight(i); sColInit.width = MULTIOP_COLOUR_WIDTH; sColInit.height = MULTIOP_PLAYERHEIGHT; if (selectedPlayer == i || NetPlay.isHost) { sColInit.pTip = _("Click to change player colour"); } sColInit.pDisplay = displayColour; sColInit.UserData = i; widgAddButton(psWScreen, &sColInit); if (ingame.localOptionsReceived) { if (!allOnSameTeam) { drawReadyButton(i); } // draw player info box W_BUTINIT sButInit; sButInit.formID = MULTIOP_PLAYERS; sButInit.id = MULTIOP_PLAYER_START+i; sButInit.x = 7 + MULTIOP_TEAMSWIDTH + MULTIOP_COLOUR_WIDTH; sButInit.y = playerBoxHeight(i); sButInit.width = MULTIOP_PLAYERWIDTH - MULTIOP_TEAMSWIDTH - MULTIOP_READY_WIDTH - MULTIOP_COLOUR_WIDTH; sButInit.height = MULTIOP_PLAYERHEIGHT; if ((selectedPlayer == i || NetPlay.isHost) && NetPlay.players[i].allocated && !locked.position) { sButInit.pTip = _("Click to change player position"); } else if (!NetPlay.players[i].allocated && NetPlay.isHost && !locked.ai) { sButInit.pTip = _("Click to change AI"); } if (NetPlay.players[i].allocated && !getMultiStats(i).identity.empty()) { if (!sButInit.pTip.isEmpty()) { sButInit.pTip += "\n"; } EcKey::Key bytes = getMultiStats(i).identity.toBytes(EcKey::Public); sButInit.pTip += _("Player ID: "); if (!bytes.empty()) { sButInit.pTip += sha256Sum(&bytes[0], bytes.size()).toString().substr(0, 20).c_str(); } else { sButInit.pTip += _("(none)"); } } sButInit.pDisplay = displayPlayer; sButInit.UserData = i; widgAddButton(psWScreen, &sButInit); } } } } /* * Notify all players of host launching the game */ static void SendFireUp(void) { uint32_t randomSeed = rand(); // Pick a random random seed for the synchronised random number generator. NETbeginEncode(NETbroadcastQueue(), NET_FIREUP); NETuint32_t(&randomSeed); NETend(); printSearchPath(); gameSRand(randomSeed); // Set the seed for the synchronised random number generator. The clients will use the same seed. } // host kicks a player from a game. void kickPlayer(uint32_t player_id, const char *reason, LOBBY_ERROR_TYPES type) { // send a kick msg NETbeginEncode(NETbroadcastQueue(), NET_KICK); NETuint32_t(&player_id); NETstring( (char *) reason, MAX_KICK_REASON); NETenum(&type); NETend(); NETflush(); wzDelay(300); debug(LOG_NET, "Kicking player %u (%s).", (unsigned int)player_id, getPlayerName(player_id)); NETplayerKicked(player_id); } static void addChatBox(void) { if(widgGetFromID(psWScreen,FRONTEND_TOPFORM)) { widgDelete(psWScreen,FRONTEND_TOPFORM); } if(widgGetFromID(psWScreen,MULTIOP_CHATBOX)) { return; } WIDGET *parent = widgGetFromID(psWScreen, FRONTEND_BACKDROP); IntFormAnimated *chatBox = new IntFormAnimated(parent); chatBox->id = MULTIOP_CHATBOX; chatBox->setGeometry(MULTIOP_CHATBOXX, MULTIOP_CHATBOXY, MULTIOP_CHATBOXW, MULTIOP_CHATBOXH); addSideText(FRONTEND_SIDETEXT4,MULTIOP_CHATBOXX-3,MULTIOP_CHATBOXY,_("CHAT")); flushConsoleMessages(); // add the chatbox. initConsoleMessages(); enableConsoleDisplay(true); setConsoleBackdropStatus(false); setConsoleSizePos(MULTIOP_CHATBOXX+4+D_W, MULTIOP_CHATBOXY+14+D_H, MULTIOP_CHATBOXW-4); setConsolePermanence(true,true); setConsoleLineInfo(5); // use x lines on chat window W_EDBINIT sEdInit; // add the edit box sEdInit.formID = MULTIOP_CHATBOX; sEdInit.id = MULTIOP_CHATEDIT; sEdInit.x = MULTIOP_CHATEDITX; sEdInit.y = MULTIOP_CHATEDITY; sEdInit.width = MULTIOP_CHATEDITW; sEdInit.height = MULTIOP_CHATEDITH; sEdInit.pUserData = NULL; sEdInit.pBoxDisplay = displayChatEdit; widgAddEditBox(psWScreen, &sEdInit); if (*getModList()) { std::string modListMessage = _("Mod: "); modListMessage += getModList(); addConsoleMessage(modListMessage.c_str(), DEFAULT_JUSTIFY, SYSTEM_MESSAGE); if (NetPlay.bComms) { addConsoleMessage(_("All players need to have the same mods to join your game."),DEFAULT_JUSTIFY, SYSTEM_MESSAGE); } } return; } static void addConsoleBox(void) { if(widgGetFromID(psWScreen, FRONTEND_TOPFORM)) { widgDelete(psWScreen, FRONTEND_TOPFORM); } if(widgGetFromID(psWScreen, MULTIOP_CONSOLEBOX)) { return; } WIDGET *parent = widgGetFromID(psWScreen, FRONTEND_BACKDROP); IntFormAnimated *consoleBox = new IntFormAnimated(parent); consoleBox->id = MULTIOP_CONSOLEBOX; consoleBox->setGeometry(MULTIOP_CONSOLEBOXX, MULTIOP_CONSOLEBOXY, MULTIOP_CONSOLEBOXW, MULTIOP_CONSOLEBOXH); flushConsoleMessages(); // add the chatbox. initConsoleMessages(); enableConsoleDisplay(true); setConsoleBackdropStatus(false); setConsoleSizePos(MULTIOP_CONSOLEBOXX + 4 + D_W, MULTIOP_CONSOLEBOXY + 14 + D_H, MULTIOP_CONSOLEBOXW - 4); setConsolePermanence(true, true); setConsoleLineInfo(3); // use x lines on chat window addConsoleMessage(_("Connecting to the lobby server..."), DEFAULT_JUSTIFY, SYSTEM_MESSAGE); displayConsoleMessages(); return; } // //////////////////////////////////////////////////////////////////////////// static void disableMultiButs(void) { // edit box icons. widgSetButtonState(psWScreen, MULTIOP_GNAME_ICON, WBUT_DISABLE); widgSetButtonState(psWScreen, MULTIOP_MAP_ICON, WBUT_DISABLE); // edit boxes widgSetButtonState(psWScreen,MULTIOP_GNAME,WEDBS_DISABLE); if (!NetPlay.isHost) { ((MultichoiceWidget *)widgGetFromID(psWScreen, MULTIOP_GAMETYPE))->disable(); // Scavengers. ((MultichoiceWidget *)widgGetFromID(psWScreen, MULTIOP_BASETYPE))->disable(); // camapign subtype. ((MultichoiceWidget *)widgGetFromID(psWScreen, MULTIOP_POWER))->disable(); // pow levels ((MultichoiceWidget *)widgGetFromID(psWScreen, MULTIOP_ALLIANCES))->disable(); } } //////////////////////////////////////////////////////////////////////////// static void stopJoining(void) { dwSelectedGame = 0; reloadMPConfig(); // reload own settings debug(LOG_NET,"player %u (Host is %s) stopping.", selectedPlayer, NetPlay.isHost ? "true" : "false"); if(bHosted) // cancel a hosted game. { // annouce we are leaving... debug(LOG_NET, "Host is quitting game..."); NETbeginEncode(NETbroadcastQueue(), NET_HOST_DROPPED); NETend(); sendLeavingMsg(); // say goodbye NETclose(); // quit running game. bHosted = false; // stop host mode. widgDelete(psWScreen,FRONTEND_BACKDROP); // refresh options screen. startMultiOptions(false); ingame.localJoiningInProgress = false; return; } else if(ingame.localJoiningInProgress) // cancel a joined game. { debug(LOG_NET, "Canceling game..."); sendLeavingMsg(); // say goodbye NETclose(); // quit running game. // if we were in a midle of transfering a file, then close the file handle if (NetPlay.pMapFileHandle) { debug(LOG_NET, "closing aborted file"); // no need to delete it, we do size check on (map) file PHYSFS_close(NetPlay.pMapFileHandle); NetPlay.pMapFileHandle = NULL; } ingame.localJoiningInProgress = false; // reset local flags ingame.localOptionsReceived = false; if(!ingame.bHostSetup && NetPlay.isHost) // joining and host was transfered. { NetPlay.isHost = false; } changeTitleMode(MULTI); selectedPlayer = 0; realSelectedPlayer = 0; return; } debug(LOG_NET, "We have stopped joining."); changeTitleMode(lastTitleMode); selectedPlayer = 0; realSelectedPlayer = 0; if (ingame.bHostSetup) { pie_LoadBackDrop(SCREEN_RANDOMBDROP); } } static void loadMapSettings1() { char aFileName[256]; const char *ininame = challengeActive ? sRequestResult : aFileName; LEVEL_DATASET *psLevel = levFindDataSet(game.map, &game.hash); ASSERT_OR_RETURN(, psLevel, "No level found for %s", game.map); sstrcpy(aFileName, psLevel->apDataFiles[psLevel->game]); aFileName[strlen(aFileName) - 4] = '\0'; sstrcat(aFileName, ".ini"); WzConfig ini(ininame, WzConfig::ReadOnly); if (!PHYSFS_exists(ininame)) { return; } ini.beginGroup("locked"); // GUI lockdown locked.power = ini.value("power", challengeActive).toBool(); locked.alliances = ini.value("alliances", challengeActive).toBool(); locked.teams = ini.value("teams", challengeActive).toBool(); locked.difficulty = ini.value("difficulty", challengeActive).toBool(); locked.ai = ini.value("ai", challengeActive).toBool(); locked.scavengers = ini.value("scavengers", challengeActive).toBool(); locked.position = ini.value("position", challengeActive).toBool(); locked.bases = ini.value("bases", challengeActive).toBool(); ini.endGroup(); ini.beginGroup("defaults"); game.scavengers = ini.value("scavengers", game.scavengers).toBool(); game.base = ini.value("bases", game.base).toInt(); game.alliance = ini.value("alliances", game.alliance).toInt(); if (ini.contains("powerLevel")) { game.power = ini.value("powerLevel", game.power).toInt(); } ini.endGroup(); } static void loadMapSettings2() { char aFileName[256]; const char *ininame = challengeActive ? sRequestResult : aFileName; LEVEL_DATASET *psLevel = levFindDataSet(game.map, &game.hash); ASSERT_OR_RETURN(, psLevel, "No level found for %s", game.map); sstrcpy(aFileName, psLevel->apDataFiles[psLevel->game]); aFileName[strlen(aFileName) - 4] = '\0'; sstrcat(aFileName, ".ini"); WzConfig ini(ininame, WzConfig::ReadOnly); if (!PHYSFS_exists(ininame)) { return; } int offbyone = 0; // for compatibility with 3.1 and earlier challenges ini.beginGroup("challenge"); // backwards compatibility mode if (challengeActive && ini.value("version", 1).toInt() <= 1) { offbyone = 1; // old version, counts from 1 } ini.endGroup(); for (int i = 0; i < MAX_PLAYERS; i++) { ini.beginGroup("player_" + QString::number(i + offbyone)); if (ini.contains("team")) { NetPlay.players[i].team = ini.value("team").toInt() - offbyone; } else if (challengeActive) // team is a required key for challenges { NetPlay.players[i].ai = AI_CLOSED; } if (ini.contains("name")) { sstrcpy(nameOverrides[i], ini.value("name").toString().toUtf8().constData()); } if (ini.contains("position")) { changePosition(i, ini.value("position", NetPlay.players[i].position).toInt()); } if (ini.contains("difficulty")) { QString value = ini.value("difficulty", "Medium").toString(); for (int j = 0; j < ARRAY_SIZE(difficultyList); j++) { if (strcasecmp(difficultyList[j], value.toUtf8().constData()) == 0) { game.skDiff[i] = difficultyValue[j]; NetPlay.players[i].difficulty = j; } } } ini.endGroup(); } } /* * Process click events on the multiplayer/skirmish options screen * 'id' is id of the button that was pressed */ static void processMultiopWidgets(UDWORD id) { PLAYERSTATS playerStats; // host, who is setting up the game if((ingame.bHostSetup && !bHosted)) { switch(id) // Options buttons { case MULTIOP_GNAME: // we get this when nec. sstrcpy(game.name,widgGetString(psWScreen, MULTIOP_GNAME)); break; case MULTIOP_GNAME_ICON: break; case MULTIOP_MAP: widgDelete(psWScreen, MULTIOP_PLAYERS); widgDelete(psWScreen, FRONTEND_SIDETEXT2); // del text too, debug(LOG_WZ, "processMultiopWidgets[MULTIOP_MAP_ICON]: %s.wrf", MultiCustomMapsPath); addMultiRequest(MultiCustomMapsPath, ".wrf", MULTIOP_MAP, current_tech, 0, widgGetString(psWScreen, MULTIOP_MAP)); widgSetString(psWScreen, MULTIOP_MAP+1 ,game.map); //What a horrible hack! FIX ME! (See addBlueForm()) widgReveal(psWScreen, MULTIOP_MAP_MOD); break; case MULTIOP_MAP_ICON: widgDelete(psWScreen,MULTIOP_PLAYERS); widgDelete(psWScreen,FRONTEND_SIDETEXT2); // del text too, debug(LOG_WZ, "processMultiopWidgets[MULTIOP_MAP_ICON]: %s.wrf", MultiCustomMapsPath); addMultiRequest(MultiCustomMapsPath, ".wrf", MULTIOP_MAP, current_tech, current_numplayers); break; case MULTIOP_MAP_PREVIEW: loadMapPreview(true); break; } } // host who is setting up or has hosted if(ingame.bHostSetup)// || NetPlay.isHost) // FIXME Was: if(ingame.bHostSetup);{} ??? Note the ; ! { switch(id) { case MULTIOP_GAMETYPE: game.scavengers = ((MultichoiceWidget *)widgGetFromID(psWScreen, MULTIOP_GAMETYPE))->currentValue(); resetReadyStatus(false); if(bHosted) { sendOptions(); } break; case MULTIOP_BASETYPE: game.base = ((MultichoiceWidget *)widgGetFromID(psWScreen, MULTIOP_BASETYPE))->currentValue(); addGameOptions(); resetReadyStatus(false); if(bHosted) { sendOptions(); disableMultiButs(); } break; case MULTIOP_ALLIANCES: game.alliance = ((MultichoiceWidget *)widgGetFromID(psWScreen, MULTIOP_ALLIANCES))->currentValue(); resetReadyStatus(false); netPlayersUpdated = true; if(bHosted) { sendOptions(); } break; case MULTIOP_POWER: // set power level game.power = ((MultichoiceWidget *)widgGetFromID(psWScreen, MULTIOP_POWER))->currentValue(); resetReadyStatus(false); if(bHosted) { sendOptions(); } break; case MULTIOP_PASSWORD_EDIT: { unsigned result = widgGetButtonState(psWScreen, MULTIOP_PASSWORD_BUT); if (result != 0) { break; } } // Continue, do not break, since we just set a password. case MULTIOP_PASSWORD_BUT: { char buf[255]; bool willSet = widgGetButtonState(psWScreen, MULTIOP_PASSWORD_BUT) == 0; debug(LOG_NET, "Password button hit, %d", (int)willSet); widgSetButtonState(psWScreen, MULTIOP_PASSWORD_BUT, willSet? WBUT_CLICKLOCK : 0); widgSetButtonState(psWScreen, MULTIOP_PASSWORD_EDIT, willSet? WEDBS_DISABLE : 0); if (willSet) { char game_password[64]; sstrcpy(game_password, widgGetString(psWScreen, MULTIOP_PASSWORD_EDIT)); NETsetGamePassword(game_password); // say password is now required to join games? ssprintf(buf, _("*** password [%s] is now required! ***"), NetPlay.gamePassword); addConsoleMessage(buf, DEFAULT_JUSTIFY, NOTIFY_MESSAGE); } else { NETresetGamePassword(); ssprintf(buf, _("*** password is NOT required! ***")); addConsoleMessage(buf, DEFAULT_JUSTIFY, NOTIFY_MESSAGE); } NETGameLocked(willSet); } break; } } // these work all the time. switch(id) { case MULTIOP_MAP_MOD: char buf[256]; ssprintf(buf, _("This is a map-mod, it can change your playing experience!")); addConsoleMessage(buf, DEFAULT_JUSTIFY, SYSTEM_MESSAGE); break; case MULTIOP_STRUCTLIMITS: changeTitleMode(MULTILIMIT); break; case MULTIOP_PNAME: sstrcpy(sPlayer,widgGetString(psWScreen, MULTIOP_PNAME)); // chop to 15 chars.. while(strlen(sPlayer) > 15) // clip name. { sPlayer[strlen(sPlayer)-1]='\0'; } // update string. widgSetString(psWScreen, MULTIOP_PNAME,sPlayer); removeWildcards((char*)sPlayer); printConsoleNameChange(NetPlay.players[selectedPlayer].name, sPlayer); NETchangePlayerName(selectedPlayer, (char*)sPlayer); // update if joined. loadMultiStats((char*)sPlayer,&playerStats); setMultiStats(selectedPlayer,playerStats,false); setMultiStats(selectedPlayer,playerStats,true); netPlayersUpdated = true; break; case MULTIOP_PNAME_ICON: widgDelete(psWScreen,MULTIOP_PLAYERS); widgDelete(psWScreen,FRONTEND_SIDETEXT2); // del text too, addMultiRequest(MultiPlayersPath, ".sta", MULTIOP_PNAME, 0, 0); break; case MULTIOP_HOST: debug(LOG_NET, "MULTIOP_HOST enabled"); sstrcpy(game.name, widgGetString(psWScreen, MULTIOP_GNAME)); // game name sstrcpy(sPlayer, widgGetString(psWScreen, MULTIOP_PNAME)); // pname resetReadyStatus(false); resetDataHash(); removeWildcards((char*)sPlayer); if (!hostCampaign((char*)game.name,(char*)sPlayer)) { addConsoleMessage(_("Sorry! Failed to host the game."), DEFAULT_JUSTIFY, SYSTEM_MESSAGE); break; } bHosted = true; loadMapSettings2(); widgDelete(psWScreen, MULTIOP_REFRESH); widgDelete(psWScreen, MULTIOP_HOST); widgDelete(psWScreen, MULTIOP_FILTER_TOGGLE); ingame.localOptionsReceived = true; addGameOptions(); // update game options box. addChatBox(); disableMultiButs(); addPlayerBox(!ingame.bHostSetup || bHosted); //to make sure host can't skip player selection menu (sets game.skdiff to UBYTE_MAX for humans) break; case MULTIOP_CHATEDIT: // don't send empty lines to other players in the lobby if(!strcmp(widgGetString(psWScreen, MULTIOP_CHATEDIT), "")) break; sendTextMessage(widgGetString(psWScreen, MULTIOP_CHATEDIT),true); //send widgSetString(psWScreen, MULTIOP_CHATEDIT, ""); // clear box break; case CON_CANCEL: pie_LoadBackDrop(SCREEN_RANDOMBDROP); if (!challengeActive) { NETGameLocked(false); // reset status on a cancel stopJoining(); } else { NETclose(); bHosted = false; ingame.localJoiningInProgress = false; changeTitleMode(SINGLE); addChallenges(); } break; case MULTIOP_MAP_PREVIEW: loadMapPreview(true); break; case MULTIOP_AI_CLOSED: NetPlay.players[aiChooserUp].ai = AI_CLOSED; break; case MULTIOP_AI_OPEN: NetPlay.players[aiChooserUp].ai = AI_OPEN; break; default: break; } if (id == MULTIOP_AI_CLOSED || id == MULTIOP_AI_OPEN) // common code { game.skDiff[aiChooserUp] = 0; // disable AI for this slot NETBroadcastPlayerInfo(aiChooserUp); closeAiChooser(); addPlayerBox(!ingame.bHostSetup || bHosted); } if (id >= MULTIOP_DIFFICULTY_CHOOSE_START && id <= MULTIOP_DIFFICULTY_CHOOSE_END && difficultyChooserUp != -1) { int idx = id - MULTIOP_DIFFICULTY_CHOOSE_START; NetPlay.players[difficultyChooserUp].difficulty = idx; game.skDiff[difficultyChooserUp] = difficultyValue[idx]; NETBroadcastPlayerInfo(difficultyChooserUp); closeDifficultyChooser(); addPlayerBox(!ingame.bHostSetup || bHosted); } if (id >= MULTIOP_AI_START && id <= MULTIOP_AI_END && aiChooserUp != -1) { int idx = id - MULTIOP_AI_START; NetPlay.players[aiChooserUp].ai = idx; sstrcpy(NetPlay.players[aiChooserUp].name, getAIName(aiChooserUp)); game.skDiff[aiChooserUp] = difficultyValue[NetPlay.players[aiChooserUp].difficulty]; // set difficulty, in case re-enabled NETBroadcastPlayerInfo(aiChooserUp); closeAiChooser(); addPlayerBox(!ingame.bHostSetup || bHosted); } STATIC_ASSERT(MULTIOP_TEAMS_START + MAX_PLAYERS - 1 <= MULTIOP_TEAMS_END); if (id >= MULTIOP_TEAMS_START && id <= MULTIOP_TEAMS_START + MAX_PLAYERS - 1 && !locked.teams) // Clicked on a team chooser { int clickedMenuID = id - MULTIOP_TEAMS_START; //make sure team chooser is not up before adding new one for another player if (teamChooserUp < 0 && colourChooserUp < 0 && canChooseTeamFor(clickedMenuID) && positionChooserUp < 0) { addTeamChooser(clickedMenuID); } } //clicked on a team STATIC_ASSERT(MULTIOP_TEAMCHOOSER + MAX_PLAYERS - 1 <= MULTIOP_TEAMCHOOSER_END); if(id >= MULTIOP_TEAMCHOOSER && id <= MULTIOP_TEAMCHOOSER + MAX_PLAYERS - 1) { ASSERT(teamChooserUp >= 0, "teamChooserUp < 0"); ASSERT(id >= MULTIOP_TEAMCHOOSER && (id - MULTIOP_TEAMCHOOSER) < MAX_PLAYERS, "processMultiopWidgets: wrong id - MULTIOP_TEAMCHOOSER value (%d)", id - MULTIOP_TEAMCHOOSER); resetReadyStatus(false); // will reset only locally if not a host SendTeamRequest(teamChooserUp, (UBYTE)id - MULTIOP_TEAMCHOOSER); debug(LOG_WZ, "Changed team for player %d to %d", teamChooserUp, NetPlay.players[teamChooserUp].team); closeTeamChooser(); addPlayerBox( !ingame.bHostSetup || bHosted); //restore initial options screen } // 'ready' button if(id >= MULTIOP_READY_START && id <= MULTIOP_READY_END) // clicked on a player { UBYTE player = (UBYTE)(id-MULTIOP_READY_START); if (player == selectedPlayer && teamChooserUp < 0 && positionChooserUp < 0) { SendReadyRequest(selectedPlayer, !NetPlay.players[player].ready); // if hosting try to start the game if everyone is ready if(NetPlay.isHost && multiplayPlayersReady(false)) { startMultiplayerGame(); // reset flag in case people dropped/quit on join screen NETsetPlayerConnectionStatus(CONNECTIONSTATUS_NORMAL, NET_ALL_PLAYERS); } } if (NetPlay.isHost && !alliancesSetTeamsBeforeGame(game.alliance)) { if(mouseDown(MOUSE_RMB) && player != NetPlay.hostPlayer) // both buttons.... { char *msg; sasprintf(&msg, _("The host has kicked %s from the game!"), getPlayerName(player)); sendTextMessage(msg, true); kickPlayer(player, "you are unwanted by the host.", ERROR_KICKED); resetReadyStatus(true); //reset and send notification to all clients } } } if (id >= MULTIOP_COLOUR_START && id <= MULTIOP_COLOUR_END && (id - MULTIOP_COLOUR_START == selectedPlayer || NetPlay.isHost)) { if (teamChooserUp < 0 && positionChooserUp < 0 && colourChooserUp < 0) // not choosing something else already { addColourChooser(id - MULTIOP_COLOUR_START); } } // clicked on a player STATIC_ASSERT(MULTIOP_PLAYER_START + MAX_PLAYERS - 1 <= MULTIOP_PLAYER_END); if (id >= MULTIOP_PLAYER_START && id <= MULTIOP_PLAYER_START + MAX_PLAYERS - 1 && !locked.position && (id - MULTIOP_PLAYER_START == selectedPlayer || NetPlay.isHost || (positionChooserUp >= 0 && !isHumanPlayer(id - MULTIOP_PLAYER_START)))) { int player = id - MULTIOP_PLAYER_START; if ((player == selectedPlayer || (NetPlay.players[player].allocated && NetPlay.isHost)) && positionChooserUp < 0 && teamChooserUp < 0 && colourChooserUp < 0) { addPositionChooser(player); } else if (positionChooserUp == player) { closePositionChooser(); // changed his mind addPlayerBox(!ingame.bHostSetup || bHosted); } else if (positionChooserUp >= 0) { // Switch player resetReadyStatus(false); // will reset only locally if not a host SendPositionRequest(positionChooserUp, NetPlay.players[player].position); closePositionChooser(); addPlayerBox(!ingame.bHostSetup || bHosted); } else if (!NetPlay.players[player].allocated && !locked.ai && NetPlay.isHost && positionChooserUp < 0 && teamChooserUp < 0 && colourChooserUp < 0) { addAiChooser(player); } } if (id >= MULTIOP_DIFFICULTY_INIT_START && id <= MULTIOP_DIFFICULTY_INIT_END && !locked.difficulty && NetPlay.isHost && positionChooserUp < 0 && teamChooserUp < 0 && colourChooserUp < 0) { addDifficultyChooser(id - MULTIOP_DIFFICULTY_INIT_START); addPlayerBox(!ingame.bHostSetup || bHosted); } STATIC_ASSERT(MULTIOP_COLCHOOSER + MAX_PLAYERS - 1 <= MULTIOP_COLCHOOSER_END); if (id >= MULTIOP_COLCHOOSER && id < MULTIOP_COLCHOOSER + MAX_PLAYERS - 1) // chose a new colour. { resetReadyStatus(false); // will reset only locally if not a host SendColourRequest(colourChooserUp, id - MULTIOP_COLCHOOSER); closeColourChooser(); addPlayerBox(!ingame.bHostSetup || bHosted); } if (id == MULTIOP_TEAMCHOOSER_KICK) { char *msg; sasprintf(&msg, _("The host has kicked %s from the game!"), getPlayerName(teamChooserUp)); kickPlayer(teamChooserUp, "you are unwanted by the host.", ERROR_KICKED); sendTextMessage(msg, true); resetReadyStatus(true); //reset and send notification to all clients closeTeamChooser(); } } /* Start a multiplayer or skirmish game */ void startMultiplayerGame(void) { if (!bHosted) { debug(LOG_ERROR, "Multiple start requests received when we already started."); return; } decideWRF(); // set up swrf & game.map bMultiPlayer = true; bMultiMessages = true; NETsetPlayerConnectionStatus(CONNECTIONSTATUS_NORMAL, NET_ALL_PLAYERS); // reset disconnect conditions initLoadingScreen(true); if (NetPlay.isHost) { // This sets the limits to whatever the defaults are for the limiter screen // If host sets limits, then we do not need to do the following routine. if (!bLimiterLoaded) { debug(LOG_NET, "limiter was NOT activated, setting defaults"); if (!resLoad("wrf/limiter_data.wrf", 503)) { debug(LOG_INFO, "Unable to load limiter_data."); } } else { debug(LOG_NET, "limiter was activated"); } resetDataHash(); // need to reset it, since host's data has changed. createLimitSet(); debug(LOG_NET,"sending our options to all clients"); sendOptions(); NEThaltJoining(); // stop new players entering. ingame.TimeEveryoneIsInGame = 0; ingame.isAllPlayersDataOK = false; memset(&ingame.DataIntegrity, 0x0, sizeof(ingame.DataIntegrity)); //clear all player's array SendFireUp(); //bcast a fireup message } debug(LOG_NET, "title mode STARTGAME is set--Starting Game!"); changeTitleMode(STARTGAME); bHosted = false; if (NetPlay.isHost) { sendTextMessage(_("Host is Starting Game"),true); } } // //////////////////////////////////////////////////////////////////////////// // Net message handling void frontendMultiMessages(void) { NETQUEUE queue; uint8_t type; while (NETrecvNet(&queue, &type)) { // Copy the message to the global one used by the new NET API switch(type) { case NET_FILE_REQUESTED: recvMapFileRequested(queue); break; case NET_FILE_PAYLOAD: { bool done = recvMapFileData(queue); ((MultibuttonWidget *)widgGetFromID(psWScreen, MULTIOP_MAP_PREVIEW))->enable(done); // turn preview button on or off break; } case NET_FILE_CANCELLED: // host only routine { uint32_t reason; uint32_t victim; if(!NetPlay.isHost) // only host should act { ASSERT(false, "Host only routine detected for client!"); break; } NETbeginDecode(queue, NET_FILE_CANCELLED); NETuint32_t(&victim); NETuint32_t(&reason); NETend(); if (whosResponsible(victim) != queue.index) { HandleBadParam("NET_FILE_CANCELLED given incorrect params.", victim, queue.index); return; } switch (reason) { case STUCK_IN_FILE_LOOP: debug(LOG_WARNING, "Received file cancel request from player %u, They are stuck in a loop?", victim); kickPlayer(victim, "the host couldn't send a file for some reason.", ERROR_UNKNOWNFILEISSUE); NetPlay.players[victim].wzFile.isCancelled = true; NetPlay.players[victim].wzFile.isSending = false; break; case ALREADY_HAVE_FILE: default: debug(LOG_WARNING, "Received file cancel request from player %u, they already have the file.", victim); NetPlay.players[victim].wzFile.isCancelled = true; NetPlay.players[victim].wzFile.isSending = false; break; } } break; case NET_OPTIONS: // incoming options file. recvOptions(queue); ingame.localOptionsReceived = true; if(titleMode == MULTIOPTION) { addGameOptions(); disableMultiButs(); addChatBox(); } break; case GAME_ALLIANCE: recvAlliance(queue, false); break; case NET_COLOURREQUEST: recvColourRequest(queue); break; case NET_POSITIONREQUEST: recvPositionRequest(queue); break; case NET_TEAMREQUEST: recvTeamRequest(queue); break; case NET_READY_REQUEST: recvReadyRequest(queue); // If hosting and game not yet started, try to start the game if everyone is ready. if (NetPlay.isHost && bHosted && multiplayPlayersReady(false)) { startMultiplayerGame(); } break; case NET_PING: // diagnostic ping msg. recvPing(queue); break; case NET_PLAYER_DROPPED: // remote player got disconnected { uint32_t player_id = MAX_PLAYERS; resetReadyStatus(false); NETbeginDecode(queue, NET_PLAYER_DROPPED); { NETuint32_t(&player_id); } NETend(); if (player_id >= MAX_PLAYERS) { debug(LOG_INFO, "** player %u has dropped - huh?", player_id); break; } if (whosResponsible(player_id) != queue.index && queue.index != NET_HOST_ONLY) { HandleBadParam("NET_PLAYER_DROPPED given incorrect params.", player_id, queue.index); break; } debug(LOG_INFO,"** player %u has dropped!", player_id); MultiPlayerLeave(player_id); // get rid of their stuff NET_InitPlayer(player_id, false); // sets index player's array to false NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_DROPPED, player_id); if (player_id == NetPlay.hostPlayer || player_id == selectedPlayer) // if host quits or we quit, abort out { stopJoining(); } break; } case NET_PLAYERRESPONDING: // remote player is now playing. { uint32_t player_id; resetReadyStatus(false); NETbeginDecode(queue, NET_PLAYERRESPONDING); // the player that has just responded NETuint32_t(&player_id); NETend(); ingame.JoiningInProgress[player_id] = false; ingame.DataIntegrity[player_id] = false; break; } case NET_FIREUP: // campaign game started.. can fire the whole shebang up... if (NET_HOST_ONLY != queue.index) { HandleBadParam("NET_FIREUP given incorrect params.", 255, queue.index); break; } debug(LOG_NET, "NET_FIREUP was received ..."); if(ingame.localOptionsReceived) { uint32_t randomSeed = 0; NETbeginDecode(queue, NET_FIREUP); NETuint32_t(&randomSeed); NETend(); gameSRand(randomSeed); // Set the seed for the synchronised random number generator, using the seed given by the host. debug(LOG_NET, "& local Options Received (MP game)"); ingame.TimeEveryoneIsInGame = 0; // reset time resetDataHash(); decideWRF(); bMultiPlayer = true; bMultiMessages = true; changeTitleMode(STARTGAME); bHosted = false; // Start the game before processing more messages. NETpop(queue); return; } ASSERT(false, "NET_FIREUP was received, but !ingame.localOptionsReceived."); break; case NET_KICK: // player is forcing someone to leave { uint32_t player_id; char reason[MAX_KICK_REASON]; LOBBY_ERROR_TYPES KICK_TYPE = ERROR_NOERROR; NETbeginDecode(queue, NET_KICK); NETuint32_t(&player_id); NETstring(reason, MAX_KICK_REASON); NETenum(&KICK_TYPE); NETend(); if (player_id == NET_HOST_ONLY) { char buf[250]= {'\0'}; ssprintf(buf, "*Player %d (%s : %s) tried to kick %u", (int) queue.index, NetPlay.players[queue.index].name, NetPlay.players[queue.index].IPtextAddress, player_id); NETlogEntry(buf, SYNC_FLAG, 0); debug(LOG_ERROR, "%s", buf); if (NetPlay.isHost) { NETplayerKicked((unsigned int) queue.index); } break; } if (selectedPlayer == player_id) // we've been told to leave. { setLobbyError(KICK_TYPE); stopJoining(); //screen_RestartBackDrop(); changeTitleMode(GAMEFIND); pie_LoadBackDrop(SCREEN_RANDOMBDROP); debug(LOG_ERROR, "You have been kicked, because %s ", reason); } else { NETplayerKicked(player_id); } break; } case NET_HOST_DROPPED: NETbeginDecode(queue, NET_HOST_DROPPED); NETend(); stopJoining(); debug(LOG_NET, "The host has quit!"); setLobbyError(ERROR_HOSTDROPPED); changeTitleMode(GAMEFIND); break; case NET_TEXTMSG: // Chat message if(ingame.localOptionsReceived) { recvTextMessage(queue); } break; default: debug(LOG_ERROR, "Didn't handle %s message!", messageTypeToString(type)); break; } NETpop(queue); } } void runMultiOptions(void) { static UDWORD lastrefresh = 0; char oldGameMap[128]; int oldMaxPlayers; PLAYERSTATS playerStats; W_CONTEXT context; KEY_CODE k; char str[3]; frontendMultiMessages(); if (NetPlay.isHost) { for (unsigned i = 0; i < MAX_PLAYERS; i++) { // send it for each player that needs it if (NetPlay.players[i].wzFile.isSending) { sendMap(); } } } // update boxes? if (netPlayersUpdated || (NetPlay.isHost && mouseDown(MOUSE_LMB) && gameTime-lastrefresh>500)) { netPlayersUpdated = false; lastrefresh= gameTime; if (!multiRequestUp && (bHosted || ingame.localJoiningInProgress)) { addPlayerBox(true); // update the player box. loadMapPreview(false); } } // update scores and pings if far enough into the game if(ingame.localOptionsReceived && ingame.localJoiningInProgress) { sendScoreCheck(); sendPing(); } // if typing and not in an edit box then jump to chat box. k = getQwertyKey(); if( k && psWScreen->psFocus == NULL) { context.xOffset = 0; context.yOffset = 0; context.mx = mouseX(); context.my = mouseY(); keyScanToString(k,(char*)&str,3); if(widgGetFromID(psWScreen,MULTIOP_CHATEDIT)) { widgSetString(psWScreen, MULTIOP_CHATEDIT, (char*)&str); // start it up! widgGetFromID(psWScreen, MULTIOP_CHATEDIT)->clicked(&context); } } // chat box handling if(widgGetFromID(psWScreen,MULTIOP_CHATBOX)) { while(getNumberConsoleMessages() >getConsoleLineInfo()) { removeTopConsoleMessage(); } updateConsoleMessages(); // run the chatbox } // widget handling if(multiRequestUp) { WidgetTriggers const &triggers = widgRunScreen(psRScreen); unsigned id = triggers.empty()? 0 : triggers.front().widget->id; // Just use first click here, since the next click could be on another menu. LEVEL_DATASET *mapData; bool isHoverPreview; QString sTemp; if (runMultiRequester(id, &id, &sTemp, &mapData, &isHoverPreview)) { Sha256 oldGameHash; switch(id) { case MULTIOP_PNAME: sstrcpy(sPlayer, sTemp.toUtf8().constData()); widgSetString(psWScreen, MULTIOP_PNAME, sTemp.toUtf8().constData()); removeWildcards((char*)sPlayer); printConsoleNameChange(NetPlay.players[selectedPlayer].name, sPlayer); NETchangePlayerName(selectedPlayer, (char*)sPlayer); loadMultiStats((char*)sPlayer,&playerStats); setMultiStats(selectedPlayer,playerStats,false); setMultiStats(selectedPlayer,playerStats,true); netPlayersUpdated = true; break; case MULTIOP_MAP: { sstrcpy(oldGameMap, game.map); oldGameHash = game.hash; oldMaxPlayers = game.maxPlayers; sstrcpy(game.map, mapData->pName); game.hash = levGetFileHash(mapData); game.maxPlayers = mapData->players; game.isMapMod = CheckForMod(mapData->realFileName); loadMapPreview(!isHoverPreview); if (isHoverPreview) { sstrcpy(game.map, oldGameMap); game.hash = oldGameHash; game.maxPlayers = oldMaxPlayers; } else { loadMapSettings1(); } widgSetString(psWScreen, MULTIOP_MAP+1, mapData->pName); //What a horrible, horrible way to do this! FIX ME! (See addBlueForm) addGameOptions(); break; } default: loadMapPreview(false); // Restore the preview of the old map. break; } if (!isHoverPreview) { addPlayerBox( !ingame.bHostSetup ); } } } else { if(hideTime != 0) { // we abort the 'hidetime' on press of a mouse button. if(gameTime-hideTime < MAP_PREVIEW_DISPLAY_TIME && !mousePressed(MOUSE_LMB) && !mousePressed(MOUSE_RMB)) { return; } inputLoseFocus(); // remove the mousepress from the input stream. hideTime = 0; } WidgetTriggers const &triggers = widgRunScreen(psWScreen); unsigned id = triggers.empty()? 0 : triggers.front().widget->id; // Just use first click here, since the next click could be on another menu. processMultiopWidgets(id); } widgDisplayScreen(psWScreen); // show the widgets currently running if(multiRequestUp) { widgDisplayScreen(psRScreen); // show the Requester running } if(widgGetFromID(psWScreen,MULTIOP_CHATBOX)) { iV_SetFont(font_regular); // switch to small font. displayConsoleMessages(); // draw the chatbox } if (CancelPressed()) { processMultiopWidgets(CON_CANCEL); // "Press" the cancel button to clean up net connections and stuff. } if (!NetPlay.isHostAlive && !ingame.bHostSetup) { changeTitleMode(GAMEFIND); screen_RestartBackDrop(); } } bool startMultiOptions(bool bReenter) { PLAYERSTATS nullStats; UBYTE i; netPlayersUpdated = true; addBackdrop(); loadMapPreview(false); addTopForm(); if (getLobbyError() != ERROR_INVALID) { setLobbyError(ERROR_NOERROR); } // free limiter structure if(!bReenter || challengeActive) { if(ingame.numStructureLimits) { ingame.numStructureLimits = 0; free(ingame.pStructureLimits); ingame.pStructureLimits = NULL; } } if(!bReenter) { memset(nameOverrides, 0, sizeof(nameOverrides)); memset(&locked, 0, sizeof(locked)); // nothing is locked by default teamChooserUp = -1; aiChooserUp = -1; difficultyChooserUp = -1; positionChooserUp = -1; colourChooserUp = -1; for(i=0; i < MAX_PLAYERS; i++) { game.skDiff[i] = (DIFF_SLIDER_STOPS / 2); // reset AI (turn it on again) setPlayerColour(i,i); //reset all colors as well } game.isMapMod = false; // reset map-mod status game.mapHasScavengers = true; // FIXME, should default to false if(!NetPlay.bComms) // force skirmish if no comms. { game.type = SKIRMISH; sstrcpy(game.map, DEFAULTSKIRMISHMAP); game.hash = levGetMapNameHash(game.map); game.maxPlayers = 4; } ingame.localOptionsReceived = false; loadMultiStats((char*)sPlayer,&nullStats); } if (!bReenter && challengeActive) { resetReadyStatus(false); removeWildcards((char*)sPlayer); if (!hostCampaign((char*)game.name,(char*)sPlayer)) { debug(LOG_ERROR, "Failed to host the challenge."); return false; } bHosted = true; loadMapSettings1(); loadMapSettings2(); WzConfig ini(sRequestResult, WzConfig::ReadOnly); ini.beginGroup("challenge"); sstrcpy(game.map, ini.value("Map", game.map).toString().toUtf8().constData()); game.hash = levGetMapNameHash(game.map); game.maxPlayers = ini.value("MaxPlayers", game.maxPlayers).toInt(); // TODO, read from map itself, not here!! game.scavengers = ini.value("Scavengers", game.scavengers).toBool(); game.alliance = ALLIANCES_TEAMS; netPlayersUpdated = true; mapDownloadProgress = 100; game.power = ini.value("powerLevel", game.power).toInt(); game.base = ini.value("Bases", game.base + 1).toInt() - 1; // count from 1 like the humans do sstrcpy(game.name, ini.value("name").toString().toUtf8().constData()); locked.position = ini.value("AllowPositionChange", locked.position).toBool(); ini.endGroup(); ingame.localOptionsReceived = true; addGameOptions(); // update game options box. addChatBox(); disableMultiButs(); addPlayerBox(true); } else { addPlayerBox(false); // Players addGameOptions(); addChatBox(); if (ingame.bHostSetup) { char buf[512] = {'\0'}; if (NetPlay.bComms) { if (NetPlay.isUPNP) { if (NetPlay.isUPNP_CONFIGURED) { ssprintf(buf, _("UPnP has been enabled.")); } else { if (NetPlay.isUPNP_ERROR) { ssprintf(buf, _("UPnP detection faled. You must manually configure router yourself.")); } else { ssprintf(buf, _("UPnP detection is in progress...")); } } addConsoleMessage(buf, DEFAULT_JUSTIFY, NOTIFY_MESSAGE); } else { ssprintf(buf, _("UPnP detection disabled by user. Autoconfig of port 2100 will not happen.")); addConsoleMessage(buf, DEFAULT_JUSTIFY, NOTIFY_MESSAGE); } } if (challengeActive) { ssprintf(buf, _("Hit the ready box to begin your challenge!")); } else { ssprintf(buf, _("Press the start hosting button to begin hosting a game.")); } addConsoleMessage(buf, DEFAULT_JUSTIFY, NOTIFY_MESSAGE); } } // going back to multiop after setting limits up.. if(bReenter && bHosted) { disableMultiButs(); } loadMapPreview(false); return true; } ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// // Drawing functions void displayChatEdit(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { int x = xOffset + psWidget->x(); int y = yOffset + psWidget->y() - 4; // 4 is the magic number. // draws the line at the bottom of the multiplayer join dialog separating the chat // box from the input box iV_Line(x, y, x + psWidget->width(), y, WZCOL_MENU_SEPARATOR); return; } // //////////////////////////////////////////////////////////////////////////// void displayRemoteGame(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { int x = xOffset + psWidget->x(); int y = yOffset + psWidget->y(); UDWORD gameID = psWidget->UserData; char tmp[80], name[StringSize]; if ( (LobbyError != ERROR_NOERROR) && (bMultiPlayer && !NetPlay.bComms)) { addConsoleMessage(_("Can't connect to lobby server!"), DEFAULT_JUSTIFY, NOTIFY_MESSAGE); return; } // Draw blue boxes for games (buttons) & headers drawBlueBox(x, y, psWidget->width(), psWidget->height()); drawBlueBox(x, y, GAMES_STATUS_START - 4 ,psWidget->height()); drawBlueBox(x, y, GAMES_PLAYERS_START - 4 ,psWidget->height()); drawBlueBox(x, y, GAMES_MAPNAME_START - 4, psWidget->height()); //draw game info iV_SetFont(font_regular); int lamp = IMAGE_LAMP_RED; int statusStart = IMAGE_NOJOIN; bool disableButton = true; iV_SetTextColour(WZCOL_TEXT_DARK); // As long as they got room, and mods are the same then we proccess the button(s) if (NETisCorrectVersion(NetPlay.games[gameID].game_version_major, NetPlay.games[gameID].game_version_minor)) { if (strcmp(NetPlay.games[gameID].modlist, getModList()) != 0) { // If wrong mod loaded. statusStart = IMAGE_NOJOIN_MOD; } else if (NetPlay.games[gameID].desc.dwCurrentPlayers >= NetPlay.games[gameID].desc.dwMaxPlayers) { // If game is full. statusStart = IMAGE_NOJOIN_FULL; } else { // Game is ok to join! iV_SetTextColour(WZCOL_FORM_TEXT); lamp = IMAGE_LAMP_GREEN; statusStart = IMAGE_SKIRMISH_OVER; disableButton = false; if (NetPlay.games[gameID].privateGame) // check to see if it is a private game { statusStart = IMAGE_LOCKED_NOBG; lamp = IMAGE_LAMP_AMBER; } } ssprintf(tmp, "%d/%d", NetPlay.games[gameID].desc.dwCurrentPlayers, NetPlay.games[gameID].desc.dwMaxPlayers); iV_DrawText(tmp, x + GAMES_PLAYERS_START + 4 , y + 18); // see what host limits are... then draw them. if (NetPlay.games[gameID].limits) { if(NetPlay.games[gameID].limits & NO_TANKS) iV_DrawImage(FrontImages, IMAGE_NO_TANK, x + GAMES_STATUS_START + 37, y + 2); if(NetPlay.games[gameID].limits & NO_BORGS) iV_DrawImage(FrontImages, IMAGE_NO_CYBORG, x + GAMES_STATUS_START + (37 * 2), y + 2); if(NetPlay.games[gameID].limits & NO_VTOLS) iV_DrawImage(FrontImages, IMAGE_NO_VTOL, x + GAMES_STATUS_START + (37 * 3) , y + 2); } } // Draw type overlay. iV_DrawImage(FrontImages, statusStart, x + GAMES_STATUS_START, y + 3); iV_DrawImage(FrontImages, lamp, x - 14, y + 8); if (disableButton) { widgSetButtonState(psWScreen, psWidget->id, WBUT_DISABLE); } //draw game name, chop when we get a too long name sstrcpy(name, NetPlay.games[gameID].name); // box size in pixels while (iV_GetTextWidth(name) > (GAMES_MAPNAME_START - GAMES_GAMENAME_START- 4) ) { name[strlen(name)-1]='\0'; } iV_DrawText(name, x + GAMES_GAMENAME_START, y + 12); if (NetPlay.games[gameID].pureMap) { iV_SetTextColour(WZCOL_RED); } else { iV_SetTextColour(WZCOL_FORM_TEXT); } // draw map name, chop when we get a too long name sstrcpy(name, NetPlay.games[gameID].mapname); // box size in pixels while (iV_GetTextWidth(name) > (GAMES_PLAYERS_START - GAMES_MAPNAME_START - 4) ) { name[strlen(name)-1]='\0'; } iV_DrawText(name, x + GAMES_MAPNAME_START, y + 12); // map name iV_SetTextColour(WZCOL_FORM_TEXT); iV_SetFont(font_small); // draw mod name (if any) if (strlen(NetPlay.games[gameID].modlist)) { // FIXME: we really don't have enough space to list all mods char tmp[300]; sprintf(tmp, _("Mods: %s"), NetPlay.games[gameID].modlist); tmp[StringSize] = '\0'; sstrcpy(name, tmp); } else { sstrcpy(name, _("Mods: None!")); } // box size in pixels while (iV_GetTextWidth(name) > (GAMES_PLAYERS_START - GAMES_MAPNAME_START - 8) ) { name[strlen(name)-1]='\0'; } iV_DrawText(name, x + GAMES_MODNAME_START, y + 24 ); // draw version string sprintf(name, _("Version: %s"), NetPlay.games[gameID].versionstring); // box size in pixels while (iV_GetTextWidth(name) > (GAMES_MAPNAME_START - 6 - GAMES_GAMENAME_START - 4) ) { name[strlen(name)-1]='\0'; } iV_DrawText(name, x + GAMES_GAMENAME_START + 6, y + 24 ); // crappy hack to only draw this once for the header. TODO fix GUI if (gameID == 0) { iV_SetTextColour(WZCOL_YELLOW); // make the 'header' for the table... drawBlueBox(x , y - 12 , GAMES_GAMEWIDTH, 12); ssprintf(tmp, "Game Name"); iV_DrawText(tmp, x - 2 + GAMES_GAMENAME_START + 48, y - 3); ssprintf(tmp, "Map Name"); iV_DrawText(tmp, x - 2 + GAMES_MAPNAME_START + 48, y - 3); ssprintf(tmp, "Players"); iV_DrawText(tmp, x - 2 + GAMES_PLAYERS_START, y - 3); ssprintf(tmp, "Status"); iV_DrawText(tmp, x - 2 + GAMES_STATUS_START + 48, y - 3); } } void displayTeamChooser(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { int x = xOffset + psWidget->x(); int y = yOffset + psWidget->y(); UDWORD i = psWidget->UserData; ASSERT(i < MAX_PLAYERS && NetPlay.players[i].team >= 0 && NetPlay.players[i].team < MAX_PLAYERS, "Team index out of bounds" ); drawBlueBox(x, y, psWidget->width(), psWidget->height()); if (game.skDiff[i]) { iV_DrawImage(FrontImages, IMAGE_TEAM0 + NetPlay.players[i].team, x + 2, y + 8); } } void displayPosition(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { const int x = xOffset + psWidget->x(); const int y = yOffset + psWidget->y(); const int i = psWidget->UserData; char text[80]; drawBlueBox(x, y, psWidget->width(), psWidget->height()); iV_SetFont(font_regular); iV_SetTextColour(WZCOL_FORM_TEXT); ssprintf(text, _("Click to take player slot %d"), NetPlay.players[i].position); iV_DrawText(text, x + 10, y + 22); } static void displayAi(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { const int x = xOffset + psWidget->x(); const int y = yOffset + psWidget->y(); const int j = psWidget->UserData; const char *commsText[] = { N_("Open"), N_("Closed") }; drawBlueBox(x, y, psWidget->width(), psWidget->height()); iV_SetFont(font_regular); iV_SetTextColour(WZCOL_FORM_TEXT); iV_DrawText((j >= 0) ? aidata[j].name : gettext(commsText[j + 2]), x + 10, y + 22); } static int difficultyIcon(int difficulty) { switch (difficulty) { case 0: return IMAGE_EASY; case 1: return IMAGE_MEDIUM; case 2: return IMAGE_HARD; case 3: return IMAGE_INSANE; default: return IMAGE_NO; /// what?? } } static void displayDifficulty(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { const int x = xOffset + psWidget->x(); const int y = yOffset + psWidget->y(); const int j = psWidget->UserData; ASSERT_OR_RETURN(, j < ARRAY_SIZE(difficultyList), "Bad difficulty found: %d", j); drawBlueBox(x, y, psWidget->width(), psWidget->height()); iV_SetFont(font_regular); iV_SetTextColour(WZCOL_FORM_TEXT); iV_DrawImage(FrontImages, difficultyIcon(j), x + 5, y + 5); iV_DrawText(gettext(difficultyList[j]), x + 42, y + 22); } static bool isKnownPlayer(std::map const &knownPlayers, std::string const &name, EcKey const &key) { if (key.empty()) { return false; } std::map::const_iterator i = knownPlayers.find(name); return i != knownPlayers.end() && key.toBytes(EcKey::Public) == i->second; } // //////////////////////////////////////////////////////////////////////////// void displayPlayer(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { int x = xOffset + psWidget->x(); int y = yOffset + psWidget->y(); UDWORD j = psWidget->UserData, eval; const int nameX = 32; //bluboxes. drawBlueBox(x, y, psWidget->width(), psWidget->height()); if (NetPlay.isHost && NetPlay.players[j].wzFile.isSending) { static char mapProgressString[MAX_STR_LENGTH] = {'\0'}; int progress = (NetPlay.players[j].wzFile.currPos * 100) / NetPlay.players[j].wzFile.fileSize_32; snprintf(mapProgressString, MAX_STR_LENGTH, _("Sending Map: %d%% "), progress); iV_SetFont(font_regular); // font iV_SetTextColour(WZCOL_FORM_TEXT); iV_DrawText(mapProgressString, x + 15, y + 22); } else if (mapDownloadProgress != 100 && j == selectedPlayer) { static char mapProgressString[MAX_STR_LENGTH] = {'\0'}; snprintf(mapProgressString, MAX_STR_LENGTH, _("Map: %d%% downloaded"), mapDownloadProgress); iV_SetFont(font_regular); // font iV_SetTextColour(WZCOL_FORM_TEXT); iV_DrawText(mapProgressString, x + 5, y + 22); return; } else if (ingame.localOptionsReceived && NetPlay.players[j].allocated) // only draw if real player! { std::string name = NetPlay.players[j].name; drawBlueBox(x, y, psWidget->width(), psWidget->height()); iV_SetFont(font_regular); // font std::map serverPlayers; // TODO Fill this with players known to the server (needs implementing on the server, too). Currently useless. if (ingame.PingTimes[j] >= PING_LIMIT) { iV_SetTextColour(WZCOL_FORM_PLAYER_NOPING); } else if (isKnownPlayer(serverPlayers, name, getMultiStats(j).identity)) { iV_SetTextColour(WZCOL_FORM_PLAYER_KNOWN_BY_SERVER); } else if (isKnownPlayer(getKnownPlayers(), name, getMultiStats(j).identity)) { iV_SetTextColour(WZCOL_FORM_PLAYER_KNOWN); } else { iV_SetTextColour(WZCOL_FORM_PLAYER_UNKNOWN); } // name if (iV_GetTextWidth(name.c_str()) > psWidget->width() - nameX) { while (!name.empty() && iV_GetTextWidth((name + "...").c_str()) > psWidget->width() - nameX) { name.resize(name.size() - 1); // Clip name. } name += "..."; } std::string subText; if (j == NET_HOST_ONLY && NetPlay.bComms) { subText += _("HOST"); } if (NetPlay.bComms && j != selectedPlayer) { char buf[250] = {'\0'}; // show "actual" ping time ssprintf(buf, "%s%s: ", subText.empty()? "" : ", ", _("Ping")); subText += buf; if (ingame.PingTimes[j] < PING_LIMIT) { ssprintf(buf, "%03d", ingame.PingTimes[j]); } else { ssprintf(buf, "∞"); // Player has ping of somewhat questionable quality. } subText += buf; } iV_DrawText(name.c_str(), x + nameX, y + (subText.empty()? 22 : 18)); if (!subText.empty()) { iV_SetFont(font_small); iV_SetTextColour(WZCOL_TEXT_MEDIUM); iV_DrawText(subText.c_str(), x + nameX, y + 28); iV_SetFont(font_regular); iV_SetTextColour(WZCOL_FORM_TEXT); } PLAYERSTATS stat = getMultiStats(j); if(stat.wins + stat.losses < 5) { iV_DrawImage(FrontImages, IMAGE_MEDAL_DUMMY, x + 4, y + 13); } else { PLAYERSTATS stat = getMultiStats(j); // star 1 total droid kills eval = stat.totalKills; if(eval >600) { iV_DrawImage(FrontImages, IMAGE_MULTIRANK1, x + 4, y + 3); } else if(eval >300) { iV_DrawImage(FrontImages, IMAGE_MULTIRANK2, x + 4, y + 3); } else if(eval >150) { iV_DrawImage(FrontImages, IMAGE_MULTIRANK3, x + 4, y + 3); } // star 2 games played (Cannot use stat.played, since that's just the number of times the player exited via the game menu, not the number of games played.) eval = stat.wins + stat.losses; if(eval >200) { iV_DrawImage(FrontImages, IMAGE_MULTIRANK1, x + 4, y + 13); } else if(eval >100) { iV_DrawImage(FrontImages, IMAGE_MULTIRANK2, x + 4, y + 13); } else if(eval >50) { iV_DrawImage(FrontImages, IMAGE_MULTIRANK3, x + 4, y + 13); } // star 3 games won. eval = stat.wins; if(eval >80) { iV_DrawImage(FrontImages, IMAGE_MULTIRANK1, x + 4, y + 23); } else if(eval >40) { iV_DrawImage(FrontImages, IMAGE_MULTIRANK2, x + 4, y + 23); } else if(eval >10) { iV_DrawImage(FrontImages, IMAGE_MULTIRANK3, x + 4, y + 23); } // medals. if ((stat.wins >= 6) && (stat.wins > (2 * stat.losses))) // bronze requirement. { if ((stat.wins >= 12) && (stat.wins > (4 * stat.losses))) // silver requirement. { if ((stat.wins >= 24) && (stat.wins > (8 * stat.losses))) // gold requirement { iV_DrawImage(FrontImages, IMAGE_MEDAL_GOLD, x + 16, y + 11); } else { iV_DrawImage(FrontImages, IMAGE_MEDAL_SILVER, x + 16, y + 11); } } else { iV_DrawImage(FrontImages, IMAGE_MEDAL_BRONZE, x + 16, y + 11); } } } game.skDiff[j] = UBYTE_MAX; // set AI difficulty to 0xFF (i.e. not an AI) } else // AI { char aitext[80]; if (NetPlay.players[j].ai >= 0) { iV_DrawImage(FrontImages, IMAGE_PLAYER_PC, x, y + 11); } iV_SetFont(font_regular); iV_SetTextColour(WZCOL_FORM_TEXT); ASSERT_OR_RETURN(, NetPlay.players[j].ai < (int)aidata.size(), "Uh-oh, AI index out of bounds"); switch (NetPlay.players[j].ai) { case AI_OPEN: sstrcpy(aitext, _("Open")); break; case AI_CLOSED: sstrcpy(aitext, _("Closed")); break; default: sstrcpy(aitext, nameOverrides[j][0] == '\0'? NetPlay.isHost? aidata[NetPlay.players[j].ai].name : NetPlay.players[j].name : nameOverrides[j]); break; } iV_DrawText(aitext, x + nameX, y + 22); } } void displayColour(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { const int x = xOffset + psWidget->x(); const int y = yOffset + psWidget->y(); const int j = psWidget->UserData; drawBlueBox(x, y, psWidget->width(), psWidget->height()); if (!NetPlay.players[j].wzFile.isSending && game.skDiff[j]) { int player = getPlayerColour(j); STATIC_ASSERT(MAX_PLAYERS <= 16); iV_DrawImageTc(FrontImages, IMAGE_PLAYERN, IMAGE_PLAYERN_TC, x + 7, y + 9, pal_GetTeamColour(player)); } } // //////////////////////////////////////////////////////////////////////////// // Display blue box void intDisplayFeBox(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { int x = xOffset + psWidget->x(); int y = yOffset + psWidget->y(); int w = psWidget->width(); int h = psWidget->height(); drawBlueBox(x,y,w,h); } // //////////////////////////////////////////////////////////////////////////// // Display edit box void displayMultiEditBox(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset) { int x = xOffset + psWidget->x(); int y = yOffset + psWidget->y(); drawBlueBox(x, y, psWidget->width(), psWidget->height()); if( ((W_EDITBOX*)psWidget)->state & WEDBS_DISABLE) // disabled { PIELIGHT colour; colour.byte.r = FILLRED; colour.byte.b = FILLBLUE; colour.byte.g = FILLGREEN; colour.byte.a = FILLTRANS; pie_UniTransBoxFill(x, y, x + psWidget->width() + psWidget->height(), y + psWidget->height(), colour); } } static Image getFrontHighlightImage(Image image) { if (image.isNull()) { return Image(); } switch (image.width()) { case 30: return Image(FrontImages, IMAGE_HI34); case 60: return Image(FrontImages, IMAGE_HI64); case 19: return Image(FrontImages, IMAGE_HI23); case 27: return Image(FrontImages, IMAGE_HI31); case 35: return Image(FrontImages, IMAGE_HI39); case 37: return Image(FrontImages, IMAGE_HI41); case 56: return Image(FrontImages, IMAGE_HI56); } return Image(); } void WzMultiButton::display(int xOffset, int yOffset) { int x0 = xOffset + x(); int y0 = yOffset + y(); Image hiToUse(NULL, 0); // FIXME: This seems to be a way to conserve space, so you can use a // transparent icon with these edit boxes. // hack for multieditbox if (imNormal.id == IMAGE_EDIT_MAP || imNormal.id == IMAGE_EDIT_GAME || imNormal.id == IMAGE_EDIT_PLAYER || imNormal.id == IMAGE_LOCK_BLUE || imNormal.id == IMAGE_UNLOCK_BLUE) { drawBlueBox(x0 - 2, y0 - 2, height(), height()); // box on end. } // evaluate auto-frame bool highlight = (getState() & WBUT_HIGHLIGHT) != 0; // evaluate auto-frame if (doHighlight == 1 && highlight) { hiToUse = getFrontHighlightImage(imNormal); } bool down = (getState() & (WBUT_DOWN | WBUT_LOCK | WBUT_CLICKLOCK)) != 0; bool grey = (getState() & WBUT_DISABLE) != 0; Image toDraw[3]; int numToDraw = 0; // now display toDraw[numToDraw++] = imNormal; // hilights etc.. if (down) { toDraw[numToDraw++] = imDown; } if (highlight && !grey && hiToUse.images != NULL) { toDraw[numToDraw++] = hiToUse; } for (int n = 0; n < numToDraw; ++n) { Image tcImage(toDraw[n].images, toDraw[n].id + 1); if (tc == MAX_PLAYERS) { iV_DrawImage(toDraw[n], x0, y0); } else if (tc == MAX_PLAYERS + 1) { const int scale = 4000; int f = realTime%scale; PIELIGHT mix; mix.byte.r = 128 + iSinR(65536*f/scale + 65536*0/3, 127); mix.byte.g = 128 + iSinR(65536*f/scale + 65536*1/3, 127); mix.byte.b = 128 + iSinR(65536*f/scale + 65536*2/3, 127); mix.byte.a = 255; iV_DrawImageTc(toDraw[n], tcImage, x0, y0, mix); } else { iV_DrawImageTc(toDraw[n], tcImage, x0, y0, pal_GetTeamColour(tc)); } } if (grey) { // disabled, render something over it! iV_TransBoxFill(x0, y0, x0 + width(), y0 + height()); } } ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// // common widgets static bool addMultiEditBox(UDWORD formid, UDWORD id, UDWORD x, UDWORD y, char const *tip, char const *tipres, UDWORD icon, UDWORD iconhi, UDWORD iconid) { W_EDBINIT sEdInit; // editbox sEdInit.formID = formid; sEdInit.id = id; sEdInit.x = (short)x; sEdInit.y = (short)y; sEdInit.width = MULTIOP_EDITBOXW; sEdInit.height = MULTIOP_EDITBOXH; sEdInit.pText = tipres; sEdInit.pBoxDisplay = displayMultiEditBox; if (!widgAddEditBox(psWScreen, &sEdInit)) { return false; } addMultiBut(psWScreen, MULTIOP_OPTIONS, iconid, x + MULTIOP_EDITBOXW + 2, y + 2, MULTIOP_EDITBOXH, MULTIOP_EDITBOXH, tip, icon, iconhi, iconhi); return true; } ///////////////////////////////////////////////////////////////////////////////////////// bool addMultiBut(W_SCREEN *screen, UDWORD formid, UDWORD id, UDWORD x, UDWORD y, UDWORD width, UDWORD height, const char* tipres, UDWORD norm, UDWORD down, UDWORD hi, unsigned tc) { WzMultiButton *button = new WzMultiButton(widgGetFromID(screen, formid)); button->id = id; button->setGeometry(x, y, width, height); button->setTip(QString::fromUtf8(tipres)); button->imNormal = Image(FrontImages, norm); button->imDown = Image(FrontImages, down); button->doHighlight = hi; button->tc = tc; return true; } /* Returns true if all human players clicked on the 'ready' button */ bool multiplayPlayersReady(bool bNotifyStatus) { unsigned int player,playerID; bool bReady; bReady = true; for(player = 0; player < game.maxPlayers; player++) { // check if this human player is ready, ignore AIs if (NetPlay.players[player].allocated && (!NetPlay.players[player].ready || ingame.PingTimes[player] >= PING_LIMIT)) { if(bNotifyStatus) { for (playerID = 0; playerID <= game.maxPlayers && playerID != player; playerID++) ; console("%s is not ready", getPlayerName(playerID)); } bReady = false; } } return bReady; }