Пример #1
0
// accept and process incoming ping messages.
bool recvPing(NETQUEUE queue)
{
	bool	isNew = false;
	uint8_t	sender, us = selectedPlayer;
	uint8_t challenge[sizeof(pingChallenge)];
	EcKey::Sig challengeResponse;

	NETbeginDecode(queue, NET_PING);
	NETuint8_t(&sender);
	NETbool(&isNew);
	if (isNew)
	{
		NETbin(challenge, sizeof(pingChallenge));
	}
	else
	{
		NETbytes(&challengeResponse);
	}
	NETend();

	if (sender >= MAX_PLAYERS)
	{
		debug(LOG_ERROR, "Bad NET_PING packet, sender is %d", (int)sender);
		return false;
	}

	// If this is a new ping, respond to it
	if (isNew)
	{
		challengeResponse = getMultiStats(us).identity.sign(&challenge, sizeof(pingChallenge));

		NETbeginEncode(NETnetQueue(sender), NET_PING);
		// We are responding to a new ping
		isNew = false;

		NETuint8_t(&us);
		NETbool(&isNew);
		NETbytes(&challengeResponse);
		NETend();
	}
	// They are responding to one of our pings
	else
	{
		if (!getMultiStats(sender).identity.empty() && !getMultiStats(sender).identity.verify(challengeResponse, pingChallenge, sizeof(pingChallenge)))
		{
			// Either bad signature, or we sent more than one ping packet and this response is to an older one than the latest.
			debug(LOG_NEVER, "Bad and/or old NET_PING packet, alleged sender is %d", (int)sender);
			return false;
		}

		// Work out how long it took them to respond
		ingame.PingTimes[sender] = (realTime - PingSend[sender]) / 2;

		// Note that we have received it
		PingSend[sender] = 0;
	}

	return true;
}
Пример #2
0
// update players damage stats.
void updateMultiStatsDamage(UDWORD attacker, UDWORD defender, UDWORD inflicted)
{
	PLAYERSTATS st;

	if (Cheated)
	{
		return;
	}
	// FIXME: Why in the world are we using two different structs for stats when we can use only one?
	// Host controls self + AI, so update the scores for them as well.
	if (attacker < MAX_PLAYERS)
	{
		if (myResponsibility(attacker) && NetPlay.bComms)
		{
			st = getMultiStats(attacker);	// get stats
			if (NetPlay.bComms)
			{
				st.scoreToAdd += (2 * inflicted);
			}
			else
			{
				st.recentScore += (2 * inflicted);
			}
			setMultiStats(attacker, st, true);
		}
		else
		{
			ingame.skScores[attacker][0] += (2 * inflicted);	// increment skirmish players rough score.
		}
	}

	// FIXME: Why in the world are we using two different structs for stats when we can use only one?
	// Host controls self + AI, so update the scores for them as well.
	if (defender < MAX_PLAYERS)
	{
		if (myResponsibility(defender) && NetPlay.bComms)
		{
			st = getMultiStats(defender);	// get stats
			if (NetPlay.bComms)
			{
				st.scoreToAdd  -= inflicted;
			}
			else
			{
				st.recentScore  -= inflicted;
			}
			setMultiStats(defender, st, true);
		}
		else
		{
			ingame.skScores[defender][0] -= inflicted;	// increment skirmish players rough score.
		}
	}
}
Пример #3
0
// ////////////////////////////////////////////////////////////////////////////
// Setup Stuff for a new player.
void setupNewPlayer(UDWORD player)
{
	UDWORD i;

	ingame.PingTimes[player] = 0;					// Reset ping time
	ingame.JoiningInProgress[player] = true;			// Note that player is now joining
	ingame.DataIntegrity[player] = false;

	for (i = 0; i < MAX_PLAYERS; i++)				// Set all alliances to broken
	{
		alliances[selectedPlayer][i] = ALLIANCE_BROKEN;
		alliances[i][selectedPlayer] = ALLIANCE_BROKEN;
	}

	resetMultiVisibility(player);						// set visibility flags.

	setMultiStats(player, getMultiStats(player), true);  // get the players score

	if (selectedPlayer != player)
	{
		char buf[255];
		ssprintf(buf, _("%s is joining the game"), getPlayerName(player));
		addConsoleMessage(buf, DEFAULT_JUSTIFY, SYSTEM_MESSAGE);
	}
}
Пример #4
0
// update kills
void updateMultiStatsKills(BASE_OBJECT *psKilled,UDWORD player)
{
	PLAYERSTATS	st;

	if (Cheated)
	{
		return;
	}
	// FIXME: Why in the world are we using two different structs for stats when we can use only one?
	// Host controls self + AI, so update the scores for them as well.
	if(myResponsibility(player) && NetPlay.bComms)
	{
		st  = getMultiStats(player);

		if(NetPlay.bComms)
		{
			st.killsToAdd++;		// increase kill count;
		}
		else
		{
			st.recentKills++;
		}
		setMultiStats(player, st, true);
	}
	else
	{
		ingame.skScores[player][1]++;
	}
}
Пример #5
0
bool displayGameOver(bool bDidit)
{
	if (bDidit)
	{
		setPlayerHasWon(true);
		multiplayerWinSequence(true);
		if (bMultiPlayer)
		{
			updateMultiStatsWins();
		}
	}
	else
	{
		setPlayerHasLost(true);
		if (bMultiPlayer)
		{
			updateMultiStatsLoses();
		}
	}
	if (bMultiPlayer)
	{
		PLAYERSTATS st = getMultiStats(selectedPlayer);
		saveMultiStats(getPlayerName(selectedPlayer), getPlayerName(selectedPlayer), &st);
	}

	//clear out any mission widgets - timers etc that may be on the screen
	clearMissionWidgets();
	intAddMissionResult(bDidit, true);

	return true;
}
Пример #6
0
// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Score
// We use setMultiStats() to broadcast the score when needed.
bool sendScoreCheck(void)
{
	static UDWORD	lastsent = 0;

	if (lastsent > gameTime)
	{
		lastsent= 0;
	}

	if (gameTime - lastsent < SCORE_FREQUENCY)
	{
		return true;
	}

	lastsent = gameTime;

	// Broadcast any changes in other players, but not in FRONTEND!!!
	if (titleMode != MULTIOPTION && titleMode != MULTILIMIT)
	{
		uint8_t			i;

		for (i = 0; i < game.maxPlayers; i++)
		{
			// Host controls AI's scores + his own...
			if (myResponsibility(i))
			{
				// Send score to everyone else
				setMultiStats(i, getMultiStats(i), false);
			}
		}
	}

	return true;
}
Пример #7
0
//games lost.
void updateMultiStatsLoses(void)
{
	PLAYERSTATS	st;
	if (Cheated)
	{
		return;
	}
	st  = getMultiStats(selectedPlayer);
	++st.losses;
	setMultiStats(selectedPlayer, st, true);
}
Пример #8
0
// games won
void updateMultiStatsWins(void)
{
	PLAYERSTATS	st;
	if (Cheated)
	{
		return;
	}
	st  = getMultiStats(selectedPlayer);
	st.wins ++;
	setMultiStats(selectedPlayer, st, true);
}
Пример #9
0
// ////////////////////////////////////////////////////////////////////////////
// A Remote Player has joined the game.
bool MultiPlayerJoin(UDWORD playerIndex)
{
	if(widgGetFromID(psWScreen,IDRET_FORM))	// if ingame.
	{
		audio_QueueTrack( ID_CLAN_ENTER );
	}

	if(widgGetFromID(psWScreen,MULTIOP_PLAYERS))	// if in multimenu.
	{
		if (!multiRequestUp && (bHosted || ingame.localJoiningInProgress))
		{
			addPlayerBox(true);	// update the player box.
		}
	}

	if(NetPlay.isHost)		// host responsible for welcoming this player.
	{
		// if we've already received a request from this player don't reallocate.
		if (ingame.JoiningInProgress[playerIndex])
		{
			return true;
		}
		ASSERT(NetPlay.playercount <= MAX_PLAYERS, "Too many players!");

		// setup data for this player, then broadcast it to the other players.
		setupNewPlayer(playerIndex);						// setup all the guff for that player.
		if (bHosted)
		{
			sendOptions();
		}
		// if skirmish and game full, then kick...
		if (NetPlay.playercount > game.maxPlayers)
		{
			kickPlayer(playerIndex, "the game is already full.", ERROR_FULL);
		}
		// send everyone's stats to the new guy
		{
			int i;

			for (i = 0; i < MAX_PLAYERS; i++)
			{
				if (NetPlay.players[i].allocated)
				{
					setMultiStats(i, getMultiStats(i), false);
				}
			}
		}
	}
	return true;
}
Пример #10
0
// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Score
// We use setMultiStats() to broadcast the score when needed.
BOOL sendScoreCheck(void)
{
	static UDWORD	lastsent = 0;

	if (lastsent > gameTime)
	{
		lastsent= 0;
	}

	if (gameTime - lastsent < SCORE_FREQUENCY)
	{
		return true;
	}

	lastsent = gameTime;

	// Broadcast any changes in other players, but not in FRONTEND!!!
	if (titleMode != MULTIOPTION && titleMode != MULTILIMIT)
	{
		uint8_t			i;

		for (i = 0; i < MAX_PLAYERS; i++)
		{
			PLAYERSTATS		stats;

			// Host controls AI's scores + his own...
			if (myResponsibility(i))
			{
				// Update score
				stats = getMultiStats(i);

				// Add recently scored points
				stats.recentKills += stats.killsToAdd;
				stats.totalKills  += stats.killsToAdd;
				stats.recentScore += stats.scoreToAdd;
				stats.totalScore  += stats.scoreToAdd;

				// Zero them out
				stats.killsToAdd = stats.scoreToAdd = 0;

				// Send score to everyone else
				setMultiStats(i, stats, false);
			}
		}
	}

	return true;
}
Пример #11
0
////////////////////////////////
// at the end of every game.
bool multiGameShutdown(void)
{
	PLAYERSTATS	st;
	uint32_t        time;

	debug(LOG_NET, "%s is shutting down.", getPlayerName(selectedPlayer));

	sendLeavingMsg();							// say goodbye
	updateMultiStatsGames();					// update games played.

	st = getMultiStats(selectedPlayer);	// save stats

	saveMultiStats(getPlayerName(selectedPlayer), getPlayerName(selectedPlayer), &st);

	// if we terminate the socket too quickly, then, it is possible not to get the leave message
	time = wzGetTicks();
	while (wzGetTicks() - time < 1000)
	{
		wzYieldCurrentThread();  // TODO Make a wzDelay() function?
	}
	// close game
	NETclose();
	NETremRedirects();

	if (ingame.numStructureLimits)
	{
		ingame.numStructureLimits = 0;
		free(ingame.pStructureLimits);
		ingame.pStructureLimits = NULL;
	}
	ingame.flags = 0;

	ingame.localJoiningInProgress = false; // Clean up
	ingame.localOptionsReceived = false;
	ingame.bHostSetup = false;	// Dont attempt a host
	ingame.TimeEveryoneIsInGame = 0;
	ingame.startTime = 0;
	NetPlay.isHost					= false;
	bMultiPlayer					= false;	// Back to single player mode
	bMultiMessages					= false;
	selectedPlayer					= 0;		// Back to use player 0 (single player friendly)

	return true;
}
Пример #12
0
// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Score
// We use setMultiStats() to broadcast the score when needed.
bool sendScoreCheck(void)
{
	// Broadcast any changes in other players, but not in FRONTEND!!!
	if (titleMode != MULTIOPTION && titleMode != MULTILIMIT)
	{
		uint8_t			i;

		for (i = 0; i < game.maxPlayers; i++)
		{
			// Host controls AI's scores + his own...
			if (myResponsibility(i))
			{
				// Send score to everyone else
				setMultiStats(i, getMultiStats(i), false);
			}
		}
	}

	return true;
}
Пример #13
0
////////////////////////////////
// at the end of every game.
BOOL multiGameShutdown(void)
{
    PLAYERSTATS	st;

    debug(LOG_NET,"%s is shutting down.",getPlayerName(selectedPlayer));

    sendLeavingMsg();							// say goodbye
    updateMultiStatsGames();					// update games played.

    st = getMultiStats(selectedPlayer);	// save stats

    saveMultiStats(getPlayerName(selectedPlayer), getPlayerName(selectedPlayer), &st);

    // if we terminate the socket too quickly, then, it is possible not to get the leave message
    SDL_Delay(1000);
    // close game
    NETclose();
    NETremRedirects();

    if (ingame.numStructureLimits)
    {
        ingame.numStructureLimits = 0;
        free(ingame.pStructureLimits);
        ingame.pStructureLimits = NULL;
    }

    ingame.localJoiningInProgress = false; // Clean up
    ingame.localOptionsReceived = false;
    ingame.bHostSetup = false;	// Dont attempt a host
    NetPlay.isHost					= false;
    bMultiPlayer					= false;	// Back to single player mode
    bMultiMessages					= false;
    selectedPlayer					= 0;		// Back to use player 0 (single player friendly)

    return true;
}
Пример #14
0
static void displayMultiPlayer(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset, PIELIGHT *pColours)
{
	char			str[128];
	UDWORD			x					= xOffset+psWidget->x;
	UDWORD			y					= yOffset+psWidget->y;
	UDWORD			player = psWidget->UserData; //get the in game player number.
	Position		position;
	Vector3i 		rotation;

	if( responsibleFor(player,0) )
	{
		displayExtraGubbins(widgGetFromID(psWScreen,MULTIMENU_FORM)->height);
	}

	iV_SetFont(font_regular);											// font
	iV_SetTextColour(WZCOL_TEXT_BRIGHT);

	if(isHumanPlayer(player) || (game.type == SKIRMISH && player<game.maxPlayers) )
	{
		ssprintf(str, "%d: %s", NetPlay.players[player].position, getPlayerName(player));
		if (isHumanPlayer(player))
		{
			SetPlayerTextColor(alliances[selectedPlayer][player], player);
		}
		else
		{
			SetPlayerTextColor(alliances[selectedPlayer][player], player);
		}

		while(iV_GetTextWidth(str) >= (MULTIMENU_C0-MULTIMENU_C2-10) )
		{
			str[strlen(str)-1]='\0';
		}
		iV_DrawText(str, x+MULTIMENU_C2, y+MULTIMENU_FONT_OSET);

		//c3-7 alliance
		//manage buttons by showing or hiding them. gifts only in campaign,
		{
			if(game.alliance != NO_ALLIANCES)
			{
				if(alliances[selectedPlayer][player] == ALLIANCE_FORMED)
				{
					if(player != selectedPlayer &&  !giftsUp[player] )
					{
						if (game.alliance != ALLIANCES_TEAMS)
						{
							widgReveal(psWScreen,MULTIMENU_GIFT_RAD+ player);
							widgReveal(psWScreen,MULTIMENU_GIFT_RES+ player);
						}
						widgReveal(psWScreen,MULTIMENU_GIFT_DRO+ player);
						widgReveal(psWScreen,MULTIMENU_GIFT_POW+ player);
						giftsUp[player] = true;
					}
				}
				else
				{
					if(player != selectedPlayer && giftsUp[player])
					{
						if (game.alliance != ALLIANCES_TEAMS)
						{
							widgHide(psWScreen,MULTIMENU_GIFT_RAD+ player);
							widgHide(psWScreen,MULTIMENU_GIFT_RES+ player);
						}
						widgHide(psWScreen,MULTIMENU_GIFT_DRO+ player);
						widgHide(psWScreen,MULTIMENU_GIFT_POW+ player);
						giftsUp[player] = false;
					}
				}
			}
		}
	}
	if(isHumanPlayer(player))
	{
		SetPlayerTextColor(alliances[selectedPlayer][player], player);

		// Let's use the real score for MP games
		if (NetPlay.bComms)
		{
			//c8:score,
			if (Cheated)
			{
				sprintf(str,"(cheated)");
			}
			else
			{
				sprintf(str,"%d",getMultiStats(player).recentScore);
			}
			iV_DrawText(str, x+MULTIMENU_C8, y+MULTIMENU_FONT_OSET);

			//c9:kills,
			sprintf(str,"%d",getMultiStats(player).recentKills);
			iV_DrawText(str, x+MULTIMENU_C9, y+MULTIMENU_FONT_OSET);
		}
		else
		{
			// estimate of score for skirmish games
			sprintf(str,"%d",ingame.skScores[player][0]);
			iV_DrawText(str, x+MULTIMENU_C8, y+MULTIMENU_FONT_OSET);
			// estimated kills
			sprintf(str,"%d",ingame.skScores[player][1]);
			iV_DrawText(str, x+MULTIMENU_C9, y+MULTIMENU_FONT_OSET);
		}

		if(!getDebugMappingStatus())
		{
			//only show player's units, and nobody elses.
			//c10:units
			if (myResponsibility(player))
			{
				SetPlayerTextColor(alliances[selectedPlayer][player], player);
				sprintf(str, "%d", getNumDroids(player) + getNumTransporterDroids(player));
				iV_DrawText(str, x+MULTIMENU_C10, y+MULTIMENU_FONT_OSET);
			}

			if (runningMultiplayer())
			{
				//c11:ping
				if (player != selectedPlayer)
				{
					if (ingame.PingTimes[player] >= 2000)
					{
						sprintf(str,"???");
					}
					else
					{
						sprintf(str, "%d", ingame.PingTimes[player]);
					}
					iV_DrawText(str, x+MULTIMENU_C11, y+MULTIMENU_FONT_OSET);
				}
			}
			else
			{
				int num;
				STRUCTURE *temp;
				// NOTE, This tallys up *all* the structures you have. Test out via 'start with no base'.
				for (num = 0, temp = apsStructLists[player]; temp != NULL;num++,temp = temp->psNext);
				//c11: Structures
				sprintf(str, "%d", num);
				iV_DrawText(str, x+MULTIMENU_C11, y+MULTIMENU_FONT_OSET);
			}
		}
	}
	else
	{
		SetPlayerTextColor(alliances[selectedPlayer][player], player);

		// Let's use the real score for MP games
		if (NetPlay.bComms)
		{
			//c8:score,
			if (Cheated)
			{
				sprintf(str,"(cheated)");
			}
			else
			{
				sprintf(str,"%d",getMultiStats(player).recentScore);
			}
			iV_DrawText(str, x+MULTIMENU_C8, y+MULTIMENU_FONT_OSET);

			//c9:kills,
			sprintf(str,"%d",getMultiStats(player).recentKills);
			iV_DrawText(str, x+MULTIMENU_C9, y+MULTIMENU_FONT_OSET);
		}
		else
		{
			// estimate of score for skirmish games
			sprintf(str,"%d",ingame.skScores[player][0]);
			iV_DrawText(str, x+MULTIMENU_C8, y+MULTIMENU_FONT_OSET);
			// estimated kills
			sprintf(str,"%d",ingame.skScores[player][1]);
			iV_DrawText(str, x+MULTIMENU_C9, y+MULTIMENU_FONT_OSET);
		}
	}

	/* Display player power instead of number of played games
	  * and number of units instead of ping when in debug mode
	  */
	if(getDebugMappingStatus())			//Won't pass this when in both release and multiplayer modes
	{
		//c10: Total number of player units in possession
		sprintf(str,"%d",getNumDroids(player) + getNumTransporterDroids(player));
		iV_DrawText(str, x+MULTIMENU_C10, y+MULTIMENU_FONT_OSET);

		//c11: Player power
		sprintf(str, "%u", (int)getPower(player));
		iV_DrawText(str, MULTIMENU_FORM_X+MULTIMENU_C11, y+MULTIMENU_FONT_OSET);
	}

	// a droid of theirs.
	if(apsDroidLists[player])
	{
		pie_SetGeometricOffset( MULTIMENU_FORM_X+MULTIMENU_C1 ,y+MULTIMENU_PLAYER_H);
		rotation.x = -15;
		rotation.y = 45;
		rotation.z = 0;
		position.x = 0;
		position.y = 0;
		position.z = 2000;		//scale them!

		displayComponentButtonObject(apsDroidLists[player],&rotation,&position,false, 100);
	}

	// clean up widgets if player leaves while menu is up.
	if(!isHumanPlayer(player) && !(game.type == SKIRMISH && player<game.maxPlayers))
	{
		if(widgGetFromID(psWScreen,MULTIMENU_CHANNEL+player))
		{
			widgDelete(psWScreen,MULTIMENU_CHANNEL+ player);
		}

		if(widgGetFromID(psWScreen,MULTIMENU_ALLIANCE_BASE+player) )
		{
			widgDelete(psWScreen,MULTIMENU_ALLIANCE_BASE+ player);
			widgDelete(psWScreen,MULTIMENU_GIFT_RAD+ player);
			widgDelete(psWScreen,MULTIMENU_GIFT_RES+ player);
			widgDelete(psWScreen,MULTIMENU_GIFT_DRO+ player);
			widgDelete(psWScreen,MULTIMENU_GIFT_POW+ player);
			giftsUp[player] = false;
		}
	}
}
Пример #15
0
static void displayMultiPlayer(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
	char str[128];
	int x = xOffset + psWidget->x();
	int y = yOffset + psWidget->y();
	unsigned player = psWidget->UserData;  // Get the in game player number.

	if (responsibleFor(player, 0))
	{
		displayExtraGubbins(widgGetFromID(psWScreen,MULTIMENU_FORM)->height());
	}

	iV_SetFont(font_regular);  // font
	iV_SetTextColour(WZCOL_TEXT_BRIGHT);

	const bool isHuman = isHumanPlayer(player);
	const bool isAlly = aiCheckAlliances(selectedPlayer, player);
	const bool isSelectedPlayer = player == selectedPlayer;

	SetPlayerTextColor(alliances[selectedPlayer][player], player);

	if (isHuman || (game.type == SKIRMISH && player<game.maxPlayers) )
	{
		ssprintf(str, "%d: %s", NetPlay.players[player].position, getPlayerName(player));

		while (iV_GetTextWidth(str) >= MULTIMENU_C0 - MULTIMENU_C2 - 10)
		{
			str[strlen(str) - 1] = '\0';
		}
		iV_DrawText(str, x + MULTIMENU_C2, y + MULTIMENU_FONT_OSET);

		//c3-7 alliance
		//manage buttons by showing or hiding them. gifts only in campaign,
		if (alliancesCanGiveAnything(game.alliance))
		{
			if (isAlly && !isSelectedPlayer && !giftsUp[player] )
			{
				if (alliancesCanGiveResearchAndRadar(game.alliance))
				{
					widgReveal(psWScreen, MULTIMENU_GIFT_RAD + player);
					widgReveal(psWScreen, MULTIMENU_GIFT_RES + player);
				}
				widgReveal(psWScreen, MULTIMENU_GIFT_DRO + player);
				widgReveal(psWScreen, MULTIMENU_GIFT_POW + player);
				giftsUp[player] = true;
			}
			else if (!isAlly && !isSelectedPlayer && giftsUp[player])
			{
				if (alliancesCanGiveResearchAndRadar(game.alliance))
				{
					widgHide(psWScreen, MULTIMENU_GIFT_RAD + player);
					widgHide(psWScreen, MULTIMENU_GIFT_RES + player);
				}
				widgHide(psWScreen, MULTIMENU_GIFT_DRO + player);
				widgHide(psWScreen, MULTIMENU_GIFT_POW + player);
				giftsUp[player] = false;
			}
		}
	}

	// Let's use the real score for MP games
	if (NetPlay.bComms)
	{
		//c8:score,
		if (Cheated)
		{
			sprintf(str, "(cheated)");
		}
		else
		{
			sprintf(str, "%d", getMultiStats(player).recentScore);
		}
		iV_DrawText(str, x + MULTIMENU_C8, y + MULTIMENU_FONT_OSET);

		//c9:kills,
		sprintf(str, "%d", getMultiStats(player).recentKills);
		iV_DrawText(str, x + MULTIMENU_C9, y + MULTIMENU_FONT_OSET);
	}
	else
	{
		// estimate of score for skirmish games
		sprintf(str, "%d", ingame.skScores[player][0]);
		iV_DrawText(str, x + MULTIMENU_C8, y + MULTIMENU_FONT_OSET);
		// estimated kills
		sprintf(str, "%d", ingame.skScores[player][1]);
		iV_DrawText(str, x + MULTIMENU_C9, y + MULTIMENU_FONT_OSET);
	}

	//only show player's and allies' unit counts, and nobody elses.
	//c10:units
	if (isAlly || getDebugMappingStatus())
	{
		sprintf(str, "%d", getNumDroids(player) + getNumTransporterDroids(player));
		iV_DrawText(str, x + MULTIMENU_C10, y + MULTIMENU_FONT_OSET);
	}

	/* Display player power instead of number of played games
	  * and number of units instead of ping when in debug mode
	  */
	if (getDebugMappingStatus())  //Won't pass this when in both release and multiplayer modes
	{
		//c11: Player power
		sprintf(str, "%u", (int)getPower(player));
		iV_DrawText(str, MULTIMENU_FORM_X + MULTIMENU_C11, y + MULTIMENU_FONT_OSET);
	}
	else if (runningMultiplayer())
	{
		//c11:ping
		if (!isSelectedPlayer && isHuman)
		{
			if (ingame.PingTimes[player] < PING_LIMIT)
			{
				sprintf(str, "%03d", ingame.PingTimes[player]);
			}
			else
			{
				sprintf(str, "∞");
			}
			iV_DrawText(str, x + MULTIMENU_C11, y + MULTIMENU_FONT_OSET);
		}
	}
	else
	{
		//c11: Structures
		if (isAlly || getDebugMappingStatus())
		{
			// NOTE, This tallys up *all* the structures you have. Test out via 'start with no base'.
			int num = 0;
			for (STRUCTURE *temp = apsStructLists[player]; temp != NULL; temp = temp->psNext)
			{
				++num;
			}
			sprintf(str, "%d", num);
			iV_DrawText(str, x + MULTIMENU_C11, y + MULTIMENU_FONT_OSET);
		}
	}

	// a droid of theirs.
	DROID *displayDroid = apsDroidLists[player];
	while (displayDroid != NULL && !displayDroid->visible[selectedPlayer])
	{
		displayDroid = displayDroid->psNext;
	}
	if (displayDroid)
	{
		pie_SetGeometricOffset( MULTIMENU_FORM_X+MULTIMENU_C1 ,y+MULTIMENU_PLAYER_H);
		Vector3i rotation(-15, 45, 0);
		Position position(0, 0, BUTTON_DEPTH);  // Scale them.
		if (displayDroid->droidType == DROID_SUPERTRANSPORTER)
		{
			position.z = 7850;
		}
		else if (displayDroid->droidType == DROID_TRANSPORTER)
		{
			position.z = 4100;
		}

		displayComponentButtonObject(displayDroid, &rotation, &position, false, 100);
	}
	else if (apsDroidLists[player])
	{
		// Show that they have droids, but not which droids, since we can't see them.
		iV_DrawImageTc(IntImages, IMAGE_GENERIC_TANK, IMAGE_GENERIC_TANK_TC, MULTIMENU_FORM_X + MULTIMENU_C1 - iV_GetImageWidth(IntImages, IMAGE_GENERIC_TANK)/2, y + MULTIMENU_PLAYER_H - iV_GetImageHeight(IntImages, IMAGE_GENERIC_TANK), pal_GetTeamColour(getPlayerColour(player)));
	}

	// clean up widgets if player leaves while menu is up.
	if (!isHuman && !(game.type == SKIRMISH && player < game.maxPlayers))
	{
		if (widgGetFromID(psWScreen, MULTIMENU_CHANNEL + player) != NULL)
		{
			widgDelete(psWScreen, MULTIMENU_CHANNEL + player);
		}

		if (widgGetFromID(psWScreen, MULTIMENU_ALLIANCE_BASE + player) != NULL)
		{
			widgDelete(psWScreen, MULTIMENU_ALLIANCE_BASE + player);
			widgDelete(psWScreen, MULTIMENU_GIFT_RAD + player);
			widgDelete(psWScreen, MULTIMENU_GIFT_RES + player);
			widgDelete(psWScreen, MULTIMENU_GIFT_DRO + player);
			widgDelete(psWScreen, MULTIMENU_GIFT_POW + player);
			giftsUp[player] = false;
		}
	}
}