2112 lines
52 KiB
C++
2112 lines
52 KiB
C++
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 1999-2004 Eidos Interactive
|
|
Copyright (C) 2005-2013 Warzone 2100 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 <string.h>
|
|
|
|
#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 <time.h> // 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 "research.h"
|
|
#include "init.h"
|
|
#include "warcam.h" // these 4 for fireworks
|
|
#include "mission.h"
|
|
#include "effects.h"
|
|
#include "lib/gamelib/gtime.h"
|
|
#include "keybind.h"
|
|
#include "qtscript.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 "template.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.
|
|
#include "multiint.h"
|
|
#include "keymap.h"
|
|
#include "cheat.h"
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// globals.
|
|
bool bMultiPlayer = false; // true when more than 1 player.
|
|
bool bMultiMessages = false; // == bMultiPlayer unless multimessages are disabled
|
|
bool openchannels[MAX_PLAYERS]={true};
|
|
UBYTE bDisplayMultiJoiningStatus;
|
|
|
|
MULTIPLAYERGAME game; //info to describe game.
|
|
MULTIPLAYERINGAME ingame;
|
|
|
|
char beaconReceiveMsg[MAX_PLAYERS][MAX_CONSOLE_STRING_LENGTH]; //beacon msg for each player
|
|
char playerName[MAX_PLAYERS][MAX_STR_LENGTH]; //Array to store all player names (humans and AIs)
|
|
|
|
/////////////////////////////////////
|
|
/* multiplayer message stack stuff */
|
|
/////////////////////////////////////
|
|
#define MAX_MSG_STACK 100 // must be *at least* 64
|
|
|
|
static char msgStr[MAX_MSG_STACK][MAX_STR_LENGTH];
|
|
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
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Local Prototypes
|
|
|
|
static bool recvBeacon(NETQUEUE queue);
|
|
static bool recvDestroyTemplate(NETQUEUE queue);
|
|
static bool recvResearch(NETQUEUE queue);
|
|
static bool sendAIMessage(char *pStr, UDWORD player, UDWORD to);
|
|
|
|
bool multiplayPlayersReady (bool bNotifyStatus);
|
|
void startMultiplayerGame (void);
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// temporarily disable multiplayer mode.
|
|
void turnOffMultiMsg(bool bDoit)
|
|
{
|
|
if (!bMultiPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bMultiMessages = !bDoit;
|
|
return;
|
|
}
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// throw a party when you win!
|
|
bool multiplayerWinSequence(bool firstCall)
|
|
{
|
|
static Position pos;
|
|
Position 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, ModeQueue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// rotate world
|
|
if (MissionResUp && !getWarCamStatus())
|
|
{
|
|
rotAmount = graphicsTimeAdjustedIncrement(MAP_SPIN_RATE / 12);
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// quikie functions.
|
|
|
|
// to get droids ...
|
|
DROID *IdToDroid(UDWORD id, UDWORD player)
|
|
{
|
|
if (player == ANYPLAYER)
|
|
{
|
|
for (int i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
for (DROID *d = apsDroidLists[i]; d; d = d->psNext)
|
|
{
|
|
if (d->id == id)
|
|
{
|
|
return d;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (player < MAX_PLAYERS)
|
|
{
|
|
for (DROID *d = apsDroidLists[player]; d; d = d->psNext)
|
|
{
|
|
if (d->id == id)
|
|
{
|
|
return d;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// find a structure
|
|
STRUCTURE *IdToStruct(UDWORD id, UDWORD player)
|
|
{
|
|
int beginPlayer = 0, endPlayer = MAX_PLAYERS;
|
|
if (player != ANYPLAYER)
|
|
{
|
|
beginPlayer = player;
|
|
endPlayer = std::min<int>(player + 1, MAX_PLAYERS);
|
|
}
|
|
STRUCTURE **lists[2] = {apsStructLists, mission.apsStructLists};
|
|
for (int j = 0; j < 2; ++j)
|
|
{
|
|
for (int i = beginPlayer; i < endPlayer; ++i)
|
|
{
|
|
for (STRUCTURE *d = lists[j][i]; d; d = d->psNext)
|
|
{
|
|
if (d->id == id)
|
|
{
|
|
return d;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// find a feature
|
|
FEATURE *IdToFeature(UDWORD id, UDWORD player)
|
|
{
|
|
(void)player; // unused, all features go into player 0
|
|
for (FEATURE *d = apsFeatureLists[0]; d; d = d->psNext)
|
|
{
|
|
if (d->id == id)
|
|
{
|
|
return d;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
|
|
DROID_TEMPLATE *IdToTemplate(UDWORD tempId, UDWORD player)
|
|
{
|
|
DROID_TEMPLATE *psTempl = NULL;
|
|
UDWORD i;
|
|
|
|
// Check if we know which player this is from, in that case, assume it is a player template
|
|
// FIXME: nuke the ANYPLAYER hack
|
|
if (player != ANYPLAYER && player < MAX_PLAYERS )
|
|
{
|
|
for (psTempl = apsDroidTemplates[player]; psTempl && (psTempl->multiPlayerID != tempId); psTempl = psTempl->psNext)
|
|
{} // follow templates
|
|
|
|
if (psTempl)
|
|
{
|
|
return psTempl;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// It could be a AI template...or that of another player
|
|
for (i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
for (psTempl = apsDroidTemplates[i]; psTempl && psTempl->multiPlayerID != tempId; psTempl = psTempl->psNext)
|
|
{} // follow templates
|
|
|
|
if (psTempl)
|
|
{
|
|
debug(LOG_NEVER, "Found template ID %d, for player %d, but found it in player's %d list?",tempId, player, i);
|
|
return psTempl;
|
|
}
|
|
}
|
|
// no error, since it is possible that we don't have this template defined yet.
|
|
return NULL;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// 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.
|
|
|
|
pD = IdToDroid(id, player);
|
|
if (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.
|
|
const char* getPlayerName(int player)
|
|
{
|
|
ASSERT_OR_RETURN(NULL, player < MAX_PLAYERS , "Wrong player index: %u", player);
|
|
|
|
if (game.type != CAMPAIGN)
|
|
{
|
|
if (strcmp(playerName[player], "") != 0)
|
|
{
|
|
return (char*)&playerName[player];
|
|
}
|
|
}
|
|
|
|
if (strlen(NetPlay.players[player].name) == 0)
|
|
{
|
|
// make up a name for this player.
|
|
return getPlayerColourName(player);
|
|
}
|
|
else if (NetPlay.players[player].ai >= 0 && !NetPlay.players[player].allocated)
|
|
{
|
|
static char names[MAX_PLAYERS][StringSize]; // Must be static, since the getPlayerName() return value is used in tool tips... Long live the widget system.
|
|
// Add colour to player name.
|
|
sstrcpy(names[player], getPlayerColourName(player));
|
|
sstrcat(names[player], "-");
|
|
sstrcat(names[player], NetPlay.players[player].name);
|
|
return names[player];
|
|
}
|
|
|
|
return NetPlay.players[player].name;
|
|
}
|
|
|
|
bool setPlayerName(int player, const char *sName)
|
|
{
|
|
ASSERT_OR_RETURN(false, player < MAX_PLAYERS && player >= 0, "Player index (%u) out of range", player);
|
|
sstrcpy(playerName[player], sName);
|
|
return true;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// to determine human/computer players and responsibilities of each..
|
|
bool isHumanPlayer(int player)
|
|
{
|
|
if (player >= MAX_PLAYERS || player < 0)
|
|
{
|
|
return false; // obvious, really
|
|
}
|
|
return NetPlay.players[player].allocated;
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
//returns true if selected player is responsible for 'player'
|
|
bool myResponsibility(int player)
|
|
{
|
|
return (whosResponsible(player) == selectedPlayer || whosResponsible(player) == realSelectedPlayer);
|
|
}
|
|
|
|
//returns true if 'player' is responsible for 'playerinquestion'
|
|
bool responsibleFor(int player, int playerinquestion)
|
|
{
|
|
return whosResponsible(playerinquestion) == player;
|
|
}
|
|
|
|
bool canGiveOrdersFor(int player, int playerInQuestion)
|
|
{
|
|
return playerInQuestion >= 0 && playerInQuestion < MAX_PLAYERS &&
|
|
(player == playerInQuestion || responsibleFor(player, playerInQuestion) || getDebugMappingStatus());
|
|
}
|
|
|
|
int scavengerSlot()
|
|
{
|
|
// Scavengers used to always be in position 7, when scavengers were only supported in less than 8 player maps.
|
|
// Scavengers should be in position N in N-player maps, where N ≥ 8.
|
|
return MAX(game.maxPlayers, 7);
|
|
}
|
|
|
|
int scavengerPlayer()
|
|
{
|
|
return game.scavengers? scavengerSlot() : -1;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// probably temporary. Places the camera on the players 1st droid or struct.
|
|
Vector3i cameraToHome(UDWORD player,bool scroll)
|
|
{
|
|
Vector3i res;
|
|
UDWORD x,y;
|
|
STRUCTURE *psBuilding;
|
|
|
|
for (psBuilding = apsStructLists[player]; psBuilding && (psBuilding->pStructureType->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;
|
|
}
|
|
|
|
static void recvSyncRequest(NETQUEUE queue)
|
|
{
|
|
int32_t req_id, x, y, obj_id, obj_id2, player_id, player_id2;
|
|
BASE_OBJECT *psObj = NULL, *psObj2 = NULL;
|
|
|
|
NETbeginDecode(queue, GAME_SYNC_REQUEST);
|
|
NETint32_t(&req_id);
|
|
NETint32_t(&x);
|
|
NETint32_t(&y);
|
|
NETint32_t(&obj_id);
|
|
NETint32_t(&player_id);
|
|
NETint32_t(&obj_id2);
|
|
NETint32_t(&player_id2);
|
|
NETend();
|
|
|
|
syncDebug("sync request received from%d req_id%d x%u y%u %obj1 %obj2", queue.index, req_id, x, y, obj_id, obj_id2);
|
|
if (obj_id)
|
|
{
|
|
psObj = IdToPointer(obj_id, player_id);
|
|
}
|
|
if (obj_id2)
|
|
{
|
|
psObj2 = IdToPointer(obj_id2, player_id2);
|
|
}
|
|
triggerEventSyncRequest(queue.index, req_id, x, y, psObj, psObj2);
|
|
}
|
|
|
|
static void sendObj(BASE_OBJECT *psObj)
|
|
{
|
|
if (psObj)
|
|
{
|
|
int32_t obj_id = psObj->id;
|
|
int32_t player = psObj->player;
|
|
NETint32_t(&obj_id);
|
|
NETint32_t(&player);
|
|
}
|
|
else
|
|
{
|
|
int32_t dummy = 0;
|
|
NETint32_t(&dummy);
|
|
NETint32_t(&dummy);
|
|
}
|
|
}
|
|
|
|
void sendSyncRequest(int32_t req_id, int32_t x, int32_t y, BASE_OBJECT *psObj, BASE_OBJECT *psObj2)
|
|
{
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_SYNC_REQUEST);
|
|
NETint32_t(&req_id);
|
|
NETint32_t(&x);
|
|
NETint32_t(&y);
|
|
sendObj(psObj);
|
|
sendObj(psObj2);
|
|
NETend();
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Recv Messages. Get a message and dispatch to relevant function.
|
|
bool recvMessage(void)
|
|
{
|
|
NETQUEUE queue;
|
|
uint8_t type;
|
|
|
|
while (NETrecvNet(&queue, &type) || NETrecvGame(&queue, &type)) // for all incoming messages.
|
|
{
|
|
bool processedMessage1 = false;
|
|
bool processedMessage2 = false;
|
|
|
|
if (queue.queueType == QUEUE_GAME)
|
|
{
|
|
syncDebug("Processing player %d, message %s", queue.index, messageTypeToString(type));
|
|
}
|
|
|
|
// messages only in game.
|
|
if(!ingame.localJoiningInProgress)
|
|
{
|
|
processedMessage1 = true;
|
|
switch(type)
|
|
{
|
|
case GAME_DROIDINFO: //droid update info
|
|
recvDroidInfo(queue);
|
|
break;
|
|
case NET_TEXTMSG: // simple text message
|
|
recvTextMessage(queue);
|
|
break;
|
|
case NET_DATA_CHECK:
|
|
recvDataCheck(queue);
|
|
break;
|
|
case NET_AITEXTMSG: //multiplayer AI text message
|
|
recvTextMessageAI(queue);
|
|
break;
|
|
case NET_BEACONMSG: //beacon (blip) message
|
|
recvBeacon(queue);
|
|
break;
|
|
case GAME_SYNC_REQUEST:
|
|
recvSyncRequest(queue);
|
|
break;
|
|
case GAME_DROIDDISEMBARK:
|
|
recvDroidDisEmbark(queue); //droid has disembarked from a Transporter
|
|
break;
|
|
case GAME_GIFT: // an alliance gift from one player to another.
|
|
recvGift(queue);
|
|
break;
|
|
case GAME_LASSAT:
|
|
recvLasSat(queue);
|
|
break;
|
|
case GAME_DEBUG_MODE:
|
|
recvProcessDebugMappings(queue);
|
|
break;
|
|
case GAME_DEBUG_ADD_DROID:
|
|
recvDroid(queue);
|
|
break;
|
|
case GAME_DEBUG_ADD_STRUCTURE:
|
|
recvBuildFinished(queue);
|
|
break;
|
|
case GAME_DEBUG_ADD_FEATURE:
|
|
recvMultiPlayerFeature(queue);
|
|
break;
|
|
case GAME_DEBUG_REMOVE_DROID:
|
|
recvDestroyDroid(queue);
|
|
break;
|
|
case GAME_DEBUG_REMOVE_STRUCTURE:
|
|
recvDestroyStructure(queue);
|
|
break;
|
|
case GAME_DEBUG_REMOVE_FEATURE:
|
|
recvDestroyFeature(queue);
|
|
break;
|
|
case GAME_DEBUG_FINISH_RESEARCH:
|
|
recvResearch(queue);
|
|
break;
|
|
default:
|
|
processedMessage1 = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// messages usable all the time
|
|
processedMessage2 = true;
|
|
switch(type)
|
|
{
|
|
case GAME_TEMPLATE: // new template
|
|
recvTemplate(queue);
|
|
break;
|
|
case GAME_TEMPLATEDEST: // template destroy
|
|
recvDestroyTemplate(queue);
|
|
break;
|
|
case NET_PING: // diagnostic ping msg.
|
|
recvPing(queue);
|
|
break;
|
|
case NET_OPTIONS:
|
|
recvOptions(queue);
|
|
break;
|
|
case NET_PLAYER_DROPPED: // remote player got disconnected
|
|
{
|
|
uint32_t player_id;
|
|
|
|
NETbeginDecode(queue, NET_PLAYER_DROPPED);
|
|
{
|
|
NETuint32_t(&player_id);
|
|
}
|
|
NETend();
|
|
|
|
if (whosResponsible(player_id) != queue.index && queue.index != NET_HOST_ONLY)
|
|
{
|
|
HandleBadParam("NET_PLAYER_DROPPED given incorrect params.", player_id, queue.index);
|
|
break;
|
|
}
|
|
|
|
debug(LOG_INFO,"** player %u has dropped!", player_id);
|
|
|
|
if (NetPlay.players[player_id].allocated)
|
|
{
|
|
MultiPlayerLeave(player_id); // get rid of their stuff
|
|
NET_InitPlayer(player_id, false);
|
|
}
|
|
NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_DROPPED, player_id);
|
|
break;
|
|
}
|
|
case NET_PLAYERRESPONDING: // remote player is now playing
|
|
{
|
|
uint32_t player_id;
|
|
|
|
resetReadyStatus(false);
|
|
|
|
NETbeginDecode(queue, NET_PLAYERRESPONDING);
|
|
// the player that has just responded
|
|
NETuint32_t(&player_id);
|
|
NETend();
|
|
if (player_id >= MAX_PLAYERS)
|
|
{
|
|
debug(LOG_ERROR, "Bad NET_PLAYERRESPONDING received, ID is %d", (int)player_id);
|
|
break;
|
|
}
|
|
// This player is now with us!
|
|
if (ingame.JoiningInProgress[player_id])
|
|
{
|
|
addKnownPlayer(NetPlay.players[player_id].name, getMultiStats(player_id).identity);
|
|
}
|
|
ingame.JoiningInProgress[player_id] = false;
|
|
break;
|
|
}
|
|
// FIXME: the next 4 cases might not belong here --check (we got two loops for this)
|
|
case NET_COLOURREQUEST:
|
|
recvColourRequest(queue);
|
|
break;
|
|
case NET_POSITIONREQUEST:
|
|
recvPositionRequest(queue);
|
|
break;
|
|
case NET_TEAMREQUEST:
|
|
recvTeamRequest(queue);
|
|
break;
|
|
case NET_READY_REQUEST:
|
|
recvReadyRequest(queue);
|
|
|
|
// if hosting try to start the game if everyone is ready
|
|
if(NetPlay.isHost && multiplayPlayersReady(false))
|
|
{
|
|
startMultiplayerGame();
|
|
}
|
|
break;
|
|
case GAME_ALLIANCE:
|
|
recvAlliance(queue, true);
|
|
break;
|
|
case NET_KICK: // in-game kick message
|
|
{
|
|
uint32_t player_id;
|
|
char reason[MAX_KICK_REASON];
|
|
LOBBY_ERROR_TYPES KICK_TYPE = ERROR_NOERROR;
|
|
|
|
NETbeginDecode(queue, NET_KICK);
|
|
NETuint32_t(&player_id);
|
|
NETstring(reason, MAX_KICK_REASON);
|
|
NETenum(&KICK_TYPE);
|
|
NETend();
|
|
|
|
if (player_id == NET_HOST_ONLY)
|
|
{
|
|
char buf[250]= {'\0'};
|
|
|
|
ssprintf(buf, "Player %d (%s : %s) tried to kick %u", (int) queue.index, NetPlay.players[queue.index].name, NetPlay.players[queue.index].IPtextAddress, player_id);
|
|
NETlogEntry(buf, SYNC_FLAG, 0);
|
|
debug(LOG_ERROR, "%s", buf);
|
|
if (NetPlay.isHost)
|
|
{
|
|
NETplayerKicked((unsigned int) queue.index);
|
|
}
|
|
break;
|
|
}
|
|
else if (selectedPlayer == player_id) // we've been told to leave.
|
|
{
|
|
debug(LOG_ERROR, "You were kicked because %s", reason);
|
|
setPlayerHasLost(true);
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_NET, "Player %d was kicked: %s", player_id, reason);
|
|
NETplayerKicked(player_id);
|
|
}
|
|
break;
|
|
}
|
|
case GAME_RESEARCHSTATUS:
|
|
recvResearchStatus(queue);
|
|
break;
|
|
case GAME_STRUCTUREINFO:
|
|
recvStructureInfo(queue);
|
|
break;
|
|
case NET_PLAYER_STATS:
|
|
recvMultiStats(queue);
|
|
break;
|
|
case GAME_PLAYER_LEFT:
|
|
recvPlayerLeft(queue);
|
|
break;
|
|
default:
|
|
processedMessage2 = false;
|
|
break;
|
|
}
|
|
|
|
if (processedMessage1 && processedMessage2)
|
|
{
|
|
debug(LOG_ERROR, "Processed %s message twice!", messageTypeToString(type));
|
|
}
|
|
if (!processedMessage1 && !processedMessage2)
|
|
{
|
|
debug(LOG_ERROR, "Didn't handle %s message!", messageTypeToString(type));
|
|
}
|
|
|
|
NETpop(queue);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void HandleBadParam(const char *msg, const int from, const int actual)
|
|
{
|
|
char buf[255];
|
|
LOBBY_ERROR_TYPES KICK_TYPE = ERROR_INVALID;
|
|
|
|
ssprintf(buf, "!!>Msg: %s, Actual: %d, Bad: %d", msg, actual, from);
|
|
NETlogEntry(buf, SYNC_FLAG, actual);
|
|
if (NetPlay.isHost)
|
|
{
|
|
ssprintf(buf, "Auto kicking player %s, invalid command received.", NetPlay.players[actual].name);
|
|
sendTextMessage(buf, true);
|
|
kickPlayer(actual, buf, KICK_TYPE);
|
|
}
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Research Stuff. Nat games only send the result of research procedures.
|
|
bool SendResearch(uint8_t player, uint32_t index, bool trigger)
|
|
{
|
|
// Send the player that is researching the topic and the topic itself
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_DEBUG_FINISH_RESEARCH);
|
|
NETuint8_t(&player);
|
|
NETuint32_t(&index);
|
|
NETend();
|
|
|
|
return true;
|
|
}
|
|
|
|
// recv a research topic that is now complete.
|
|
static bool recvResearch(NETQUEUE queue)
|
|
{
|
|
uint8_t player;
|
|
uint32_t index;
|
|
int i;
|
|
PLAYER_RESEARCH *pPlayerRes;
|
|
|
|
NETbeginDecode(queue, GAME_DEBUG_FINISH_RESEARCH);
|
|
NETuint8_t(&player);
|
|
NETuint32_t(&index);
|
|
NETend();
|
|
|
|
if (!getDebugMappingStatus() && bMultiPlayer)
|
|
{
|
|
debug(LOG_WARNING, "Failed to finish research for player %u.", NetPlay.players[queue.index].position);
|
|
return false;
|
|
}
|
|
|
|
syncDebug("player%d, index%u", player, index);
|
|
|
|
if (player >= MAX_PLAYERS || index >= asResearch.size())
|
|
{
|
|
debug(LOG_ERROR, "Bad GAME_DEBUG_FINISH_RESEARCH received, player is %d, index is %u", (int)player, index);
|
|
return false;
|
|
}
|
|
|
|
pPlayerRes = &asPlayerResList[player][index];
|
|
syncDebug("research status = %d", pPlayerRes->ResearchStatus & RESBITS);
|
|
|
|
if (!IsResearchCompleted(pPlayerRes))
|
|
{
|
|
MakeResearchCompleted(pPlayerRes);
|
|
researchResult(index, player, false, NULL, true);
|
|
}
|
|
|
|
// Update allies research accordingly
|
|
if (game.type == SKIRMISH)
|
|
{
|
|
for (i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
if (alliances[i][player] == ALLIANCE_FORMED)
|
|
{
|
|
pPlayerRes = &asPlayerResList[i][index];
|
|
|
|
if (!IsResearchCompleted(pPlayerRes))
|
|
{
|
|
// Do the research for that player
|
|
MakeResearchCompleted(pPlayerRes);
|
|
researchResult(index, i, false, NULL, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// New research stuff, so you can see what others are up to!
|
|
// inform others that I'm researching this.
|
|
bool sendResearchStatus(STRUCTURE *psBuilding, uint32_t index, uint8_t player, bool bStart)
|
|
{
|
|
if (!myResponsibility(player) || gameTime < 5)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_RESEARCHSTATUS);
|
|
NETuint8_t(&player);
|
|
NETbool(&bStart);
|
|
|
|
// If we know the building researching it then send its ID
|
|
if (psBuilding)
|
|
{
|
|
NETuint32_t(&psBuilding->id);
|
|
}
|
|
else
|
|
{
|
|
uint32_t zero = 0;
|
|
NETuint32_t(&zero);
|
|
}
|
|
|
|
// Finally the topic in question
|
|
NETuint32_t(&index);
|
|
NETend();
|
|
|
|
// Tell UI to remove from the list of available research.
|
|
MakeResearchStartedPending(&asPlayerResList[player][index]);
|
|
|
|
return true;
|
|
}
|
|
|
|
STRUCTURE *findResearchingFacilityByResearchIndex(unsigned player, unsigned index)
|
|
{
|
|
// Go through the structs to find the one doing this topic
|
|
for (STRUCTURE *psBuilding = apsStructLists[player]; psBuilding; psBuilding = psBuilding->psNext)
|
|
{
|
|
if (psBuilding->pStructureType->type == REF_RESEARCH
|
|
&& ((RESEARCH_FACILITY *)psBuilding->pFunctionality)->psSubject
|
|
&& ((RESEARCH_FACILITY *)psBuilding->pFunctionality)->psSubject->ref - REF_RESEARCH_START == index)
|
|
{
|
|
return psBuilding;
|
|
}
|
|
}
|
|
return NULL; // Not found.
|
|
}
|
|
|
|
bool recvResearchStatus(NETQUEUE queue)
|
|
{
|
|
STRUCTURE *psBuilding;
|
|
PLAYER_RESEARCH *pPlayerRes;
|
|
RESEARCH_FACILITY *psResFacilty;
|
|
RESEARCH *pResearch;
|
|
uint8_t player;
|
|
bool bStart;
|
|
uint32_t index, structRef;
|
|
|
|
NETbeginDecode(queue, GAME_RESEARCHSTATUS);
|
|
NETuint8_t(&player);
|
|
NETbool(&bStart);
|
|
NETuint32_t(&structRef);
|
|
NETuint32_t(&index);
|
|
NETend();
|
|
|
|
syncDebug("player%d, bStart%d, structRef%u, index%u", player, bStart, structRef, index);
|
|
|
|
if (player >= MAX_PLAYERS || index >= asResearch.size())
|
|
{
|
|
debug(LOG_ERROR, "Bad GAME_RESEARCHSTATUS received, player is %d, index is %u", (int)player, index);
|
|
return false;
|
|
}
|
|
if (!canGiveOrdersFor(queue.index, player))
|
|
{
|
|
debug(LOG_WARNING, "Droid order for wrong player.");
|
|
syncDebug("Wrong player.");
|
|
return false;
|
|
}
|
|
|
|
int prevResearchState = 0;
|
|
if (aiCheckAlliances(selectedPlayer, player))
|
|
{
|
|
prevResearchState = intGetResearchState();
|
|
}
|
|
|
|
pPlayerRes = &asPlayerResList[player][index];
|
|
|
|
// psBuilding may be null if finishing
|
|
if (bStart) // Starting research
|
|
{
|
|
ResetPendingResearchStatus(pPlayerRes); // Reset pending state, even if research state is not changed due to the structure being destroyed.
|
|
|
|
psBuilding = IdToStruct(structRef, player);
|
|
|
|
// Set that facility to research
|
|
if (psBuilding && psBuilding->pFunctionality)
|
|
{
|
|
psResFacilty = (RESEARCH_FACILITY *) psBuilding->pFunctionality;
|
|
|
|
popStatusPending(*psResFacilty); // Research is no longer pending, as it's actually starting now.
|
|
|
|
if (psResFacilty->psSubject)
|
|
{
|
|
cancelResearch(psBuilding, ModeImmediate);
|
|
}
|
|
|
|
if (IsResearchStarted(pPlayerRes))
|
|
{
|
|
STRUCTURE *psOtherBuilding = findResearchingFacilityByResearchIndex(player, index);
|
|
ASSERT(psOtherBuilding != NULL, "Something researched but no facility.");
|
|
if (psOtherBuilding != NULL)
|
|
{
|
|
cancelResearch(psOtherBuilding, ModeImmediate);
|
|
}
|
|
}
|
|
|
|
if (!researchAvailable(index, player, ModeImmediate) && bMultiPlayer)
|
|
{
|
|
debug(LOG_ERROR, "Player %d researching impossible topic \"%s\".", player, getName(&asResearch[index]));
|
|
return false;
|
|
}
|
|
|
|
// Set the subject up
|
|
pResearch = &asResearch[index];
|
|
psResFacilty->psSubject = pResearch;
|
|
|
|
// Start the research
|
|
MakeResearchStarted(pPlayerRes);
|
|
psResFacilty->timeStartHold = 0;
|
|
}
|
|
}
|
|
// Finished/cancelled research
|
|
else
|
|
{
|
|
// If they completed the research, we're done
|
|
if (IsResearchCompleted(pPlayerRes))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If they did not say what facility it was, look it up orselves
|
|
if (!structRef)
|
|
{
|
|
psBuilding = findResearchingFacilityByResearchIndex(player, index);
|
|
}
|
|
else
|
|
{
|
|
psBuilding = IdToStruct(structRef, player);
|
|
}
|
|
|
|
// Stop the facility doing any research
|
|
if (psBuilding)
|
|
{
|
|
cancelResearch(psBuilding, ModeImmediate);
|
|
popStatusPending(*(RESEARCH_FACILITY *)psBuilding->pFunctionality); // Research cancellation is no longer pending, as it's actually cancelling now.
|
|
}
|
|
}
|
|
|
|
if (aiCheckAlliances(selectedPlayer, player))
|
|
{
|
|
intAlliedResearchChanged();
|
|
intNotifyResearchButton(prevResearchState);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void printchatmsg(const char *text, int from)
|
|
{
|
|
char msg[MAX_CONSOLE_STRING_LENGTH];
|
|
|
|
sstrcpy(msg, NetPlay.players[from].name); // name
|
|
sstrcat(msg, ": "); // seperator
|
|
sstrcat(msg, text); // add message
|
|
addConsoleMessage(msg, DEFAULT_JUSTIFY, from); // display
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// 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(msg)); //clear buffer
|
|
memset(sendto,0x0, sizeof(sendto)); //clear private flag
|
|
memset(posTable,0x0, sizeof(posTable)); //clear buffer
|
|
sstrcpy(msg, curStr);
|
|
|
|
// This is for local display
|
|
if (from == selectedPlayer)
|
|
{
|
|
printchatmsg(normal ? curStr : display, from);
|
|
}
|
|
|
|
triggerEventChat(from, from, pStr); // send to self
|
|
|
|
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();
|
|
for (i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
if (i == selectedPlayer && from != i)
|
|
{
|
|
printchatmsg(display, from); // also display it
|
|
}
|
|
if (i != from && !isHumanPlayer(i) && myResponsibility(i))
|
|
{
|
|
msgStackPush(CALL_AI_MSG, from, i, msg, -1, -1, NULL);
|
|
triggerEventChat(from, i, msg);
|
|
}
|
|
else if (i != from && !isHumanPlayer(i) && !myResponsibility(i))
|
|
{
|
|
sendAIMessage(msg, from, i);
|
|
}
|
|
}
|
|
}
|
|
else if (normal)
|
|
{
|
|
for (i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
if (i != from && openchannels[i])
|
|
{
|
|
if (i == selectedPlayer)
|
|
{
|
|
printchatmsg(curStr, from); // also display it
|
|
}
|
|
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 if (myResponsibility(i))
|
|
{
|
|
msgStackPush(CALL_AI_MSG, from, i, msg, -1, -1, NULL);
|
|
triggerEventChat(from, i, msg);
|
|
}
|
|
else // send to AIs on different host
|
|
{
|
|
sendAIMessage(msg, from, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else //private msg
|
|
{
|
|
for (i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
if (sendto[i])
|
|
{
|
|
if (i == selectedPlayer)
|
|
{
|
|
printchatmsg(display, from); // also display it
|
|
}
|
|
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 if (myResponsibility(i))
|
|
{
|
|
msgStackPush(CALL_AI_MSG, from, i, curStr, -1, -1, NULL);
|
|
triggerEventChat(from, i, curStr);
|
|
}
|
|
else //also send to AIs now (non-humans), needed for AI
|
|
{
|
|
sendAIMessage(curStr, from, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void printConsoleNameChange(const char *oldName, const char *newName)
|
|
{
|
|
char msg[MAX_CONSOLE_STRING_LENGTH];
|
|
|
|
// Player changed name.
|
|
sstrcpy(msg, oldName); // Old name.
|
|
sstrcat(msg, " → "); // Separator
|
|
sstrcat(msg, newName); // New name.
|
|
|
|
addConsoleMessage(msg, DEFAULT_JUSTIFY, selectedPlayer); // display
|
|
}
|
|
|
|
|
|
//AI multiplayer message, send from a certain player index to another player index
|
|
static bool sendAIMessage(char *pStr, UDWORD player, UDWORD to)
|
|
{
|
|
UDWORD sendPlayer;
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
// Write a message to the console.
|
|
bool recvTextMessage(NETQUEUE queue)
|
|
{
|
|
UDWORD playerIndex;
|
|
char msg[MAX_CONSOLE_STRING_LENGTH];
|
|
char newmsg[MAX_CONSOLE_STRING_LENGTH];
|
|
|
|
memset(msg, 0x0, sizeof(msg));
|
|
memset(newmsg, 0x0, sizeof(newmsg));
|
|
|
|
NETbeginDecode(queue, NET_TEXTMSG);
|
|
// Who this msg is from
|
|
NETuint32_t(&playerIndex);
|
|
// The message to receive
|
|
NETstring(newmsg, MAX_CONSOLE_STRING_LENGTH);
|
|
NETend();
|
|
|
|
if (whosResponsible(playerIndex) != queue.index)
|
|
{
|
|
playerIndex = queue.index; // Fix corrupted playerIndex.
|
|
}
|
|
|
|
if (playerIndex >= MAX_PLAYERS || (!NetPlay.players[playerIndex].allocated && NetPlay.players[playerIndex].ai == AI_OPEN))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
sstrcpy(msg, NetPlay.players[playerIndex].name);
|
|
// Seperator
|
|
sstrcat(msg, ": ");
|
|
// Add message
|
|
sstrcat(msg, newmsg);
|
|
|
|
addConsoleMessage(msg, DEFAULT_JUSTIFY, playerIndex);
|
|
|
|
// Multiplayer message callback
|
|
// Received a console message from a player, save
|
|
MultiMsgPlayerFrom = playerIndex;
|
|
MultiMsgPlayerTo = selectedPlayer;
|
|
|
|
sstrcpy(MultiplayMsg, newmsg);
|
|
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 for AI (for hosted scripts)
|
|
bool recvTextMessageAI(NETQUEUE queue)
|
|
{
|
|
UDWORD sender, receiver;
|
|
char msg[MAX_CONSOLE_STRING_LENGTH];
|
|
char newmsg[MAX_CONSOLE_STRING_LENGTH];
|
|
|
|
NETbeginDecode(queue, NET_AITEXTMSG);
|
|
NETuint32_t(&sender); //in-game player index ('normal' one)
|
|
NETuint32_t(&receiver); //in-game player index
|
|
NETstring(newmsg,MAX_CONSOLE_STRING_LENGTH);
|
|
NETend();
|
|
|
|
if (whosResponsible(sender) != queue.index)
|
|
{
|
|
sender = queue.index; // Fix corrupted sender.
|
|
}
|
|
|
|
sstrcpy(msg, newmsg);
|
|
triggerEventChat(sender, receiver, newmsg);
|
|
|
|
//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
|
|
|
|
static void NETtemplate(DROID_TEMPLATE *pTempl)
|
|
{
|
|
NETqstring(pTempl->name);
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(pTempl->asParts); ++i)
|
|
{
|
|
NETuint8_t(&pTempl->asParts[i]);
|
|
}
|
|
|
|
NETint8_t(&pTempl->numWeaps);
|
|
NETbool(&pTempl->stored); // other players don't need to know, but we need to keep the knowledge in the loop somehow...
|
|
|
|
for (int i = 0; i < DROID_MAXWEAPS; ++i)
|
|
{
|
|
NETuint8_t(&pTempl->asWeaps[i]);
|
|
}
|
|
|
|
NETenum(&pTempl->droidType);
|
|
NETuint32_t(&pTempl->multiPlayerID);
|
|
}
|
|
|
|
// send a newly created template to other players
|
|
bool sendTemplate(uint32_t player, DROID_TEMPLATE *pTempl)
|
|
{
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_TEMPLATE);
|
|
NETuint32_t(&player);
|
|
NETtemplate(pTempl);
|
|
return NETend();
|
|
}
|
|
|
|
// receive a template created by another player
|
|
bool recvTemplate(NETQUEUE queue)
|
|
{
|
|
uint32_t player = 0;
|
|
DROID_TEMPLATE *psTempl;
|
|
DROID_TEMPLATE t;
|
|
|
|
NETbeginDecode(queue, GAME_TEMPLATE);
|
|
NETuint32_t(&player);
|
|
ASSERT_OR_RETURN(false, player < MAX_PLAYERS, "invalid player size: %d", player);
|
|
|
|
NETtemplate(&t);
|
|
NETend();
|
|
if (!canGiveOrdersFor(queue.index, player))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
t.prefab = false;
|
|
t.psNext = NULL;
|
|
t.ref = REF_TEMPLATE_START;
|
|
|
|
psTempl = IdToTemplate(t.multiPlayerID,player);
|
|
|
|
// Already exists
|
|
if (psTempl)
|
|
{
|
|
t.psNext = psTempl->psNext;
|
|
*psTempl = t;
|
|
debug(LOG_SYNC, "Updating MP template %d (stored=%s)", (int)t.multiPlayerID, t.stored ? "yes" : "no");
|
|
}
|
|
else
|
|
{
|
|
addTemplateBack(player, &t); // Add to back of list, to avoid game state templates being in wrong order, which matters when saving games.
|
|
debug(LOG_SYNC, "Creating MP template %d (stored=%s)", (int)t.multiPlayerID, t.stored ? "yes" : "no");
|
|
}
|
|
if (!t.prefab && player == selectedPlayer)
|
|
{
|
|
storeTemplates();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// inform others that you no longer have a template
|
|
|
|
bool SendDestroyTemplate(DROID_TEMPLATE *t, uint8_t player)
|
|
{
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_TEMPLATEDEST);
|
|
NETuint8_t(&player);
|
|
NETuint32_t(&t->multiPlayerID);
|
|
NETend();
|
|
|
|
return true;
|
|
}
|
|
|
|
// acknowledge another player no longer has a template
|
|
static bool recvDestroyTemplate(NETQUEUE queue)
|
|
{
|
|
uint8_t player;
|
|
uint32_t templateID;
|
|
DROID_TEMPLATE *psTempl, *psTempPrev = NULL;
|
|
|
|
NETbeginDecode(queue, GAME_TEMPLATEDEST);
|
|
NETuint8_t(&player);
|
|
NETuint32_t(&templateID);
|
|
NETend();
|
|
if (!canGiveOrdersFor(queue.index, player))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ASSERT_OR_RETURN(false, player < MAX_PLAYERS, "invalid player size: %d", player);
|
|
|
|
// Find the template in the list
|
|
for (psTempl = apsDroidTemplates[player]; psTempl; psTempl = psTempl->psNext)
|
|
{
|
|
if (psTempl->multiPlayerID == templateID)
|
|
{
|
|
break;
|
|
}
|
|
|
|
psTempPrev = psTempl;
|
|
}
|
|
|
|
// If we found it then delete it
|
|
if (psTempl)
|
|
{
|
|
// Update the linked list
|
|
if (psTempPrev)
|
|
{
|
|
psTempPrev->psNext = psTempl->psNext;
|
|
}
|
|
else
|
|
{
|
|
apsDroidTemplates[player] = psTempl->psNext;
|
|
}
|
|
|
|
// Delete the template.
|
|
//before deleting the template, need to make sure not being used in production
|
|
deleteTemplateFromProduction(psTempl, player, ModeImmediate);
|
|
delete psTempl;
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_ERROR, "Would delete missing template %d", templateID);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Features
|
|
|
|
// send a destruct feature message.
|
|
bool SendDestroyFeature(FEATURE *pF)
|
|
{
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_DEBUG_REMOVE_FEATURE);
|
|
NETuint32_t(&pF->id);
|
|
return NETend();
|
|
}
|
|
|
|
// process a destroy feature msg.
|
|
bool recvDestroyFeature(NETQUEUE queue)
|
|
{
|
|
FEATURE *pF;
|
|
uint32_t id;
|
|
|
|
NETbeginDecode(queue, GAME_DEBUG_REMOVE_FEATURE);
|
|
NETuint32_t(&id);
|
|
NETend();
|
|
|
|
if (!getDebugMappingStatus() && bMultiPlayer)
|
|
{
|
|
debug(LOG_WARNING, "Failed to remove feature for player %u.", NetPlay.players[queue.index].position);
|
|
return false;
|
|
}
|
|
|
|
pF = IdToFeature(id,ANYPLAYER);
|
|
if (pF == NULL)
|
|
{
|
|
debug(LOG_FEATURE, "feature id %d not found (probably already destroyed)", id);
|
|
return false;
|
|
}
|
|
|
|
debug(LOG_FEATURE, "p%d feature id %d destroyed (%s)", pF->player, pF->id, getName(pF->psStats));
|
|
// Remove the feature locally
|
|
turnOffMultiMsg(true);
|
|
destroyFeature(pF, gameTime - deltaGameTime + 1); // deltaGameTime is actually 0 here, since we're between updates. However, the value of gameTime - deltaGameTime + 1 will not change when we start the next tick.
|
|
turnOffMultiMsg(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Network File packet processor.
|
|
bool recvMapFileRequested(NETQUEUE queue)
|
|
{
|
|
//char mapStr[256],mapName[256],fixedname[256];
|
|
uint32_t player;
|
|
|
|
PHYSFS_sint64 fileSize_64;
|
|
PHYSFS_file *pFileHandle;
|
|
|
|
if(!NetPlay.isHost) // only host should act
|
|
{
|
|
ASSERT(false, "Host only routine detected for client!");
|
|
return false;
|
|
}
|
|
|
|
// Check to see who wants the file
|
|
NETbeginDecode(queue, NET_FILE_REQUESTED);
|
|
NETuint32_t(&player);
|
|
NETend();
|
|
|
|
if (!NetPlay.players[player].wzFile.isSending)
|
|
{
|
|
NetPlay.players[player].needFile = true;
|
|
NetPlay.players[player].wzFile.isCancelled = false;
|
|
NetPlay.players[player].wzFile.isSending = true;
|
|
|
|
LEVEL_DATASET *mapData = levFindDataSet(game.map, &game.hash);
|
|
|
|
addConsoleMessage("Map was requested: SENDING MAP!",DEFAULT_JUSTIFY, SYSTEM_MESSAGE);
|
|
|
|
char *mapStr = mapData->realFileName;
|
|
debug(LOG_INFO, "Map was requested. Looking for %s", mapStr);
|
|
|
|
// Checking to see if file is available...
|
|
pFileHandle = PHYSFS_openRead(mapStr);
|
|
if (pFileHandle == NULL)
|
|
{
|
|
debug(LOG_ERROR, "Failed to open %s for reading: %s", mapStr, PHYSFS_getLastError());
|
|
debug(LOG_FATAL, "You have a map (%s) that can't be located.\n\nMake sure it is in the correct directory and or format! (No map packs!)", mapStr);
|
|
// NOTE: if we get here, then the game is basically over, The host can't send the file for whatever reason...
|
|
// Which also means, that we can't continue.
|
|
debug(LOG_NET, "***Host has a file issue, and is being forced to quit!***");
|
|
NETbeginEncode(NETbroadcastQueue(), NET_HOST_DROPPED);
|
|
NETend();
|
|
abort();
|
|
}
|
|
|
|
// get the file's size.
|
|
fileSize_64 = PHYSFS_fileLength(pFileHandle);
|
|
debug(LOG_INFO, "File is valid, sending [directory: %s] %s to client %u", PHYSFS_getRealDir(mapStr), mapStr, player);
|
|
|
|
NetPlay.players[player].wzFile.pFileHandle = pFileHandle;
|
|
NetPlay.players[player].wzFile.fileSize_32 = (int32_t) fileSize_64; //we don't support 64bit int nettypes.
|
|
NetPlay.players[player].wzFile.currPos = 0;
|
|
|
|
NETsendFile(game.map, game.hash, player);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// continue sending the map
|
|
void sendMap(void)
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
if (NetPlay.players[i].wzFile.isSending)
|
|
{
|
|
int done = NETsendFile(game.map, game.hash, i);
|
|
if (done == 100)
|
|
{
|
|
addConsoleMessage("MAP SENT!",DEFAULT_JUSTIFY, SYSTEM_MESSAGE);
|
|
debug(LOG_INFO, "=== File has been sent to player %d ===", i);
|
|
NetPlay.players[i].wzFile.isSending = false;
|
|
NetPlay.players[i].needFile = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Another player is broadcasting a map, recv a chunk. Returns false if not yet done.
|
|
bool recvMapFileData(NETQUEUE queue)
|
|
{
|
|
mapDownloadProgress = NETrecvFile(queue);
|
|
if (mapDownloadProgress == 100)
|
|
{
|
|
addConsoleMessage("MAP DOWNLOADED!",DEFAULT_JUSTIFY, SYSTEM_MESSAGE);
|
|
sendTextMessage("MAP DOWNLOADED",true); //send
|
|
debug(LOG_INFO, "=== File has been received. ===");
|
|
|
|
// clear out the old level list.
|
|
levShutDown();
|
|
levInitialise();
|
|
rebuildSearchPath(mod_multiplay, true); // MUST rebuild search path for the new maps we just got!
|
|
if (!buildMapList())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LEVEL_DATASET *mapData = levFindDataSet(game.map, &game.hash);
|
|
if ( mapData && CheckForMod(mapData->realFileName))
|
|
{
|
|
char buf[256];
|
|
if (game.isMapMod)
|
|
{
|
|
ssprintf(buf, _("Warning, this is a map-mod, it could alter normal gameplay."));
|
|
}
|
|
else
|
|
{
|
|
ssprintf(buf, _("Warning, HOST has altered the game code, and can't be trusted!"));
|
|
}
|
|
addConsoleMessage(buf, DEFAULT_JUSTIFY, NOTIFY_MESSAGE);
|
|
game.isMapMod = true;
|
|
widgReveal(psWScreen, MULTIOP_MAP_MOD);
|
|
}
|
|
|
|
loadMapPreview(false);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------------------------//
|
|
|
|
/* 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 + 1 >= 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<msgStackPos;i++)
|
|
{
|
|
msgPlFrom[i] = msgPlFrom[i+1];
|
|
msgPlTo[i] = msgPlTo[i+1];
|
|
|
|
callbackType[i] = callbackType[i+1];
|
|
locx[i] = locx[i+1];
|
|
locy[i] = locy[i+1];
|
|
|
|
strcpy(msgStr[i], msgStr[i+1]);
|
|
}
|
|
|
|
//erase top element
|
|
msgPlFrom[msgStackPos] = -2;
|
|
msgPlTo[msgStackPos] = -2;
|
|
|
|
callbackType[msgStackPos] = -2;
|
|
locx[msgStackPos] = -2;
|
|
locy[msgStackPos] = -2;
|
|
|
|
strcpy(msgStr[msgStackPos], "ERROR char!!!!!!!!");
|
|
|
|
msgStackPos--; //since removed the top element
|
|
|
|
return true;
|
|
}
|
|
|
|
bool msgStackPop(void)
|
|
{
|
|
debug(LOG_WZ, "msgStackPop: stack size %d", msgStackPos);
|
|
|
|
if(msgStackPos < 0 || msgStackPos > 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(NETQUEUE queue)
|
|
{
|
|
int32_t sender, receiver,locX, locY;
|
|
char msg[MAX_CONSOLE_STRING_LENGTH];
|
|
|
|
NETbeginDecode(queue, NET_BEACONMSG);
|
|
NETint32_t(&sender); // the actual sender
|
|
NETint32_t(&receiver); // the actual receiver (might not be the same as the one we are actually sending to, in case of AIs)
|
|
NETint32_t(&locX);
|
|
NETint32_t(&locY);
|
|
NETstring(msg, sizeof(msg)); // Receive the actual message
|
|
NETend();
|
|
|
|
debug(LOG_WZ, "Received beacon for player: %d, from: %d",receiver, sender);
|
|
|
|
sstrcat(msg, NetPlay.players[sender].name); // name
|
|
sstrcpy(beaconReceiveMsg[sender], msg);
|
|
|
|
return addBeaconBlip(locX, locY, receiver, sender, beaconReceiveMsg[sender]);
|
|
}
|
|
|
|
const char* getPlayerColourName(int player)
|
|
{
|
|
static const char* playerColors[] =
|
|
{
|
|
N_("Green"),
|
|
N_("Orange"),
|
|
N_("Grey"),
|
|
N_("Black"),
|
|
N_("Red"),
|
|
N_("Blue"),
|
|
N_("Pink"),
|
|
N_("Cyan"),
|
|
N_("Yellow"),
|
|
N_("Purple"),
|
|
N_("White"),
|
|
N_("Bright blue"),
|
|
N_("Neon green"),
|
|
N_("Infrared"),
|
|
N_("Ultraviolet"),
|
|
N_("Brown"),
|
|
};
|
|
STATIC_ASSERT(MAX_PLAYERS <= ARRAY_SIZE(playerColors));
|
|
|
|
ASSERT(player < ARRAY_SIZE(playerColors), "player number (%d) exceeds maximum (%lu)", player, (unsigned long) ARRAY_SIZE(playerColors));
|
|
|
|
if (player >= ARRAY_SIZE(playerColors))
|
|
{
|
|
return "";
|
|
}
|
|
|
|
return gettext(playerColors[getPlayerColour(player)]);
|
|
}
|
|
|
|
/* Reset ready status for all players */
|
|
void resetReadyStatus(bool bSendOptions)
|
|
{
|
|
// notify all clients if needed
|
|
if(bSendOptions)
|
|
{
|
|
sendOptions();
|
|
}
|
|
netPlayersUpdated = true;
|
|
}
|