warzone2100/src/multiint.cpp

4450 lines
128 KiB
C++

/*
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 <time.h>
#include "lib/framework/frameresource.h"
#include "lib/framework/frameint.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
{
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> 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<AIDATA>::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_INFO, "Loading map preview: \"%s\" builtin t%d", psLevel->pName, psLevel->dataDir);
}
else
{
debug(LOG_INFO, "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<AIDATA>::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<AIDATA>::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<MaxGames; i++)
{
widgDelete(psWScreen, GAMES_GAMESTART+i); // remove old widget
}
gcount = 0;
}
// in case they refresh, and a game becomes available.
widgDelete(psWScreen, FRONTEND_NOGAMESAVAILABLE);
// only have to do this if we have any games available.
if (!getLobbyError() && gcount)
{
for (i=0; i<MaxGames; i++) // draw games
{
widgDelete(psWScreen, GAMES_GAMESTART+i); // remove old icon.
if (NetPlay.games[i].desc.dwSize !=0 )
{ // either display all games, or games that are the client's specific version
if (toggleFilter)
{
if ((NetPlay.games[i].game_version_major != NETGetMajorVersion()) || (NetPlay.games[i].game_version_minor != NETGetMinorVersion()))
continue;
}
added++;
sButInit.id = GAMES_GAMESTART+i;
sButInit.x = 20;
sButInit.y = (UWORD)(45+((5+GAMES_GAMEHEIGHT)*i) );
// display the correct tooltip message.
if (!NETisCorrectVersion(NetPlay.games[i].game_version_major, NetPlay.games[i].game_version_minor))
{
sButInit.pTip = wrongVersionTip;
}
else if (strcmp(NetPlay.games[i].modlist,getModList()) != 0)
{
sButInit.pTip = badModTip;
}
else
{
std::string flags;
if (NetPlay.games[i].privateGame)
{
flags += " "; flags += _("[Password required]");
}
if (NetPlay.games[i].limits & NO_TANKS)
{
flags += " "; flags += _("[No Tanks]");
}
if (NetPlay.games[i].limits & NO_BORGS)
{
flags += " "; flags += _("[No Cyborgs]");
}
if (NetPlay.games[i].limits & NO_VTOLS)
{
flags += " "; flags += _("[No VTOLs]");
}
if (flags.empty())
{
ssprintf(tooltipbuffer[i], _("Hosted by %s"), NetPlay.games[i].hostname);
}
else
{
ssprintf(tooltipbuffer[i], _("Hosted by %s —%s"), NetPlay.games[i].hostname, flags.c_str());
}
sButInit.pTip = tooltipbuffer[i];
}
sButInit.UserData = i;
widgAddButton(psWScreen, &sButInit);
}
}
if (added < gcount)
{
sButInit = W_BUTINIT();
sButInit.formID = FRONTEND_BOTFORM;
sButInit.id = FRONTEND_NOGAMESAVAILABLE;
sButInit.x = 70;
sButInit.y = 378;
sButInit.style = WBUT_TXTCENTRE;
sButInit.width = FRONTEND_BUTWIDTH;
sButInit.UserData = 0; // store disable state
sButInit.height = FRONTEND_BUTHEIGHT;
sButInit.pDisplay = displayTextOption;
sButInit.FontID = font_large;
sButInit.pText = _("Can't find any games for your version.");
widgAddButton(psWScreen, &sButInit);
}
}
else
{
// display lobby message based on results.
// This is a 'button', not text so it can be hilighted/centered.
const char *txt;
W_BUTINIT sButInit;
switch (getLobbyError())
{
case ERROR_NOERROR:
txt = _("No games are available");
break;
case ERROR_FULL:
txt = _("Game is full");
break;
case ERROR_KICKED:
case ERROR_INVALID:
txt = _("You were kicked!");
break;
case ERROR_WRONGVERSION:
txt = _("Wrong Game Version!");
break;
case ERROR_WRONGDATA:
txt = _("You have an incompatible mod.");
break;
// AFAIK, the only way this can really happy is if the Host's file is named wrong, or a client side error.
case ERROR_UNKNOWNFILEISSUE:
txt = _("Host couldn't send file?");
debug(LOG_POPUP, "Warzone couldn't complete a file request.\n\nPossibly, Host's file is incorrect. Check your logs for more details.");
break;
case ERROR_WRONGPASSWORD:
txt = _("Incorrect Password!");
break;
case ERROR_HOSTDROPPED:
txt = _("Host has dropped connection!");
break;
case ERROR_CONNECTION:
default:
txt = _("Connection Error");
break;
}
// delete old widget if necessary
widgDelete(psWScreen,FRONTEND_NOGAMESAVAILABLE);
sButInit = W_BUTINIT();
sButInit.formID = FRONTEND_BOTFORM;
sButInit.id = FRONTEND_NOGAMESAVAILABLE;
sButInit.x = 70;
sButInit.y = 50;
sButInit.style = WBUT_TXTCENTRE;
sButInit.width = FRONTEND_BUTWIDTH;
sButInit.UserData = 0; // store disable state
sButInit.height = FRONTEND_BUTHEIGHT;
sButInit.pDisplay = displayTextOption;
sButInit.FontID = font_large;
sButInit.pText = txt;
widgAddButton(psWScreen, &sButInit);
}
}
static void removeGames(void)
{
int i;
for (i = 0; i<MaxGames; i++)
{
widgDelete(psWScreen, GAMES_GAMESTART+i); // remove old widget
}
widgDelete(psWScreen,FRONTEND_NOGAMESAVAILABLE);
}
void runGameFind(void )
{
static UDWORD lastupdate=0;
static char game_password[StringSize];
if (lastupdate > 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;
}
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_REFRESH,IMAGE_REFRESH,false); // Find Games button
//filter toggle
addMultiBut(psWScreen,FRONTEND_BOTFORM,MULTIOP_FILTER_TOGGLE, MULTIOP_CHATBOXW-MULTIOP_OKW-45,5,MULTIOP_OKW,MULTIOP_OKH,
_("Filter Games List"),IMAGE_FOG_OFF,IMAGE_FOG_OFF_HI,false); // Find Games button
if (safeSearch || disableLobbyRefresh)
{
widgHide(psWScreen, MULTIOP_REFRESH);
}
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);
}
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);
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<std::pair<W_BUTTON *, int> >::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<std::pair<W_BUTTON *, int> >::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<<i;
}
ingame.flags = flags;
}
// need to check for side effects.
static void addGameOptions()
{
widgDelete(psWScreen,MULTIOP_OPTIONS); // clear options list
widgDelete(psWScreen,FRONTEND_SIDETEXT3); // del text..
iV_SetFont(font_regular);
WIDGET *parent = widgGetFromID(psWScreen, FRONTEND_BACKDROP);
// draw options box.
IntFormAnimated *optionsForm = new IntFormAnimated(parent, false);
optionsForm->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
addMultiEditBox(MULTIOP_OPTIONS, MULTIOP_MAP , MCOL0, MROW3, _("Select Map"), game.map, IMAGE_EDIT_MAP, IMAGE_EDIT_MAP_HI, MULTIOP_MAP_ICON);
// disable for challenges
if (challengeActive)
{
widgSetButtonState(psWScreen, MULTIOP_MAP, WEDBS_DISABLE);
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<<i) != 0)
{
if (!skip)
{ // only add this once.
addBlueForm(MULTIOP_OPTIONS, MULTIOP_NO_SOMETHING, "", MULTIOP_HOSTX, MULTIOP_NO_SOMETHINGY, 41, 152);
}
addMultiBut(psWScreen, MULTIOP_NO_SOMETHING, MULTIOP_NO_SOMETHINGY + i, MULTIOP_NO_SOMETHINGX, y,
35, 28, _(limitIcons[i].desc),
limitIcons[i].icon, limitIcons[i].icon, limitIcons[i].icon);
y += 28 + 3;
skip = true;
}
}
}
// ////////////////////////////////////////////////////////////////////////////
// Colour functions
static bool safeToUseColour(unsigned player, unsigned otherPlayer)
{
// Player wants to take the colour from otherPlayer. May not take from a human otherPlayer, unless we're the host.
return player == otherPlayer || NetPlay.isHost || !isHumanPlayer(otherPlayer);
}
static void initChooser(int player)
{
// delete that players box,
widgDelete(psWScreen, MULTIOP_PLAYER_START + player);
// delete team chooser button
widgDelete(psWScreen, MULTIOP_TEAMS_START + player);
// delete 'ready' button
widgDelete(psWScreen, MULTIOP_READY_FORM_ID + player);
// delete 'colour' button
widgDelete(psWScreen, MULTIOP_COLOUR_START + player);
// remove any choosers already up
closeColourChooser();
closeTeamChooser();
}
static void addDifficultyChooser(int player)
{
closeColourChooser();
closeTeamChooser();
widgDelete(psWScreen, MULTIOP_AI_FORM);
widgDelete(psWScreen, MULTIOP_PLAYERS);
widgDelete(psWScreen, FRONTEND_SIDETEXT2);
difficultyChooserUp = 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, _("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() > 8)
{
debug(LOG_INFO, "You have too many AI's loaded for the GUI to handle. Only the first 8 will be shown.");
addConsoleMessage("You have too many AI's loaded for the GUI to handle. Only the first 8 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();
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);
widgSetButtonState(psWScreen,MULTIOP_MAP,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;
NETremRedirects();
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, game.map);
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_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);
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();
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);
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;
loadMapPreview(!isHoverPreview);
if (isHoverPreview)
{
sstrcpy(game.map, oldGameMap);
game.hash = oldGameHash;
game.maxPlayers = oldMaxPlayers;
}
else
{
loadMapSettings1();
}
widgSetString(psWScreen, MULTIOP_MAP, mapData->pName);
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.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
{
char buf[512] = {'\0'};
addPlayerBox(false); // Players
addGameOptions();
addChatBox();
if (NetPlay.isUPNP)
{
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);
}
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);
// 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_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<std::string, EcKey::Key> const &knownPlayers, std::string const &name, EcKey const &key)
{
if (key.empty())
{
return false;
}
std::map<std::string, EcKey::Key>::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<std::string, EcKey::Key> 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;
}