bool intDisplayMultiJoiningStatus(UBYTE joinCount) { UDWORD x,y,w,h; char sTmp[6]; w = RET_FORMWIDTH; h = RET_FORMHEIGHT; x = RET_X; y = RET_Y; // cameraToHome(selectedPlayer); // home the camera to the player. RenderWindowFrame(FRAME_NORMAL, x, y ,w, h); // draw a wee blu box. // display how far done.. iV_SetFont(font_regular); iV_DrawText(_("Players Still Joining"), x+(w/2)-(iV_GetTextWidth(_("Players Still Joining"))/2), y+(h/2)-8 ); unsigned playerCount = 0; // Calculate what NetPlay.playercount should be, which is apparently only non-zero for the host. for (unsigned player = 0; player < game.maxPlayers; ++player) { if (isHumanPlayer(player)) { ++playerCount; } } if (!playerCount) { return true; } iV_SetFont(font_large); sprintf(sTmp, "%d%%", PERCENT(playerCount - joinCount, playerCount)); iV_DrawText(sTmp ,x + (w / 2) - 10, y + (h / 2) + 10); iV_SetFont(font_small); int yStep = iV_GetTextLineSize(); int yPos = RET_Y - yStep*game.maxPlayers; static const std::string statusStrings[3] = {"☐ ", "☑ ", "☒ "}; for (unsigned player = 0; player < game.maxPlayers; ++player) { int status = -1; if (isHumanPlayer(player)) { status = ingame.JoiningInProgress[player]? 0 : 1; // Human player, still joining or joined. } else if (NetPlay.players[player].ai >= 0) { status = 2; // AI player (automatically joined). } if (status >= 0) { iV_DrawText((statusStrings[status] + getPlayerName(player)).c_str(), x + 5, yPos + yStep*NetPlay.players[player].position); } } return true; }
void fpathSetBlockingMap(PATHJOB *psJob) { if (fpathCurrentGameTime != gameTime) { // New tick, remove maps which are no longer needed. fpathCurrentGameTime = gameTime; fpathPrevBlockingMaps.swap(fpathBlockingMaps); fpathBlockingMaps.clear(); } // Figure out which map we are looking for. PathBlockingType type; type.gameTime = gameTime; type.propulsion = psJob->propulsion; type.owner = psJob->owner; type.moveType = psJob->moveType; // Find the map. std::list<PathBlockingMap>::iterator i = std::find(fpathBlockingMaps.begin(), fpathBlockingMaps.end(), type); if (i == fpathBlockingMaps.end()) { // Didn't find the map, so i does not point to a map. fpathBlockingMaps.push_back(PathBlockingMap()); --i; // i now points to an empty map with no data. Fill the map. i->type = type; std::vector<bool> &map = i->map; map.resize(mapWidth*mapHeight); uint32_t checksumMap = 0, checksumDangerMap = 0, factor = 0; for (int y = 0; y < mapHeight; ++y) for (int x = 0; x < mapWidth; ++x) { map[x + y*mapWidth] = fpathBaseBlockingTile(x, y, type.propulsion, type.owner, type.moveType); checksumMap ^= map[x + y*mapWidth]*(factor = 3*factor + 1); } if (!isHumanPlayer(type.owner) && type.moveType == FMT_MOVE) { std::vector<bool> &dangerMap = i->dangerMap; dangerMap.resize(mapWidth*mapHeight); for (int y = 0; y < mapHeight; ++y) for (int x = 0; x < mapWidth; ++x) { dangerMap[x + y*mapWidth] = auxTile(x, y, type.owner) & AUXBITS_THREAT; checksumDangerMap ^= map[x + y*mapWidth]*(factor = 3*factor + 1); } } syncDebug("blockingMap(%d,%d,%d,%d) = %08X %08X", gameTime, psJob->propulsion, psJob->owner, psJob->moveType, checksumMap, checksumDangerMap); } else { syncDebug("blockingMap(%d,%d,%d,%d) = cached", gameTime, psJob->propulsion, psJob->owner, psJob->moveType); } // i now points to the correct map. Make psJob->blockingMap point to it. psJob->blockingMap = &*i; }
bool intAddMultiMenu(void) { UDWORD i; //check for already open. if(widgGetFromID(psWScreen,MULTIMENU_FORM)) { intCloseMultiMenu(); return true; } if (intMode != INT_INTELMAP) { intResetScreen(false); } WIDGET *parent = psWScreen->psForm; // add form IntFormAnimated *multiMenuForm = new IntFormAnimated(parent); multiMenuForm->id = MULTIMENU_FORM; multiMenuForm->setGeometry(MULTIMENU_FORM_X, MULTIMENU_FORM_Y, MULTIMENU_FORM_W, MULTIMENU_PLAYER_H*game.maxPlayers + MULTIMENU_PLAYER_H + 7); // add any players for(i=0;i<MAX_PLAYERS;i++) { if(isHumanPlayer(i) || (game.type == SKIRMISH && i<game.maxPlayers && game.skDiff[i] ) ) { addMultiPlayer(i,NetPlay.players[i].position); } } // Add the close button. W_BUTINIT sButInit; sButInit.formID = MULTIMENU_FORM; sButInit.id = MULTIMENU_CLOSE; sButInit.x = MULTIMENU_FORM_W - CLOSE_WIDTH; sButInit.y = 0; sButInit.width = CLOSE_WIDTH; sButInit.height = CLOSE_HEIGHT; sButInit.pTip = _("Close"); sButInit.pDisplay = intDisplayImageHilight; sButInit.UserData = PACKDWORD_TRI(0,IMAGE_CLOSEHILIGHT , IMAGE_CLOSE); if (!widgAddButton(psWScreen, &sButInit)) { return false; } intShowPowerBar(); // add power bar if (intMode != INT_INTELMAP) { intMode = INT_MULTIMENU; // change interface mode. } MultiMenuUp = true; return true; }
//AI multiplayer message, send from a certain player index to another player index bool sendAIMessage(char *pStr, UDWORD player, UDWORD to) { UDWORD sendPlayer; //check if this is one of the local players, don't need net send then if (to == selectedPlayer || myResponsibility(to)) //(the only) human on this machine or AI on this machine { triggerEventChat(player, to, pStr); //Just show "him" the message displayAIMessage(pStr, player, to); //Received a console message from a player callback //store and call later //------------------------------------------------- if (!msgStackPush(CALL_AI_MSG,player,to,pStr,-1,-1,NULL)) { debug(LOG_ERROR, "sendAIMessage() - msgStackPush - stack failed"); return false; } } else //not a local player (use multiplayer mode) { if (!ingame.localOptionsReceived) { return true; } //find machine that is hosting this human or AI sendPlayer = whosResponsible(to); if (sendPlayer >= MAX_PLAYERS) { debug(LOG_ERROR, "sendAIMessage() - sendPlayer >= MAX_PLAYERS"); return false; } if (!isHumanPlayer(sendPlayer)) //NETsend can't send to non-humans { debug(LOG_ERROR, "sendAIMessage() - player is not human."); return false; } //send to the player who is hosting 'to' player (might be himself if human and not AI) NETbeginEncode(NETnetQueue(sendPlayer), NET_AITEXTMSG); NETuint32_t(&player); //save the actual sender //save the actual player that is to get this msg on the source machine (source can host many AIs) NETuint32_t(&to); //save the actual receiver (might not be the same as the one we are actually sending to, in case of AIs) NETstring(pStr, MAX_CONSOLE_STRING_LENGTH); // copy message in. NETend(); } return true; }
void displayAIMessage(char *pStr, SDWORD from, SDWORD to) { char tmp[255]; if (isHumanPlayer(to)) //display text only if receiver is the (human) host machine itself { strcpy(tmp, getPlayerName(from)); strcat(tmp, ": "); // seperator strcat(tmp, pStr); // add message addConsoleMessage(tmp, DEFAULT_JUSTIFY, from); } }
static UDWORD averagePing(void) { UDWORD i, count = 0, total = 0; for (i = 0; i < MAX_PLAYERS; i++) { if (isHumanPlayer(i)) { total += ingame.PingTimes[i]; count ++; } } return total / MAX(count, 1); }
// recv lassat info on the receiving end. bool recvLasSat(NETQUEUE queue) { BASE_OBJECT *psObj; UBYTE player, targetplayer; STRUCTURE *psStruct; uint32_t id, targetid; NETbeginDecode(queue, GAME_LASSAT); NETuint8_t(&player); NETuint32_t(&id); NETuint32_t(&targetid); NETuint8_t(&targetplayer); NETend(); psStruct = IdToStruct(id, player); psObj = IdToPointer(targetid, targetplayer); if (psStruct && !canGiveOrdersFor(queue.index, psStruct->player)) { syncDebug("Wrong player."); return false; } if (psStruct && psObj && psStruct->pStructureType->psWeapStat[0]->weaponSubClass == WSC_LAS_SAT) { // Lassats have just one weapon unsigned firePause = weaponFirePause(&asWeaponStats[psStruct->asWeaps[0].nStat], player); unsigned damLevel = PERCENT(psStruct->body, structureBody(psStruct)); if (damLevel < HEAVY_DAMAGE_LEVEL) { firePause += firePause; } if (isHumanPlayer(player) && gameTime - psStruct->asWeaps[0].lastFired <= firePause) { /* Too soon to fire again */ return true ^ false; // Return value meaningless and ignored. } // Give enemy no quarter, unleash the lasat proj_SendProjectile(&psStruct->asWeaps[0], NULL, player, psObj->pos, psObj, true, 0); psStruct->asWeaps[0].lastFired = gameTime; psStruct->asWeaps[0].ammo = 1; // abducting this field for keeping track of triggers // Play 5 second countdown message audio_QueueTrackPos(ID_SOUND_LAS_SAT_COUNTDOWN, psObj->pos.x, psObj->pos.y, psObj->pos.z); } return true; }
// returns player responsible for 'player' int whosResponsible(int player) { if (isHumanPlayer(player)) { return player; // Responsible for him or her self } else if (player == selectedPlayer) { return player; // We are responsibly for ourselves } else { return NET_HOST_ONLY; // host responsible for all AIs } }
BOOL recvPowerCheck(NETQUEUE queue) { uint8_t player; uint32_t synchTime; int64_t power; NETbeginDecode(queue, GAME_CHECK_POWER); NETuint8_t(&player); NETuint32_t(&synchTime); NETint64_t(&power); NETend(); if (powerCheckLastSent != synchTime) { debug(LOG_ERROR, "Power synch check out of synch!"); return false; } if (player >= MAX_PLAYERS) { debug(LOG_ERROR, "Bad GAME_CHECK_POWER packet: player is %d : %s", (int)player, isHumanPlayer(player) ? "Human" : "AI"); return false; } if (power != powerCheckLastPower[player]) { int64_t powerFrom = getPrecisePower(player); int64_t powerTo = powerFrom + power - powerCheckLastPower[player]; debug(LOG_SYNC, "GAME_CHECK_POWER: Adjusting power for player %d (%s) from %lf to %lf", (int)player, isHumanPlayer(player) ? "Human" : "AI", powerFrom/4294967296., powerTo/4294967296.); syncDebug("Adjusting power for player %d (%s) from %"PRId64" to %"PRId64"", (int)player, isHumanPlayer(player) ? "Human" : "AI", powerFrom, powerTo); setPrecisePower(player, powerTo); } return true; }
// //////////////////////////////////////////////////////////////////////////// // Droid checking info. keep position and damage in sync. BOOL sendCheck(void) { UDWORD i; NETgetBytesSent(); // update stats. NETgetBytesRecvd(); NETgetPacketsSent(); NETgetPacketsRecvd(); // dont send checks till all players are present. for(i=0;i<MAX_PLAYERS;i++) { if(isHumanPlayer(i) && ingame.JoiningInProgress[i]) { return true; } } // send Checks. note each send has it's own send criteria, so might not send anything. // Priority is droids -> structures -> power -> score -> ping sendDroidCheck(); sync_counter.sentDroidCheck++; sendStructureCheck(); sync_counter.sentStructureCheck++; sendPowerCheck(); sync_counter.sentPowerCheck++; if(okToSend()) { sendScoreCheck(); sync_counter.sentScoreCheck++; } else { sync_counter.unsentScoreCheck++; } if(okToSend()) { sendPing(); sync_counter.sentPing++; } else { sync_counter.unsentPing++; } return true; }
// Find a route for an DROID to a location in world coordinates FPATH_RETVAL fpathDroidRoute(DROID *psDroid, SDWORD tX, SDWORD tY, FPATH_MOVETYPE moveType) { bool acceptNearest; PROPULSION_STATS *psPropStats = getPropulsionStats(psDroid); // override for AI to blast our way through stuff if (!isHumanPlayer(psDroid->player) && moveType == FMT_MOVE) { moveType = (psDroid->asWeaps[0].nStat == 0) ? FMT_MOVE : FMT_ATTACK; } ASSERT_OR_RETURN(FPR_FAILED, psPropStats != NULL, "invalid propulsion stats pointer"); ASSERT_OR_RETURN(FPR_FAILED, psDroid->type == OBJ_DROID, "We got passed an object that isn't a DROID!"); // Check whether the start and end points of the route are blocking tiles and find an alternative if they are. Position startPos = psDroid->pos; Position endPos = Position(tX, tY, 0); StructureBounds dstStructure = getStructureBounds(worldTile(endPos.xy)->psObject); startPos = findNonblockingPosition(startPos, getPropulsionStats(psDroid)->propulsionType, psDroid->player, moveType); if (!dstStructure.valid()) // If there's a structure over the destination, ignore it, otherwise pathfind from somewhere around the obstruction. { endPos = findNonblockingPosition(endPos, getPropulsionStats(psDroid)->propulsionType, psDroid->player, moveType); } objTrace(psDroid->id, "Want to go to (%d, %d) -> (%d, %d), going (%d, %d) -> (%d, %d)", map_coord(psDroid->pos.x), map_coord(psDroid->pos.y), map_coord(tX), map_coord(tY), map_coord(startPos.x), map_coord(startPos.y), map_coord(endPos.x), map_coord(endPos.y)); switch (psDroid->order.type) { case DORDER_BUILD: case DORDER_LINEBUILD: // build a number of structures in a row (walls + bridges) dstStructure = getStructureBounds(psDroid->order.psStats, psDroid->order.pos, psDroid->order.direction); // Just need to get close enough to build (can be diagonally), do not need to reach the destination tile. // fallthrough case DORDER_HELPBUILD: // help to build a structure case DORDER_DEMOLISH: // demolish a structure case DORDER_REPAIR: acceptNearest = false; break; default: acceptNearest = true; break; } return fpathRoute(&psDroid->sMove, psDroid->id, startPos.x, startPos.y, endPos.x, endPos.y, psPropStats->propulsionType, psDroid->droidType, moveType, psDroid->player, acceptNearest, dstStructure); }
/*! * Gets a template from its name * relies on the name being unique (or it will return the first one it finds!) * \param pName Template name * \param player Player number * \pre pName has to be the unique, untranslated name! * \pre player \< MAX_PLAYERS */ DROID_TEMPLATE * getTemplateFromUniqueName(const char *pName, unsigned int player) { DROID_TEMPLATE *psCurr; DROID_TEMPLATE *list = apsStaticTemplates; // assume AI if (isHumanPlayer(player)) { list = apsDroidTemplates[player]; // was human } for (psCurr = list; psCurr != NULL; psCurr = psCurr->psNext) { if (strcmp(psCurr->pName, pName) == 0) { return psCurr; } } return NULL; }
/* Sets the player's text color depending on if alliance formed, or if dead. * \param mode the specified alliance * \param player the specified player */ static void SetPlayerTextColor( int mode, UDWORD player ) { // override color if they are dead... if (!apsDroidLists[player] && !apsStructLists[player]) { iV_SetTextColour(WZCOL_GREY); // dead text color } // the colors were chosen to match the FRIEND/FOE radar map colors. else if (mode == ALLIANCE_FORMED) { iV_SetTextColour(WZCOL_YELLOW); // Human alliance text color } else if (isHumanPlayer(player)) // Human player, no alliance { iV_SetTextColour(WZCOL_TEXT_BRIGHT); // Normal text color } else { iV_SetTextColour(WZCOL_RED); // Enemy color } }
// //////////////////////////////////////////////////////////////////////////// // receive droid creation information from other players bool recvDroid(NETQUEUE queue) { DROID_TEMPLATE t, *pT = &t; DROID *psDroid; uint8_t player; uint32_t id; Position pos; bool haveInitialOrders; INITIAL_DROID_ORDERS initialOrders; NETbeginDecode(queue, GAME_DEBUG_ADD_DROID); { int32_t droidType; NETuint8_t(&player); NETuint32_t(&id); NETPosition(&pos); NETqstring(pT->name); pT->id = pT->name; NETint32_t(&droidType); NETuint8_t(&pT->asParts[COMP_BODY]); NETuint8_t(&pT->asParts[COMP_BRAIN]); NETuint8_t(&pT->asParts[COMP_PROPULSION]); NETuint8_t(&pT->asParts[COMP_REPAIRUNIT]); NETuint8_t(&pT->asParts[COMP_ECM]); NETuint8_t(&pT->asParts[COMP_SENSOR]); NETuint8_t(&pT->asParts[COMP_CONSTRUCT]); NETint8_t(&pT->numWeaps); for (int i = 0; i < pT->numWeaps; i++) { NETuint8_t(&pT->asWeaps[i]); } NETbool(&haveInitialOrders); if (haveInitialOrders) { NETuint32_t(&initialOrders.secondaryOrder); NETint32_t(&initialOrders.moveToX); NETint32_t(&initialOrders.moveToY); NETuint32_t(&initialOrders.factoryId); // For making scripts happy. } pT->droidType = (DROID_TYPE)droidType; } NETend(); if (!getDebugMappingStatus() && bMultiPlayer) { debug(LOG_WARNING, "Failed to add droid for player %u.", NetPlay.players[queue.index].position); return false; } ASSERT_OR_RETURN(false, player < MAX_PLAYERS, "invalid player %u", player); debug(LOG_LIFE, "<=== getting Droid from %u id of %u ", player, id); if ((pos.x == 0 && pos.y == 0) || pos.x > world_coord(mapWidth) || pos.y > world_coord(mapHeight)) { debug(LOG_ERROR, "Received bad droid position (%d, %d) from %d about p%d (%s)", (int)pos.x, (int)pos.y, queue.index, player, isHumanPlayer(player) ? "Human" : "AI"); return false; } // Create that droid on this machine. psDroid = reallyBuildDroid(pT, pos, player, false); // If we were able to build the droid set it up if (psDroid) { psDroid->id = id; addDroid(psDroid, apsDroidLists); if (haveInitialOrders) { psDroid->secondaryOrder = initialOrders.secondaryOrder; psDroid->secondaryOrderPending = psDroid->secondaryOrder; orderDroidLoc(psDroid, DORDER_MOVE, initialOrders.moveToX, initialOrders.moveToY, ModeImmediate); cbNewDroid(IdToStruct(initialOrders.factoryId, ANYPLAYER), psDroid); } syncDebugDroid(psDroid, '+'); } else { debug(LOG_ERROR, "Packet from %d cannot create droid for p%d (%s)!", queue.index, player, isHumanPlayer(player) ? "Human" : "AI"); #ifdef DEBUG CONPRINTF(ConsoleString, (ConsoleString, "MULTIPLAYER: Couldn't build a remote droid, relying on checking to resync")); #endif return false; } return true; }
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; } } }
bool sendPing(void) { bool isNew = true; uint8_t player = selectedPlayer; int i; static UDWORD lastPing = 0; // Last time we sent a ping static UDWORD lastav = 0; // Last time we updated average // Only ping every so often if (lastPing > realTime) { lastPing = 0; } if (realTime - lastPing < PING_FREQUENCY) { return true; } lastPing = realTime; // If host, also update the average ping stat for joiners if (NetPlay.isHost) { if (lastav > realTime) { lastav = 0; } if (realTime - lastav > AV_PING_FREQUENCY) { NETsetGameFlags(2, averagePing()); lastav = realTime; } } /* * Before we send the ping, if any player failed to respond to the last one * we should re-enumerate the players. */ for (i = 0; i < MAX_PLAYERS; i++) { if (isHumanPlayer(i) && PingSend[i] && ingame.PingTimes[i] && i != selectedPlayer) { ingame.PingTimes[i] = PING_LIMIT; } else if (!isHumanPlayer(i) && PingSend[i] && ingame.PingTimes[i] && i != selectedPlayer) { ingame.PingTimes[i] = 0; } } uint64_t pingChallengei = (uint64_t)rand() << 32 | rand(); memcpy(pingChallenge, &pingChallengei, sizeof(pingChallenge)); NETbeginEncode(NETbroadcastQueue(), NET_PING); NETuint8_t(&player); NETbool(&isNew); NETbin(pingChallenge, sizeof(pingChallenge)); NETend(); // Note when we sent the ping for (i = 0; i < MAX_PLAYERS; i++) { PingSend[i] = realTime; } return true; }
// //////////////////////////////////////////////////////////////////////////// // receive droid information form other players. BOOL recvDroidInfo(NETQUEUE queue) { NETbeginDecode(queue, GAME_DROIDINFO); { QueuedDroidInfo info; memset(&info, 0x00, sizeof(info)); NETQueuedDroidInfo(&info); STRUCTURE_STATS *psStats = NULL; if (info.order == DORDER_BUILD || info.order == DORDER_LINEBUILD) { // Find structure target for (unsigned typeIndex = 0; typeIndex < numStructureStats; typeIndex++) { if (asStructureStats[typeIndex].ref == info.structRef) { psStats = asStructureStats + typeIndex; break; } } } if (info.subType) { syncDebug("Order=%s,%d(%d)", getDroidOrderName(info.order), info.destId, info.destType); } else { syncDebug("Order=%s,(%d,%d)", getDroidOrderName(info.order), info.x, info.y); } DROID_ORDER_DATA sOrder; memset(&sOrder, 0x00, sizeof(sOrder)); sOrder.order = info.order; sOrder.x = info.x; sOrder.y = info.y; sOrder.x2 = info.x2; sOrder.y2 = info.y2; sOrder.direction = info.direction; sOrder.psObj = processDroidTarget(info.destType, info.destId); sOrder.psStats = (BASE_STATS *)psStats; uint32_t num = 0; NETuint32_t(&num); for (unsigned n = 0; n < num; ++n) { // Get the next droid ID which is being given this order. uint32_t deltaDroidId = 0; NETuint32_t(&deltaDroidId); info.droidId += deltaDroidId; DROID *psDroid = NULL; if (!IdToDroid(info.droidId, ANYPLAYER, &psDroid)) { debug(LOG_NEVER, "Packet from %d refers to non-existent droid %u, [%s : p%d]", queue.index, info.droidId, isHumanPlayer(info.player) ? "Human" : "AI", info.player); syncDebug("Droid %d missing", info.droidId); continue; // Can't find the droid, so skip this droid. } CHECK_DROID(psDroid); syncDebugDroid(psDroid, '<'); psDroid->waitingForOwnReceiveDroidInfoMessage = false; /* * If the current order not is a command order and we are not a * commander yet are in the commander group remove us from it. */ if (hasCommander(psDroid)) { grpLeave(psDroid->psGroup, psDroid); } if (sOrder.psObj != TargetMissing) // Only do order if the target didn't die. { orderDroidBase(psDroid, &sOrder); } syncDebugDroid(psDroid, '>'); CHECK_DROID(psDroid); } } NETend(); return true; }
// //////////////////////////////////////////////////////////////////////////// // receive droid creation information from other players BOOL recvDroid(NETQUEUE queue) { DROID_TEMPLATE* pT; DROID* psDroid; uint8_t player; uint32_t id; Position pos; uint32_t templateID; BOOL haveInitialOrders; INITIAL_DROID_ORDERS initialOrders; NETbeginDecode(queue, GAME_DROID); { NETuint8_t(&player); NETuint32_t(&id); NETPosition(&pos); NETuint32_t(&templateID); NETbool(&haveInitialOrders); if (haveInitialOrders) { NETuint32_t(&initialOrders.secondaryOrder); NETint32_t(&initialOrders.moveToX); NETint32_t(&initialOrders.moveToY); NETuint32_t(&initialOrders.factoryId); // For making scripts happy. } pT = IdToTemplate(templateID, player); } NETend(); ASSERT( player < MAX_PLAYERS, "invalid player %u", player); debug(LOG_LIFE, "<=== getting Droid from %u id of %u ",player,id); if ((pos.x == 0 && pos.y == 0) || pos.x > world_coord(mapWidth) || pos.y > world_coord(mapHeight)) { debug(LOG_ERROR, "Received bad droid position (%d, %d) from %d about p%d (%s)", (int)pos.x, (int)pos.y, queue.index, player, isHumanPlayer(player) ? "Human" : "AI"); return false; } // If we can not find the template ask for the entire droid instead if (!pT) { debug(LOG_ERROR, "Packet from %d refers to non-existent template %u, [%s : p%d]", queue.index, templateID, isHumanPlayer(player) ? "Human" : "AI", player); return false; } // Create that droid on this machine. psDroid = reallyBuildDroid(pT, pos.x, pos.y, player, false); // If we were able to build the droid set it up if (psDroid) { psDroid->id = id; addDroid(psDroid, apsDroidLists); if (haveInitialOrders) { psDroid->secondaryOrder = initialOrders.secondaryOrder; orderDroidLoc(psDroid, DORDER_MOVE, initialOrders.moveToX, initialOrders.moveToY, ModeImmediate); cbNewDroid(IdToStruct(initialOrders.factoryId, ANYPLAYER), psDroid); } } else { debug(LOG_ERROR, "Packet from %d cannot create droid for p%d (%s)!", queue.index, player, isHumanPlayer(player) ? "Human" : "AI"); #ifdef DEBUG CONPRINTF(ConsoleString, (ConsoleString, "MULTIPLAYER: Couldn't build a remote droid, relying on checking to resync")); #endif return false; } return true; }
// Find a route for an DROID to a location in world coordinates FPATH_RETVAL fpathDroidRoute(DROID* psDroid, SDWORD tX, SDWORD tY, FPATH_MOVETYPE moveType) { bool acceptNearest; PROPULSION_STATS *psPropStats = getPropulsionStats(psDroid); // override for AI to blast our way through stuff if (!isHumanPlayer(psDroid->player) && moveType == FMT_MOVE) { moveType = (psDroid->asWeaps[0].nStat == 0) ? FMT_MOVE : FMT_ATTACK; } ASSERT_OR_RETURN(FPR_FAILED, psPropStats != NULL, "invalid propulsion stats pointer"); ASSERT_OR_RETURN(FPR_FAILED, psDroid->type == OBJ_DROID, "We got passed an object that isn't a DROID!"); // check whether the end point of the route // is a blocking tile and find an alternative if it is if (psDroid->sMove.Status != MOVEWAITROUTE && fpathDroidBlockingTile(psDroid, map_coord(tX), map_coord(tY), moveType)) { // find the nearest non blocking tile to the DROID int minDist = SDWORD_MAX; int nearestDir = NUM_DIR; int dir; objTrace(psDroid->id, "BLOCKED(%d,%d) - trying workaround", map_coord(tX), map_coord(tY)); for (dir = 0; dir < NUM_DIR; dir++) { int x = map_coord(tX) + aDirOffset[dir].x; int y = map_coord(tY) + aDirOffset[dir].y; if (tileOnMap(x, y) && !fpathDroidBlockingTile(psDroid, x, y, moveType)) { // pick the adjacent tile closest to our starting point int tileDist = fpathDistToTile(x, y, psDroid->pos.x, psDroid->pos.y); if (tileDist < minDist) { minDist = tileDist; nearestDir = dir; } } if (dir == NUM_BASIC - 1 && nearestDir != NUM_DIR) { break; // found a solution without checking at greater distance } } if (nearestDir == NUM_DIR) { // surrounded by blocking tiles, give up objTrace(psDroid->id, "route to (%d, %d) failed - target blocked", map_coord(tX), map_coord(tY)); return FPR_FAILED; } else { tX = world_coord(map_coord(tX) + aDirOffset[nearestDir].x) + TILE_UNITS / 2; tY = world_coord(map_coord(tY) + aDirOffset[nearestDir].y) + TILE_UNITS / 2; objTrace(psDroid->id, "Workaround found at (%d, %d)", map_coord(tX), map_coord(tY)); } } switch (psDroid->order) { case DORDER_BUILD: case DORDER_HELPBUILD: // help to build a structure case DORDER_LINEBUILD: // 6 - build a number of structures in a row (walls + bridges) case DORDER_DEMOLISH: // demolish a structure case DORDER_REPAIR: acceptNearest = false; break; default: acceptNearest = true; break; } return fpathRoute(&psDroid->sMove, psDroid->id, psDroid->pos.x, psDroid->pos.y, tX, tY, psPropStats->propulsionType, psDroid->droidType, moveType, psDroid->player, acceptNearest); }
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; } } }
// //////////////////////////////////////////////////////////////////////////// // receive droid information form other players. bool recvDroidInfo(NETQUEUE queue) { NETbeginDecode(queue, GAME_DROIDINFO); { QueuedDroidInfo info; memset(&info, 0x00, sizeof(info)); NETQueuedDroidInfo(&info); STRUCTURE_STATS *psStats = NULL; if (info.subType == LocOrder && (info.order == DORDER_BUILD || info.order == DORDER_LINEBUILD)) { // Find structure target for (unsigned typeIndex = 0; typeIndex < numStructureStats; typeIndex++) { if (asStructureStats[typeIndex].ref == info.structRef) { psStats = asStructureStats + typeIndex; break; } } } switch (info.subType) { case ObjOrder: syncDebug("Order=%s,%d(%d)", getDroidOrderName(info.order), info.destId, info.destType); break; case LocOrder: syncDebug("Order=%s,(%d,%d)", getDroidOrderName(info.order), info.pos.x, info.pos.y); break; case SecondaryOrder: syncDebug("SecondaryOrder=%d,%08X", (int)info.secOrder, (int)info.secState); break; } DROID_ORDER_DATA sOrder = infoToOrderData(info, psStats); uint32_t num = 0; NETuint32_t(&num); for (unsigned n = 0; n < num; ++n) { // Get the next droid ID which is being given this order. uint32_t deltaDroidId = 0; NETuint32_t(&deltaDroidId); info.droidId += deltaDroidId; DROID *psDroid = IdToDroid(info.droidId, info.player); if (!psDroid) { debug(LOG_NEVER, "Packet from %d refers to non-existent droid %u, [%s : p%d]", queue.index, info.droidId, isHumanPlayer(info.player) ? "Human" : "AI", info.player); syncDebug("Droid %d missing", info.droidId); continue; // Can't find the droid, so skip this droid. } CHECK_DROID(psDroid); syncDebugDroid(psDroid, '<'); switch (info.subType) { case ObjOrder: case LocOrder: /* * If the current order not is a command order and we are not a * commander yet are in the commander group remove us from it. */ if (hasCommander(psDroid)) { psDroid->psGroup->remove(psDroid); } if (sOrder.psObj != TargetMissing) // Only do order if the target didn't die. { if (!info.add) { orderDroidListEraseRange(psDroid, 0, psDroid->listSize + 1); // Clear all non-pending orders, plus the first pending order (which is probably the order we just received). orderDroidBase(psDroid, &sOrder); // Execute the order immediately (even if in the middle of another order. } else { orderDroidAdd(psDroid, &sOrder); // Add the order to the (non-pending) list. Will probably overwrite the corresponding pending order, assuming all pending orders were written to the list. } } break; case SecondaryOrder: // Set the droids secondary order turnOffMultiMsg(true); secondarySetState(psDroid, info.secOrder, info.secState); turnOffMultiMsg(false); break; } syncDebugDroid(psDroid, '>'); CHECK_DROID(psDroid); } } NETend(); return true; }
BOOL intAddMultiMenu(void) { W_FORMINIT sFormInit; W_BUTINIT sButInit; UDWORD i; //check for already open. if(widgGetFromID(psWScreen,MULTIMENU_FORM)) { intCloseMultiMenu(); return true; } if (intMode != INT_INTELMAP) { intResetScreen(false); } // add form memset(&sFormInit, 0, sizeof(W_FORMINIT)); sFormInit.formID = 0; sFormInit.id = MULTIMENU_FORM; sFormInit.style = WFORM_PLAIN; sFormInit.x =(SWORD)(MULTIMENU_FORM_X); sFormInit.y =(SWORD)(MULTIMENU_FORM_Y); sFormInit.width = MULTIMENU_FORM_W; sFormInit.height = (UWORD)(MULTIMENU_PLAYER_H*game.maxPlayers + MULTIMENU_PLAYER_H+7); //MULTIMENU_FORM_H; sFormInit.pDisplay = intOpenPlainForm; sFormInit.disableChildren = true; if (!widgAddForm(psWScreen, &sFormInit)) { return false; } // add any players for(i=0;i<MAX_PLAYERS;i++) { if(isHumanPlayer(i) || (game.type == SKIRMISH && i<game.maxPlayers && game.skDiff[i] ) ) { addMultiPlayer(i,NetPlay.players[i].position); } } // Add the close button. memset(&sButInit, 0, sizeof(W_BUTINIT)); sButInit.formID = MULTIMENU_FORM; sButInit.id = MULTIMENU_CLOSE; sButInit.style = WBUT_PLAIN; sButInit.x = MULTIMENU_FORM_W - CLOSE_WIDTH; sButInit.y = 0; sButInit.width = CLOSE_WIDTH; sButInit.height = CLOSE_HEIGHT; sButInit.pTip = _("Close"); sButInit.FontID = font_regular; sButInit.pDisplay = intDisplayImageHilight; sButInit.UserData = PACKDWORD_TRI(0,IMAGE_CLOSEHILIGHT , IMAGE_CLOSE); if (!widgAddButton(psWScreen, &sButInit)) { return false; } intShowPowerBar(); // add power bar if (intMode != INT_INTELMAP) { intMode = INT_MULTIMENU; // change interface mode. } MultiMenuUp = true; return true; }
// //////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////// // MultiPlayer main game loop code. bool multiPlayerLoop(void) { UDWORD i; UBYTE joinCount; joinCount =0; for(i=0;i<MAX_PLAYERS;i++) { if(isHumanPlayer(i) && ingame.JoiningInProgress[i] ) { joinCount++; } } if(joinCount) { setWidgetsStatus(false); bDisplayMultiJoiningStatus = joinCount; // someone is still joining! say So // deselect anything selected. selDroidDeselect(selectedPlayer); if(keyPressed(KEY_ESC) )// check for cancel { bDisplayMultiJoiningStatus = 0; setWidgetsStatus(true); setPlayerHasLost(true); } } else //everyone is in the game now! { if(bDisplayMultiJoiningStatus) { bDisplayMultiJoiningStatus = 0; setWidgetsStatus(true); } if (!ingame.TimeEveryoneIsInGame) { ingame.TimeEveryoneIsInGame = gameTime; debug(LOG_NET, "I have entered the game @ %d", ingame.TimeEveryoneIsInGame ); if (!NetPlay.isHost) { debug(LOG_NET, "=== Sending hash to host ==="); sendDataCheck(); } } if (NetPlay.bComms) { sendPing(); } // Only have to do this on a true MP game if (NetPlay.isHost && !ingame.isAllPlayersDataOK && NetPlay.bComms) { if (gameTime - ingame.TimeEveryoneIsInGame > GAME_TICKS_PER_SEC * 60) { // we waited 60 secs to make sure people didn't bypass the data integrity checks int index; for (index=0; index < MAX_PLAYERS; index++) { if (ingame.DataIntegrity[index] == false && isHumanPlayer(index) && index != NET_HOST_ONLY) { char msg[256] = {'\0'}; sprintf(msg, _("Kicking player %s, because they tried to bypass data integrity check!"), getPlayerName(index)); sendTextMessage(msg, true); addConsoleMessage(msg, LEFT_JUSTIFY, NOTIFY_MESSAGE); NETlogEntry(msg, SYNC_FLAG, index); #ifndef DEBUG kickPlayer(index, "invalid data!", ERROR_INVALID); #endif debug(LOG_WARNING, "Kicking Player %s (%u), they tried to bypass data integrity check!", getPlayerName(index), index); } } ingame.isAllPlayersDataOK = true; } } } // if player has won then process the win effects... if(testPlayerHasWon()) { multiplayerWinSequence(false); } return true; }
/* Do the AI for a droid */ void aiUpdateDroid(DROID *psDroid) { BASE_OBJECT *psTarget; bool lookForTarget, updateTarget; ASSERT(psDroid != NULL, "Invalid droid pointer"); if (!psDroid || isDead((BASE_OBJECT *)psDroid)) { return; } lookForTarget = false; updateTarget = false; // look for a target if doing nothing if (orderState(psDroid, DORDER_NONE) || orderState(psDroid, DORDER_GUARD) || orderState(psDroid, DORDER_HOLD)) { lookForTarget = true; } // but do not choose another target if doing anything while guarding // exception for sensors, to allow re-targetting when target is doomed if (orderState(psDroid, DORDER_GUARD) && psDroid->action != DACTION_NONE && psDroid->droidType != DROID_SENSOR) { lookForTarget = false; } // don't look for a target if sulking if (psDroid->action == DACTION_SULK) { lookForTarget = false; } /* Only try to update target if already have some target */ if (psDroid->action == DACTION_ATTACK || psDroid->action == DACTION_MOVEFIRE || psDroid->action == DACTION_MOVETOATTACK || psDroid->action == DACTION_ROTATETOATTACK) { updateTarget = true; } if ((orderState(psDroid, DORDER_OBSERVE) || orderState(psDroid, DORDER_ATTACKTARGET)) && psDroid->order.psObj && psDroid->order.psObj->died) { lookForTarget = true; updateTarget = false; } /* Don't update target if we are sent to attack and reached attack destination (attacking our target) */ if (orderState(psDroid, DORDER_ATTACK) && psDroid->psActionTarget[0] == psDroid->order.psObj) { updateTarget = false; } // don't look for a target if there are any queued orders if (psDroid->listSize > 0) { lookForTarget = false; updateTarget = false; } // don't allow units to start attacking if they will switch to guarding the commander if (hasCommander(psDroid)) { lookForTarget = false; updateTarget = false; } if (bMultiPlayer && isVtolDroid(psDroid) && isHumanPlayer(psDroid->player)) { lookForTarget = false; updateTarget = false; } // do not look for a target if droid is currently under direct control. if (driveModeActive() && (psDroid == driveGetDriven())) { lookForTarget = false; updateTarget = false; } // CB and VTOL CB droids can't autotarget. if (psDroid->droidType == DROID_SENSOR && !standardSensorDroid(psDroid)) { lookForTarget = false; updateTarget = false; } // do not attack if the attack level is wrong if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) != DSS_ALEV_ALWAYS) { lookForTarget = false; } /* For commanders and non-assigned non-commanders: look for a better target once in a while */ if (!lookForTarget && updateTarget && psDroid->numWeaps > 0 && !hasCommander(psDroid) && (psDroid->id + gameTime) / TARGET_UPD_SKIP_FRAMES != (psDroid->id + gameTime - deltaGameTime) / TARGET_UPD_SKIP_FRAMES) { for (int i = 0; i < psDroid->numWeaps; ++i) { updateAttackTarget((BASE_OBJECT *)psDroid, i); } } /* Null target - see if there is an enemy to attack */ if (lookForTarget && !updateTarget) { if (psDroid->droidType == DROID_SENSOR) { if (aiChooseSensorTarget((BASE_OBJECT *)psDroid, &psTarget)) { actionDroid(psDroid, DACTION_OBSERVE, psTarget); } } else { if (aiChooseTarget((BASE_OBJECT *)psDroid, &psTarget, 0, true, NULL)) { actionDroid(psDroid, DACTION_ATTACK, psTarget); } } } }
/* Do the AI for a droid */ void aiUpdateDroid(DROID *psDroid) { BASE_OBJECT *psTarget; BOOL lookForTarget,updateTarget; ASSERT(psDroid != NULL, "Invalid droid pointer"); if (!psDroid || isDead((BASE_OBJECT *)psDroid)) { return; } // HACK: we always want to update orders when NOT running a MP game, // and we don't want to update when the droid belongs to another human player if (!myResponsibility(psDroid->player) && bMultiPlayer && isHumanPlayer(psDroid->player)) { return; // we should not order this droid around } lookForTarget = false; updateTarget = false; // look for a target if doing nothing if (orderState(psDroid, DORDER_NONE) || orderState(psDroid, DORDER_GUARD) || orderState(psDroid, DORDER_TEMP_HOLD)) { lookForTarget = true; } // but do not choose another target if doing anything while guarding if (orderState(psDroid, DORDER_GUARD) && (psDroid->action != DACTION_NONE)) { lookForTarget = false; } // except when self-repairing if (psDroid->action == DACTION_DROIDREPAIR && psDroid->psActionTarget[0] == (BASE_OBJECT *)psDroid) { lookForTarget = true; } // don't look for a target if sulking if (psDroid->action == DACTION_SULK) { lookForTarget = false; } /* Only try to update target if already have some target */ if (psDroid->action == DACTION_ATTACK || psDroid->action == DACTION_MOVEFIRE || psDroid->action == DACTION_MOVETOATTACK || psDroid->action == DACTION_ROTATETOATTACK) { updateTarget = true; } if ((orderState(psDroid, DORDER_OBSERVE) || orderState(psDroid, DORDER_ATTACKTARGET)) && psDroid->psTarget && aiObjectIsProbablyDoomed(psDroid->psTarget)) { lookForTarget = true; updateTarget = false; } /* Don't update target if we are sent to attack and reached attack destination (attacking our target) */ if (orderState(psDroid, DORDER_ATTACK) && psDroid->psActionTarget[0] == psDroid->psTarget) { updateTarget = false; } // don't look for a target if there are any queued orders if (psDroid->listSize > 0) { lookForTarget = false; updateTarget = false; } // horrible check to stop droids looking for a target if // they would switch to the guard order in the order update loop if ((psDroid->order == DORDER_NONE) && (psDroid->player == selectedPlayer) && !isVtolDroid(psDroid) && secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_GUARD) { lookForTarget = false; updateTarget = false; } // don't allow units to start attacking if they will switch to guarding the commander if(hasCommander(psDroid)) { lookForTarget = false; updateTarget = false; } if(bMultiPlayer && isVtolDroid(psDroid) && isHumanPlayer(psDroid->player)) { lookForTarget = false; updateTarget = false; } // do not look for a target if droid is currently under direct control. if(driveModeActive() && (psDroid == driveGetDriven())) { lookForTarget = false; updateTarget = false; } // CB and VTOL CB droids can't autotarget. if (psDroid->droidType == DROID_SENSOR && !standardSensorDroid(psDroid)) { lookForTarget = false; updateTarget = false; } // do not attack if the attack level is wrong if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) != DSS_ALEV_ALWAYS) { lookForTarget = false; } /* For commanders and non-assigned non-commanders: look for a better target once in a while */ if(!lookForTarget && updateTarget) { if((psDroid->numWeaps > 0) && !hasCommander(psDroid)) //not assigned to commander { if((psDroid->id + gameTime)/TARGET_UPD_SKIP_FRAMES != (psDroid->id + gameTime - deltaGameTime)/TARGET_UPD_SKIP_FRAMES) { unsigned int i; (void)updateAttackTarget((BASE_OBJECT*)psDroid, 0); // this function always has to be called on weapon-slot 0 (even if ->numWeaps == 0) //updates all targets for (i = 1; i < psDroid->numWeaps; ++i) { (void)updateAttackTarget((BASE_OBJECT*)psDroid, i); } } } } /* Null target - see if there is an enemy to attack */ if (lookForTarget && !updateTarget) { if (psDroid->droidType == DROID_SENSOR) { if (aiChooseSensorTarget((BASE_OBJECT *)psDroid, &psTarget)) { orderDroidObj(psDroid, DORDER_OBSERVE, psTarget); } } else { if (aiChooseTarget((BASE_OBJECT *)psDroid, &psTarget, 0, true, NULL)) { orderDroidObj(psDroid, DORDER_ATTACKTARGET, psTarget); } } } }
// //////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////// // Text Messaging between players. proceed string with players to send to. // eg "123hi there" sends "hi there" to players 1,2 and 3. bool sendTextMessage(const char *pStr, bool all, uint32_t from) { bool normal = true; bool sendto[MAX_PLAYERS]; int posTable[MAX_PLAYERS]; UDWORD i; char display[MAX_CONSOLE_STRING_LENGTH]; char msg[MAX_CONSOLE_STRING_LENGTH]; char* curStr = (char*)pStr; memset(display,0x0, sizeof(display)); //clear buffer memset(msg,0x0, sizeof(display)); //clear buffer memset(sendto,0x0, sizeof(sendto)); //clear private flag memset(posTable,0x0, sizeof(posTable)); //clear buffer sstrcpy(msg, curStr); if (!all) { // create a position table for (i = 0; i < game.maxPlayers; i++) { posTable[NetPlay.players[i].position] = i; } if (curStr[0] == '.') { curStr++; for (i = 0; i < game.maxPlayers; i++) { if (i != from && aiCheckAlliances(from, i)) { sendto[i] = true; } } normal = false; if (!all) { sstrcpy(display, _("(allies")); } } for (; curStr[0] >= '0' && curStr[0] <= '9'; ++curStr) // for each 0..9 numeric char encountered { i = posTable[curStr[0]-'0']; if (normal) { sstrcpy(display, _("(private to ")); } else { sstrcat(display, ", "); } if ((isHumanPlayer(i) || (game.type == SKIRMISH && i<game.maxPlayers && game.skDiff[i] ) )) { sstrcat(display, getPlayerName(posTable[curStr[0]-'0'])); sendto[i] = true; } else { sstrcat(display, _("[invalid]")); } normal = false; } if (!normal) // lets user know it is a private message { if (curStr[0] == ' ') { curStr++; } sstrcat(display, ") "); sstrcat(display, curStr); } } if (all) //broadcast { NETbeginEncode(NETbroadcastQueue(), NET_TEXTMSG); NETuint32_t(&from); // who this msg is from NETstring(msg,MAX_CONSOLE_STRING_LENGTH); // the message to send NETend(); } else if (normal) { for (i = 0; i < MAX_PLAYERS; i++) { if (i != from && openchannels[i]) { if (isHumanPlayer(i)) { NETbeginEncode(NETnetQueue(i), NET_TEXTMSG); NETuint32_t(&from); // who this msg is from NETstring(msg,MAX_CONSOLE_STRING_LENGTH); // the message to send NETend(); } else //also send to AIs now (non-humans), needed for AI { sendAIMessage(msg, from, i); } } } } else //private msg { for (i = 0; i < MAX_PLAYERS; i++) { if (sendto[i]) { if (isHumanPlayer(i)) { NETbeginEncode(NETnetQueue(i), NET_TEXTMSG); NETuint32_t(&from); // who this msg is from NETstring(display, MAX_CONSOLE_STRING_LENGTH); // the message to send NETend(); } else //also send to AIs now (non-humans), needed for AI { sendAIMessage(curStr, from, i); } } } } // This is for local display if (from == selectedPlayer) { sstrcpy(msg, NetPlay.players[from].name); // name sstrcat(msg, ": "); // seperator sstrcat(msg, (normal ? curStr : display)); // add message addConsoleMessage(msg, DEFAULT_JUSTIFY, from); // display } return true; }
// Find the best nearest target for a droid // Returns integer representing target priority, -1 if failed SDWORD aiBestNearestTarget(DROID *psDroid, BASE_OBJECT **ppsObj, int weapon_slot, UWORD *targetOrigin) { SDWORD bestMod = 0,newMod, failure = -1; BASE_OBJECT *psTarget = NULL, *friendlyObj, *bestTarget = NULL, *iter, *targetInQuestion, *tempTarget; BOOL electronic = false; STRUCTURE *targetStructure; WEAPON_EFFECT weaponEffect; UWORD tmpOrigin = ORIGIN_UNKNOWN; // reset origin if (targetOrigin) { *targetOrigin = ORIGIN_UNKNOWN; } //don't bother looking if empty vtol droid if (vtolEmpty(psDroid)) { return failure; } /* Return if have no weapons */ // The ai orders a non-combat droid to patrol = crash without it... if ((psDroid->asWeaps[0].nStat == 0 || psDroid->numWeaps == 0) && psDroid->droidType != DROID_SENSOR) { return failure; } // Check if we have a CB target to begin with if (!proj_Direct(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat)) { WEAPON_STATS *psWStats = psWStats = psDroid->asWeaps[weapon_slot].nStat + asWeaponStats; bestTarget = aiSearchSensorTargets((BASE_OBJECT *)psDroid, weapon_slot, psWStats, &tmpOrigin); bestMod = targetAttackWeight(bestTarget, (BASE_OBJECT *)psDroid, weapon_slot); } weaponEffect = ((WEAPON_STATS *)(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat))->weaponEffect; electronic = electronicDroid(psDroid); // Range was previously 9*TILE_UNITS. Increasing this doesn't seem to help much, though. Not sure why. gridStartIterate(psDroid->pos.x, psDroid->pos.y, psDroid->sensorRange + 6*TILE_UNITS); for (iter = gridIterate(); iter != NULL; iter = gridIterate()) { friendlyObj = NULL; targetInQuestion = iter; /* This is a friendly unit, check if we can reuse its target */ if(aiCheckAlliances(targetInQuestion->player,psDroid->player)) { friendlyObj = targetInQuestion; targetInQuestion = NULL; /* Can we see what it is doing? */ if(friendlyObj->visible[psDroid->player]) { if(friendlyObj->type == OBJ_DROID) { DROID *friendlyDroid = (DROID *)friendlyObj; /* See if friendly droid has a target */ tempTarget = friendlyDroid->psActionTarget[0]; if(tempTarget && !aiObjectIsProbablyDoomed(tempTarget)) { //make sure a weapon droid is targeting it if(friendlyDroid->numWeaps > 0) { // make sure this target wasn't assigned explicitly to this droid if(friendlyDroid->order != DORDER_ATTACK) { // make sure target is near enough if (aiDroidHasRange(psDroid, tempTarget, weapon_slot)) { targetInQuestion = tempTarget; //consider this target } } } } } else if(friendlyObj->type == OBJ_STRUCTURE) { tempTarget = ((STRUCTURE*)friendlyObj)->psTarget[0]; if (tempTarget && !aiObjectIsProbablyDoomed(tempTarget) && aiDroidHasRange(psDroid, tempTarget, weapon_slot)) { targetInQuestion = tempTarget; } } } } if (targetInQuestion != NULL && targetInQuestion != (BASE_OBJECT *)psDroid // in case friendly unit had me as target && (targetInQuestion->type == OBJ_DROID || targetInQuestion->type == OBJ_STRUCTURE || targetInQuestion->type == OBJ_FEATURE) && targetInQuestion->visible[psDroid->player] && !aiCheckAlliances(targetInQuestion->player,psDroid->player) && validTarget((BASE_OBJECT *)psDroid, targetInQuestion, weapon_slot) && aiDroidHasRange(psDroid, targetInQuestion, weapon_slot)) { if (targetInQuestion->type == OBJ_DROID) { // in multiPlayer - don't attack Transporters with EW if (bMultiPlayer) { // if not electronic then valid target if (!electronic || (electronic && ((DROID *)targetInQuestion)->droidType != DROID_TRANSPORTER)) { //only a valid target if NOT a transporter psTarget = targetInQuestion; } } else { psTarget = targetInQuestion; } } else if (targetInQuestion->type == OBJ_STRUCTURE) { STRUCTURE *psStruct = (STRUCTURE *)targetInQuestion; if (electronic) { /* don't want to target structures with resistance of zero if using electronic warfare */ if (validStructResistance((STRUCTURE *)targetInQuestion)) { psTarget = targetInQuestion; } } else if (psStruct->asWeaps[weapon_slot].nStat > 0) { // structure with weapons - go for this psTarget = targetInQuestion; } else if ((psStruct->pStructureType->type != REF_WALL && psStruct->pStructureType->type != REF_WALLCORNER) || driveModeActive() || (bMultiPlayer && !isHumanPlayer(psDroid->player))) { psTarget = targetInQuestion; } } else if (targetInQuestion->type == OBJ_FEATURE && gameTime - psDroid->lastFrustratedTime < FRUSTRATED_TIME && ((FEATURE *)targetInQuestion)->psStats->damageable && !(game.scavengers && psDroid->player == 7)) // hack to avoid scavs blowing up their nice feature walls { psTarget = targetInQuestion; } /* Check if our weapon is most effective against this object */ if(psTarget != NULL && psTarget == targetInQuestion) //was assigned? { newMod = targetAttackWeight(psTarget, (BASE_OBJECT *)psDroid, weapon_slot); /* Remember this one if it's our best target so far */ if( newMod >= 0 && (newMod > bestMod || bestTarget == NULL)) { bestMod = newMod; tmpOrigin = ORIGIN_ALLY; bestTarget = psTarget; } } } } if (bestTarget) { ASSERT(!bestTarget->died, "aiBestNearestTarget: AI gave us a target that is already dead."); targetStructure = visGetBlockingWall((BASE_OBJECT *)psDroid, bestTarget); /* See if target is blocked by a wall; only affects direct weapons */ if (proj_Direct(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat) && targetStructure) { //are we any good against walls? if(asStructStrengthModifier[weaponEffect][targetStructure->pStructureType->strength] >= 100) //can attack atleast with default strength { bestTarget = (BASE_OBJECT *)targetStructure; //attack wall } } if (targetOrigin) { *targetOrigin = tmpOrigin; } *ppsObj = bestTarget; return bestMod; } return failure; }