//------------------------------------------------------------------------- /* Copyright (C) 2010 EDuke32 developers and contributors This file is part of EDuke32. EDuke32 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //------------------------------------------------------------------------- #include "duke3d.h" #include "game.h" #include "gamedef.h" #include "net.h" #include "premap.h" #include "savegame.h" #include "input.h" #include "enet/enet.h" #include "lz4.h" #include "crc32.h" ENetHost *g_netServer = NULL; ENetHost *g_netClient = NULL; ENetPeer *g_netClientPeer = NULL; enet_uint16 g_netPort = 23513; int32_t g_netDisconnect = 0; char g_netPassword[32]; int32_t g_netPlayersWaiting = 0; int32_t g_networkMode = NET_CLIENT; int32_t g_netIndex = 2; newgame_t pendingnewgame; #ifdef NETCODE_DISABLE void faketimerhandler(void) {} #else static char recbuf[180]; static int32_t g_chatPlayer = -1; // sprites of these statnums are synced to clients by the server int16_t g_netStatnums[] = { STAT_PROJECTILE, //STAT_PLAYER, STAT_STANDABLE, //STAT_ACTIVATOR, //STAT_TRANSPORT, //STAT_EFFECTOR, STAT_ACTOR, STAT_ZOMBIEACTOR, STAT_MISC, STAT_DEFAULT, STAT_NETALLOC, MAXSTATUS }; #pragma pack(push,1) uint32_t g_netMapRevision = 0; netmapstate_t g_mapStartState; netmapstate_t *g_mapStateHistory[NET_REVISIONS]; uint8_t tempnetbuf[sizeof(netmapstate_t) + 400]; netmapdiff_t tempMapDiff; #pragma pack(pop) #define tempnetbufsize sizeof(tempnetbuf) static void P_RemovePlayer(int32_t p) { // server obviously can't leave the game, and index 0 shows up for disconnect events from // players that haven't gotten far enough into the connection process to get a player ID if (p == 0) return; g_player[p].playerquitflag = 0; Bsprintf(recbuf,"%s^00 is history!",g_player[p].user_name); G_AddUserQuote(recbuf); if (numplayers == 1) S_PlaySound(GENERIC_AMBIENCE17); if (g_player[myconnectindex].ps->gm & MODE_GAME) { if (screenpeek == p) screenpeek = myconnectindex; pub = NUMPAGES; pus = NUMPAGES; G_UpdateScreenArea(); P_QuickKill(g_player[p].ps); if (voting == p) { for (p=0; pftq = QUOTE_RESERVED2; g_player[myconnectindex].ps->fta = 180; } } // sync a connecting player up with the current game state void Net_SyncPlayer(ENetEvent *event) { int32_t i, j; if (numplayers + g_netPlayersWaiting >= MAXPLAYERS) { enet_peer_disconnect_later(event->peer, DISC_SERVER_FULL); initprintf("Refused peer; server full.\n"); return; } g_netPlayersWaiting++; S_PlaySound(DUKE_GETWEAPON2); // open a new slot if necessary and save off the resulting slot # for future reference for (TRAVERSE_CONNECT(i)) { if (g_player[i].playerquitflag == 0) { break; } } if (i == -1) { i = playerswhenstarted++; } event->peer->data = (void *)(intptr_t)i; g_player[i].netsynctime = totalclock; g_player[i].playerquitflag = 1; //g_player[i].revision = g_netMapRevision; for (j=0; jpeer); Net_SendClientInfo(); Net_SendUserMapName(); Net_SendNewGame(0, event->peer); } void Net_SpawnPlayer(int32_t player) { int32_t j = 0; packbuf[j++] = PACKET_PLAYER_SPAWN; packbuf[j++] = player; Bmemcpy(&packbuf[j], &g_player[player].ps->pos.x, sizeof(vec3_t) * 2); j += sizeof(vec3_t) * 2; packbuf[j++] = 0; enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(packbuf, j, ENET_PACKET_FLAG_RELIABLE)); } static void display_betascreen(void) { rotatesprite_fs(160<<16,100<<16,65536,0,BETASCREEN,0,0,2+8+64+(ud.bgstretch?1024:0)); rotatesprite_fs(160<<16,(104)<<16,60<<10,0,DUKENUKEM,0,0,2+8); rotatesprite_fs(160<<16,(129)<<16,30<<11,0,THREEDEE,0,0,2+8); if (PLUTOPAK) // JBF 20030804 rotatesprite_fs(160<<16,(151)<<16,30<<11,0,PLUTOPAKSPRITE+1,0,0,2+8); } void faketimerhandler(void) { if (g_netServer==NULL && g_netClient==NULL) return; enet_host_service(g_netServer ? g_netServer : g_netClient, NULL, 0); } void Net_WaitForServer(void) { int32_t server_ready = g_player[0].pingcnt; if (numplayers < 2 || g_netServer) return; P_SetGamePalette(g_player[myconnectindex].ps, TITLEPAL, 8+2+1); do { if (quitevent || keystatus[1]) G_GameExit(""); display_betascreen(); gametext(160,170,"Waiting for server",14,2); nextpage(); packbuf[0] = PACKET_PLAYER_PING; packbuf[1] = myconnectindex; if (g_netClientPeer) enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(packbuf, 2, ENET_PACKET_FLAG_RELIABLE)); G_HandleAsync(); if (g_player[0].pingcnt > server_ready) { P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 8+2+1); return; } } while (1); } void Net_ResetPrediction(void) { Bmemcpy(&my, &g_player[myconnectindex].ps, sizeof(vec3_t)); Bmemcpy(&omy, &g_player[myconnectindex].ps, sizeof(vec3_t)); Bmemset(&myvel, 0, sizeof(vec3_t)); myang = omyang = g_player[myconnectindex].ps->ang; myhoriz = omyhoriz = g_player[myconnectindex].ps->horiz; myhorizoff = omyhorizoff = g_player[myconnectindex].ps->horizoff; mycursectnum = g_player[myconnectindex].ps->cursectnum; myjumpingcounter = g_player[myconnectindex].ps->jumping_counter; myjumpingtoggle = g_player[myconnectindex].ps->jumping_toggle; myonground = g_player[myconnectindex].ps->on_ground; myhardlanding = g_player[myconnectindex].ps->hard_landing; myreturntocenter = g_player[myconnectindex].ps->return_to_center; } //////////////////////////////////////////////////////////////////////////////// // Connect/Disconnect void Net_Connect(const char *srvaddr) { ENetAddress address; ENetEvent event; char *addrstr = NULL; int32_t i; char *oursrvaddr = Xstrdup(srvaddr); Net_Disconnect(); g_netClient = enet_host_create(NULL, 1, CHAN_MAX, 0, 0); if (g_netClient == NULL) { initprintf("An error occurred while trying to create an ENet client host.\n"); return; } addrstr = strtok(oursrvaddr, ":"); enet_address_set_host(&address, addrstr); addrstr = strtok(NULL, ":"); address.port = addrstr==NULL ? g_netPort : Batoi(addrstr); g_netClientPeer = enet_host_connect(g_netClient, &address, CHAN_MAX, 0); if (g_netClientPeer == NULL) { initprintf("No available peers for initiating an ENet connection.\n"); return; } for (i=4; i>0; i--) { /* Wait up to 5 seconds for the connection attempt to succeed. */ if (enet_host_service(g_netClient, & event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { initprintf("Connection to %s:%d succeeded.\n", oursrvaddr, address.port); Bfree(oursrvaddr); return; } else { /* Either the 5 seconds are up or a disconnect event was */ /* received. Reset the peer in the event the 5 seconds */ /* had run out without any significant event. */ enet_peer_reset(g_netClientPeer); initprintf("Connection to %s:%d failed.\n", oursrvaddr, address.port); } initprintf(i ? "Retrying...\n" : "Giving up connection attempt.\n"); } Bfree(oursrvaddr); Net_Disconnect(); } void Net_Disconnect(void) { if (g_netClient) { ENetEvent event; if (g_netClientPeer) enet_peer_disconnect_later(g_netClientPeer, 0); while (enet_host_service(g_netClient, & event, 3000) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: case ENET_EVENT_TYPE_NONE: case ENET_EVENT_TYPE_RECEIVE: if (event.packet) enet_packet_destroy(event.packet); break; case ENET_EVENT_TYPE_DISCONNECT: numplayers = playerswhenstarted = ud.multimode = 1; myconnectindex = screenpeek = 0; G_BackToMenu(); break; } } enet_peer_reset(g_netClientPeer); g_netClientPeer = NULL; enet_host_destroy(g_netClient); g_netClient = NULL; return; } if (g_netServer) { int32_t i; ENetEvent event; for (i=0; i<(signed)g_netServer->peerCount; i++) enet_peer_disconnect_later(&g_netServer->peers[i], DISC_SERVER_QUIT); while (enet_host_service(g_netServer, & event, 3000) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: case ENET_EVENT_TYPE_NONE: case ENET_EVENT_TYPE_RECEIVE: case ENET_EVENT_TYPE_DISCONNECT: if (event.packet) enet_packet_destroy(event.packet); break; } } enet_host_destroy(g_netServer); g_netServer = NULL; } } void Net_ReceiveDisconnect(ENetEvent *event) { g_netDisconnect = 1; numplayers = playerswhenstarted = ud.multimode = 1; myconnectindex = screenpeek = 0; G_BackToMenu(); switch (event->data) { case DISC_BAD_PASSWORD: initprintf("Bad password.\n"); return; case DISC_VERSION_MISMATCH: initprintf("Version mismatch.\n"); return; case DISC_INVALID: initprintf("Invalid data detected.\n"); return; case DISC_SERVER_QUIT: initprintf("The server is quitting.\n"); return; case DISC_SERVER_FULL: initprintf("The server is full.\n"); return; case DISC_KICKED: initprintf("You have been kicked from the server.\n"); return; case DISC_BANNED: initprintf("You are banned from this server.\n"); return; default: initprintf("Disconnected.\n"); return; } } //////////////////////////////////////////////////////////////////////////////// // Packet Handlers #endif void Net_GetPackets(void) { sampletimer(); MUSIC_Update(); S_Update(); G_HandleSpecialKeys(); if (g_netDisconnect) { Net_Disconnect(); g_netDisconnect = 0; if (g_gameQuit) G_GameExit(" "); return; } if (g_netServer) { Net_HandleClientPackets(); } else if (g_netClient) { Net_HandleServerPackets(); } } #ifndef NETCODE_DISABLE void Net_HandleClientPackets(void) { ENetEvent event; // pull events from the wire into the packet queue without dispatching them, once per Net_GetPackets() call enet_host_service(g_netServer, NULL, 0); // dispatch any pending events from the local packet queue while (enet_host_check_events(g_netServer, &event) > 0) { const intptr_t playeridx = (intptr_t)event.peer->data; if (playeridx < 0 || playeridx >= MAXPLAYERS) { enet_peer_disconnect_later(event.peer, DISC_INVALID); initprintf("Invalid player id (%" PRIdPTR ") from client.\n", playeridx); continue; } switch (event.type) { case ENET_EVENT_TYPE_CONNECT: { char ipaddr[32]; enet_address_get_host_ip(&event.peer->address, ipaddr, sizeof(ipaddr)); initprintf("A new client connected from %s:%u.\n", ipaddr, event.peer->address.port); Net_SendAcknowledge(event.peer); break; } case ENET_EVENT_TYPE_RECEIVE: /* initprintf ("A packet of length %u containing %s was received from player %d on channel %u.\n", event.packet -> dataLength, event.packet -> data, event.peer -> data, event.channelID); */ Net_ParseClientPacket(&event); // broadcast takes care of enet_packet_destroy itself // we set the state to disconnected so enet_host_broadcast // doesn't send the player back his own packets if ((event.channelID == CHAN_GAMESTATE && event.packet->data[0] > PACKET_BROADCAST) || event.channelID == CHAN_CHAT) { const ENetPacket *pak = event.packet; event.peer->state = ENET_PEER_STATE_DISCONNECTED; enet_host_broadcast(g_netServer, event.channelID, enet_packet_create(pak->data, pak->dataLength, pak->flags&ENET_PACKET_FLAG_RELIABLE)); event.peer->state = ENET_PEER_STATE_CONNECTED; } enet_packet_destroy(event.packet); g_player[playeridx].ping = (event.peer->lastRoundTripTime + event.peer->roundTripTime)/2; break; case ENET_EVENT_TYPE_DISCONNECT: numplayers--; ud.multimode--; P_RemovePlayer(playeridx); packbuf[0] = PACKET_PLAYER_DISCONNECTED; packbuf[1] = playeridx; packbuf[2] = numplayers; packbuf[3] = ud.multimode; packbuf[4] = playerswhenstarted; packbuf[5] = myconnectindex; enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(packbuf, 6, ENET_PACKET_FLAG_RELIABLE)); initprintf("%s disconnected.\n", g_player[playeridx].user_name); event.peer->data = NULL; break; default: break; } } } void Net_HandleServerPackets(void) { ENetEvent event; enet_host_service(g_netClient, NULL, 0); while (enet_host_check_events(g_netClient, &event) > 0) { if (event.type == ENET_EVENT_TYPE_DISCONNECT) { Net_ReceiveDisconnect(&event); } else if (event.type == ENET_EVENT_TYPE_RECEIVE) { Net_ParseServerPacket(&event); } enet_packet_destroy(event.packet); } } void Net_ParseClientPacket(ENetEvent *event) { uint8_t *pbuf = event->packet->data; int32_t packbufleng = event->packet->dataLength; int16_t j; int32_t other = pbuf[--packbufleng]; #if 0 initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng); #endif switch (pbuf[0]) { case PACKET_SLAVE_TO_MASTER: //[1] (receive slave sync buffer) Net_ReceiveClientUpdate(event); break; case PACKET_PLAYER_READY: if (other == 0) { break; } j = g_player[other].ps->i; Bmemcpy(g_player[other].ps, g_player[0].ps, sizeof(DukePlayer_t)); g_player[other].ps->i = j; changespritestat(j, STAT_PLAYER); g_player[other].ps->last_extra = sprite[g_player[other].ps->i].extra = g_player[other].ps->max_player_health; sprite[g_player[other].ps->i].cstat = 1+256; actor[g_player[other].ps->i].t_data[2] = actor[g_player[other].ps->i].t_data[3] = actor[g_player[other].ps->i].t_data[4] = 0; P_ResetPlayer(other); Net_SpawnPlayer(other); break; case PACKET_PLAYER_PING: if (g_player[myconnectindex].ps->gm & MODE_GAME) { packbuf[0] = PACKET_PLAYER_PING; packbuf[1] = myconnectindex; enet_peer_send(event->peer, CHAN_GAMESTATE, enet_packet_create(packbuf, 2, ENET_PACKET_FLAG_RELIABLE)); } g_player[other].pingcnt++; break; case PACKET_AUTH: Net_ReceiveChallenge(pbuf, packbufleng, event); break; default: Net_ParsePacketCommon(pbuf, packbufleng, 0); break; } } void Net_ParseServerPacket(ENetEvent *event) { uint8_t *pbuf = event->packet->data; int32_t packbufleng = event->packet->dataLength; // input_t *nsyn; --packbufleng; // int32_t other = pbuf[--packbufleng]; #if 0 initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng); #endif switch (pbuf[0]) { case PACKET_MASTER_TO_SLAVE: if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) { return; } Net_ReceiveServerUpdate(event); break; case PACKET_MAP_STREAM: if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) return; Net_ReceiveMapUpdate(event); break; case PACKET_NEW_GAME: Net_ReceiveNewGame(event); break; case PACKET_ACK: Net_ReceiveAcknowledge(pbuf, packbufleng); break; case PACKET_NUM_PLAYERS: Net_ReceiveNewPlayer(event->packet->data, event->packet->dataLength); break; case PACKET_PLAYER_INDEX: Net_ReceivePlayerIndex(event->packet->data, event->packet->dataLength); break; case PACKET_PLAYER_DISCONNECTED: if ((g_player[myconnectindex].ps->gm & MODE_GAME)) P_RemovePlayer(pbuf[1]); numplayers = pbuf[2]; ud.multimode = pbuf[3]; playerswhenstarted = pbuf[4]; break; case PACKET_PLAYER_SPAWN: if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) break; P_ResetPlayer(pbuf[1]); Bmemcpy(&g_player[pbuf[1]].ps->pos.x, &pbuf[2], sizeof(vec3_t) * 2); Bmemcpy(&sprite[g_player[pbuf[1]].ps->i], &pbuf[2], sizeof(vec3_t)); break; case PACKET_PLAYER_PING: g_player[0].pingcnt++; return; case PACKET_FRAG: if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) break; g_player[pbuf[1]].ps->frag_ps = pbuf[2]; actor[g_player[pbuf[1]].ps->i].picnum = pbuf[3]; ticrandomseed = B_UNBUF32(&pbuf[4]); P_FragPlayer(pbuf[1]); break; default: Net_ParsePacketCommon(pbuf, packbufleng, 1); break; } } void Net_ParsePacketCommon(uint8_t *pbuf, int32_t packbufleng, int32_t serverpacketp) { switch (pbuf[0]) { case PACKET_MESSAGE: Net_ReceiveMessage(pbuf, packbufleng); break; case PACKET_CLIENT_INFO: Net_ReceiveClientInfo(pbuf, packbufleng, serverpacketp); break; case PACKET_RTS: G_StartRTS(pbuf[1], 0); break; case PACKET_USER_MAP: Net_ReceiveUserMapName(pbuf, packbufleng); break; case PACKET_MAP_VOTE: Net_ReceiveMapVote(pbuf); break; case PACKET_MAP_VOTE_INITIATE: // call map vote Net_ReceiveMapVoteInitiate(pbuf); break; case PACKET_MAP_VOTE_CANCEL: // cancel map vote Net_ReceiveMapVoteCancel(pbuf); break; } } //////////////////////////////////////////////////////////////////////////////// // Acknowledgement Packets void Net_SendAcknowledge(ENetPeer *client) { if (!g_netServer) return; tempnetbuf[0] = PACKET_ACK; tempnetbuf[1] = myconnectindex; enet_peer_send(client, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 2, ENET_PACKET_FLAG_RELIABLE)); } void Net_ReceiveAcknowledge(uint8_t *pbuf, int32_t packbufleng) { UNREFERENCED_PARAMETER(pbuf); // remove when this variable is used UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used Net_SendChallenge(); } //////////////////////////////////////////////////////////////////////////////// // Challenge Packets // sends the version and a simple crc32 of the current password, all verified by the server before the connection can continue void Net_SendChallenge() { if (!g_netClientPeer) { return; } tempnetbuf[0] = PACKET_AUTH; B_BUF16(&tempnetbuf[1], BYTEVERSION); B_BUF16(&tempnetbuf[3], NETVERSION); B_BUF32(&tempnetbuf[5], crc32once((uint8_t *)g_netPassword, Bstrlen(g_netPassword))); tempnetbuf[9] = myconnectindex; enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 10, ENET_PACKET_FLAG_RELIABLE)); } void Net_ReceiveChallenge(uint8_t *pbuf, int32_t packbufleng, ENetEvent *event) { const uint16_t byteVersion = B_UNBUF16(&pbuf[1]); const uint16_t netVersion = B_UNBUF16(&pbuf[3]); const uint32_t crc = B_UNBUF32(&pbuf[5]); UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used if (byteVersion != BYTEVERSION || netVersion != NETVERSION) { enet_peer_disconnect_later(event->peer, DISC_VERSION_MISMATCH); initprintf("Bad client protocol: version %u.%u\n", byteVersion, netVersion); return; } if (crc != crc32once((uint8_t *)g_netPassword, Bstrlen(g_netPassword))) { enet_peer_disconnect_later(event->peer, DISC_BAD_PASSWORD); initprintf("Bad password from client.\n"); return; } Net_SyncPlayer(event); } //////////////////////////////////////////////////////////////////////////////// // Num Players Packets void Net_SendNewPlayer(int32_t newplayerindex) { packbuf[0] = PACKET_NUM_PLAYERS; packbuf[1] = numplayers; packbuf[2] = playerswhenstarted; packbuf[3] = ud.multimode; packbuf[4] = newplayerindex; packbuf[5] = g_networkMode; packbuf[6] = myconnectindex; enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(packbuf, 7, ENET_PACKET_FLAG_RELIABLE)); } void Net_ReceiveNewPlayer(uint8_t *pbuf, int32_t packbufleng) { int32_t i; UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used numplayers = pbuf[1]; playerswhenstarted = pbuf[2]; ud.multimode = pbuf[3]; if (pbuf[4]) // ID of new player { g_player[pbuf[4]].playerquitflag = 1; if (!g_player[pbuf[4]].ps) { g_player[pbuf[4]].ps = (DukePlayer_t *) Xcalloc(1,sizeof(DukePlayer_t)); } if (!g_player[pbuf[4]].sync) { g_player[pbuf[4]].sync = (input_t *) Xcalloc(1,sizeof(input_t)); } } if (pbuf[5] == NET_DEDICATED_SERVER) { g_networkMode = NET_DEDICATED_CLIENT; } for (i=0; iaim_mode = ud.mouseaiming; tempnetbuf[l++] = g_player[myconnectindex].ps->auto_aim = ud.config.AutoAim; tempnetbuf[l++] = g_player[myconnectindex].ps->weaponswitch = ud.weaponswitch; tempnetbuf[l++] = g_player[myconnectindex].ps->palookup = g_player[myconnectindex].pcolor = ud.color; tempnetbuf[l++] = g_player[myconnectindex].pteam = ud.team; for (i=0; i<10; i++) { g_player[myconnectindex].wchoice[i] = g_player[0].wchoice[i]; tempnetbuf[l++] = (uint8_t)g_player[0].wchoice[i]; } tempnetbuf[l++] = myconnectindex; if (g_netClient) { enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], l, ENET_PACKET_FLAG_RELIABLE)); } else if (g_netServer) { enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], l, ENET_PACKET_FLAG_RELIABLE)); } } void Net_ReceiveClientInfo(uint8_t *pbuf, int32_t packbufleng, int32_t fromserver) { uint32_t i, j; int32_t other = pbuf[packbufleng]; for (i=1; pbuf[i]; i++) { g_player[other].user_name[i-1] = pbuf[i]; } g_player[other].user_name[i-1] = 0; i++; g_player[other].ps->aim_mode = pbuf[i++]; g_player[other].ps->auto_aim = pbuf[i++]; g_player[other].ps->weaponswitch = pbuf[i++]; g_player[other].ps->palookup = g_player[other].pcolor = pbuf[i++]; g_player[other].pteam = pbuf[i++]; for (j=i; i-j<10; i++) { g_player[other].wchoice[i-j] = pbuf[i]; } if (fromserver) { g_player[other].playerquitflag = 1; } } //////////////////////////////////////////////////////////////////////////////// // Map Name Packets void Net_SendUserMapName(void) { int32_t j; if (numplayers < 2) return; packbuf[0] = PACKET_USER_MAP; Bcorrectfilename(boardfilename,0); // user map name is sent with a NUL at the end j = Bstrlen(boardfilename)+1; Bmemcpy(&packbuf[1], boardfilename, j); j++; packbuf[j++] = myconnectindex; if (g_netClient) { enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(packbuf, j, ENET_PACKET_FLAG_RELIABLE)); } else if (g_netServer) { enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(packbuf, j, ENET_PACKET_FLAG_RELIABLE)); } } void Net_ReceiveUserMapName(uint8_t *pbuf, int32_t packbufleng) { int32_t i; Bstrcpy(boardfilename,(char *)pbuf+1); boardfilename[packbufleng-1] = 0; Bcorrectfilename(boardfilename,0); if (boardfilename[0] != 0) { if ((i = kopen4loadfrommod(boardfilename,0)) < 0) { Bmemset(boardfilename,0,sizeof(boardfilename)); Net_SendUserMapName(); } else kclose(i); } if (ud.m_level_number == 7 && ud.m_volume_number == 0 && boardfilename[0] == 0) ud.m_level_number = 0; } //////////////////////////////////////////////////////////////////////////////// // Map Update Packets netmapstate_t *Net_GetRevision(uint8_t revision, uint8_t cancreate) { assert(revision < NET_REVISIONS); if (revision == 0) { return &g_mapStartState; } if (cancreate && g_mapStateHistory[revision] == NULL) { g_mapStateHistory[revision] = (netmapstate_t *) Xcalloc(1, sizeof(netmapstate_t)); Bmemset(g_mapStateHistory[revision], 0, sizeof(netmapstate_t)); } return g_mapStateHistory[revision]; } void Net_SendMapUpdate(void) { int32_t pi; // uint32_t numdiff = 0; uint32_t diffsize = 0; uint32_t packetsize = 0; // uint32_t prevMapDiff = g_netMapRevision; if (!g_netServer || numplayers < 2) { return; } g_netMapRevision++; if (g_netMapRevision >= NET_REVISIONS) { g_netMapRevision = 1; } Net_SaveMapState(Net_GetRevision(g_netMapRevision, 1)); // I use this to test server diffs without requiring a client. //Net_FillMapDiff(prevMapDiff, g_netMapRevision); for (pi = 0; pi < (signed) g_netServer->peerCount; pi++) { ENetPeer *const currentPeer = &g_netServer->peers[pi]; const intptr_t playeridx = (intptr_t) currentPeer->data; if (playeridx < 0 || playeridx >= MAXPLAYERS) { continue; } if (currentPeer->state != ENET_PEER_STATE_CONNECTED || !g_player[playeridx].playerquitflag) { continue; } if (g_player[playeridx].revision == g_netMapRevision) { continue; } Net_FillMapDiff(g_player[playeridx].revision, g_netMapRevision); diffsize = 4 * sizeof(uint32_t); diffsize += tempMapDiff.numActors * sizeof(netactor_t); diffsize += tempMapDiff.numToDelete * sizeof(int32_t); packetsize = LZ4_compress_limitedOutput((const char*)&tempMapDiff, (char*)&tempnetbuf[5], diffsize, tempnetbufsize - 5); if (packetsize == 0) return; // apply header tempnetbuf[0] = PACKET_MAP_STREAM; // apply uncompressed size B_BUF32(&tempnetbuf[1], diffsize); packetsize += 5; //initprintf("update packet size: %d - revision (%d->%d) - num actors: %d\n", packetsize, g_player[playeridx].revision, g_netMapRevision, tempMapDiff.numActors); enet_peer_send(currentPeer, CHAN_GAMESTATE, enet_packet_create(tempnetbuf, packetsize, ENET_PACKET_FLAG_RELIABLE)); } } void Net_ReceiveMapUpdate(ENetEvent *event) { const uint8_t *pktBuf = (uint8_t *) event->packet->data; uint32_t diffsize = B_UNBUF32(&pktBuf[1]); LZ4_decompress_safe((const char*)&pktBuf[5], (char*)&tempMapDiff, diffsize, sizeof(netmapdiff_t)); Net_RestoreMapState(); //initprintf("Update packet size: %d - num actors: %d\n", event->packet->dataLength, tempMapDiff.numActors); } //////////////////////////////////////////////////////////////////////////////// // Map State static int Net_CompareActors(const void *actor1, const void *actor2) { return ((netactor_t *)actor1)->netIndex - ((netactor_t *)actor2)->netIndex; } void Net_SaveMapState(netmapstate_t *save) { int32_t i; int32_t statIndex; if (save == NULL) { return; } save->numActors = 0; for (statIndex = 0; g_netStatnums[statIndex] != MAXSTATUS; ++statIndex) { i = headspritestat[g_netStatnums[statIndex]]; for (; i >= 0; i = nextspritestat[i]) { if (save->numActors >= NETMAXACTORS) { break; } if (Net_IsRelevantSprite(i) && sprite[i].statnum != STAT_NETALLOC) { netactor_t *tempActor = &save->actor[save->numActors]; Net_CopyToNet(i, tempActor); save->numActors++; } } } qsort(save->actor, save->numActors, sizeof(netactor_t), &Net_CompareActors); } void Net_FillMapDiff(uint32_t fromRevision, uint32_t toRevision) { uint32_t fromIndex = 0; uint32_t toIndex = 0; netmapstate_t *fromState; netmapstate_t *toState; int32_t *deleteBuf; netactor_t *actorBuf; // First check to see if the tempMapDiff is already filled with the diff we want if (tempMapDiff.fromRevision == fromRevision && tempMapDiff.toRevision == toRevision) { return; } tempMapDiff.fromRevision = fromRevision; tempMapDiff.toRevision = toRevision; tempMapDiff.numActors = 0; tempMapDiff.numToDelete = 0; actorBuf = (netactor_t *) tempMapDiff.data; deleteBuf = (int32_t *) tempnetbuf; fromState = Net_GetRevision(fromRevision, 0); toState = Net_GetRevision(toRevision, 0); assert(fromState != NULL); assert(toState != NULL); while (fromIndex < fromState->numActors || toIndex < toState->numActors) { const int32_t fromNet = fromState->actor[fromIndex].netIndex; const int32_t toNet = toState->actor[toIndex].netIndex; const int32_t fromValid = fromIndex < fromState->numActors; const int32_t toValid = toIndex < toState->numActors; if (toValid && (!fromValid || fromNet > toNet)) { //initprintf("This actor is new: %d - %d\n", toState->actor[toIndex].netIndex, toState->actor[toIndex].sprite.picnum); // Add the "to" data. It's a new actor. memcpy(&actorBuf[tempMapDiff.numActors], &toState->actor[toIndex], sizeof(netactor_t)); tempMapDiff.numActors++; toIndex++; } else if (fromValid && (!toValid || toNet > fromNet)) { //initprintf("This actor is deleted: %d - %d\n", fromState->actor[fromIndex].netIndex, fromState->actor[fromIndex].sprite.picnum); // Add the "fromNet" to the list of deleted actors deleteBuf[tempMapDiff.numToDelete] = fromState->actor[fromIndex].netIndex; tempMapDiff.numToDelete++; fromIndex++; } else //if (fromNet == toNet) { assert(fromNet == toNet); if (Net_ActorsAreDifferent(&fromState->actor[fromIndex], &toState->actor[toIndex])) { //initprintf("This actor is different: %d - %d\n", toState->actor[toIndex].netIndex, toState->actor[toIndex].sprite.picnum); // Add the "to" data. It's changed. memcpy(&actorBuf[tempMapDiff.numActors], &toState->actor[toIndex], sizeof(netactor_t)); tempMapDiff.numActors++; } fromIndex++; toIndex++; } } if (tempMapDiff.numToDelete > 0) { memcpy(&actorBuf[tempMapDiff.numActors], deleteBuf, tempMapDiff.numToDelete * sizeof(int32_t)); } } void Net_RestoreMapState() { int32_t i; uint32_t j; int32_t *deleteBuf; netactor_t *actorBuf; // Make sure we're using a diff from our current revision if (tempMapDiff.fromRevision != g_player[myconnectindex].revision) { return; } g_player[myconnectindex].revision = tempMapDiff.toRevision; actorBuf = (netactor_t *) tempMapDiff.data; for (j = 0; j < tempMapDiff.numActors; ++j) { i = actorBuf[j].netIndex; /* if (Net_IsRelevantSprite(i)) { initprintf("This actor is different: %d - %d\n", i, actorBuf[j].sprite.picnum); } else { initprintf("This actor is new: %d - %d\n", i, actorBuf[j].sprite.picnum); } */ Net_CopyFromNet(i, &actorBuf[j]); } deleteBuf = (int32_t *) &actorBuf[tempMapDiff.numActors]; for (j = 0; j < tempMapDiff.numToDelete; ++j) { //initprintf("This actor is deleted: %d - %d\n", deleteBuf[j], sprite[deleteBuf[j]].picnum); A_DeleteSprite(deleteBuf[j]); } } void Net_CopyToNet(int32_t i, netactor_t *netactor) { netactor->netIndex = i; netactor->picnum = actor[i].picnum; netactor->ang = actor[i].ang; netactor->extra = actor[i].extra; netactor->owner = actor[i].owner; netactor->movflag = actor[i].movflag; netactor->tempang = actor[i].tempang; netactor->timetosleep = actor[i].timetosleep; netactor->flags = actor[i].flags; netactor->floorz = actor[i].floorz; netactor->lastvx = actor[i].lastvx; netactor->lastvy = actor[i].lastvy; netactor->lasttransport = actor[i].lasttransport; netactor->actorstayput = actor[i].actorstayput; //netactor->dispicnum = actor[i].dispicnum; netactor->cgg = actor[i].cgg; netactor->owner = actor[i].owner; Bmemcpy(netactor->t_data, actor[i].t_data, 10 * sizeof(int32_t)); Bmemcpy(&netactor->sprite, &sprite[i], sizeof(spritetype)); } void Net_CopyFromNet(int32_t i, netactor_t *netactor) { if (netactor->sprite.statnum == STAT_NETALLOC) { // Do nothing if it's going to be deleted return; } else if (sprite[i].statnum == STAT_NETALLOC) { changespritestat(i, netactor->sprite.statnum); do_insertsprite_at_headofsect(i, netactor->sprite.sectnum); } else { // These functions already check to see if the sprite already has the stat/sect value. No need to do it twice. changespritestat(i, netactor->sprite.statnum); changespritesect(i, netactor->sprite.sectnum); } actor[i].picnum = netactor->picnum; actor[i].ang = netactor->ang; actor[i].extra = netactor->extra; actor[i].owner = netactor->owner; actor[i].movflag = netactor->movflag; actor[i].tempang = netactor->tempang; actor[i].timetosleep = netactor->timetosleep; actor[i].flags = netactor->flags; actor[i].floorz = netactor->floorz; actor[i].lastvx = netactor->lastvx; actor[i].lastvy = netactor->lastvy; actor[i].lasttransport = netactor->lasttransport; actor[i].actorstayput = netactor->actorstayput; actor[i].dispicnum = netactor->dispicnum; actor[i].cgg = netactor->cgg; actor[i].owner = netactor->owner; Bmemcpy(actor[i].t_data, netactor->t_data, 10 * sizeof(int32_t)); Bmemcpy(&sprite[i], &netactor->sprite, sizeof(spritetype)); } int32_t Net_ActorsAreDifferent(netactor_t *actor1, netactor_t *actor2) { int32_t allDiff; int32_t finalDiff; int32_t nonStandableDiff; allDiff = actor1->picnum != actor2->picnum || actor1->ang != actor2->ang || actor1->extra != actor2->extra || actor1->owner != actor2->owner || actor1->movflag != actor2->movflag || actor1->tempang != actor2->tempang || //actor1->timetosleep != actor2->timetosleep || actor1->flags != actor2->flags || actor1->floorz != actor2->floorz || actor1->lastvx != actor2->lastvx || actor1->lastvy != actor2->lastvy || actor1->lasttransport != actor2->lasttransport || actor1->actorstayput != actor2->actorstayput || //actor1->dispicnum != actor2->dispicnum || //actor1->cgg != actor2->cgg || actor1->sprite.owner != actor2->sprite.owner || actor1->sprite.statnum != actor2->sprite.statnum || actor1->sprite.sectnum != actor2->sprite.sectnum || actor1->sprite.picnum != actor2->sprite.picnum || //actor1->sprite.shade != actor2->sprite.shade || actor1->sprite.xrepeat != actor2->sprite.xrepeat || actor1->sprite.yrepeat != actor2->sprite.yrepeat;// || //actor1->sprite.ang != actor2->sprite.ang || nonStandableDiff = actor1->sprite.x != actor2->sprite.x || actor1->sprite.y != actor2->sprite.y || actor1->sprite.z != actor2->sprite.z || actor1->sprite.xvel != actor2->sprite.xvel || actor1->sprite.yvel != actor2->sprite.yvel || actor1->sprite.zvel != actor2->sprite.zvel; finalDiff = allDiff || (actor1->sprite.statnum != STAT_STANDABLE && nonStandableDiff); return finalDiff; } int32_t Net_IsRelevantSprite(int32_t i) { if (g_netServer == NULL && g_netClient == NULL) { return 0; } else if (i < 0 || i >= MAXSPRITES) { return 0; } else { return Net_IsRelevantStat(sprite[i].statnum); } } int32_t Net_IsRelevantStat(int32_t stat) { int32_t statIndex; if (g_netServer == NULL && g_netClient == NULL) { return 0; } for (statIndex = 0; g_netStatnums[statIndex] != MAXSTATUS; ++statIndex) { if (g_netStatnums[statIndex] == stat) { return 1; } } return 0; } int32_t Net_InsertSprite(int32_t sect, int32_t stat) { int32_t i = headspritestat[STAT_NETALLOC]; // This means that we've run out of server-side actors if (i == -1) { return -1; } changespritestat(i, stat); do_insertsprite_at_headofsect(i, sect); return i; } void Net_DeleteSprite(int32_t spritenum) { if (sprite[spritenum].statnum == STAT_NETALLOC) { return; } changespritestat(spritenum, STAT_NETALLOC); do_deletespritesect(spritenum); sprite[spritenum].sectnum = MAXSECTORS; } extern void Gv_RefreshPointers(void); //////////////////////////////////////////////////////////////////////////////// // Player Updates void Net_FillPlayerUpdate(playerupdate_t *update, int32_t player) { update->playerindex = player; update->pos = g_player[player].ps->pos; update->opos = g_player[player].ps->opos; update->vel = g_player[player].ps->vel; update->ang = g_player[player].ps->ang; update->horiz = g_player[player].ps->horiz; update->horizoff = g_player[player].ps->horizoff; update->ping = g_player[player].ping; update->deadflag = g_player[player].ps->dead_flag; update->playerquitflag = g_player[player].playerquitflag; } void Net_ExtractPlayerUpdate(playerupdate_t *update, int32_t type) { const int32_t playerindex = update->playerindex; if (playerindex != myconnectindex) { g_player[playerindex].ps->pos = update->pos; g_player[playerindex].ps->opos = update->opos; g_player[playerindex].ps->vel = update->vel; g_player[playerindex].ps->ang = update->ang; g_player[playerindex].ps->horiz = update->horiz; g_player[playerindex].ps->horizoff = update->horizoff; } if (type == PACKET_MASTER_TO_SLAVE) { g_player[playerindex].ping = update->ping; g_player[playerindex].ps->dead_flag = update->deadflag; g_player[playerindex].playerquitflag = update->playerquitflag; } //updatesectorz(g_player[other].ps->pos.x, g_player[other].ps->pos.y, g_player[other].ps->pos.z, &g_player[other].ps->cursectnum); //changespritesect(g_player[other].ps->i, g_player[other].ps->cursectnum); } //////////////////////////////////////////////////////////////////////////////// // Server Update Packets #pragma pack(push,1) typedef struct { uint8_t header; uint8_t numplayers; input_t nsyn; int32_t seed; int16_t pause_on; } serverupdate_t; #pragma pack(pop) #pragma pack(push,1) typedef struct { uint8_t extra; int16_t cstat; uint16_t owner; uint16_t picnum; uint16_t gotweapon; uint8_t kickback_pic; uint8_t frags[MAXPLAYERS]; int16_t inv_amount[GET_MAX]; int16_t ammo_amount[MAX_WEAPONS]; uint8_t curr_weapon; uint8_t last_weapon; uint8_t wantweaponfire; uint8_t weapon_pos; uint8_t frag_ps; uint8_t frag; uint8_t fraggedself; uint8_t last_extra; uint8_t pal; uint16_t ping; uint16_t newowner; playerupdate_t player; } serverplayerupdate_t; #pragma pack(pop) void Net_SendServerUpdates(void) { int16_t i; uint8_t *updatebuf; serverupdate_t serverupdate; serverplayerupdate_t playerupdate; input_t *osyn = (input_t *)&inputfifo[1][0]; input_t *nsyn = (input_t *)&inputfifo[0][0]; ticrandomseed = randomseed; if (g_netServer) { Bmemcpy(&osyn[0], &nsyn[0], sizeof(input_t)); } if (!g_netServer || numplayers < 2) { return; } serverupdate.header = PACKET_MASTER_TO_SLAVE; serverupdate.seed = ticrandomseed; serverupdate.nsyn = *nsyn; serverupdate.pause_on = ud.pause_on; serverupdate.numplayers = 0; updatebuf = tempnetbuf + sizeof(serverupdate_t); for (TRAVERSE_CONNECT(i)) { if (g_player[i].playerquitflag == 0) { continue; } Net_FillPlayerUpdate(&playerupdate.player, i); playerupdate.gotweapon = g_player[i].ps->gotweapon; playerupdate.extra = sprite[g_player[i].ps->i].extra; playerupdate.cstat = sprite[g_player[i].ps->i].cstat; playerupdate.owner = actor[g_player[i].ps->i].owner; playerupdate.picnum = actor[g_player[i].ps->i].picnum; playerupdate.kickback_pic = g_player[i].ps->kickback_pic; Bmemcpy(playerupdate.frags, g_player[i].frags, sizeof(playerupdate.frags)); Bmemcpy(playerupdate.inv_amount, g_player[i].ps->inv_amount, sizeof(playerupdate.inv_amount)); Bmemcpy(playerupdate.ammo_amount, g_player[i].ps->ammo_amount, sizeof(playerupdate.ammo_amount)); playerupdate.curr_weapon = g_player[i].ps->curr_weapon; playerupdate.last_weapon = g_player[i].ps->last_weapon; playerupdate.wantweaponfire = g_player[i].ps->wantweaponfire; playerupdate.weapon_pos = g_player[i].ps->weapon_pos; playerupdate.frag_ps = g_player[i].ps->frag_ps; playerupdate.frag = g_player[i].ps->frag; playerupdate.fraggedself = g_player[i].ps->fraggedself; playerupdate.last_extra = g_player[i].ps->last_extra; playerupdate.ping = g_player[i].ping; playerupdate.newowner = g_player[i].ps->newowner; playerupdate.pal = sprite[g_player[i].ps->i].pal; Bmemcpy(updatebuf, &playerupdate, sizeof(serverplayerupdate_t)); updatebuf += sizeof(serverplayerupdate_t); serverupdate.numplayers++; } if (serverupdate.numplayers == 0) { return; } Bmemcpy(tempnetbuf, &serverupdate, sizeof(serverupdate_t)); enet_host_broadcast(g_netServer, CHAN_MOVE, enet_packet_create(tempnetbuf, sizeof(serverupdate_t) + (serverupdate.numplayers * sizeof(serverplayerupdate_t)), 0)); } void Net_ReceiveServerUpdate(ENetEvent *event) { int32_t i; uint8_t *updatebuf; // int8_t numupdates; serverupdate_t serverupdate; serverplayerupdate_t playerupdate; if (((event->packet->dataLength - sizeof(serverupdate_t)) % sizeof(serverplayerupdate_t)) != 0) { return; } updatebuf = (uint8_t *) event->packet->data; Bmemcpy(&serverupdate, updatebuf, sizeof(serverupdate_t)); updatebuf += sizeof(serverupdate_t); inputfifo[0][0] = serverupdate.nsyn; ud.pause_on = serverupdate.pause_on; ticrandomseed = serverupdate.seed; for (i = 0; i < serverupdate.numplayers; ++i) { Bmemcpy(&playerupdate, updatebuf, sizeof(serverplayerupdate_t)); updatebuf += sizeof(serverplayerupdate_t); Net_ExtractPlayerUpdate(&playerupdate.player, PACKET_MASTER_TO_SLAVE); g_player[i].ps->gotweapon = playerupdate.gotweapon; sprite[g_player[i].ps->i].extra = playerupdate.extra; sprite[g_player[i].ps->i].cstat = playerupdate.cstat; //actor[g_player[i].ps->i].owner = playerupdate.owner; // This makes baby jesus cry actor[g_player[i].ps->i].picnum = playerupdate.picnum; g_player[i].ps->kickback_pic = playerupdate.kickback_pic; Bmemcpy(g_player[i].frags, playerupdate.frags, sizeof(playerupdate.frags)); Bmemcpy(g_player[i].ps->inv_amount, playerupdate.inv_amount, sizeof(playerupdate.inv_amount)); Bmemcpy(g_player[i].ps->ammo_amount, playerupdate.ammo_amount, sizeof(playerupdate.ammo_amount)); g_player[i].ps->curr_weapon = playerupdate.curr_weapon; g_player[i].ps->last_weapon = playerupdate.last_weapon; g_player[i].ps->wantweaponfire = playerupdate.wantweaponfire; g_player[i].ps->weapon_pos = playerupdate.weapon_pos; g_player[i].ps->frag_ps = playerupdate.frag_ps; g_player[i].ps->frag = playerupdate.frag; g_player[i].ps->fraggedself = playerupdate.fraggedself; g_player[i].ps->last_extra = playerupdate.last_extra; g_player[i].ping = playerupdate.ping; sprite[g_player[i].ps->i].pal = playerupdate.pal; g_player[i].ps->newowner = playerupdate.newowner; } } //////////////////////////////////////////////////////////////////////////////// // Client Update Packets #pragma pack(push,1) typedef struct { uint8_t header; uint32_t revision; input_t nsyn; playerupdate_t player; } clientupdate_t; #pragma pack(pop) void Net_SendClientUpdate(void) { clientupdate_t update; update.header = PACKET_SLAVE_TO_MASTER; update.revision = g_player[myconnectindex].revision; update.nsyn = inputfifo[0][myconnectindex]; Net_FillPlayerUpdate(&update.player, myconnectindex); enet_peer_send(g_netClientPeer, CHAN_MOVE, enet_packet_create(&update, sizeof(clientupdate_t), 0)); } void Net_ReceiveClientUpdate(ENetEvent *event) { int32_t playeridx; clientupdate_t update; if (event->packet->dataLength != sizeof(clientupdate_t)) { return; } Bmemcpy(&update, (char *) event->packet->data, sizeof(clientupdate_t)); playeridx = (int32_t)(intptr_t) event->peer->data; if (playeridx < 0 || playeridx >= MAXPLAYERS) { return; } g_player[playeridx].revision = update.revision; inputfifo[0][playeridx] = update.nsyn; Net_ExtractPlayerUpdate(&update.player, PACKET_SLAVE_TO_MASTER); } //////////////////////////////////////////////////////////////////////////////// // Message Packets void Net_SendMessage(void) { int32_t hitstate, i, j, l; if (g_player[myconnectindex].ps->gm&MODE_SENDTOWHOM) { if (g_chatPlayer != -1 || ud.multimode < 3) { tempbuf[0] = PACKET_MESSAGE; tempbuf[2] = 0; recbuf[0] = 0; if (ud.multimode < 3) g_chatPlayer = 2; if (typebuf[0] == '/' && Btoupper(typebuf[1]) == 'M' && Btoupper(typebuf[2]) == 'E') { Bstrcat(recbuf,"* "); i = 3, j = Bstrlen(typebuf); Bstrcpy(tempbuf,typebuf); while (i < j) { typebuf[i-3] = tempbuf[i]; i++; } typebuf[i-3] = '\0'; Bstrcat(recbuf,g_player[myconnectindex].user_name); } else { Bstrcat(recbuf,g_player[myconnectindex].user_name); Bstrcat(recbuf,": "); } Bstrcat(recbuf,"^00"); Bstrcat(recbuf,typebuf); j = Bstrlen(recbuf); recbuf[j] = 0; Bstrcat(tempbuf+2,recbuf); if (g_chatPlayer >= ud.multimode) { tempbuf[1] = 255; tempbuf[j+2] = myconnectindex; j++; if (g_netServer) enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(tempbuf, j+2, 0)); else if (g_netClient) enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(tempbuf, j+2, 0)); G_AddUserQuote(recbuf); quotebot += 8; l = G_GameTextLen(USERQUOTE_LEFTOFFSET,OSD_StripColors(tempbuf,recbuf)); while (l > (ud.config.ScreenWidth - USERQUOTE_RIGHTOFFSET)) { l -= (ud.config.ScreenWidth - USERQUOTE_RIGHTOFFSET); quotebot += 8; } quotebotgoal = quotebot; } g_chatPlayer = -1; g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM); } else if (g_chatPlayer == -1) { j = 50; gametext(320>>1,j,"Send message to...",0,2+8+16); j += 8; for (TRAVERSE_CONNECT(i)) { if (i == myconnectindex) { minitextshade((320>>1)-40+1,j+1,"A/ENTER - ALL",26,0,2+8+16); minitext((320>>1)-40,j,"A/ENTER - ALL",0,2+8+16); j += 7; } else { Bsprintf(recbuf," %d - %s",i+1,g_player[i].user_name); minitextshade((320>>1)-40-6+1,j+1,recbuf,26,0,2+8+16); minitext((320>>1)-40-6,j,recbuf,0,2+8+16); j += 7; } } minitextshade((320>>1)-40-4+1,j+1," ESC - Abort",26,0,2+8+16); minitext((320>>1)-40-4,j," ESC - Abort",0,2+8+16); j += 7; if (ud.screen_size > 0) j = 200-45; else j = 200-8; mpgametext(j,typebuf,0,2+8+16); if (KB_KeyWaiting()) { i = KB_GetCh(); if (i == 'A' || i == 'a' || i == 13) g_chatPlayer = ud.multimode; else if (i >= '1' || i <= (ud.multimode + '1')) g_chatPlayer = i - '1'; else { g_chatPlayer = ud.multimode; if (i == 27) { g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM); g_chatPlayer = -1; } else typebuf[0] = 0; } KB_ClearKeyDown(sc_1); KB_ClearKeyDown(sc_2); KB_ClearKeyDown(sc_3); KB_ClearKeyDown(sc_4); KB_ClearKeyDown(sc_5); KB_ClearKeyDown(sc_6); KB_ClearKeyDown(sc_7); KB_ClearKeyDown(sc_8); KB_ClearKeyDown(sc_A); KB_ClearKeyDown(sc_Escape); KB_ClearKeyDown(sc_Enter); } } } else { hitstate = I_EnterText(typebuf, 120, 0); if (ud.screen_size > 1) j = (200-45)<<16; else j = (200-8)<<16; if (xdim >= 640 && ydim >= 480) j = scale(j,ydim,200); i = textsc(5<<16); { const vec2_t dim = G_ScreenTextSize(STARTALPHANUM, i, 0, textsc(65536), 0, typebuf, 8|16|ROTATESPRITE_FULL16, 5<<16, 7<<16, 0, 0, TEXT_LITERALESCAPE, 0, 0, xdim-1, ydim-1); l = i + dim.x + scale(textsc((tilesizx[SPINNINGNUKEICON]+2)<<13), ydim, 200); } if (l >= (xdim<<16)) i -= (l - (xdim<<16)); { const vec2_t dim = G_ScreenText(STARTALPHANUM, i, j, textsc(65536), 0, 0, typebuf, 1, 0, 8|16|ROTATESPRITE_FULL16, 0, 5<<16, 7<<16, 0, 0, TEXT_YCENTER|TEXT_LITERALESCAPE, 0, 0, xdim-1, ydim-1); i += dim.x + scale(textsc((tilesizx[SPINNINGNUKEICON]+1)<<12), ydim, 200); } rotatesprite_fs(i, j, textsc(32768), 0, SPINNINGNUKEICON+((totalclock>>3)%7), 4-(sintable[(totalclock<<4)&2047]>>11), 0, 0); if (hitstate == 1) { KB_ClearKeyDown(sc_Enter); if (Bstrlen(typebuf) == 0) { g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM); return; } if (ud.automsg) { if (SHIFTS_IS_PRESSED) g_chatPlayer = -1; else g_chatPlayer = ud.multimode; } g_player[myconnectindex].ps->gm |= MODE_SENDTOWHOM; } else if (hitstate == -1) g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM); else pub = NUMPAGES; } } void Net_ReceiveMessage(uint8_t *pbuf, int32_t packbufleng) { Bstrncpy(recbuf, (char *)pbuf+2, packbufleng-2); recbuf[packbufleng-2] = 0; G_AddUserQuote(recbuf); S_PlaySound(EXITMENUSOUND); pus = pub = NUMPAGES; } //////////////////////////////////////////////////////////////////////////////// // New Game Packets void Net_StartNewGame() { int32_t i; for (TRAVERSE_CONNECT(i)) { P_ResetWeapons(i); P_ResetInventory(i); g_player[i].revision = 0; } Net_ExtractNewGame(&pendingnewgame, 0); G_NewGame(ud.volume_number,ud.level_number,ud.player_skill); ud.coop = ud.m_coop; g_netMapRevision = 0; if (G_EnterLevel(MODE_GAME)) { G_BackToMenu(); } } void Net_NotifyNewGame() { int32_t i; int32_t statIndex; int32_t numSprites = 0; int32_t numSpritesToNetAlloc = 0; if (!g_netServer && !g_netClient) { return; } // Grab the total number of sprites at level load for (statIndex = 0; statIndex < MAXSTATUS; ++statIndex) { i = headspritestat[statIndex]; for (; i >= 0; i = nextspritestat[i]) { numSprites++; } } // Take half of the leftover sprites and allocate them for the network's nefarious purposes. numSpritesToNetAlloc = (MAXSPRITES - numSprites) / 2; for (i = 0; i < numSpritesToNetAlloc; ++i) { int32_t newSprite = insertspritestat(STAT_NETALLOC); sprite[newSprite].sectnum = MAXSECTORS; Numsprites++; } Net_SaveMapState(&g_mapStartState); } void Net_SendNewGame(int32_t frommenu, ENetPeer *peer) { newgame_t newgame; newgame.header = PACKET_NEW_GAME; Net_FillNewGame(&newgame, frommenu); if (peer) { enet_peer_send(peer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE)); } else { enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE)); } } void Net_ReceiveNewGame(ENetEvent *event) { if ((vote_map + vote_episode + voting) != -3) G_AddUserQuote("Vote Succeeded"); Bmemcpy(&pendingnewgame, event->packet->data, sizeof(newgame_t)); Net_StartNewGame(); packbuf[0] = PACKET_PLAYER_READY; packbuf[1] = myconnectindex; if (g_netClientPeer) { enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(packbuf, 2, ENET_PACKET_FLAG_RELIABLE)); } g_player[myconnectindex].ps->gm = MODE_GAME; ready2send = 1; } void Net_FillNewGame(newgame_t *newgame, int32_t frommenu) { if (frommenu) { newgame->level_number = ud.m_level_number; newgame->volume_number = ud.m_volume_number; newgame->player_skill = ud.m_player_skill; newgame->monsters_off = ud.m_monsters_off; newgame->respawn_monsters = ud.m_respawn_monsters; newgame->respawn_items = ud.m_respawn_items; newgame->respawn_inventory = ud.m_respawn_inventory; newgame->ffire = ud.m_ffire; newgame->noexits = ud.m_noexits; newgame->coop = ud.m_coop; } else { newgame->level_number = ud.level_number; newgame->volume_number = ud.volume_number; newgame->player_skill = ud.player_skill; newgame->monsters_off = ud.monsters_off; newgame->respawn_monsters = ud.respawn_monsters; newgame->respawn_items = ud.respawn_items; newgame->respawn_inventory = ud.respawn_inventory; newgame->ffire = ud.ffire; newgame->noexits = ud.noexits; newgame->coop = ud.coop; } } void Net_ExtractNewGame(newgame_t *newgame, int32_t menuonly) { ud.m_level_number = newgame->level_number; ud.m_volume_number = newgame->volume_number; ud.m_player_skill = newgame->player_skill; ud.m_monsters_off = newgame->monsters_off; ud.m_respawn_monsters = newgame->respawn_monsters; ud.m_respawn_items = newgame->respawn_items; ud.m_respawn_inventory = newgame->respawn_inventory; ud.m_ffire = newgame->ffire; ud.m_noexits = newgame->noexits; ud.m_coop = newgame->coop; if (!menuonly) { ud.level_number = newgame->level_number; ud.volume_number = newgame->volume_number; ud.player_skill = newgame->player_skill; ud.monsters_off = newgame->monsters_off; ud.respawn_monsters = newgame->respawn_monsters; ud.respawn_monsters = newgame->respawn_items; ud.respawn_inventory = newgame->respawn_inventory; ud.ffire = newgame->ffire; ud.noexits = newgame->noexits; ud.coop = newgame->coop; } } //////////////////////////////////////////////////////////////////////////////// // Map Vote Packets void Net_SendMapVoteInitiate(void) { newgame_t newgame; if (!g_netClient) { return; } voting = myconnectindex; newgame.header = PACKET_MAP_VOTE_INITIATE; newgame.connection = myconnectindex; Net_FillNewGame(&newgame, 1); enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE)); } void Net_ReceiveMapVoteInitiate(uint8_t *pbuf) { int32_t i; Bmemcpy(&pendingnewgame, pbuf, sizeof(newgame_t)); Net_ExtractNewGame(&pendingnewgame, 1); voting = pendingnewgame.connection; vote_episode = pendingnewgame.volume_number; vote_map = pendingnewgame.level_number; Bsprintf(tempbuf,"%s^00 has called a vote to change map to %s (E%dL%d)", g_player[voting].user_name, MapInfo[(uint8_t)(vote_episode*MAXLEVELS + vote_map)].name, vote_episode+1,vote_map+1); G_AddUserQuote(tempbuf); Bsprintf(tempbuf,"Press F1 to Accept, F2 to Decline"); G_AddUserQuote(tempbuf); for (i=MAXPLAYERS-1; i>=0; i--) { g_player[i].vote = 0; g_player[i].gotvote = 0; } g_player[voting].gotvote = g_player[voting].vote = 1; } void Net_SendMapVote(int32_t votefor) { voting = -1; g_player[myconnectindex].gotvote = 1; g_player[myconnectindex].vote = votefor; tempbuf[0] = PACKET_MAP_VOTE; tempbuf[1] = myconnectindex; tempbuf[2] = votefor; tempbuf[3] = myconnectindex; if (g_netClient) { enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(tempbuf, 4, ENET_PACKET_FLAG_RELIABLE)); } else if (g_netServer) { enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(tempbuf, 4, ENET_PACKET_FLAG_RELIABLE)); } Net_CheckForEnoughVotes(); } void Net_ReceiveMapVote(uint8_t *pbuf) { if (voting == myconnectindex && g_player[(uint8_t)pbuf[1]].gotvote == 0) { Bsprintf(tempbuf,"Confirmed vote from %s",g_player[(uint8_t)pbuf[1]].user_name); G_AddUserQuote(tempbuf); } if (!g_netServer) { return; } g_player[(uint8_t)pbuf[1]].gotvote = 1; g_player[(uint8_t)pbuf[1]].vote = pbuf[2]; Net_CheckForEnoughVotes(); } void Net_CheckForEnoughVotes() { int32_t i; int32_t requiredvotes; int32_t numfor, numagainst; // Only the server can decide map changes if (!g_netServer || numplayers <= 1) { return; } // If there are just two players, both of them deserve a vote if (numplayers == 2) { requiredvotes = 2; } else { // If more than two players, we need at least 50% of the players to vote // Which means that if there's an odd number of players, we'll need slightly more than 50% of the vote. requiredvotes = numplayers / 2; if (numplayers % 2 == 1) { requiredvotes++; } } numfor = numagainst = 0; for (i=0; i= requiredvotes) { Net_StartNewGame(); Net_SendNewGame(1, NULL); } else if (numagainst >= requiredvotes || (numfor + numagainst) == numplayers) { Net_SendMapVoteCancel(1); } } // If failed is true, that means the vote lost. Otherwise it was cancelled by the client who initiated it. void Net_SendMapVoteCancel(int32_t failed) { // Only the server or the client that initiated the vote can cancel the vote if (g_netClient && voting != myconnectindex) { return; } tempbuf[0] = PACKET_MAP_VOTE_CANCEL; tempbuf[1] = myconnectindex; // If we're forwarding a cancelled message, change the connection index to the one who cancelled it. if (g_netServer && !failed) { tempbuf[1] = voting; } voting = -1; if (g_netClient) { enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(tempbuf, 2, ENET_PACKET_FLAG_RELIABLE)); } else if (g_netServer) { enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(tempbuf, 2, ENET_PACKET_FLAG_RELIABLE)); } } void Net_ReceiveMapVoteCancel(uint8_t *pbuf) { // int32_t numvotes; // Ignore if we're not voting if (voting == -1) { return; } // Ignore cancellations from clients that did not initiate the map vote if (voting != pbuf[1] && voting != myconnectindex) { return; } if (voting == myconnectindex || voting != pbuf[1]) { Bsprintf(tempbuf,"Vote Failed"); } else if (voting == pbuf[1]) { Bsprintf(tempbuf,"%s^00 has canceled the vote",g_player[voting].user_name); } G_AddUserQuote(tempbuf); if (g_netServer) { Net_SendMapVoteCancel(0); } voting = -1; } #endif // !defined NETCODE_DISABLE