/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2007 Warzone Resurrection Project Warzone 2100 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Warzone 2100 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Multiplay.c * * Alex Lee, Sep97, Pumpkin Studios * * Contains the day to day networking stuff, and received message handler. */ #include #include "lib/framework/frame.h" #include "lib/framework/input.h" #include "lib/framework/strres.h" #include "map.h" #include "stats.h" // for templates. #include "game.h" // for loading maps #include "hci.h" #include // for recording ping times. #include "research.h" #include "display3d.h" // for changing the viewpoint #include "console.h" // for screen messages #include "power.h" #include "cmddroid.h" // for commanddroidupdatekills #include "wrappers.h" // for game over #include "component.h" #include "frontend.h" #include "lib/sound/audio.h" #include "lib/sound/audio_id.h" #include "levels.h" #include "selection.h" #include "init.h" #include "warcam.h" // these 4 for fireworks #include "effects.h" #include "lib/gamelib/gtime.h" #include "keybind.h" #include "lib/script/script.h" //Because of "ScriptTabs.h" #include "scripttabs.h" //because of CALL_AI_MSG #include "scriptcb.h" //for console callback #include "scriptfuncs.h" #include "lib/netplay/netplay.h" // the netplay library. #include "multiplay.h" // warzone net stuff. #include "multijoin.h" // player management stuff. #include "multirecv.h" // incoming messages stuff #include "multistat.h" #include "multigifts.h" // gifts and alliances. // //////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////// // globals. BOOL bMultiPlayer = FALSE; // true when more than 1 player. char sForceName[256] = "Default"; SDWORD player2dpid[MAX_PLAYERS] ={0,0,0,0,0,0,0,0}; //stores dpids of each player. FILTHY HACK (ASSUMES 8 players) //UDWORD arenaPlayersReceived=0; BOOL openchannels[MAX_PLAYERS]={TRUE}; UBYTE bDisplayMultiJoiningStatus; MULTIPLAYERGAME game; //info to describe game. MULTIPLAYERINGAME ingame; BOOL bSendingMap = FALSE; // map broadcasting. char tempString[12]; char beaconReceiveMsg[MAX_PLAYERS][MAX_CONSOLE_STRING_LENGTH]; //beacon msg for each player char playerName[MAX_PLAYERS][MAX_NAME_SIZE]; //Array to store all player names (humans and AIs) ///////////////////////////////////// /* multiplayer message stack stuff */ ///////////////////////////////////// #define MAX_MSG_STACK 50 #define MAX_STR 255 static char msgStr[MAX_MSG_STACK][MAX_STR]; static SDWORD msgPlFrom[MAX_MSG_STACK]; static SDWORD msgPlTo[MAX_MSG_STACK]; static SDWORD callbackType[MAX_MSG_STACK]; static SDWORD locx[MAX_MSG_STACK]; static SDWORD locy[MAX_MSG_STACK]; static DROID *msgDroid[MAX_MSG_STACK]; static SDWORD msgStackPos = -1; //top element pointer // //////////////////////////////////////////////////////////////////////////// // Remote Prototypes extern RESEARCH* asResearch; //list of possible research items. extern PLAYER_RESEARCH* asPlayerResList[MAX_PLAYERS]; // //////////////////////////////////////////////////////////////////////////// // Local Prototypes static BOOL recvBeacon(NETMSG *pMsg); static BOOL recvDestroyTemplate(NETMSG *pMsg); static BOOL recvResearch(NETMSG *pMsg); // //////////////////////////////////////////////////////////////////////////// // temporarily disable multiplayer mode. BOOL turnOffMultiMsg(BOOL bDoit) { static BOOL bTemp; if(bDoit) // turn off msgs. { if(bTemp == TRUE) { debug(LOG_NET, "turnOffMultiMsg: multiple calls to turn off"); } if(bMultiPlayer) { bMultiPlayer = FALSE; bTemp = TRUE; } } else // turn on msgs. { if(bTemp) { bMultiPlayer = TRUE; bTemp = FALSE; } } return TRUE; } // //////////////////////////////////////////////////////////////////////////// // throw a pary when you win! BOOL multiplayerWinSequence(BOOL firstCall) { static Vector3i pos; Vector3i pos2; static UDWORD last=0; float rotAmount; STRUCTURE *psStruct; if(firstCall) { pos = cameraToHome(selectedPlayer,TRUE); // pan the camera to home if not already doing so last =0; // stop all research CancelAllResearch(selectedPlayer); // stop all manufacture. for(psStruct=apsStructLists[selectedPlayer];psStruct;psStruct = psStruct->psNext) { if (StructIsFactory(psStruct)) { if (((FACTORY *)psStruct->pFunctionality)->psSubject)//check if active { cancelProduction(psStruct); } } } } // rotate world if(!getWarCamStatus()) { rotAmount = timeAdjustedIncrement(MAP_SPIN_RATE / 12, TRUE); player.r.y += rotAmount; } if(last > gameTime)last= 0; if((gameTime-last) < 500 ) // only if not done recently. { return TRUE; } last = gameTime; if(rand()%3 == 0) { pos2=pos; pos2.x += (rand() % world_coord(8)) - world_coord(4); pos2.z += (rand() % world_coord(8)) - world_coord(4); if (pos2.x < 0) pos2.x = 128; if ((unsigned)pos2.x > world_coord(mapWidth)) pos2.x = world_coord(mapWidth); if (pos2.z < 0) pos2.z = 128; if ((unsigned)pos2.z > world_coord(mapHeight)) pos2.z = world_coord(mapHeight); addEffect(&pos2,EFFECT_FIREWORK,FIREWORK_TYPE_LAUNCHER,FALSE,NULL,0); // throw up some fire works. } // show the score.. return TRUE; } // //////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////// // MultiPlayer main game loop code. BOOL multiPlayerLoop(void) { UDWORD i; UBYTE joinCount; sendCheck(); // send some checking info if possible processMultiPlayerArtifacts(); // process artifacts joinCount =0; for(i=0;iid !=id) )d=d->psNext; if(d) { *psDroid = d; return TRUE; } } return FALSE; } else // find the droid, given player { d = apsDroidLists[player]; while( (d != NULL ) && (d->id !=id))d=d->psNext; if(d) { *psDroid = d; return TRUE; } return FALSE; } } // //////////////////////////////////////////////////////////////////////////// // find a structure STRUCTURE *IdToStruct(UDWORD id,UDWORD player) { STRUCTURE *psStr = NULL; UDWORD i; if(player == ANYPLAYER) { for(i=0;iid != id)); psStr=psStr->psNext); if(psStr) { return psStr; } } } else { for(psStr=apsStructLists[player];((psStr != NULL )&&(psStr->id != id) );psStr=psStr->psNext); } return psStr; } // //////////////////////////////////////////////////////////////////////////// // find a feature FEATURE *IdToFeature(UDWORD id,UDWORD player) { FEATURE *psF =NULL; UDWORD i; if(player == ANYPLAYER) { for(i=0;iid != id)); psF=psF->psNext); if(psF) { return psF; } } } else { for(psF=apsFeatureLists[player];((psF != NULL )&&(psF->id != id) );psF=psF->psNext); } return psF; } // //////////////////////////////////////////////////////////////////////////// DROID_TEMPLATE *IdToTemplate(UDWORD tempId,UDWORD player) { DROID_TEMPLATE *psTempl = NULL; UDWORD i; if(player != ANYPLAYER) { for (psTempl = apsDroidTemplates[player]; // follow templates (psTempl && (psTempl->multiPlayerID != tempId )); psTempl = psTempl->psNext); } else { // REALLY DANGEROUS!!! ID's are NOT assumed to be unique for TEMPLATES. debug( LOG_NEVER, "Really Dodgy Check performed for a template" ); for(i=0;imultiPlayerID != tempId )); psTempl = psTempl->psNext); if(psTempl) { return psTempl; } } } return psTempl; } // the same as above, but only checks names in similarity. DROID_TEMPLATE *NameToTemplate(const char *sName,UDWORD player) { DROID_TEMPLATE *psTempl = NULL; for (psTempl = apsDroidTemplates[player]; // follow templates (psTempl && (strcmp(psTempl->aName,sName) != 0) ); psTempl = psTempl->psNext); return psTempl; } ///////////////////////////////////////////////////////////////////////////////// // Returns a pointer to base object, given an id and optionally a player. BASE_OBJECT *IdToPointer(UDWORD id,UDWORD player) { DROID *pD; STRUCTURE *pS; FEATURE *pF; // droids. if (IdToDroid(id,player,&pD)) { return (BASE_OBJECT*)pD; } // structures pS = IdToStruct(id,player); if(pS) { return (BASE_OBJECT*)pS; } // features pF = IdToFeature(id,player); if(pF) { return (BASE_OBJECT*)pF; } return NULL; } // //////////////////////////////////////////////////////////////////////////// // return a players name. char *getPlayerName(UDWORD player) { UDWORD i; ASSERT( player < MAX_PLAYERS , "getPlayerName: wrong player index: %d", player); //Try NetPlay.playerName first (since supports AIs) if(game.type != CAMPAIGN) if(strcmp(playerName[player], "") != 0) return (char*)&playerName[player]; //Use the ordinary way if failed for(i=0;i MAX_PLAYERS) { ASSERT(FALSE, "setPlayerName: wrong player index (%d)", player); return FALSE; } strcpy(playerName[player],sName); return TRUE; } // //////////////////////////////////////////////////////////////////////////// // to determine human/computer players and responsibilities of each.. BOOL isHumanPlayer(UDWORD player) { if (player >= MAX_PLAYERS) return FALSE; return (BOOL) (player2dpid[player] != 0); } // returns player responsible for 'player' UDWORD whosResponsible(UDWORD player) { UDWORD c; SDWORD i; c = ANYPLAYER; if (isHumanPlayer(player)) { c = player; } else if(player == selectedPlayer) { c = player; } else { // crawl down array to find a responsible fellow, for(i=player; i>=0; i--) { if(isHumanPlayer(i)) { c = i; } } // else crawl up to find a responsible fellow if(c == ANYPLAYER) { for(i=player; ipStructureType->type != REF_HQ); psBuilding= psBuilding->psNext); if(psBuilding) { x= map_coord(psBuilding->pos.x); y= map_coord(psBuilding->pos.y); } else if (apsDroidLists[player]) // or first droid { x= map_coord(apsDroidLists[player]->pos.x); y= map_coord(apsDroidLists[player]->pos.y); } else if (apsStructLists[player]) // center on first struct { x= map_coord(apsStructLists[player]->pos.x); y= map_coord(apsStructLists[player]->pos.y); } else //or map center. { x= mapWidth/2; y= mapHeight/2; } if(scroll) { requestRadarTrack(world_coord(x), world_coord(y)); } else { setViewPos(x,y,TRUE); } res.x = world_coord(x); res.y = map_TileHeight(x,y); res.z = world_coord(y); return res; } // //////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////// // Recv Messages. Get a message and dispatch to relevant function. BOOL recvMessage(void) { NETMSG msg; UDWORD dp; UDWORD a; while(NETrecv(&msg) == TRUE) // for all incoming messages. { // Cocpy the message to the global one used by the new NET API NetMsg = msg; // messages only in game. if(!ingame.localJoiningInProgress) { switch(msg.type) { case AUDIOMSG: recvAudioMsg(&msg); break; case NET_DROID: // new droid of known type recvDroid(); break; case NET_DROIDINFO: //droid update info recvDroidInfo(); break; case NET_DROIDDEST: // droid destroy recvDestroyDroid(); break; case NET_DESTROYXTRA: recvDestroyExtra(&msg); // a generic destroy, complete wiht killer info. break; case NET_DROIDMOVE: // move a droid to x,y command. recvDroidMove(); break; case NET_GROUPORDER: // an order for more than 1 droid. recvGroupOrder(); break; case NET_CHECK_DROID: // droid damage and position checks recvDroidCheck(); break; case NET_CHECK_STRUCT: // structure damage checks. recvStructureCheck(); break; case NET_CHECK_POWER: // Power level syncing. recvPowerCheck(); break; case NET_TEXTMSG: // simple text message recvTextMessage(&msg); break; case NET_AITEXTMSG: //multiplayer AI text message recvTextMessageAI(&msg); break; case NET_BEACONMSG: //beacon (blip) message recvBeacon(&msg); break; case NET_BUILD: // a build order has been sent. recvBuildStarted(); break; case NET_BUILDFINISHED: // a building is complete recvBuildFinished(); break; case NET_STRUCTDEST: // structure destroy recvDestroyStructure(); break; case NET_SECONDARY: // set a droids secondary order level. recvDroidSecondary(); break; case NET_SECONDARY_ALL: // set a droids secondary order level. recvDroidSecondaryAll(); break; case NET_DROIDEMBARK: recvDroidEmbark(); //droid has embarked on a Transporter break; case NET_DROIDDISEMBARK: recvDroidDisEmbark(); //droid has disembarked from a Transporter break; case NET_REQUESTDROID: // player requires a droid that they dont have. recvRequestDroid(); break; // case NET_REQUESTPLAYER: // a new player requires information // multiPlayerRequest(&msg); // break; case NET_GIFT: // an alliance gift from one player to another. recvGift(&msg); break; case NET_SCORESUBMIT: // a score update from another player recvScoreSubmission(); break; case NET_VTOL: recvHappyVtol(); break; case NET_LASSAT: recvLasSat(); break; default: break; } } // messages usable all the time switch(msg.type) { case NET_TEMPLATE: // new template recvTemplate(&msg); break; case NET_TEMPLATEDEST: // template destroy recvDestroyTemplate(&msg); break; case NET_FEATUREDEST: // feature destroy recvDestroyFeature(&msg); break; case NET_PING: // diagnostic ping msg. recvPing(); break; case NET_DEMOLISH: // structure demolished. recvDemolishFinished(); break; case NET_RESEARCH: // some research has been done. recvResearch(&msg); break; case NET_LEAVING: // player leaving nicely NetGet((&msg),0,dp); MultiPlayerLeave(dp); break; case NET_WHOLEDROID: // a complete droid description has arrived. receiveWholeDroid(&msg); break; case NET_OPTIONS: recvOptions(&msg); break; case NET_VERSION: recvVersionCheck(&msg); break; case NET_PLAYERRESPONDING: // remote player is now playing NetGet((&msg),0,a); ingame.JoiningInProgress[a] = FALSE; // player is with us! break; case NET_COLOURREQUEST: recvColourRequest(&msg); break; case NET_TEAMREQUEST: recvTeamRequest(&msg); break; case NET_ARTIFACTS: recvMultiPlayerRandomArtifacts(); break; case NET_ALLIANCE: recvAlliance(TRUE); break; case NET_KICK: NetGet((&msg),0,dp); if(NetPlay.dpidPlayer == dp) // we've been told to leave. { setPlayerHasLost(TRUE); } break; case NET_FIREUP: // frontend only break; case NET_RESEARCHSTATUS: recvResearchStatus(&msg); break; default: break; } } return TRUE; } // //////////////////////////////////////////////////////////////////////////// // Research Stuff. Nat games only send the result of research procedures. BOOL SendResearch(UBYTE player,UDWORD index) { NETMSG m; UBYTE i; PLAYER_RESEARCH *pPlayerRes; NetAdd(m,0,player); // player researching NetAdd(m,1,index); // reference into topic. m.size =5; m.type = NET_RESEARCH; if (game.type == SKIRMISH) { pPlayerRes = asPlayerResList[player]; pPlayerRes += index; for(i=0;ibody[0]; NetGet(m,1,index); // get the index pPlayerRes = asPlayerResList[player]; pPlayerRes += index; if(IsResearchCompleted(pPlayerRes)==FALSE) { MakeResearchCompleted(pPlayerRes); researchResult(index, player, FALSE, NULL); //take off the power if available. pResearch = asResearch + index; usePower(player, pResearch->researchPower); } if (game.type == SKIRMISH) { for(i=0;iid); // res lab. } else { NetAdd(m,2,nil); // res lab. } NetAdd(m,6,index); // topic. m.size = 10; m.type = NET_RESEARCHSTATUS; return( NETbcast(&m,FALSE) ); } BOOL recvResearchStatus(NETMSG *pMsg) { STRUCTURE *psBuilding; PLAYER_RESEARCH *pPlayerRes; RESEARCH_FACILITY *psResFacilty; RESEARCH *pResearch; UBYTE player,bStart; UDWORD index,buildingId; NetGet(pMsg,0,player); // player researching NetGet(pMsg,1,bStart); // start stop.. NetGet(pMsg,2,buildingId); // res lab. NetGet(pMsg,6,index); // topic. pPlayerRes = asPlayerResList[player] + index; // psBuilding may be null if finishing. if(bStart) //starting research { psBuilding = IdToStruct( buildingId, player); if(psBuilding) // set that facility to research { psResFacilty = (RESEARCH_FACILITY*)psBuilding->pFunctionality; if(psResFacilty->psSubject != NULL) { cancelResearch(psBuilding); } pResearch = (asResearch+index); psResFacilty->psSubject = (BASE_STATS*)pResearch; //set the subject up if (IsResearchCancelled(pPlayerRes)) { psResFacilty->powerAccrued = pResearch->researchPower;//set up as if all power available for cancelled topics } else { psResFacilty->powerAccrued = 0; } MakeResearchStarted(pPlayerRes); psResFacilty->timeStarted = ACTION_START_TIME; psResFacilty->timeStartHold = 0; psResFacilty->timeToResearch = pResearch->researchPoints / psResFacilty->researchPoints; if (psResFacilty->timeToResearch == 0) { psResFacilty->timeToResearch = 1; } } } else // finished/cancelled research { if(buildingId == 0) // find the centre doing this research.(set building { // go through the structs to find the centre that's doing this topic) for(psBuilding = apsStructLists[player];psBuilding;psBuilding = psBuilding->psNext) { if( psBuilding->pStructureType->type == REF_RESEARCH && psBuilding->status == SS_BUILT && ((RESEARCH_FACILITY *)psBuilding->pFunctionality)->psSubject && ((RESEARCH_FACILITY *)psBuilding->pFunctionality)->psSubject->ref - REF_RESEARCH_START == index ) { break; } } } else { psBuilding = IdToStruct( buildingId, player); } if(IsResearchCompleted(pPlayerRes)) { return TRUE; } // stop that facility doing any research. if(psBuilding) { cancelResearch(psBuilding); } } return TRUE; } // //////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////// // Text Messaging between players. proceed string with players to send to. // eg "123 hi there" sends "hi there" to players 1,2 and 3. BOOL sendTextMessage(const char *pStr,BOOL all) { NETMSG m; BOOL normal = TRUE; BOOL sendto[MAX_PLAYERS]; UDWORD i; char display[MAX_CONSOLE_STRING_LENGTH]; if(!ingame.localOptionsReceived) { return TRUE; } for(i=0;i= '0' ) && (pStr[i] <= '8' )) ; i++ ) // for each numeric char encountered.. { sendto[ pStr[i]-'0' ] = TRUE; normal = FALSE; } NetAdd(m,0,NetPlay.dpidPlayer); memcpy(&(m.body[4]),&(pStr[i]), strlen( &(pStr[i]) )+1); // copy message in. m.size = (UWORD)( strlen( &(pStr[i]) )+5); // package the message up and send it. m.type = NET_TEXTMSG; if(all) { NETbcast(&m,FALSE); } else if(normal) { for(i=0;i= 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; } NETsend(&m,player2dpid[sendPlayer],FALSE); //send to the player who is hosting 'to' player (might be himself if human and not AI) } return TRUE; } BOOL sendBeaconToPlayerNet(SDWORD locX, SDWORD locY, SDWORD forPlayer, SDWORD sender, char *pStr) { NETMSG m; SDWORD sendPlayer; //debug(LOG_WZ, "sendBeaconToPlayerNet: '%s'",pStr); NetAdd(m,0,sender); //save the actual sender //save the actual player that is to get this msg on the source machine (source can host many AIs) NetAdd(m,4,forPlayer); //save the actual receiver (might not be the same as the one we are actually sending to, in case of AIs) //save location NetAdd(m,8,locX); NetAdd(m,12,locY); memcpy(&(m.body[16]),&(pStr[0]), strlen( &(pStr[0]) )+1); // copy message in. m.size = (UWORD)( strlen( &(pStr[0]) )+17); // package the message up and send it. m.type = NET_BEACONMSG; //new type //find machine that is hosting this human or AI sendPlayer = whosResponsible(forPlayer); if(sendPlayer >= MAX_PLAYERS) { debug(LOG_ERROR, "sendAIMessage() - whosResponsible() failed."); return FALSE; } NETsend(&m,player2dpid[sendPlayer],FALSE); //send to the player who is hosting 'to' player (might be himself if human and not AI) 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 { //addConsoleMessage(pStr,DEFAULT_JUSTIFY); //console("%d: %s", from, pStr); strcpy(tmp,getPlayerName(from)); strcat(tmp," : "); // seperator strcat(tmp,pStr); // add message addConsoleMessage(tmp,DEFAULT_JUSTIFY); } } // Write a message to the console. BOOL recvTextMessage(NETMSG *pMsg) { SDWORD dpid; UDWORD i; char msg[MAX_CONSOLE_STRING_LENGTH]; UDWORD player=MAX_PLAYERS,j; //console callback - player who sent the message NetGet(pMsg,0,dpid); for(i = 0; NetPlay.players[i].dpid != dpid; i++); //findplayer //console callback - find real number of the player for(j = 0; ibody[4]), sizeof(msg)); // add message strlcat(msg, &(pMsg->body[4]), sizeof(msg)); // add message addConsoleMessage((char *)&msg,DEFAULT_JUSTIFY); //multiplayer message callback //Received a console message from a player, save //---------------------------------------------- MultiMsgPlayerFrom = player; MultiMsgPlayerTo = selectedPlayer; strlcpy(MultiplayMsg, &(pMsg->body[4]), sizeof(MultiplayMsg)); eventFireCallbackTrigger((TRIGGER_TYPE)CALL_AI_MSG); // make some noise! if(titleMode == MULTIOPTION || titleMode == MULTILIMIT) { audio_PlayTrack(FE_AUDIO_MESSAGEEND); } else if(!ingame.localJoiningInProgress) { audio_PlayTrack(ID_SOUND_MESSAGEEND); } return TRUE; } //AI multiplayer message - received message from AI (from scripts) BOOL recvTextMessageAI(NETMSG *pMsg) { SDWORD sender, receiver; char msg[MAX_CONSOLE_STRING_LENGTH]; NetGet(pMsg,0,sender); //in-game player index ('normal' one) NetGet(pMsg,4,receiver); //in-game player index //strlcpy(msg, getPlayerName(sender), sizeof(msg)); // name //strlcat(msg, " : ", sizeof(msg)); // seperator strlcpy(msg, &(pMsg->body[8]), sizeof(msg)); //Display the message and make the script callback displayAIMessage(msg, sender, receiver); //Received a console message from a player callback //store and call later //------------------------------------------------- if(!msgStackPush(CALL_AI_MSG,sender,receiver,msg,-1,-1,NULL)) { debug(LOG_ERROR, "recvTextMessageAI() - msgStackPush - stack failed"); return FALSE; } return TRUE; } // //////////////////////////////////////////////////////////////////////////// // Templates // send a newly created template to other players BOOL sendTemplate(DROID_TEMPLATE *pTempl) { NETMSG m; UDWORD count = 0, i; ASSERT(pTempl != NULL, "sendTemplate: Old Pumpkin bug"); if (!pTempl) return TRUE; /* hack */ // I hate adding more of this hideous code, but it is necessary for now - Per NetAddUint8(m, count, selectedPlayer); count += sizeof(Uint8); NetAddUint32(m, count, pTempl->ref); count += sizeof(Uint32); NetAdd(m, count, pTempl->aName); count += DROID_MAXNAME; NetAddUint8(m, count, pTempl->NameVersion); count += sizeof(Uint8); for (i = 0; i < DROID_MAXCOMP; i++) { // signed, but sent as a bunch of bits... NetAddUint32(m, count, pTempl->asParts[i]); count += sizeof(Uint32); } NetAddUint32(m, count, pTempl->buildPoints); count += sizeof(Uint32); NetAddUint32(m, count, pTempl->powerPoints); count += sizeof(Uint32); NetAddUint32(m, count, pTempl->storeCount); count += sizeof(Uint32); NetAddUint32(m, count, pTempl->numWeaps); count += sizeof(Uint32); for (i = 0; i < DROID_MAXWEAPS; i++) { NetAddUint32(m, count, pTempl->asWeaps[i]); count += sizeof(Uint32); } NetAddUint32(m, count, pTempl->droidType); count += sizeof(Uint32); NetAddUint32(m, count, pTempl->multiPlayerID); count += sizeof(Uint32); m.type = NET_TEMPLATE; m.size = count; return( NETbcast(&m,FALSE) ); } // receive a template created by another player BOOL recvTemplate(NETMSG * m) { UBYTE player; DROID_TEMPLATE *psTempl; DROID_TEMPLATE t, *pT = &t; unsigned int i; unsigned int count = 0; NetGetUint8(m, count, player); count += sizeof(Uint8); ASSERT( player < MAX_PLAYERS, "recvtemplate: invalid player size: %d", player ); NetGetUint32(m, count, pT->ref); count += sizeof(Uint32); NetGet(m, count, pT->aName); count += DROID_MAXNAME; NetGetUint8(m, count, pT->NameVersion); count += sizeof(Uint8); for (i = 0; i < DROID_MAXCOMP; i++) { // signed, but sent as a bunch of bits... NetGetUint32(m, count, pT->asParts[i]); count += sizeof(Uint32); } NetGetUint32(m, count, pT->buildPoints); count += sizeof(Uint32); NetGetUint32(m, count, pT->powerPoints); count += sizeof(Uint32); NetGetUint32(m, count, pT->storeCount); count += sizeof(Uint32); NetGetUint32(m, count, pT->numWeaps); count += sizeof(Uint32); for (i = 0; i < DROID_MAXWEAPS; i++) { NetGetUint32(m, count, pT->asWeaps[i]); count += sizeof(Uint32); } NetGetUint32(m, count, pT->droidType); count += sizeof(Uint32); NetGetUint32(m, count, pT->multiPlayerID); count += sizeof(Uint32); t.psNext = NULL; psTempl = IdToTemplate(t.multiPlayerID,player); if(psTempl) // already exists. { t.psNext = psTempl->psNext; memcpy(psTempl, &t, sizeof(DROID_TEMPLATE)); } else { addTemplate(player,&t); apsDroidTemplates[player]->ref = REF_TEMPLATE_START; // templates are the odd one out! } return TRUE; } // //////////////////////////////////////////////////////////////////////////// // inform others that you no longer have a template BOOL SendDestroyTemplate(DROID_TEMPLATE *t) { NETMSG m; m.body[0] = (char) selectedPlayer; // send player number // send id of template to destroy NetAdd(m,1,(t->multiPlayerID)); m.size = 5; m.type=NET_TEMPLATEDEST; return( NETbcast(&m,FALSE) ); } // acknowledge another player no longer has a template static BOOL recvDestroyTemplate(NETMSG * m) { UDWORD player,targetref; DROID_TEMPLATE *psTempl, *psTempPrev; player = m->body[0]; // decode the message NetGet(m,1,targetref); psTempPrev = NULL; // first find it. for(psTempl = apsDroidTemplates[player]; psTempl;psTempl = psTempl->psNext) { if( psTempl->multiPlayerID == targetref ) { break; } psTempPrev = psTempl; } if (psTempl) // if we found itthen delete it. { if(psTempPrev) // Update list pointers. { psTempPrev->psNext = psTempl->psNext; // It's down the list somewhere ? } else { apsDroidTemplates[player] = psTempl->psNext;// It's at the root ? } free(psTempl); // Delete the template. } return (TRUE); } // //////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////// // Features // send a destruct feature message. BOOL SendDestroyFeature(FEATURE *pF) { NETMSG m; // only send the destruction if the feature is our responsibility NetAdd(m,0,pF->id); m.size = sizeof(pF->id); m.type = NET_FEATUREDEST; return( NETbcast(&m,TRUE) ); } // process a destroy feature msg. BOOL recvDestroyFeature(NETMSG *pMsg) { FEATURE *pF; UDWORD id; NetGet(pMsg,0,id); // get feature id // for(pF = apsFeatureLists[0]; pF && (pF->id != id); pF = pF->psNext); // find the feature pF = IdToFeature(id,ANYPLAYER); if( (pF == NULL) ) // if already a gonner. { return FALSE; // feature wasnt found anyway. } bMultiPlayer = FALSE; // remove, don't broadcast. removeFeature(pF); bMultiPlayer = TRUE; return TRUE; } // //////////////////////////////////////////////////////////////////////////// // a generic destroy function, with killer info included. BOOL sendDestroyExtra(BASE_OBJECT *psKilled,BASE_OBJECT *psKiller) { NETMSG m; UDWORD n=0; /* if(psKilled != NULL) { NetAdd(m,4,psKilled->id); // id of thing killed } else { NetAdd(m,4,n); } */ if(psKiller != NULL) { NetAdd(m,0,psKiller->id); // id of killer. } else { NetAdd(m,0,n); } m.type = NET_DESTROYXTRA; m.size = 4; return NETbcast(&m,FALSE); } // //////////////////////////////////////////////////////////////////////////// BOOL recvDestroyExtra(NETMSG *pMsg) { // BASE_OBJECT *psKilled; // UDWORD killedId; BASE_OBJECT *psSrc; UDWORD srcId; DROID *psKiller; /* NetGet(pMsg,0,killedId); // remove as normal if(killedId !=0) { psKilled = IdToPointer(killedId,ANYPLAYER); if(psKilled) { switch(psKilled->type) { case OBJ_DROID: recvDestroyDroid(pMsg); break; case OBJ_STRUCTURE: recvDestroyStructure(pMsg); break; case OBJ_FEATURE: recvDestroyFeature(pMsg); break; } } } */ NetGet(pMsg,0,srcId); if(srcId != 0) { psSrc = IdToPointer(srcId,ANYPLAYER); if(psSrc && (psSrc->type == OBJ_DROID) ) // process extra bits. { psKiller = (DROID*)psSrc; #if 0 // FIXME: this code *and* the code that sends this message needs to be modified // in such a way that they update psKiller->experience with the percentage // of damage dealt rather than just a kill count. if(psKiller) { psKiller->experience++; } cmdDroidUpdateKills(psKiller); #endif return TRUE; } } return FALSE; } // //////////////////////////////////////////////////////////////////////////// // Network Audio packet processor. BOOL recvAudioMsg(NETMSG *pMsg) { return TRUE; } // //////////////////////////////////////////////////////////////////////////// // Network File packet processor. BOOL recvMapFileRequested(NETMSG *pMsg) { char mapStr[256],mapName[256],fixedname[256]; // another player is requesting the map if(!NetPlay.bHost) { return TRUE; } // start sending the map to the other players. if(!bSendingMap) { memset(mapStr,0,256); memset(mapName,0,256); memset(fixedname,0,256); bSendingMap = TRUE; addConsoleMessage("SENDING MAP!",DEFAULT_JUSTIFY); addConsoleMessage("FIX FOR LINUX!",DEFAULT_JUSTIFY); strlcpy(mapName, game.map, sizeof(mapName)); // chop off the -T1 mapName[strlen(game.map)-3] = 0; // chop off the -T1 etc.. // chop off the sk- if required. if(strncmp(mapName,"Sk-",3) == 0) { strlcpy(mapStr, &(mapName[3]), sizeof(mapStr)); strlcpy(mapName, mapStr, sizeof(mapName)); } snprintf(mapStr, sizeof(mapStr), "%dc-%s.wz", game.maxPlayers, mapName); snprintf(fixedname, sizeof(fixedname), "maps/%s", mapStr); //We know maps are in /maps dir...now. fix for linux -Q strlcpy(mapStr, fixedname, sizeof(mapStr)); NETsendFile(TRUE,mapStr,0); } return TRUE; } // continue sending the map UBYTE sendMap(void) { UBYTE done; static UDWORD lastCall; if(lastCall > gameTime)lastCall= 0; if ( (gameTime - lastCall) <200) { return 0; } lastCall = gameTime; done = NETsendFile(FALSE,game.map,0); if(done == 100) { addConsoleMessage("MAP SENT!",DEFAULT_JUSTIFY); bSendingMap = FALSE; } return done; } // another player is broadcasting a map, recv a chunk BOOL recvMapFileData(NETMSG *pMsg) { UBYTE done; done = NETrecvFile(pMsg); if(done == 100) { addConsoleMessage("MAP DOWNLOADED!",DEFAULT_JUSTIFY); sendTextMessage("MAP DOWNLOADED",TRUE); //send // clear out the old level list. levShutDown(); levInitialise(); if (!buildMapList()) { return FALSE; } } else { flushConsoleMessages(); CONPRINTF(ConsoleString,(ConsoleString,"MAP:%d%%",done)); } return TRUE; } //------------------------------------------------------------------------------------------------// /* multiplayer message stack */ void msgStackReset(void) { msgStackPos = -1; //Beginning of the stack } UDWORD msgStackPush(SDWORD CBtype, SDWORD plFrom, SDWORD plTo, const char *tStr, SDWORD x, SDWORD y, DROID *psDroid) { debug(LOG_WZ, "msgStackPush: pushing message type %d to pos %d", CBtype, msgStackPos + 1); if (msgStackPos >= MAX_MSG_STACK) { debug(LOG_ERROR, "msgStackPush() - stack full"); return FALSE; } //make point to the last valid element msgStackPos++; //remember values msgPlFrom[msgStackPos] = plFrom; msgPlTo[msgStackPos] = plTo; callbackType[msgStackPos] = CBtype; locx[msgStackPos] = x; locy[msgStackPos] = y; strcpy(msgStr[msgStackPos], tStr); msgDroid[msgStackPos] = psDroid; return TRUE; } BOOL isMsgStackEmpty(void) { if(msgStackPos <= (-1)) return TRUE; return FALSE; } BOOL msgStackGetFrom(SDWORD *psVal) { if(msgStackPos < 0) { debug(LOG_ERROR, "msgStackGetFrom: msgStackPos < 0"); return FALSE; } *psVal = msgPlFrom[0]; return TRUE; } BOOL msgStackGetTo(SDWORD *psVal) { if(msgStackPos < 0) { debug(LOG_ERROR, "msgStackGetTo: msgStackPos < 0"); return FALSE; } *psVal = msgPlTo[0]; return TRUE; } static BOOL msgStackGetCallbackType(SDWORD *psVal) { if(msgStackPos < 0) { debug(LOG_ERROR, "msgStackGetCallbackType: msgStackPos < 0"); return FALSE; } *psVal = callbackType[0]; return TRUE; } static BOOL msgStackGetXY(SDWORD *psValx, SDWORD *psValy) { if(msgStackPos < 0) { debug(LOG_ERROR, "msgStackGetXY: msgStackPos < 0"); return FALSE; } *psValx = locx[0]; *psValy = locy[0]; return TRUE; } BOOL msgStackGetMsg(char *psVal) { if(msgStackPos < 0) { debug(LOG_ERROR, "msgStackGetMsg: msgStackPos < 0"); return FALSE; } strcpy(psVal, msgStr[0]); //*psVal = msgPlTo[msgStackPos]; return TRUE; } static BOOL msgStackSort(void) { SDWORD i; //go through all-1 elements (bottom-top) for(i=0;i MAX_MSG_STACK) { debug(LOG_ERROR, "msgStackPop: wrong msgStackPos index: %d", msgStackPos); return FALSE; } return msgStackSort(); //move all elements 1 pos lower } BOOL msgStackGetDroid(DROID **ppsDroid) { if(msgStackPos < 0) { debug(LOG_ERROR, "msgStackGetDroid: msgStackPos < 0"); return FALSE; } *ppsDroid = msgDroid[0]; return TRUE; } SDWORD msgStackGetCount(void) { return msgStackPos + 1; } BOOL msgStackFireTop(void) { SDWORD _callbackType; char msg[255]; if(msgStackPos < 0) { debug(LOG_ERROR, "msgStackFireTop: msgStackPos < 0"); return FALSE; } if(!msgStackGetCallbackType(&_callbackType)) return FALSE; switch(_callbackType) { case CALL_VIDEO_QUIT: debug(LOG_SCRIPT, "msgStackFireTop: popped CALL_VIDEO_QUIT"); eventFireCallbackTrigger((TRIGGER_TYPE)CALL_VIDEO_QUIT); break; case CALL_DORDER_STOP: ASSERT(FALSE,"CALL_DORDER_STOP is currently disabled"); debug(LOG_SCRIPT, "msgStackFireTop: popped CALL_DORDER_STOP"); if(!msgStackGetDroid(&psScrCBOrderDroid)) return FALSE; eventFireCallbackTrigger((TRIGGER_TYPE)CALL_DORDER_STOP); break; case CALL_BEACON: if(!msgStackGetXY(&beaconX, &beaconY)) return FALSE; if(!msgStackGetFrom(&MultiMsgPlayerFrom)) return FALSE; if(!msgStackGetTo(&MultiMsgPlayerTo)) return FALSE; if(!msgStackGetMsg(msg)) return FALSE; strcpy(MultiplayMsg, msg); eventFireCallbackTrigger((TRIGGER_TYPE)CALL_BEACON); break; case CALL_AI_MSG: if(!msgStackGetFrom(&MultiMsgPlayerFrom)) return FALSE; if(!msgStackGetTo(&MultiMsgPlayerTo)) return FALSE; if(!msgStackGetMsg(msg)) return FALSE; strcpy(MultiplayMsg, msg); eventFireCallbackTrigger((TRIGGER_TYPE)CALL_AI_MSG); break; default: debug(LOG_ERROR, "msgStackFireTop: unknown callback type"); return FALSE; break; } if(!msgStackPop()) return FALSE; return TRUE; } static BOOL recvBeacon(NETMSG *pMsg) { SDWORD sender, receiver,locX, locY; char msg[MAX_CONSOLE_STRING_LENGTH]; NetGet(pMsg,0,sender); NetGet(pMsg,4,receiver); NetGet(pMsg,8,locX); NetGet(pMsg,12,locY); debug(LOG_WZ, "Received beacon for player: %d, from: %d",receiver, sender); strcpy(msg, &(pMsg->body[16])); strcat(msg, NetPlay.players[sender].name); // name strcpy(beaconReceiveMsg[sender], msg); return addHelpBlip(locX,locY,receiver,sender,beaconReceiveMsg[sender]); } static const char* playerColors[] = { N_("Green"), N_("Orange"), N_("Grey"), N_("Black"), N_("Red"), N_("Blue"), N_("Pink"), N_("Cyan") }; char *getPlayerColourName(SDWORD player) { static const unsigned int end = sizeof(playerColors) / sizeof(const char*); ASSERT(player < end, "getPlayerColourName: player number (%d) exceeds maximum (%d)\n", player, end - 1); if (player < end) { strcpy(tempString, _(playerColors[ getPlayerColour(player) ])); } else { tempString[0] = '0'; } return tempString; } /* * returns player in-game index from a dpid or -1 if player not found (should not happen!) */ SDWORD dpidToPlayer(SDWORD dpid) { UDWORD i; for(i=0;(i= MAX_PLAYERS) { ASSERT(i< MAX_PLAYERS, "dpidToPlayer: failed to find player with dpid %d", dpid); return -1; } return i; }