// 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; }
// 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. } } }
// //////////////////////////////////////////////////////////////////////////// // 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); } }
// 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]++; } }
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; }
// //////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////// // 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; }
//games lost. void updateMultiStatsLoses(void) { PLAYERSTATS st; if (Cheated) { return; } st = getMultiStats(selectedPlayer); ++st.losses; setMultiStats(selectedPlayer, st, true); }
// games won void updateMultiStatsWins(void) { PLAYERSTATS st; if (Cheated) { return; } st = getMultiStats(selectedPlayer); st.wins ++; setMultiStats(selectedPlayer, st, true); }
// //////////////////////////////////////////////////////////////////////////// // A Remote Player has joined the game. bool MultiPlayerJoin(UDWORD playerIndex) { if(widgGetFromID(psWScreen,IDRET_FORM)) // if ingame. { audio_QueueTrack( ID_CLAN_ENTER ); } if(widgGetFromID(psWScreen,MULTIOP_PLAYERS)) // if in multimenu. { if (!multiRequestUp && (bHosted || ingame.localJoiningInProgress)) { addPlayerBox(true); // update the player box. } } if(NetPlay.isHost) // host responsible for welcoming this player. { // if we've already received a request from this player don't reallocate. if (ingame.JoiningInProgress[playerIndex]) { return true; } ASSERT(NetPlay.playercount <= MAX_PLAYERS, "Too many players!"); // setup data for this player, then broadcast it to the other players. setupNewPlayer(playerIndex); // setup all the guff for that player. if (bHosted) { sendOptions(); } // if skirmish and game full, then kick... if (NetPlay.playercount > game.maxPlayers) { kickPlayer(playerIndex, "the game is already full.", ERROR_FULL); } // send everyone's stats to the new guy { int i; for (i = 0; i < MAX_PLAYERS; i++) { if (NetPlay.players[i].allocated) { setMultiStats(i, getMultiStats(i), false); } } } } return true; }
// //////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////// // 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; }
//////////////////////////////// // 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; }
// //////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////// // 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; }
//////////////////////////////// // 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; }
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; } } }
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; } } }