// // At this time, we do NOT support messages for beacons // bool sendBeacon(int32_t locX, int32_t locY, int32_t forPlayer, int32_t sender, const char* pStr) { int sendPlayer; //debug(LOG_WZ, "sendBeacon: '%s'",pStr); //find machine that is hosting this human or AI sendPlayer = whosResponsible(forPlayer); if(sendPlayer >= MAX_PLAYERS) { debug(LOG_ERROR, "sendAIMessage() - whosResponsible() failed."); return false; } // I assume this is correct, looks like it sends it to ONLY that person, and the routine // kf_AddHelpBlip() iterates for each player it needs. NETbeginEncode(NETnetQueue(sendPlayer), NET_BEACONMSG); // send to the player who is hosting 'to' player (might be himself if human and not AI) NETint32_t(&sender); // save the actual sender // save the actual player that is to get this msg on the source machine (source can host many AIs) NETint32_t(&forPlayer); // save the actual receiver (might not be the same as the one we are actually sending to, in case of AIs) NETint32_t(&locX); // save location NETint32_t(&locY); // const_cast: need to cast away constness because of the const-incorrectness of NETstring (const-incorrect when sending/encoding a packet) NETstring((char*)pStr, MAX_CONSOLE_STRING_LENGTH); // copy message in. NETend(); return true; }
// 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; }
//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; }
bool sendDataCheck(void) { int i = 0; NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_DATA_CHECK); // only need to send to HOST for(i = 0; i < DATA_MAXDATA; i++) { NETuint32_t(&DataHash[i]); } NETend(); debug(LOG_NET, "sent hash to host"); return true; }
// //////////////////////////////////////////////////////////////////////////// // Tell the host we are leaving the game 'nicely', (we wanted to) and not // because we have some kind of error. (dropped or disconnected) bool sendLeavingMsg(void) { debug(LOG_NET, "We are leaving 'nicely'"); NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_PLAYER_LEAVING); { bool host = NetPlay.isHost; uint32_t id = selectedPlayer; NETuint32_t(&id); NETbool(&host); } NETend(); return true; }
// accept and process incoming ping messages. BOOL recvPing(NETQUEUE queue) { BOOL isNew; uint8_t sender, us = selectedPlayer; NETbeginDecode(queue, NET_PING); NETuint8_t(&sender); NETbool(&isNew); 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) { NETbeginEncode(NETnetQueue(sender), NET_PING); // We are responding to a new ping isNew = false; NETuint8_t(&us); NETbool(&isNew); NETend(); } // They are responding to one of our pings else { // Work out how long it took them to respond ingame.PingTimes[sender] = (gameTime2 - PingSend[sender]) / 2; // Note that we have received it PingSend[sender] = 0; } return true; }
// //////////////////////////////////////////////////////////////////////////// // options for a game. (usually recvd in frontend) void recvOptions(NETQUEUE queue) { unsigned int i; debug(LOG_NET, "Receiving options from host"); NETbeginDecode(queue, NET_OPTIONS); // Get general information about the game NETuint8_t(&game.type); NETstring(game.map, 128); NETbin(game.hash.bytes, game.hash.Bytes); uint32_t modHashesSize; NETuint32_t(&modHashesSize); ASSERT_OR_RETURN(, modHashesSize < 1000000, "Way too many mods %u", modHashesSize); game.modHashes.resize(modHashesSize); for (auto &hash : game.modHashes) { NETbin(hash.bytes, hash.Bytes); } NETuint8_t(&game.maxPlayers); NETstring(game.name, 128); NETuint32_t(&game.power); NETuint8_t(&game.base); NETuint8_t(&game.alliance); NETbool(&game.scavengers); NETbool(&game.isMapMod); for (i = 0; i < MAX_PLAYERS; i++) { NETuint8_t(&game.skDiff[i]); } // Send the list of who is still joining for (i = 0; i < MAX_PLAYERS; i++) { NETbool(&ingame.JoiningInProgress[i]); } // Alliances for (i = 0; i < MAX_PLAYERS; i++) { unsigned int j; for (j = 0; j < MAX_PLAYERS; j++) { NETuint8_t(&alliances[i][j]); } } netPlayersUpdated = true; // Free any structure limits we may have in-place if (ingame.numStructureLimits) { ingame.numStructureLimits = 0; free(ingame.pStructureLimits); ingame.pStructureLimits = NULL; } // Get the number of structure limits to expect NETuint32_t(&ingame.numStructureLimits); debug(LOG_NET, "Host is sending us %u structure limits", ingame.numStructureLimits); // If there were any changes allocate memory for them if (ingame.numStructureLimits) { ingame.pStructureLimits = (MULTISTRUCTLIMITS *)malloc(ingame.numStructureLimits * sizeof(MULTISTRUCTLIMITS)); } for (i = 0; i < ingame.numStructureLimits; i++) { NETuint32_t(&ingame.pStructureLimits[i].id); NETuint32_t(&ingame.pStructureLimits[i].limit); } NETuint8_t(&ingame.flags); NETend(); // Do the skirmish slider settings if they are up for (i = 0; i < MAX_PLAYERS; i++) { if (widgGetFromID(psWScreen, MULTIOP_SKSLIDE + i)) { widgSetSliderPos(psWScreen, MULTIOP_SKSLIDE + i, game.skDiff[i]); } } debug(LOG_INFO, "Rebuilding map list"); // clear out the old level list. levShutDown(); levInitialise(); rebuildSearchPath(mod_multiplay, true); // MUST rebuild search path for the new maps we just got! buildMapList(); bool haveData = true; auto requestFile = [&haveData](Sha256 &hash, char const *filename) { if (std::any_of(NetPlay.wzFiles.begin(), NetPlay.wzFiles.end(), [&hash](WZFile const &file) { return file.hash == hash; })) { debug(LOG_INFO, "Already requested file, continue waiting."); haveData = false; return false; // Downloading the file already } if (!PHYSFS_exists(filename)) { debug(LOG_INFO, "Creating new file %s", filename); } else if (findHashOfFile(filename) != hash) { debug(LOG_INFO, "Overwriting old incomplete or corrupt file %s", filename); } else { return false; // Have the file already. } NetPlay.wzFiles.emplace_back(PHYSFS_openWrite(filename), hash); // Request the map/mod from the host NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_FILE_REQUESTED); NETbin(hash.bytes, hash.Bytes); NETend(); haveData = false; return true; // Starting download now. }; LEVEL_DATASET *mapData = levFindDataSet(game.map, &game.hash); // See if we have the map or not if (mapData == nullptr) { char mapName[256]; sstrcpy(mapName, game.map); removeWildcards(mapName); if (strlen(mapName) >= 3 && mapName[strlen(mapName) - 3] == '-' && mapName[strlen(mapName) - 2] == 'T' && unsigned(mapName[strlen(mapName) - 1] - '1') < 3) { mapName[strlen(mapName) - 3] = '\0'; // Cut off "-T1", "-T2" or "-T3". } char filename[256]; ssprintf(filename, "maps/%dc-%s-%s.wz", game.maxPlayers, mapName, game.hash.toString().c_str()); // Wonder whether game.maxPlayers is initialised already? if (requestFile(game.hash, filename)) { debug(LOG_INFO, "Map was not found, requesting map %s from host, type %d", game.map, game.isMapMod); addConsoleMessage("MAP REQUESTED!", DEFAULT_JUSTIFY, SYSTEM_MESSAGE); } else { debug(LOG_FATAL, "Can't load map %s, even though we downloaded %s", game.map, filename); abort(); } } for (Sha256 &hash : game.modHashes) { char filename[256]; ssprintf(filename, "mods/downloads/%s", hash.toString().c_str()); if (requestFile(hash, filename)) { debug(LOG_INFO, "Mod was not found, requesting mod %s from host", hash.toString().c_str()); addConsoleMessage("MOD REQUESTED!", DEFAULT_JUSTIFY, SYSTEM_MESSAGE); } } if (mapData && CheckForMod(mapData->realFileName)) { char const *str = game.isMapMod ? _("Warning, this is a map-mod, it could alter normal gameplay.") : _("Warning, HOST has altered the game code, and can't be trusted!"); addConsoleMessage(str, DEFAULT_JUSTIFY, NOTIFY_MESSAGE); game.isMapMod = true; } if (mapData) { loadMapPreview(false); } }
// //////////////////////////////////////////////////////////////////////////// // options for a game. (usually recvd in frontend) void recvOptions(NETQUEUE queue) { unsigned int i; debug(LOG_NET, "Receiving options from host"); NETbeginDecode(queue, NET_OPTIONS); // Get general information about the game NETuint8_t(&game.type); NETstring(game.map, 128); NETbin(game.hash.bytes, game.hash.Bytes); NETuint8_t(&game.maxPlayers); NETstring(game.name, 128); NETuint32_t(&game.power); NETuint8_t(&game.base); NETuint8_t(&game.alliance); NETbool(&game.scavengers); for (i = 0; i < MAX_PLAYERS; i++) { NETuint8_t(&game.skDiff[i]); } // Send the list of who is still joining for (i = 0; i < MAX_PLAYERS; i++) { NETbool(&ingame.JoiningInProgress[i]); } // Alliances for (i = 0; i < MAX_PLAYERS; i++) { unsigned int j; for (j = 0; j < MAX_PLAYERS; j++) { NETuint8_t(&alliances[i][j]); } } netPlayersUpdated = true; // Free any structure limits we may have in-place if (ingame.numStructureLimits) { ingame.numStructureLimits = 0; free(ingame.pStructureLimits); ingame.pStructureLimits = NULL; } // Get the number of structure limits to expect NETuint32_t(&ingame.numStructureLimits); debug(LOG_NET, "Host is sending us %u structure limits", ingame.numStructureLimits); // If there were any changes allocate memory for them if (ingame.numStructureLimits) { ingame.pStructureLimits = (MULTISTRUCTLIMITS *)malloc(ingame.numStructureLimits * sizeof(MULTISTRUCTLIMITS)); } for (i = 0; i < ingame.numStructureLimits; i++) { NETuint32_t(&ingame.pStructureLimits[i].id); NETuint32_t(&ingame.pStructureLimits[i].limit); } NETuint8_t(&ingame.flags); NETend(); // Do the skirmish slider settings if they are up for (i = 0; i < MAX_PLAYERS; i++) { if (widgGetFromID(psWScreen, MULTIOP_SKSLIDE + i)) { widgSetSliderPos(psWScreen, MULTIOP_SKSLIDE + i, game.skDiff[i]); } } debug(LOG_INFO, "Rebuilding map list"); // clear out the old level list. levShutDown(); levInitialise(); setCurrentMap(NULL, 42); rebuildSearchPath(mod_multiplay, true); // MUST rebuild search path for the new maps we just got! buildMapList(); // See if we have the map or not if (levFindDataSet(game.map, &game.hash) == NULL) { uint32_t player = selectedPlayer; debug(LOG_INFO, "Map was not found, requesting map %s from host.", game.map); // Request the map from the host NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_FILE_REQUESTED); NETuint32_t(&player); NETend(); addConsoleMessage("MAP REQUESTED!", DEFAULT_JUSTIFY, SYSTEM_MESSAGE); } else { loadMapPreview(false); } }
// //////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////// // 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; }