/* NETWORK.C Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc. and the "Aleph One" developers. This program 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. This program 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. This license is contained in the file "COPYING", which is included with this source code; it is available online at http://www.gnu.org/licenses/gpl.html Monday, June 20, 1994 12:22:03 PM Wednesday, June 29, 1994 9:14:21 PM made ddp ring work with more than 2 players (upring and downring were confused) Saturday, July 2, 1994 3:54:12 PM simple distribution of map Friday, July 15, 1994 10:51:38 AM gracefully handling players dropping from the game. don't allow quiting from the game while we have the ring packet. changed distribution of the map now that we transfer a level at a time. Sunday, July 17, 1994 4:01:18 PM multiple updates per packet Monday, July 18, 1994 11:51:51 AM transfering map in chunks now, since ADSP can only write 64K at a time. Tuesday, July 19, 1994 7:14:30 PM fixed one player ring bug yesterday. Wednesday, July 20, 1994 12:34:06 AM variable number of updates per packet. (can only be adjusted upward, not downward). Monday, July 25, 1994 9:04:24 PM Jason's new algorithm. dropping players and slowing down the ring doesn't work now. but performance is much smoother, and better understood, to boot. Sunday, August 21, 1994 3:58:23 PM about a week ago, added stuff to use the ring to distribute other information, like sound or text for the game. Jan 30, 2000 (Loren Petrich): Added some typecasts Feb. 4, 2000 (Loren Petrich): Changed halt() to assert(false) for better debugging Sept-Oct 2001 (Woody Zenfell): (roughly in order) Plugged in netcpy/_NET stuff and a couple #ifdef SDL byte-swappers for portable data formats. Changed a couple memcpy() calls to memmove() calls as they have overlapping source and dest. Allowed the use of the MyTM* functions, which now have SDL_thread-based implementations. Added optional NETWORK_FAUX_QUEUE mechanism, should work on either platform. It was a good idea, I think, but ultimately fairly pointless. NETWORK_ADAPTIVE_LATENCY should be better. Added optional NETWORK_ADAPTIVE_LATENCY mechanism, should work on either platform. Changed some #ifdef mac conditionals to #ifndef NETWORK_IP to better convey what we're worried about. Added NETWORK_USE_RECENT_FLAGS option to discard excess flags from the head, rather than tail, of the queue. Added... how to say... "copious" comments (ZZZ) at various times as I browsed the source and made changes. Found that a basic assumption I was using in my optimizations (i.e. that the game processed action_flags at a constant rate) was wrong, which made the Bungie way make a lot more sense. I now recommend using *none* of the three NETWORK_* options (do use NETWORK_IP though of course if appropriate). Nov 13, 2001 (Woody Zenfell): Although things were basically OK under favorable conditions, they were IMO too "fragile" - sensitive to latency and jitter. I couldn't help but try again... so NETWORK_ADAPTIVE_LATENCY_2 has been added. Also put in NETWORK_SMARTER_FLAG_DITCHING mechanism. Feb 27, 2002 (Br'fin (Jeremy Parsons)): Rewired things to more generally key off of HAVE_SDL_NET than SDL (The Carbon build has SDL_NET, but understandably lacks SDL) Uses #if HAVE_SDL_NET in place of calls to #ifndef mac to allow SDL networking under Carbon Mar 3-8, 2002 (Woody Zenfell): Changed net distribution stuff to use an STL map to associate distribution types with {lossy, handling procedure}. Now different endstations can have different distribution types installed for handling (previously they had to all install the same handlers in the same order to get the same distribution type ID's). Feb 5, 2003 (Woody Zenfell): Preliminary support for resuming saved-games networked. Feb 13, 2003 (Woody Zenfell): Resuming saved-games as network games works. May 24, 2003 (Woody Zenfell): Split out ring-protocol-specific stuff from here to RingGameProtocol.cpp. This is multiple-game-protocol-savvy now. Support for graceful handling of unknown streaming-data packet types. July 03, 2003 (jkvw): Added network lua scripts. September 17, 2004 (jkvw): NAT-friendly networking. That is, joiners behind firewalls should be able to play. Also moved to TCPMess for TCP communications. */ #if defined(DISABLE_NETWORKING) #include "network_dummy.cpp" #else /* I would really like to be able to let the Unysnc packet go around the loop, but it is difficult because all the code is currently setup to handle only one packet at a time. Currently 1 player games (when others are dropped) always have lots of late packets (never get acknowledged properly, since they are the only player.) Note that the unregister isn't fast enough, and that the registration code is stupid. Also should setup the dialog such that it doesn't allow for network play when a player is added. */ /* NetADSPRead() should time out by calling PBControl(dspStatus, ...) to see if there are enough bytes clearly this is all broken until we have packet types */ #include "cseries.h" #include "map.h" // for TICKS_PER_SECOND and "struct entry_point" #include "interface.h" // for transfering map #include "mytm.h" // ZZZ: both versions use mytm now #include "preferences.h" // for network_preferences and environment_preferences #include "sdl_network.h" #include "network_lookup_sdl.h" #include "SDL_thread.h" #include "game_errors.h" #include "CommunicationsChannel.h" #include "Console.h" #include "MessageDispatcher.h" #include "MessageInflater.h" #include "MessageHandler.h" #include "progress.h" #include "extensions.h" #include #include #include #include #include "Logging.h" // ZZZ: moved many struct definitions, constant #defines, etc. to header for (limited) sharing #include "network_private.h" // ZZZ: since network data format is now distinct from in-memory format. // (quite similar, admittedly, in this first effort... ;) ) #include "network_data_formats.h" #include "network_messages.h" #include "NetworkGameProtocol.h" #include "RingGameProtocol.h" #include "StarGameProtocol.h" #include "lua_script.h" #include "libnat.h" #include #include "network_metaserver.h" #include "network_sound.h" // For temporary multiple MML specified lua scripts hack extern bool gLoadingLuaNetscript; /* ---------- globals */ static short ddpSocket; /* our ddp socket number */ static short localPlayerIndex; static short localPlayerIdentifier; static NetTopologyPtr topology; static short sServerPlayerIndex; static bool sOldSelfSendStatus; static RingGameProtocol sRingGameProtocol; static StarGameProtocol sStarGameProtocol; static NetworkGameProtocol* sCurrentGameProtocol = NULL; static byte *deferred_script_data = NULL; static size_t deferred_script_length = 0; static bool do_netscript; static CommunicationsChannelFactory *server = NULL; typedef std::map client_map_t; static client_map_t connections_to_clients; typedef std::map client_chat_info_map_t; static client_chat_info_map_t client_chat_info; static CommunicationsChannel *connection_to_server = NULL; static int next_stream_id = 1; // 0 is local player static IPaddress host_address; static bool host_address_specified = false; static MessageInflater *inflater = NULL; static MessageDispatcher *joinDispatcher = NULL; static MessageDispatcher *gatherDispatcher = NULL; static uint32 next_join_attempt; static Capabilities my_capabilities; static GatherCallbacks *gatherCallbacks = NULL; static ChatCallbacks *chatCallbacks = NULL; static UpnpController *controller = NULL; extern MetaserverClient* gMetaserverClient; static bool sDisplayPings; struct toggle_ping_display { void operator()(const std::string&) const { sDisplayPings = !sDisplayPings; }; }; // ignore list static std::set sIgnoredPlayers; bool player_is_ignored(int player_index) { return (sIgnoredPlayers.find(player_index) != sIgnoredPlayers.end()); } struct ignore_player { void operator()(const std::string& s) const { int player_index = atoi(s.c_str()); if (player_index == localPlayerIndex) { screen_printf("you can't ignore yourself"); } else if (player_index >= 0 && player_index < topology->player_count) { if (sIgnoredPlayers.find(player_index) != sIgnoredPlayers.end()) { screen_printf("removing player %i from the ignore list", player_index); sIgnoredPlayers.erase(player_index); } else { screen_printf("adding player %i to the ignore list", player_index); sIgnoredPlayers.insert(player_index); } } else { screen_printf("invalid player %i", player_index); } } }; struct ignore_lua { void operator()(const std::string&) const { ToggleLuaMute(); } }; struct ignore_mic { void operator()(const std::string& s) const { int player_index = atoi(s.c_str()); if (player_index == localPlayerIndex) { screen_printf("you can't ignore your own mic"); } else if (player_index >= 0 && player_index < topology->player_count) { mute_player_mic(player_index); } else { screen_printf("invalid player %i", player_index); } } }; // ZZZ note: very few folks touch the streaming data, so the data-format issues outlined above with // datagrams (the data from which are passed around, interpreted, and touched by many functions) // don't matter as much. Do observe, though, that users of the "distribution" mechanism will have // to pack and unpack their own distribution data - we can't be expected to know what they're doing. // ZZZ note: read this externally with the NetState() function. static short netState= netUninitialized; // ZZZ change: now using an STL 'map' to, well, _map_ distribution types to info records. typedef std::map distribution_info_map_t; static distribution_info_map_t distribution_info_map; #ifdef NETWORK_CHAT static NetChatMessage incoming_chat_message_buffer; static bool new_incoming_chat_message = false; #endif // ZZZ: are we trying to start a new game or resume a saved-game? // This is only valid on the gatherer after NetGather() is called; // only valid on a joiner once he receives the final topology (tagRESUME_GAME) // Used, at least, on the gatherer to determine whether or not to resort players by address static bool resuming_saved_game = false; /* ---------- private prototypes */ void NetPrintInfo(void); // ZZZ: cmon, we're not fooling anyone... game_data is a game_info*; player_data is a player_info* // Originally I guess the plan was to have a strong separation between Marathon game code and the networking code, // such that they could be compiled independently and only know about each other at link-time, but I don't see any // reason to try to keep that... and I suspect Jason abandoned this separation long ago anyway. // For now, the only effect I see is a reduction in type-safety. :) static void NetInitializeTopology(void *game_data, short game_data_size, void *player_data, short player_data_size); static void NetLocalAddrBlock(NetAddrBlock *address, short socketNumber); static int net_compare(void const *p1, void const *p2); static void NetUpdateTopology(void); static void NetDistributeTopology(short tag); static bool NetSetSelfSend(bool on); static void NetDDPPacketHandler(DDPPacketBufferPtr inPacket); int getStreamIdFromChannel(CommunicationsChannel *channel) { client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->channel == channel) { return it->first; } } return -1; } //----------------------------------------------------------------------------- // Message handlers //----------------------------------------------------------------------------- // Gatherer Client::~Client() { delete channel; } CheckPlayerProcPtr Client::check_player; Client::Client(CommunicationsChannel *inChannel) : mDispatcher(new MessageDispatcher()), channel(inChannel) { mJoinerInfoMessageHandler.reset(newMessageHandlerMethod(this, &Client::handleJoinerInfoMessage)); mCapabilitiesMessageHandler.reset(newMessageHandlerMethod(this, &Client::handleCapabilitiesMessage)); mAcceptJoinMessageHandler.reset(newMessageHandlerMethod(this, &Client::handleAcceptJoinMessage)); mChatMessageHandler.reset(newMessageHandlerMethod(this, &Client::handleChatMessage)); mChangeColorsMessageHandler.reset(newMessageHandlerMethod(this, &Client::handleChangeColorsMessage)); mUnexpectedMessageHandler.reset(newMessageHandlerMethod(this, &Client::unexpectedMessageHandler)); mDispatcher->setDefaultHandler(mUnexpectedMessageHandler.get()); mDispatcher->setHandlerForType(mJoinerInfoMessageHandler.get(), JoinerInfoMessage::kType); mDispatcher->setHandlerForType(mCapabilitiesMessageHandler.get(), CapabilitiesMessage::kType); mDispatcher->setHandlerForType(mAcceptJoinMessageHandler.get(), AcceptJoinMessage::kType); mDispatcher->setHandlerForType(mChatMessageHandler.get(), NetworkChatMessage::kType); mDispatcher->setHandlerForType(mChangeColorsMessageHandler.get(), ChangeColorsMessage::kType); channel->setMessageHandler(mDispatcher.get()); } void Client::drop() { int stream_id = getStreamIdFromChannel(channel); if (client_chat_info[stream_id]) { ClientInfoMessage clientInfoMessage(stream_id, client_chat_info[stream_id], (int16) ClientInfoMessage::kRemove); client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->can_pregame_chat()) { it->second->channel->enqueueOutgoingMessage(clientInfoMessage); } } delete client_chat_info[stream_id]; client_chat_info.erase(stream_id); } if (state == _connected) { // (remove from the list of joinable players) if (gatherCallbacks) { prospective_joiner_info player; player.stream_id = getStreamIdFromChannel(channel); gatherCallbacks->JoiningPlayerDropped(&player); } } else if (state == _awaiting_map) { // need to remove from topo uint16 stream_id = getStreamIdFromChannel(channel); int i; for (i = 1; i < topology->player_count; i++) { if (topology->players[i].stream_id == stream_id) { break; } } if (i != topology->player_count) { for (; i < topology->player_count - 1; i++) { topology->players[i] = topology->players[i + 1]; } topology->player_count--; NetUpdateTopology(); if (gatherCallbacks) { prospective_joiner_info player; player.stream_id = getStreamIdFromChannel(channel); gatherCallbacks->JoinedPlayerDropped(&player); } NetDistributeTopology(tagDROPPED_PLAYER); } else { logAnomaly("a client in state _awaiting_map dropped, but was not found in the topology"); } } } bool Client::capabilities_indicate_player_is_gatherable(bool warn_joiner) { // ghs: perhaps someday there will be an elegant, extensible way to do this // but for now, this is what you get char s[256]; // As this is the first version, I do not check version numbers for // compatibility! Because I don't know if higher numbers necessarily mean // incompatibility. In the future, gatherer and joiner should advertise what // they have, and the higher one must contain the backward compatibility // logic if any exists // joiners can disable themselves from being gathered by replying with // capabilities where the "Gatherable" capability is set to 0...this will // not trigger a warning message from the gatherer // gatherer disables gathering by returning false from this function, and // sending a descriptive error message to the joiner saying he won't show up if (capabilities[Capabilities::kGatherable] == 0) { // no warning, the joiner already knows he is incompatible return false; } if (network_preferences->game_protocol == _network_game_protocol_star) { if (capabilities[Capabilities::kStar] == 0) { if (warn_joiner) { ServerWarningMessage serverWarningMessage(getcstr(s, strNETWORK_ERRORS, netWarnJoinerHasNoStar), ServerWarningMessage::kJoinerUngatherable); channel->enqueueOutgoingMessage(serverWarningMessage); } return false; } else if (capabilities[Capabilities::kStar] < Capabilities::kStarVersion) { if (warn_joiner) { ServerWarningMessage serverWarningMessage("The gatherer is using a preview version of Aleph One. You will not appear in the list of available players.", ServerWarningMessage::kJoinerUngatherable); channel->enqueueOutgoingMessage(serverWarningMessage); } return false; } } else { if (capabilities[Capabilities::kRing] == 0) { if (warn_joiner) { ServerWarningMessage serverWarningMessage(getcstr(s, strNETWORK_ERRORS, netWarnJoinerHasNoRing), ServerWarningMessage::kJoinerUngatherable); channel->enqueueOutgoingMessage(serverWarningMessage); } return false; } } if (do_netscript && capabilities[Capabilities::kLua] == 0) { if (warn_joiner) { char s[256]; ServerWarningMessage serverWarningMessage(getcstr(s, strNETWORK_ERRORS, netWarnJoinerNoLua), ServerWarningMessage::kJoinerUngatherable); channel->enqueueOutgoingMessage(serverWarningMessage); } return false; } return true; } void Client::handleJoinerInfoMessage(JoinerInfoMessage* joinerInfoMessage, CommunicationsChannel *) { if (netState == netGathering) { if (joinerInfoMessage->version() == kNetworkSetupProtocolID) { pstrcpy(name, joinerInfoMessage->info()->name); unsigned char pname[1024]; pstrcpy(pname, joinerInfoMessage->info()->name); a1_p2cstr(pname); int16 stream_id = getStreamIdFromChannel(channel); client_chat_info[stream_id] = new ClientChatInfo; client_chat_info[stream_id]->name = (char *) pname; client_chat_info[stream_id]->color = joinerInfoMessage->info()->color; client_chat_info[stream_id]->team = joinerInfoMessage->info()->team; ClientInfoMessage clientInfoMessage(stream_id, client_chat_info[stream_id], (int16) ClientInfoMessage::kAdd); client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->can_pregame_chat()) { it->second->channel->enqueueOutgoingMessage(clientInfoMessage); } } // send gatherer capabilities CapabilitiesMessage capabilitiesMessage(my_capabilities); channel->enqueueOutgoingMessage(capabilitiesMessage); state = Client::_awaiting_capabilities; } else { // strange, joiner should have realized he couldn't join // ok, disconnect him state = Client::_disconnect; } } else { logAnomaly1("unexpected joiner info message received (netState is %i)", netState); } } void Client::handleCapabilitiesMessage(CapabilitiesMessage* capabilitiesMessage, CommunicationsChannel *) { if (state == _awaiting_capabilities) { capabilities = *capabilitiesMessage->capabilities(); if (capabilities_indicate_player_is_gatherable(_warn_joiner)) { state = Client::_connected_but_not_yet_shown; } else { state = Client::_ungatherable; } client_chat_info_map_t::iterator it; for (it = client_chat_info.begin(); it != client_chat_info.end(); it++) { if (it->second) { ClientInfoMessage clientInfoMessage(it->first, it->second, (int16) ClientInfoMessage::kAdd); channel->enqueueOutgoingMessage(clientInfoMessage); } } } else { logAnomaly1("unexpected capabilities message received (state is %i)", state); } } /* void Client::handleScriptMessage(ScriptMessage* scriptMessage, CommunicationsChannel *) { if (state == _awaiting_script_message) { if (do_netscript && scriptMessage->value() != _netscript_yes_script_message) { alert_user(infoError, strNETWORK_ERRORS, netErrUngatheredPlayerUnacceptable, 0); state = _ungatherable; } else { JoinPlayerMessage joinPlayerMessage(topology->nextIdentifier); channel->enqueueOutgoingMessage(joinPlayerMessage); state = _awaiting_accept_join; } } else { logAnomaly1("unexpected script message received (state is %i)", state); } } */ void Client::handleAcceptJoinMessage(AcceptJoinMessage* acceptJoinMessage, CommunicationsChannel *) { if (state == _awaiting_accept_join) { if (acceptJoinMessage->accepted()) { topology->players[topology->player_count] = *acceptJoinMessage->player(); topology->players[topology->player_count].stream_id = getStreamIdFromChannel(channel); topology->players[topology->player_count].net_dead = false; prospective_joiner_info player; player.stream_id = topology->players[topology->player_count].stream_id; topology->players[topology->player_count].dspAddress = channel->peerAddress(); topology->players[topology->player_count].ddpAddress.host = channel->peerAddress().host; topology->player_count += 1; check_player(topology->player_count - 1, topology->player_count); NetUpdateTopology(); NetDistributeTopology(tagNEW_PLAYER); state = _awaiting_map; if (gatherCallbacks) gatherCallbacks->JoinSucceeded(&player); } else { // joiner didn't accept!? alert_user(infoError, strNETWORK_ERRORS, netErrCantAddPlayer, 0); state = _ungatherable; } } else { logAnomaly1("unexpected accept join message received (state is %i)", state); } } void Client::handleChangeColorsMessage(ChangeColorsMessage *changeColorsMessage, CommunicationsChannel *channel) { if (can_pregame_chat()) { int stream_id = getStreamIdFromChannel(channel); if (client_chat_info[stream_id]) { client_chat_info[stream_id]->color = changeColorsMessage->color(); client_chat_info[stream_id]->team = changeColorsMessage->team(); ClientInfoMessage clientInfoMessage(stream_id, client_chat_info[stream_id], (int16) ClientInfoMessage::kUpdate); client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->can_pregame_chat()) { it->second->channel->enqueueOutgoingMessage(clientInfoMessage); } } } else { logAnomaly1("change colors message received, but client chat info does not exist for %i", stream_id); } } if (state == _awaiting_map) { uint16 stream_id = getStreamIdFromChannel(channel); int i; for (i = 1; i < topology->player_count; i++) { if (topology->players[i].stream_id == stream_id) { break; } } if (i != topology->player_count) { player_info *player = &topology->players[i].player_data; if (player->desired_color != changeColorsMessage->color() || player->team != changeColorsMessage->team()) { player->desired_color = changeColorsMessage->color(); player->team = changeColorsMessage->team(); check_player(i, topology->player_count); NetUpdateTopology(); NetDistributeTopology(tagCHANGED_PLAYER); if (gatherCallbacks) { prospective_joiner_info player_to_change; player_to_change.stream_id = stream_id; gatherCallbacks->JoinedPlayerChanged(&player_to_change); } } } else { logAnomaly("a client in state _awaiting_map requested a color change, but was not found in the topology"); } } else if (!can_pregame_chat()) { logAnomaly1("unexpected change colors message received (state is %i)", state); } } void Client::handleChatMessage(NetworkChatMessage* netChatMessage, CommunicationsChannel *) { // relay this to all clients if (state == _ingame) { assert(netState == netActive); if (netChatMessage->target() == NetworkChatMessage::kTargetPlayers) { NetworkChatMessage chatMessage(netChatMessage->chatText(), getStreamIdFromChannel(channel), NetworkChatMessage::kTargetPlayers); client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->state == _ingame) { it->second->channel->enqueueOutgoingMessage(chatMessage); } } // display it locally if (chatCallbacks) { for (int playerIndex = 0; playerIndex < topology->player_count; playerIndex++) { if (topology->players[playerIndex].stream_id == getStreamIdFromChannel(channel)) { if (player_is_ignored(playerIndex)) return; static unsigned char name[MAX_NET_PLAYER_NAME_LENGTH + 1]; pstrcpy(name, topology->players[playerIndex].player_data.name); a1_p2cstr(name); chatCallbacks->ReceivedMessageFromPlayer((char *)name, netChatMessage->chatText()); return; } } } } else { logNote("in-game chat message currently only supports sending messages to all players; not relaying"); } } else if (can_pregame_chat()) { if (netChatMessage->target() == NetworkChatMessage::kTargetClients) { NetworkChatMessage chatMessage(netChatMessage->chatText(), getStreamIdFromChannel(channel), NetworkChatMessage::kTargetClients); client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->can_pregame_chat()) { it->second->channel->enqueueOutgoingMessage(chatMessage); } } if (chatCallbacks) { int stream_id = getStreamIdFromChannel(channel); if (client_chat_info[stream_id]) { chatCallbacks->ReceivedMessageFromPlayer(client_chat_info[stream_id]->name.c_str(), netChatMessage->chatText()); } else { logAnomaly1("chat message from %i, player not found", stream_id); } } } else { logNote("pre-game chat currently only supports sending messages to all players; not relaying"); } } else { logNote("non in-game/pre-game chat message received; ignoring"); } } void Client::unexpectedMessageHandler(Message *message, CommunicationsChannel *) { logAnomaly1("unexpected message type %i received\n", message->type()); } static short handlerState; static void handleHelloMessage(HelloMessage* helloMessage, CommunicationsChannel*) { if (handlerState == netAwaitingHello) { // if the network versions match, reply with my join info if (helloMessage->version() == kNetworkSetupProtocolID) { prospective_joiner_info my_info; pstrcpy(my_info.name, player_preferences->name); my_info.color = player_preferences->color; my_info.team = player_preferences->team; JoinerInfoMessage joinerInfoMessage(&my_info, kNetworkSetupProtocolID); connection_to_server->enqueueOutgoingMessage(joinerInfoMessage); handlerState = netJoining; } else { alert_user(infoError, strNETWORK_ERRORS, netErrIncompatibleVersion, 0); handlerState = netJoinErrorOccurred; } } else { logAnomaly1("unexpected hello message received (netState is %i)", netState); } } static void handleCapabilitiesMessage(CapabilitiesMessage* capabilitiesMessage, CommunicationsChannel *) { if (handlerState == netJoining) { Capabilities capabilities = *capabilitiesMessage->capabilities(); if (network_preferences->game_protocol == _network_game_protocol_star && capabilities[Capabilities::kStar] < Capabilities::kStarVersion) { // I'm not gatherable my_capabilities[Capabilities::kGatherable] = 0; CapabilitiesMessage capabilitiesMessageReply(my_capabilities); connection_to_server->enqueueOutgoingMessage(capabilitiesMessageReply); my_capabilities[Capabilities::kGatherable] = Capabilities::kGatherableVersion; char *s = strdup("The gatherer is using an old version of Aleph One. You will not appear in the list of available players."); alert_user(s); free(s); } else { // everything else is version 1 CapabilitiesMessage capabilitiesMessageReply(my_capabilities); connection_to_server->enqueueOutgoingMessage(capabilitiesMessageReply); } } else { logAnomaly1("unexpected capabilities message received (netState is %i)", netState); } } static void handleClientInfoMessage(ClientInfoMessage* clientInfoMessage, CommunicationsChannel *) { if (netState == netJoining || netState == netWaiting || netState == netStartingUp || netState == netActive) { int16 id = clientInfoMessage->stream_id(); if (clientInfoMessage->action() == ClientInfoMessage::kAdd) { if (client_chat_info[id]) { logAnomaly1("add message for client that already exists (%i)", id); delete client_chat_info[id]; client_chat_info.erase(id); } client_chat_info[id] = new ClientChatInfo; *client_chat_info[id] = *clientInfoMessage->info(); } else if (clientInfoMessage->action() == ClientInfoMessage::kRemove) { delete client_chat_info[id]; client_chat_info.erase(id); } else if (clientInfoMessage->action() == ClientInfoMessage::kUpdate) { *client_chat_info[id] = *clientInfoMessage->info(); } else { logAnomaly1("unknown client info message action %i", clientInfoMessage->action()); } } else { logAnomaly1("unexpected client info message received (netState is %i)", netState); } } static void handleJoinPlayerMessage(JoinPlayerMessage* joinPlayerMessage, CommunicationsChannel*) { if (handlerState == netJoining) { /* Note that we could set accepted to false if we wanted to for some */ /* reason- such as bad serial numbers.... */ SetNetscriptStatus (false); // Unless told otherwise, we don't expect a netscript /* Note that we share the buffers.. */ localPlayerIdentifier= joinPlayerMessage->value(); topology->players[localPlayerIndex].identifier= localPlayerIdentifier; topology->players[localPlayerIndex].net_dead= false; /* Confirm. */ AcceptJoinMessage acceptJoinMessage(true, &topology->players[localPlayerIndex]); connection_to_server->enqueueOutgoingMessage(acceptJoinMessage); if (acceptJoinMessage.accepted()) { handlerState = netWaiting; } else { handlerState = netJoinErrorOccurred; } } else { logAnomaly1("unexpected join player message received (netState is %i)", netState); } } static byte *handlerLuaBuffer = NULL; static size_t handlerLuaLength = 0; static void handleLuaMessage(LuaMessage *luaMessage, CommunicationsChannel *) { if (netState == netStartingUp || netState == netDown) { if (handlerLuaBuffer) { delete[] handlerLuaBuffer; handlerLuaBuffer = NULL; } handlerLuaLength = luaMessage->length(); if (handlerLuaLength > 0) { handlerLuaBuffer = new byte[handlerLuaLength]; memcpy(handlerLuaBuffer, luaMessage->buffer(), handlerLuaLength); } } else { logAnomaly1("unexpected lua message received (netState is %i)", netState); } } static byte *handlerMapBuffer = NULL; static size_t handlerMapLength = 0; static void handleMapMessage(MapMessage *mapMessage, CommunicationsChannel *) { if (netState == netStartingUp || netState == netDown) { if (handlerMapBuffer) { // assume the last map the server sent is right delete[] handlerMapBuffer; handlerMapBuffer = NULL; } handlerMapLength = mapMessage->length(); if (handlerMapLength > 0) { handlerMapBuffer = new byte[handlerMapLength]; memcpy(handlerMapBuffer, mapMessage->buffer(), handlerMapLength); } } else { logAnomaly1("unexpected map message received (netState is %i)", netState); } } static void handleNetworkChatMessage(NetworkChatMessage *chatMessage, CommunicationsChannel *) { if (chatCallbacks) { if (netState == netActive) { for (int playerIndex = 0; playerIndex < topology->player_count; playerIndex++) { if (topology->players[playerIndex].stream_id == chatMessage->senderID()) { if (player_is_ignored(playerIndex)) return; static unsigned char name[MAX_NET_PLAYER_NAME_LENGTH + 1]; pstrcpy(name, topology->players[playerIndex].player_data.name); a1_p2cstr(name); chatCallbacks->ReceivedMessageFromPlayer((char *)name, chatMessage->chatText()); return; } } logAnomaly1("chat message from %i, player not found", chatMessage->senderID()); } else if (netState == netJoining || netState == netWaiting) { if (client_chat_info[chatMessage->senderID()]) { chatCallbacks->ReceivedMessageFromPlayer(client_chat_info[chatMessage->senderID()]->name.c_str(), chatMessage->chatText()); } else { logAnomaly1("chat message from %i, player not found", chatMessage->senderID()); } return; } else { // not enough smarts to correctly redistrbute these, so just ignore them logNote("non in-game chat message received; ignoring"); } } } static byte *handlerPhysicsBuffer = NULL; static size_t handlerPhysicsLength = 0; static void handlePhysicsMessage(PhysicsMessage *physicsMessage, CommunicationsChannel *) { if (netState == netStartingUp || netState == netDown) { if (handlerPhysicsBuffer) { delete[] handlerPhysicsBuffer; handlerPhysicsBuffer = NULL; } handlerPhysicsLength = physicsMessage->length(); if (handlerPhysicsLength > 0) { handlerPhysicsBuffer = new byte[handlerPhysicsLength]; memcpy(handlerPhysicsBuffer, physicsMessage->buffer(), handlerPhysicsLength); } } else { logAnomaly1("unexpected physics message received (netState is %i)", netState); } } /* static void handleScriptMessage(ScriptMessage* scriptMessage, CommunicationsChannel*) { if (netState == netJoining) { ScriptMessage replyToScriptMessage; if (scriptMessage->value() == _netscript_query_message) { #ifdef HAVE_LUA replyToScriptMessage.setValue(_netscript_yes_script_message); #else replyToScriptMessage.setValue(_netscript_no_script_message); #endif } else { replyToScriptMessage.setValue(_netscript_no_script_message); } connection_to_server->enqueueOutgoingMessage(replyToScriptMessage); } else { logAnomaly1("unexpected script message received (netState is %i)", netState); } } */ static void handleServerWarningMessage(ServerWarningMessage *serverWarningMessage, CommunicationsChannel *) { char *s = strdup(serverWarningMessage->string()->c_str()); alert_user(s); free(s); } static void handleTopologyMessage(TopologyMessage* topologyMessage, CommunicationsChannel *) { if (netState == netWaiting) { *topology = *(topologyMessage->topology()); NetAddrBlock address; address = connection_to_server->peerAddress(); // ZZZ: the code below used to assume the server was _index_ 0; now, we merely // assume the server has _identifier_ 0. int theServerIndex; for(theServerIndex = 0; theServerIndex < topology->player_count; theServerIndex++) { if(topology->players[theServerIndex].identifier == 0) break; } assert(theServerIndex != topology->player_count); topology->players[theServerIndex].dspAddress= address; topology->players[theServerIndex].ddpAddress.host = address.host; NetUpdateTopology(); switch (topology->tag) { case tagNEW_PLAYER: handlerState = netPlayerAdded; break; case tagDROPPED_PLAYER: handlerState = netPlayerDropped; break; case tagCHANGED_PLAYER: handlerState = netPlayerChanged; break; case tagCANCEL_GAME: handlerState= netCancelled; alert_user(infoError, strNETWORK_ERRORS, netErrServerCanceled, 0); break; case tagSTART_GAME: handlerState = netStartingUp; resuming_saved_game = false; break; // ZZZ addition case tagRESUME_GAME: handlerState = netStartingResumeGame; resuming_saved_game = true; break; default: logAnomaly1("topology message received with unknown tag %i; ignoring", topology->tag); break; } } else { logWarning1("unexpected topology message received -- gatherer and joiner could disagree on topology! (netState is %i)", netState); } } static void handleUnexpectedMessage(Message *inMessage, CommunicationsChannel *) { if (handlerState == netAwaitingHello) { // an unexpected message before hello usually means we couldn't parse // hello; which means it's likely we're not compatible alert_user(infoError, strNETWORK_ERRORS, netErrIncompatibleVersion, 0); handlerState = netJoinErrorOccurred; } logAnomaly1("unexpected message ID %i received", inMessage->type()); } static TypedMessageHandlerFunction helloMessageHandler(&handleHelloMessage); static TypedMessageHandlerFunction joinPlayerMessageHandler(&handleJoinPlayerMessage); static TypedMessageHandlerFunction luaMessageHandler(&handleLuaMessage); static TypedMessageHandlerFunction mapMessageHandler(&handleMapMessage); static TypedMessageHandlerFunction networkChatMessageHandler(&handleNetworkChatMessage); static TypedMessageHandlerFunction physicsMessageHandler(&handlePhysicsMessage); static TypedMessageHandlerFunction capabilitiesMessageHandler(&handleCapabilitiesMessage); static TypedMessageHandlerFunction topologyMessageHandler(&handleTopologyMessage); static TypedMessageHandlerFunction serverWarningMessageHandler(&handleServerWarningMessage); static TypedMessageHandlerFunction clientInfoMessageHandler(&handleClientInfoMessage); static TypedMessageHandlerFunction unexpectedMessageHandler(&handleUnexpectedMessage); void NetSetGatherCallbacks(GatherCallbacks *gc) { gatherCallbacks = gc; } void NetSetChatCallbacks(ChatCallbacks *cc) { chatCallbacks = cc; } void ChatCallbacks::SendChatMessage(const std::string& message) { if (message == "") return; if (netState == netActive) { if (connection_to_server) { NetworkChatMessage chatMessage(message.c_str(), 0, NetworkChatMessage::kTargetPlayers); // gatherer will replace senderID with my ID connection_to_server->enqueueOutgoingMessage(chatMessage); } else { NetworkChatMessage chatMessage(message.c_str(), 0, NetworkChatMessage::kTargetPlayers); // gatherer stream ID is always 0 client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->state == Client::_ingame) { it->second->channel->enqueueOutgoingMessage(chatMessage); } } if (chatCallbacks) { for (int playerIndex = 0; playerIndex < topology->player_count; playerIndex++) { if (playerIndex == localPlayerIndex) { static unsigned char name[MAX_NET_PLAYER_NAME_LENGTH + 1]; pstrcpy(name, topology->players[playerIndex].player_data.name); a1_p2cstr(name); chatCallbacks->ReceivedMessageFromPlayer((char *)name, message.c_str()); } } } } } else if (netState == netGathering) { NetworkChatMessage chatMessage(message.c_str(), 0, NetworkChatMessage::kTargetClients); // gatherer stream ID is always 0 client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->can_pregame_chat()) { it->second->channel->enqueueOutgoingMessage(chatMessage); } } if (chatCallbacks) { chatCallbacks->ReceivedMessageFromPlayer(client_chat_info[0]->name.c_str(), message.c_str()); } } else if (netState == netJoining || netState == netWaiting) { assert(connection_to_server); NetworkChatMessage chatMessage(message.c_str(), 0, NetworkChatMessage::kTargetClients); // gatherer will replace senderID with my ID connection_to_server->enqueueOutgoingMessage(chatMessage); } else { logNote("SendChatMessage called but non in-game/pre-game chat messages are not yet implemented"); } } InGameChatCallbacks *InGameChatCallbacks::m_instance = NULL; InGameChatCallbacks *InGameChatCallbacks::instance() { if (!m_instance) { m_instance = new InGameChatCallbacks(); } return m_instance; }; std::string InGameChatCallbacks::prompt() { unsigned char name[MAX_NET_PLAYER_NAME_LENGTH + 1]; pstrcpy(name, player_preferences->name); a1_p2cstr((unsigned char *) name); return (std::string((char *)name) + ":"); } void InGameChatCallbacks::ReceivedMessageFromPlayer(const char *player_name, const char *message) { screen_printf("%s: %s", player_name, message); } bool NetEnter(void) { OSErr error; assert(netState==netUninitialized); { static bool added_exit_procedure= false; if (!added_exit_procedure) atexit(NetExit); added_exit_procedure= true; } sCurrentGameProtocol = (network_preferences->game_protocol == _network_game_protocol_star) ? static_cast(&sStarGameProtocol) : static_cast(&sRingGameProtocol); error= NetDDPOpen(); if (!error) { topology = (NetTopologyPtr)malloc(sizeof(NetTopology)); assert(topology); memset(topology, 0, sizeof(NetTopology)); NetSetServerIdentifier(0); // ZZZ: Sorry, if this swapping is not supported on all current A1 // platforms, feel free to rewrite it in a way that is. ddpSocket= SDL_SwapBE16(GAME_PORT); error= NetDDPOpenSocket(&ddpSocket, NetDDPPacketHandler); if (!error) { sOldSelfSendStatus= NetSetSelfSend(true); sServerPlayerIndex= 0; sCurrentGameProtocol->Enter(&netState); netState= netDown; handlerState = netDown; } else { logError("unable to open socket"); } } if (!inflater) { inflater = new MessageInflater (); for (int i = 0; i < NUMBER_OF_STREAM_PACKET_TYPES; i++) { BigChunkOfDataMessage *prototype = new BigChunkOfDataMessage (i); inflater->learnPrototypeForType (i, *prototype); delete prototype; } inflater->learnPrototype(AcceptJoinMessage()); inflater->learnPrototype(EndGameDataMessage()); inflater->learnPrototype(HelloMessage()); inflater->learnPrototype(JoinerInfoMessage()); inflater->learnPrototype(JoinPlayerMessage()); inflater->learnPrototype(LuaMessage()); inflater->learnPrototype(MapMessage()); inflater->learnPrototype(NetworkChatMessage()); inflater->learnPrototype(PhysicsMessage()); inflater->learnPrototype(CapabilitiesMessage()); inflater->learnPrototype(TopologyMessage()); inflater->learnPrototype(ChangeColorsMessage()); inflater->learnPrototype(ServerWarningMessage()); inflater->learnPrototype(ClientInfoMessage()); } if (!joinDispatcher) { joinDispatcher = new MessageDispatcher(); joinDispatcher->setDefaultHandler(&unexpectedMessageHandler); joinDispatcher->setHandlerForType(&helloMessageHandler, HelloMessage::kType); joinDispatcher->setHandlerForType(&joinPlayerMessageHandler, JoinPlayerMessage::kType); joinDispatcher->setHandlerForType(&luaMessageHandler, LuaMessage::kType); joinDispatcher->setHandlerForType(&mapMessageHandler, MapMessage::kType); joinDispatcher->setHandlerForType(&networkChatMessageHandler, NetworkChatMessage::kType); joinDispatcher->setHandlerForType(&physicsMessageHandler, PhysicsMessage::kType); joinDispatcher->setHandlerForType(&capabilitiesMessageHandler, CapabilitiesMessage::kType); joinDispatcher->setHandlerForType(&serverWarningMessageHandler, ServerWarningMessage::kType); joinDispatcher->setHandlerForType(&clientInfoMessageHandler, ClientInfoMessage::kType); joinDispatcher->setHandlerForType(&topologyMessageHandler, TopologyMessage::kType); } my_capabilities.clear(); my_capabilities[Capabilities::kGameworld] = Capabilities::kGameworldVersion; my_capabilities[Capabilities::kSpeex] = Capabilities::kSpeexVersion; if (network_preferences->game_protocol == _network_game_protocol_star) { my_capabilities[Capabilities::kStar] = Capabilities::kStarVersion; } else { my_capabilities[Capabilities::kRing] = Capabilities::kRingVersion; } #ifdef HAVE_LUA my_capabilities[Capabilities::kLua] = Capabilities::kLuaVersion; #endif my_capabilities[Capabilities::kGatherable] = Capabilities::kGatherableVersion; // net commands! sDisplayPings = false; Console::instance()->register_command("ping", toggle_ping_display()); sIgnoredPlayers.clear(); CommandParser IgnoreParser; IgnoreParser.register_command("player", ignore_player()); clear_player_mic_mutes(); IgnoreParser.register_command("mic", ignore_mic()); ResetLuaMute(); IgnoreParser.register_command("lua", ignore_lua()); Console::instance()->register_command("ignore", IgnoreParser); next_join_attempt = machine_tick_count(); if (error) { alert_user(infoError, strNETWORK_ERRORS, netErrCantContinue, error); NetExit(); return false; } else { return true; } } void NetDoneGathering(void) { if (server) { delete server; server = NULL; } } void NetExit( void) { OSErr error = noErr; sCurrentGameProtocol->Exit1(); // ZZZ: clean up SDL Time Manager emulation. // true says wait for any late finishers to finish // (but does NOT say to kill anyone not already removed.) myTMCleanup(true); if (netState!=netUninitialized) { error= NetDDPCloseSocket(ddpSocket); if (!error) { NetSetSelfSend(sOldSelfSendStatus); free(topology); topology= NULL; sCurrentGameProtocol->Exit2(); netState= netUninitialized; } else { logAnomaly1("NetDDPCloseSocket returned %i", error); } } if (connection_to_server) { delete connection_to_server; connection_to_server = NULL; } { client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) delete(it->second); connections_to_clients.clear(); } { client_chat_info_map_t::iterator it; for (it = client_chat_info.begin(); it != client_chat_info.end(); it++) delete(it->second); client_chat_info.clear(); } if (server) { delete server; server = NULL; } delete gMetaserverClient; gMetaserverClient = new MetaserverClient(); #ifndef __MWERKS__ if (controller) { open_progress_dialog(_closing_router_ports); LNat_Upnp_Remove_Port_Mapping(controller, 4226, "TCP"); LNat_Upnp_Remove_Port_Mapping(controller, 4226, "UDP"); LNat_Upnp_Controller_Free(&controller); controller = NULL; close_progress_dialog(); } #endif Console::instance()->unregister_command("ping"); Console::instance()->unregister_command("ignore"); NetDDPClose(); } bool NetSync() { return sCurrentGameProtocol->Sync(topology, dynamic_world->tick_count, localPlayerIndex, sServerPlayerIndex); } bool NetUnSync() { return sCurrentGameProtocol->UnSync(true, dynamic_world->tick_count); } /* Add a function for a distribution type. returns the type, or NONE if it can't be * installed. It's safe to call this function multiple times for the same proc. */ // ZZZ: changed to take in the desired type, so a given machine can handle perhaps only some // of the distribution types. void NetAddDistributionFunction(int16 inDataTypeID, NetDistributionProc inProc, bool inLossy) { // We don't support lossless distribution yet. assert(inLossy); // Prepare a NetDistributionInfo with the desired data. NetDistributionInfo theInfo; theInfo.lossy = inLossy; theInfo.distribution_proc = inProc; // Insert or update a map entry distribution_info_map[inDataTypeID] = theInfo; } /* Remove a distribution proc that has been installed. */ void NetRemoveDistributionFunction(int16 inDataTypeID) { distribution_info_map.erase(inDataTypeID); } const NetDistributionInfo* NetGetDistributionInfoForType(int16 inType) { distribution_info_map_t::const_iterator theEntry = distribution_info_map.find(inType); if(theEntry != distribution_info_map.end()) return &(theEntry->second); else return NULL; } void NetDistributeInformation( short type, void *buffer, short buffer_size, bool send_to_self, bool only_send_to_team) { sCurrentGameProtocol->DistributeInformation(type, buffer, buffer_size, send_to_self, only_send_to_team); } short NetState( void) { return netState; } /* --------- NetGather --------- ---> game data (pointer to typeless data no bigger than MAXIMUM_GAME_DATA_SIZE) ---> size of game data ---> player data of gathering player (no bigger than MAXIMUM_PLAYER_DATA_SIZE) ---> size of player data ---> lookupUpdateProc (we call NetLookupOpen() and NetLookupClose()) <--- error start gathering players. --------------- NetGatherPlayer --------------- ---> player index (into the array of looked up names) <--- error bring the given player into our game. --------------- NetCancelGather --------------- <--- error tells all players in the game that the game has been cancelled. -------- NetStart -------- <--- error start the game with the existing topology (which all players should have) */ bool NetGather( void *game_data, short game_data_size, void *player_data, short player_data_size, bool resuming_game) { resuming_saved_game = resuming_game; NetInitializeTopology(game_data, game_data_size, player_data, player_data_size); #ifndef __MWERKS__ if (network_preferences->attempt_upnp) { // open the port! open_progress_dialog(_opening_router_ports); char public_ip[32]; int ret = 0; if ((ret = LNat_Upnp_Discover(&controller)) != 0) logWarning("LibNAT: Failed to discover UPnP controller"); if (ret == 0) if ((ret = LNat_Upnp_Get_Public_Ip(controller, public_ip, 32)) != 0) logWarning("LibNAT: Failed to acquire public IP"); if (ret == 0) if ((ret = LNat_Upnp_Set_Port_Mapping(controller, NULL, 4226, "TCP")) != 0) logWarning("LibNAT: Failed to map port 4226 (TCP)"); if (ret == 0) if ((ret = LNat_Upnp_Set_Port_Mapping(controller, NULL, 4226, "UDP")) != 0) logWarning("LibNAT: Failed to map port 4226 (UDP)"); close_progress_dialog(); if (ret != 0) { controller = 0; alert_user(infoError, strNETWORK_ERRORS, netWarnUPnPConfigureFailed, ret); } } #endif // Start listening for joiners server = new CommunicationsChannelFactory(GAME_PORT); netState= netGathering; unsigned char pname[1024]; pstrcpy(pname, player_preferences->name); a1_p2cstr(pname); client_chat_info[0] = new ClientChatInfo; client_chat_info[0]->name = (char *) pname; client_chat_info[0]->color = player_preferences->color; client_chat_info[0]->team = player_preferences->team; return true; } void NetCancelGather( void) { assert(netState==netGathering); NetDistributeTopology(tagCANCEL_GAME); } bool NetStart( void) { assert(netState==netGathering); // how about we sort the players before we pass them out to everyone? // This is an attempt to have a slightly more efficent ring in a multi-zone network. // we should really do some sort of pinging to determine an optimal order (or perhaps // sort on hop counts) but we'll just order them by their network numbers. // however, we need to leave the server player index 0 because we assume that the person // that starts out at index 0 is the server. // ZZZ: do that sorting in a new game only - in a resume game, the ordering is significant // (it's how netplayers will line up with existing saved-game players). if(!resuming_saved_game) { if (topology->player_count > 2) { qsort(topology->players+1, topology->player_count-1, sizeof(struct NetPlayer), net_compare); } NetUpdateTopology(); } NetDistributeTopology(resuming_saved_game ? tagRESUME_GAME : tagSTART_GAME); return true; } static int net_compare( void const *p1, void const *p2) { uint32 p1_host = SDL_SwapBE32(((const NetPlayer *)p1)->ddpAddress.host); uint32 p2_host = SDL_SwapBE32(((const NetPlayer *)p2)->ddpAddress.host); return p2_host - p1_host; } /* ----------- NetGameJoin ----------- ---> player name (to register) ---> player type (to register) ---> player data (no larger than MAXIMUM_PLAYER_DATA_SIZE) ---> size of player data ---> version number of network protocol (used with player type to construct entity name) ---> host address <--- error ------------------ NetUpdateJoinState ------------------ <--- new state (==netJoined,netWaiting,netStartingUp) ------------- NetCancelJoin ------------- <--- error canÕt be called after the player has been gathered */ bool NetGameJoin( void *player_data, short player_data_size, const char* host_addr_string ) { OSErr error = noErr; /* Attempt a connection to host */ host_address_specified = (host_addr_string != NULL); if (host_address_specified) { // SDL_net declares ResolveAddress without "const" on the char; we can't guarantee // our caller that it will remain const unless we protect it like this. char* theStringCopy = strdup(host_addr_string); error = SDLNet_ResolveHost(&host_address, theStringCopy, GAME_PORT); free(theStringCopy); } if (!error) { connection_to_server = new CommunicationsChannel(); connection_to_server->setMessageInflater(inflater); connection_to_server->setMessageHandler(joinDispatcher); netState = netConnecting; NetInitializeTopology((void *) NULL, 0, player_data, player_data_size); return true; } else { alert_user(infoError, strNETWORK_ERRORS, netErrCouldntResolve, error); return false; } } void NetRetargetJoinAttempts(const IPaddress* inAddress) { host_address_specified = (inAddress != NULL); if(host_address_specified) { host_address = *inAddress; host_address.port = SDL_SwapBE16(GAME_PORT); } } void NetCancelJoin( void) { assert(netState==netConnecting||netState==netJoining||netState==netWaiting||netState==netCancelled||netState==netJoinErrorOccurred); } void NetChangeColors(int16 color, int16 team) { assert(netState == netWaiting || netState == netGathering); if (netState == netWaiting) { ChangeColorsMessage changeColorsMessage(color, team); connection_to_server->enqueueOutgoingMessage(changeColorsMessage); } else if (netState == netGathering) { player_info *player = &topology->players[localPlayerIndex].player_data; if (player->desired_color != color || player->team != team) { player->desired_color = color; player->team = team; Client::check_player(localPlayerIndex, topology->player_count); NetUpdateTopology(); NetDistributeTopology(tagCHANGED_PLAYER); } } } /* Externally, this is only called before a new game. It removes the reliance that localPlayerIndex of zero is the server, which is not necessarily true if we are resyncing for another cooperative level. */ void NetSetServerIdentifier( short identifier) { sServerPlayerIndex= identifier; } /* net accessor functions */ short NetGetLocalPlayerIndex( void) { assert(netState!=netUninitialized&&netState!=netDown&&netState!=netJoining); return localPlayerIndex; } short NetGetPlayerIdentifier( short player_index) { assert(netState!=netUninitialized&&netState!=netDown&&netState!=netJoining); assert(player_index>=0&&player_indexplayer_count); return topology->players[player_index].identifier; } bool NetNumberOfPlayerIsValid( void) { bool valid; switch(netState) { case netUninitialized: case netConnecting: case netJoining: case netJoinErrorOccurred: valid= false; break; default: valid= true; break; } return valid; } short NetGetNumberOfPlayers( void) { assert(netState!=netUninitialized /* &&netState!=netDown*/ &&netState!=netJoining); return topology->player_count; } void *NetGetPlayerData( short player_index) { assert(netState!=netUninitialized/* && netState!=netDown */ &&netState!=netJoining); assert(player_index>=0&&player_indexplayer_count); return (void *) &topology->players[player_index].player_data; } void *NetGetGameData( void) { assert(netState!=netUninitialized&&netState!=netDown&&netState!=netJoining); return &topology->game_data; } /* ZZZ addition: -------------------------- NetSetupTopologyFromStarts -------------------------- ---> array of starts ---> count of starts This should be called once (at most) per game, after deciding who's going to take over which player, before calling NetStart(). */ void NetSetupTopologyFromStarts(const player_start_data* inStartArray, short inStartCount) { NetPlayer thePlayers[MAXIMUM_NUMBER_OF_NETWORK_PLAYERS]; memcpy(thePlayers, topology->players, sizeof(thePlayers)); for(int s = 0; s < inStartCount; s++) { if(inStartArray[s].identifier == NONE) { // Is this really all I have to do here? // NO, I need to set up the player name, color, team, etc. for transmission to others. // That requires knowledge of the player_info or player_data (whichever one the net system // uses for such transmission.) topology->players[s].identifier = NONE; // XXX ZZZ violation of separation of church and state - oops I mean net code and game code player_info* thePlayerInfo = (player_info*) &topology->players[s].player_data; // XXX ZZZ faux security - we strncpy but the following line assumes the length is within range (i.e. that there's a null) strncpy((char*)&(thePlayerInfo->name[1]), inStartArray[s].name, MAX_NET_PLAYER_NAME_LENGTH); thePlayerInfo->name[0] = strlen(inStartArray[s].name); thePlayerInfo->desired_color = 0; // currently unused thePlayerInfo->team = inStartArray[s].team; thePlayerInfo->color = inStartArray[s].color; memset(thePlayerInfo->long_serial_number, 0, LONG_SERIAL_NUMBER_LENGTH); } else { int p; for(p = 0; p < topology->player_count; p++) { if(thePlayers[p].identifier == inStartArray[s].identifier) break; } assert(p != topology->player_count); topology->players[s] = thePlayers[p]; } } topology->player_count = inStartCount; NetUpdateTopology(); } /* ------------------ NetEntityNotInGame ------------------ ---> entity ---> address <--- true if the entity is not in the game, false otherwise used to filter entities which have been added to a game out of the lookup list */ /* if the given address is already added to our game, filter it out of the gather dialog */ bool NetEntityNotInGame( NetEntityName *entity, NetAddrBlock *address) { short player_index; bool valid= true; (void) (entity); for (player_index=0;player_indexplayer_count;++player_index) { NetAddrBlock *player_address= &topology->players[player_index].dspAddress; if (address->host == player_address->host && address->port == player_address->port) { valid= false; break; } } return valid; } /* ---------- private code */ void NetDDPPacketHandler(DDPPacketBufferPtr packet) { sCurrentGameProtocol->PacketHandler(packet); } /* local player initializers */ static void NetInitializeTopology( void *game_data, short game_data_size, void *player_data, short player_data_size) { NetPlayerPtr local_player; assert(player_data_size>=0&&player_data_size=0&&game_data_sizeplayers + localPlayerIndex; local_player->identifier= localPlayerIdentifier; local_player->net_dead= false; local_player->stream_id = 0; NetLocalAddrBlock(&local_player->dspAddress, GAME_PORT); NetLocalAddrBlock(&local_player->ddpAddress, ddpSocket); memcpy(&local_player->player_data, player_data, player_data_size); /* initialize the network topology (assume weÕre the only player) */ topology->player_count= 1; topology->nextIdentifier= 1; memcpy(&topology->game_data, game_data, game_data_size); } static void NetLocalAddrBlock( NetAddrBlock *address, short socketNumber) { address->host = 0x7f000001; //!! XXX (ZZZ) yeah, that's really bad. address->port = socketNumber; // OTOH, I guess others are set up to "stuff" the address they actually saw for us instead of // this, anyway... right?? So maybe it's not that big a deal....... } static void NetUpdateTopology( void) { /* recalculate localPlayerIndex */ for (localPlayerIndex=0;localPlayerIndexplayer_count;++localPlayerIndex) { if (topology->players[localPlayerIndex].identifier==localPlayerIdentifier) break; } #ifdef DEBUG if (localPlayerIndex==topology->player_count) fdprintf("couldnÕt find my identifier: %p", topology); #endif } static bool NetSetSelfSend( bool on) { return false; } /* ------ this needs to let the gatherer keep going if there was an error.. */ /* ¥¥¥ÊMarathon Specific Code ¥¥¥ */ /* Returns error code.. */ // ZZZ annotation: this function doesn't seem to belong here - maybe more like interface.cpp? bool NetChangeMap( struct entry_point *entry) { byte *wad= NULL; long length; bool success= true; /* If the guy that was the server died, and we are trying to change levels, we lose */ // ZZZ: if we used the parent_wad_checksum stuff to locate the containing Map file, // this would be the case somewhat less frequently, probably... if(localPlayerIndex==sServerPlayerIndex && localPlayerIndex != 0) { logError("server died while trying to get another level"); success= false; } else { // being the server, we must send out the map to everyone. if(localPlayerIndex==sServerPlayerIndex) { wad = (unsigned char *)get_map_for_net_transfer(entry); assert(wad); length= get_net_map_data_length(wad); NetDistributeGameDataToAllPlayers(wad, length, true); } else { // wait for de damn map. wad = NetReceiveGameData(true); if(!wad) { alert_user(infoError, strNETWORK_ERRORS, netErrCouldntReceiveMap, 0); success= false; } } /* Now load the level.. */ if (wad) { /* Note that this frees the wad as well!! */ process_net_map_data(wad); } } return success; } void DeferredScriptSend (byte* data, size_t length) { if (deferred_script_data != NULL) { delete [] deferred_script_data; deferred_script_data = NULL; } deferred_script_data = data; deferred_script_length = length; } void SetNetscriptStatus (bool status) { do_netscript = status; } // ZZZ this "ought" to distribute to all players simultaneously (by interleaving send calls) // in case the server bandwidth is much greater than the others' bandwidths. But that would // take a fair amount of reworking of the streaming system, which only groks talking with one // machine at a time. OSErr NetDistributeGameDataToAllPlayers(byte *wad_buffer, long wad_length, bool do_physics) { short playerIndex, message_id; OSErr error= noErr; long total_length, length_written; uint32 initial_ticks= machine_tick_count(); short physics_message_id; byte *physics_buffer; long physics_length; message_id= (topology->player_count==2) ? (_distribute_map_single) : (_distribute_map_multiple); physics_message_id= (topology->player_count==2) ? (_distribute_physics_single) : (_distribute_physics_multiple); open_progress_dialog(physics_message_id); /* For updating our progress bar.. */ total_length= (topology->player_count-1)*wad_length; length_written= 0l; /* Get the physics */ if(do_physics) physics_buffer= (unsigned char *)get_network_physics_buffer(&physics_length); // build a list of players to send to std::vector channels; for (playerIndex = 0; playerIndex < topology->player_count; playerIndex++) { NetPlayer player = topology->players[playerIndex]; /* If the player is not net dead. */ // ZZZ: and is not going to be a zombie and is not us if (!player.net_dead && player.identifier != NONE && playerIndex != localPlayerIndex) channels.push_back(connections_to_clients[player.stream_id]->channel); } set_progress_dialog_message(message_id); reset_progress_bar(); if (do_physics) { PhysicsMessage physicsMessage(physics_buffer, physics_length); std::for_each(channels.begin(), channels.end(), boost::bind(&CommunicationsChannel::enqueueOutgoingMessage, _1, physicsMessage)); } { MapMessage mapMessage(wad_buffer, wad_length); std::for_each(channels.begin(), channels.end(), boost::bind(&CommunicationsChannel::enqueueOutgoingMessage, _1, mapMessage)); } if (do_netscript) { LuaMessage luaMessage(deferred_script_data, deferred_script_length); std::for_each(channels.begin(), channels.end(), boost::bind(&CommunicationsChannel::enqueueOutgoingMessage, _1, luaMessage)); } { EndGameDataMessage endGameDataMessage; std::for_each(channels.begin(), channels.end(), boost::bind(&CommunicationsChannel::enqueueOutgoingMessage, _1, endGameDataMessage)); } CommunicationsChannel::multipleFlushOutgoingMessages(channels, false, 30000, 30000); for (playerIndex = 0; playerIndex < topology->player_count; playerIndex++) { if (playerIndex != localPlayerIndex) { connections_to_clients[topology->players[playerIndex].stream_id]->state = Client::_ingame; } } if (error) { // ghs: nothing above returns an error at the moment, // but I'll leave so you know what error could be displayed alert_user(infoError, strNETWORK_ERRORS, netErrCouldntDistribute, error); } else if (machine_tick_count()-initial_ticks>uint32(topology->player_count*MAP_TRANSFER_TIME_OUT)) { alert_user(infoError, strNETWORK_ERRORS, netErrWaitedTooLongForMap, error); error= 1; } if (!error) { /* Process the physics file & frees it!.. */ if (do_physics) process_network_physics_model(physics_buffer); draw_progress_bar(total_length, total_length); #ifdef HAVE_LUA if (do_netscript) { gLoadingLuaNetscript = true; LoadLuaScript ((char*)deferred_script_data, deferred_script_length); gLoadingLuaNetscript = false; } #endif } close_progress_dialog(); return error; } byte *NetReceiveGameData(bool do_physics) { byte *map_buffer= NULL; open_progress_dialog(_awaiting_map); // handlers will take care of all messages, and when they're done // the server will send us this: auto_ptr endGameDataMessage(connection_to_server->receiveSpecificMessage((Uint32) 60000, (Uint32) 30000)); if (endGameDataMessage.get()) { // game data was received OK if (do_physics && handlerPhysicsLength > 0) { process_network_physics_model(handlerPhysicsBuffer); handlerPhysicsLength = 0; handlerPhysicsBuffer = NULL; } if (handlerMapLength > 0) { map_buffer = handlerMapBuffer; handlerMapBuffer = NULL; handlerMapLength = 0; } if (handlerLuaLength > 0) { do_netscript = true; #ifdef HAVE_LUA gLoadingLuaNetscript = true; LoadLuaScript((char *) handlerLuaBuffer, handlerLuaLength); gLoadingLuaNetscript = false; #endif handlerLuaBuffer = NULL; handlerLuaLength = 0; } else { do_netscript = false; } draw_progress_bar(10, 10); close_progress_dialog(); } else { draw_progress_bar(10, 10); close_progress_dialog(); if (handlerPhysicsLength > 0) { delete[] handlerPhysicsBuffer; handlerPhysicsBuffer = NULL; handlerPhysicsLength = 0; } if (handlerMapLength > 0) { delete[] handlerMapBuffer; handlerMapBuffer = NULL; handlerMapLength = 0; } if (handlerLuaLength > 0) { delete[] handlerLuaBuffer; handlerLuaBuffer = NULL; handlerLuaLength = 0; } alert_user(infoError, strNETWORK_ERRORS, netErrMapDistribFailed, 1); } return map_buffer; } void NetSetInitialParameters( short updates_per_packet, short update_latency) { // initial_updates_per_packet= updates_per_packet; // initial_update_latency= update_latency; } long NetGetNetTime(void) { return sCurrentGameProtocol->GetNetTime(); } #ifdef __MACOS__ extern OSErr NetDDPSendUnsentFrames(); #endif void NetProcessMessagesInGame() { #ifdef __MACOS__ NetDDPSendUnsentFrames(); #endif if (connection_to_server) { connection_to_server->pump(); connection_to_server->dispatchIncomingMessages(); } else { client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { it->second->channel->pump(); it->second->channel->dispatchIncomingMessages(); } } if (gMetaserverClient && gMetaserverClient->isConnected()) gMetaserverClient->pump(); } // If a potential joiner has connected to us, handle em bool NetCheckForNewJoiner (prospective_joiner_info &info) { CommunicationsChannel *new_joiner = server->newIncomingConnection(); if (new_joiner) { new_joiner->setMessageInflater(inflater); Client *client = new Client(new_joiner); connections_to_clients[next_stream_id] = client; next_stream_id++; HelloMessage helloMessage(kNetworkSetupProtocolID); new_joiner->enqueueOutgoingMessage(helloMessage); } { client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->channel->isConnected()) { it->second->channel->pump(); it->second->channel->dispatchIncomingMessages(); } else { it->second->drop(); delete connections_to_clients[it->first]; connections_to_clients.erase(it->first); } } } // now check to see if any one has actually connected client_map_t::iterator it; for (it = connections_to_clients.begin(); it != connections_to_clients.end(); it++) { if (it->second->state == Client::_connected_but_not_yet_shown) { info.stream_id = it->first; pstrcpy(info.name, it->second->name); it->second->state = Client::_connected; info.gathering = false; return true; } else if (it->second->state == Client::_disconnect) { connections_to_clients[it->first]->drop(); delete connections_to_clients[it->first]; connections_to_clients.erase(it->first); } } return false; } /* check for messages from gather nodes; returns new state */ short NetUpdateJoinState( void) { logContext("updating network join status"); short newState= netState; switch (netState) { case netConnecting: // trying to connect to gatherer // jkvw: Here's what's up - We want to be able to click join before gatherer // has started gathering. So after a join click we attempt a connection // every five seconds. This is fine so long as remote computer quickly // refuses connection each time we poll; the dialog will remain responsive. // It's possible that a connection attempt will take a long time, however, // so we look for that and abort dialog when it happens. if (machine_tick_count() >= next_join_attempt) { uint32 ticks_before_connection_attempt = machine_tick_count(); if(host_address_specified) connection_to_server->connect(host_address); if (connection_to_server->isConnected()) { newState = netJoining; handlerState = netAwaitingHello; } else if (ticks_before_connection_attempt + 3*MACHINE_TICKS_PER_SECOND < machine_tick_count ()) { newState= netJoinErrorOccurred; alert_user(infoError, strNETWORK_ERRORS, netErrCouldntJoin, 3); } next_join_attempt = machine_tick_count() + 5*MACHINE_TICKS_PER_SECOND; } break; case netJoining: // waiting to be gathered if (!connection_to_server->isConnected ()) { newState = netJoinErrorOccurred; alert_user(infoError, strNETWORK_ERRORS, netErrLostConnection, 0); } else { connection_to_server->pump(); connection_to_server->dispatchIncomingMessages(); if (handlerState == netWaiting) { newState= netWaiting; } else if (handlerState == netJoinErrorOccurred) { newState= netJoinErrorOccurred; } } break; // netJoining case netWaiting: // have been gathered, waiting for other players / game start if (!connection_to_server->isConnected ()) { newState = netJoinErrorOccurred; alert_user(infoError, strNETWORK_ERRORS, netErrLostConnection, 0); } else { handlerState = netWaiting; connection_to_server->pump(); connection_to_server->dispatchOneIncomingMessage(); if (handlerState != netWaiting) { newState = handlerState; } } break; // netWaiting default: newState= NONE; break; } /* return netPlayerAdded to tell the caller to refresh his topology, but donÕt change netState to that */ // ZZZ: similar behavior for netChatMessageReceived and netStartingResumeGame if (newState!=netPlayerAdded && newState!=netPlayerDropped && newState!=netPlayerChanged && newState != netChatMessageReceived && newState != netStartingResumeGame && newState != NONE) netState= newState; // ZZZ: netStartingResumeGame is used as a return value only; the corresponding netState is netStartingUp. if(newState == netStartingResumeGame) netState = netStartingUp; return newState; } int NetGatherPlayer(const prospective_joiner_info &player, CheckPlayerProcPtr check_player) { assert(netState == netGathering); assert(topology->player_count < MAXIMUM_NUMBER_OF_NETWORK_PLAYERS); Client::check_player = check_player; JoinPlayerMessage joinPlayerMessage(topology->nextIdentifier++); connections_to_clients[player.stream_id]->channel->enqueueOutgoingMessage(joinPlayerMessage); connections_to_clients[player.stream_id]->state = Client::_awaiting_accept_join; /* // reject a player if he can't handle our script demands ScriptMessage scriptMessage(_netscript_query_message); connections_to_clients[player.stream_id]->channel->enqueueOutgoingMessage(scriptMessage); connections_to_clients[player.stream_id]->state = Client::_awaiting_script_message; */ // lie to the network code and say we gathered successfully return kGatherPlayerSuccessful; } void NetHandleUngatheredPlayer (prospective_joiner_info ungathered_player) { // Drop connection of ungathered player delete connections_to_clients[ungathered_player.stream_id]; connections_to_clients.erase(ungathered_player.stream_id); } /* --------------------- NetDistributeTopology --------------------- <--- error connect to everyoneÕs dspAddress and give them the latest copy of the network topology. this used to be NetStart() and it used to connect all upring and downring ADSP connections. */ static void NetDistributeTopology( short tag) { short playerIndex; assert(netState==netGathering); topology->tag= tag; TopologyMessage topologyMessage(topology); for (playerIndex=0; playerIndexplayer_count; ++playerIndex) { // ZZZ: skip players with identifier NONE - they don't really exist... also skip ourselves. if(topology->players[playerIndex].identifier != NONE && playerIndex != localPlayerIndex) { CommunicationsChannel *channel = connections_to_clients[topology->players[playerIndex].stream_id]->channel; channel->enqueueOutgoingMessage(topologyMessage); } } } NetAddrBlock *NetGetPlayerADSPAddress( short player_index) { return &topology->players[player_index].dspAddress; } bool NetAllowCrosshair() { return (dynamic_world->player_count == 1 || (dynamic_world->game_information.cheat_flags & _allow_crosshair)); } bool NetAllowTunnelVision() { return (dynamic_world->player_count == 1 || dynamic_world->game_information.cheat_flags & _allow_tunnel_vision); } bool NetAllowBehindview() { return (dynamic_world->player_count == 1 || dynamic_world->game_information.cheat_flags & _allow_behindview); } extern int32 spoke_latency(); extern int32 hub_latency(int); int32 NetGetLatency() { if (sCurrentGameProtocol == static_cast(&sStarGameProtocol) && connection_to_server) { return spoke_latency(); } else { return kNetLatencyInvalid; } } int32 NetGetLatency(int player_index) { if (sCurrentGameProtocol == static_cast(&sStarGameProtocol) && !connection_to_server) { return hub_latency(player_index); } else { return kNetLatencyInvalid; } } int32 NetGetUnconfirmedActionFlagsCount() { assert (sCurrentGameProtocol); return sCurrentGameProtocol->GetUnconfirmedActionFlagsCount(); } uint32 NetGetUnconfirmedActionFlag(int32 offset) { assert (sCurrentGameProtocol); return sCurrentGameProtocol->PeekUnconfirmedActionFlag(offset); } void NetUpdateUnconfirmedActionFlags() { assert (sCurrentGameProtocol); return sCurrentGameProtocol->UpdateUnconfirmedActionFlags(); } bool NetDisplayPings() { return sDisplayPings; } #endif // !defined(DISABLE_NETWORKING)