1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1998-2000 by DooM Legacy Team.
4 // Copyright (C) 1999-2020 by Sonic Team Junior.
5 //
6 // This program is free software distributed under the
7 // terms of the GNU General Public License, version 2.
8 // See the 'LICENSE' file for more details.
9 //-----------------------------------------------------------------------------
10 /// \file d_clisrv.c
11 /// \brief SRB2 Network game communication and protocol, all OS independent parts.
12
13 #include <time.h>
14 #ifdef __GNUC__
15 #include <unistd.h> //for unlink
16 #endif
17
18 #include "i_net.h"
19 #include "i_system.h"
20 #include "i_video.h"
21 #include "d_net.h"
22 #include "d_main.h"
23 #include "g_game.h"
24 #include "st_stuff.h"
25 #include "hu_stuff.h"
26 #include "keys.h"
27 #include "g_input.h" // JOY1
28 #include "m_menu.h"
29 #include "console.h"
30 #include "d_netfil.h"
31 #include "byteptr.h"
32 #include "p_saveg.h"
33 #include "z_zone.h"
34 #include "p_local.h"
35 #include "m_misc.h"
36 #include "am_map.h"
37 #include "m_random.h"
38 #include "mserv.h"
39 #include "y_inter.h"
40 #include "r_local.h"
41 #include "m_argv.h"
42 #include "p_setup.h"
43 #include "lzf.h"
44 #include "lua_script.h"
45 #include "lua_hook.h"
46 #include "md5.h"
47 #include "m_perfstats.h"
48
49 #ifndef NONET
50 // cl loading screen
51 #include "v_video.h"
52 #include "f_finale.h"
53 #endif
54
55 //
56 // NETWORKING
57 //
58 // gametic is the tic about to (or currently being) run
59 // Server:
60 // maketic is the tic that hasn't had control made for it yet
61 // nettics is the tic for each node
62 // firstticstosend is the lowest value of nettics
63 // Client:
64 // neededtic is the tic needed by the client to run the game
65 // firstticstosend is used to optimize a condition
66 // Normally maketic >= gametic > 0
67
68 #define PREDICTIONQUEUE BACKUPTICS
69 #define PREDICTIONMASK (PREDICTIONQUEUE-1)
70 #define MAX_REASONLENGTH 30
71
72 boolean server = true; // true or false but !server == client
73 #define client (!server)
74 boolean nodownload = false;
75 boolean serverrunning = false;
76 INT32 serverplayer = 0;
77 char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support)
78
79 // Server specific vars
80 UINT8 playernode[MAXPLAYERS];
81 char playeraddress[MAXPLAYERS][64];
82
83 // Minimum timeout for sending the savegame
84 // The actual timeout will be longer depending on the savegame length
85 tic_t jointimeout = (10*TICRATE);
86 static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame?
87 static boolean resendingsavegame[MAXNETNODES]; // Are we resending the savegame?
88 static tic_t savegameresendcooldown[MAXNETNODES]; // How long before we can resend again?
89 static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout?
90
91 // Incremented by cv_joindelay when a client joins, decremented each tic.
92 // If higher than cv_joindelay * 2 (3 joins in a short timespan), joins are temporarily disabled.
93 static tic_t joindelay = 0;
94
95 UINT16 pingmeasurecount = 1;
96 UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone.
97 UINT32 playerpingtable[MAXPLAYERS]; //table of player latency values.
98 SINT8 nodetoplayer[MAXNETNODES];
99 SINT8 nodetoplayer2[MAXNETNODES]; // say the numplayer for this node if any (splitscreen)
100 UINT8 playerpernode[MAXNETNODES]; // used specialy for scplitscreen
101 boolean nodeingame[MAXNETNODES]; // set false as nodes leave game
102 tic_t servermaxping = 800; // server's max ping. Defaults to 800
103 static tic_t nettics[MAXNETNODES]; // what tic the client have received
104 static tic_t supposedtics[MAXNETNODES]; // nettics prevision for smaller packet
105 static UINT8 nodewaiting[MAXNETNODES];
106 static tic_t firstticstosend; // min of the nettics
107 static tic_t tictoclear = 0; // optimize d_clearticcmd
108 static tic_t maketic;
109
110 static INT16 consistancy[BACKUPTICS];
111
112 static UINT8 player_joining = false;
113 UINT8 hu_redownloadinggamestate = 0;
114
115 UINT8 adminpassmd5[16];
116 boolean adminpasswordset = false;
117
118 // Client specific
119 static ticcmd_t localcmds;
120 static ticcmd_t localcmds2;
121 static boolean cl_packetmissed;
122 // here it is for the secondary local player (splitscreen)
123 static UINT8 mynode; // my address pointofview server
124 static boolean cl_redownloadinggamestate = false;
125
126 static UINT8 localtextcmd[MAXTEXTCMD];
127 static UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen
128 static tic_t neededtic;
129 SINT8 servernode = 0; // the number of the server node
130 /// \brief do we accept new players?
131 /// \todo WORK!
132 boolean acceptnewnode = true;
133
134 // engine
135
136 // Must be a power of two
137 #define TEXTCMD_HASH_SIZE 4
138
139 typedef struct textcmdplayer_s
140 {
141 INT32 playernum;
142 UINT8 cmd[MAXTEXTCMD];
143 struct textcmdplayer_s *next;
144 } textcmdplayer_t;
145
146 typedef struct textcmdtic_s
147 {
148 tic_t tic;
149 textcmdplayer_t *playercmds[TEXTCMD_HASH_SIZE];
150 struct textcmdtic_s *next;
151 } textcmdtic_t;
152
153 ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS];
154 static textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE] = {NULL};
155
156
157 consvar_t cv_showjoinaddress = CVAR_INIT ("showjoinaddress", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
158
159 static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0, NULL}};
160 consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_cons_t, NULL);
161
G_DcpyTiccmd(void * dest,const ticcmd_t * src,const size_t n)162 static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n)
163 {
164 const size_t d = n / sizeof(ticcmd_t);
165 const size_t r = n % sizeof(ticcmd_t);
166 UINT8 *ret = dest;
167
168 if (r)
169 M_Memcpy(dest, src, n);
170 else if (d)
171 G_MoveTiccmd(dest, src, d);
172 return ret+n;
173 }
174
G_ScpyTiccmd(ticcmd_t * dest,void * src,const size_t n)175 static inline void *G_ScpyTiccmd(ticcmd_t* dest, void* src, const size_t n)
176 {
177 const size_t d = n / sizeof(ticcmd_t);
178 const size_t r = n % sizeof(ticcmd_t);
179 UINT8 *ret = src;
180
181 if (r)
182 M_Memcpy(dest, src, n);
183 else if (d)
184 G_MoveTiccmd(dest, src, d);
185 return ret+n;
186 }
187
188
189
190 // Some software don't support largest packet
191 // (original sersetup, not exactely, but the probability of sending a packet
192 // of 512 bytes is like 0.1)
193 UINT16 software_MAXPACKETLENGTH;
194
195 /** Guesses the full value of a tic from its lowest byte, for a specific node
196 *
197 * \param low The lowest byte of the tic value
198 * \param node The node to deduce the tic for
199 * \return The full tic value
200 *
201 */
ExpandTics(INT32 low,INT32 node)202 tic_t ExpandTics(INT32 low, INT32 node)
203 {
204 INT32 delta;
205
206 delta = low - (nettics[node] & UINT8_MAX);
207
208 if (delta >= -64 && delta <= 64)
209 return (nettics[node] & ~UINT8_MAX) + low;
210 else if (delta > 64)
211 return (nettics[node] & ~UINT8_MAX) - 256 + low;
212 else //if (delta < -64)
213 return (nettics[node] & ~UINT8_MAX) + 256 + low;
214 }
215
216 // -----------------------------------------------------------------
217 // Some extra data function for handle textcmd buffer
218 // -----------------------------------------------------------------
219
220 static void (*listnetxcmd[MAXNETXCMD])(UINT8 **p, INT32 playernum);
221
RegisterNetXCmd(netxcmd_t id,void (* cmd_f)(UINT8 ** p,INT32 playernum))222 void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum))
223 {
224 #ifdef PARANOIA
225 if (id >= MAXNETXCMD)
226 I_Error("Command id %d too big", id);
227 if (listnetxcmd[id] != 0)
228 I_Error("Command id %d already used", id);
229 #endif
230 listnetxcmd[id] = cmd_f;
231 }
232
SendNetXCmd(netxcmd_t id,const void * param,size_t nparam)233 void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam)
234 {
235 if (localtextcmd[0]+2+nparam > MAXTEXTCMD)
236 {
237 // for future reference: if (cv_debug) != debug disabled.
238 CONS_Alert(CONS_ERROR, M_GetText("NetXCmd buffer full, cannot add netcmd %d! (size: %d, needed: %s)\n"), id, localtextcmd[0], sizeu1(nparam));
239 return;
240 }
241 localtextcmd[0]++;
242 localtextcmd[localtextcmd[0]] = (UINT8)id;
243 if (param && nparam)
244 {
245 M_Memcpy(&localtextcmd[localtextcmd[0]+1], param, nparam);
246 localtextcmd[0] = (UINT8)(localtextcmd[0] + (UINT8)nparam);
247 }
248 }
249
250 // splitscreen player
SendNetXCmd2(netxcmd_t id,const void * param,size_t nparam)251 void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam)
252 {
253 if (localtextcmd2[0]+2+nparam > MAXTEXTCMD)
254 {
255 I_Error("No more place in the buffer for netcmd %d\n",id);
256 return;
257 }
258 localtextcmd2[0]++;
259 localtextcmd2[localtextcmd2[0]] = (UINT8)id;
260 if (param && nparam)
261 {
262 M_Memcpy(&localtextcmd2[localtextcmd2[0]+1], param, nparam);
263 localtextcmd2[0] = (UINT8)(localtextcmd2[0] + (UINT8)nparam);
264 }
265 }
266
GetFreeXCmdSize(void)267 UINT8 GetFreeXCmdSize(void)
268 {
269 // -1 for the size and another -1 for the ID.
270 return (UINT8)(localtextcmd[0] - 2);
271 }
272
273 // Frees all textcmd memory for the specified tic
D_FreeTextcmd(tic_t tic)274 static void D_FreeTextcmd(tic_t tic)
275 {
276 textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
277 textcmdtic_t *textcmdtic = *tctprev;
278
279 while (textcmdtic && textcmdtic->tic != tic)
280 {
281 tctprev = &textcmdtic->next;
282 textcmdtic = textcmdtic->next;
283 }
284
285 if (textcmdtic)
286 {
287 INT32 i;
288
289 // Remove this tic from the list.
290 *tctprev = textcmdtic->next;
291
292 // Free all players.
293 for (i = 0; i < TEXTCMD_HASH_SIZE; i++)
294 {
295 textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[i];
296
297 while (textcmdplayer)
298 {
299 textcmdplayer_t *tcpnext = textcmdplayer->next;
300 Z_Free(textcmdplayer);
301 textcmdplayer = tcpnext;
302 }
303 }
304
305 // Free this tic's own memory.
306 Z_Free(textcmdtic);
307 }
308 }
309
310 // Gets the buffer for the specified ticcmd, or NULL if there isn't one
D_GetExistingTextcmd(tic_t tic,INT32 playernum)311 static UINT8* D_GetExistingTextcmd(tic_t tic, INT32 playernum)
312 {
313 textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
314 while (textcmdtic && textcmdtic->tic != tic) textcmdtic = textcmdtic->next;
315
316 // Do we have an entry for the tic? If so, look for player.
317 if (textcmdtic)
318 {
319 textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)];
320 while (textcmdplayer && textcmdplayer->playernum != playernum) textcmdplayer = textcmdplayer->next;
321
322 if (textcmdplayer) return textcmdplayer->cmd;
323 }
324
325 return NULL;
326 }
327
328 // Gets the buffer for the specified ticcmd, creating one if necessary
D_GetTextcmd(tic_t tic,INT32 playernum)329 static UINT8* D_GetTextcmd(tic_t tic, INT32 playernum)
330 {
331 textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
332 textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
333 textcmdplayer_t *textcmdplayer, **tcpprev;
334
335 // Look for the tic.
336 while (textcmdtic && textcmdtic->tic != tic)
337 {
338 tctprev = &textcmdtic->next;
339 textcmdtic = textcmdtic->next;
340 }
341
342 // If we don't have an entry for the tic, make it.
343 if (!textcmdtic)
344 {
345 textcmdtic = *tctprev = Z_Calloc(sizeof (textcmdtic_t), PU_STATIC, NULL);
346 textcmdtic->tic = tic;
347 }
348
349 tcpprev = &textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)];
350 textcmdplayer = *tcpprev;
351
352 // Look for the player.
353 while (textcmdplayer && textcmdplayer->playernum != playernum)
354 {
355 tcpprev = &textcmdplayer->next;
356 textcmdplayer = textcmdplayer->next;
357 }
358
359 // If we don't have an entry for the player, make it.
360 if (!textcmdplayer)
361 {
362 textcmdplayer = *tcpprev = Z_Calloc(sizeof (textcmdplayer_t), PU_STATIC, NULL);
363 textcmdplayer->playernum = playernum;
364 }
365
366 return textcmdplayer->cmd;
367 }
368
ExtraDataTicker(void)369 static void ExtraDataTicker(void)
370 {
371 INT32 i;
372
373 for (i = 0; i < MAXPLAYERS; i++)
374 if (playeringame[i] || i == 0)
375 {
376 UINT8 *bufferstart = D_GetExistingTextcmd(gametic, i);
377
378 if (bufferstart)
379 {
380 UINT8 *curpos = bufferstart;
381 UINT8 *bufferend = &curpos[curpos[0]+1];
382
383 curpos++;
384 while (curpos < bufferend)
385 {
386 if (*curpos < MAXNETXCMD && listnetxcmd[*curpos])
387 {
388 const UINT8 id = *curpos;
389 curpos++;
390 DEBFILE(va("executing x_cmd %s ply %u ", netxcmdnames[id - 1], i));
391 (listnetxcmd[id])(&curpos, i);
392 DEBFILE("done\n");
393 }
394 else
395 {
396 if (server)
397 {
398 SendKick(i, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
399 DEBFILE(va("player %d kicked [gametic=%u] reason as follows:\n", i, gametic));
400 }
401 CONS_Alert(CONS_WARNING, M_GetText("Got unknown net command [%s]=%d (max %d)\n"), sizeu1(curpos - bufferstart), *curpos, bufferstart[0]);
402 break;
403 }
404 }
405 }
406 }
407
408 // If you are a client, you can safely forget the net commands for this tic
409 // If you are the server, you need to remember them until every client has been acknowledged,
410 // because if you need to resend a PT_SERVERTICS packet, you will need to put the commands in it
411 if (client)
412 D_FreeTextcmd(gametic);
413 }
414
D_Clearticcmd(tic_t tic)415 static void D_Clearticcmd(tic_t tic)
416 {
417 INT32 i;
418
419 D_FreeTextcmd(tic);
420
421 for (i = 0; i < MAXPLAYERS; i++)
422 netcmds[tic%BACKUPTICS][i].angleturn = 0;
423
424 DEBFILE(va("clear tic %5u (%2u)\n", tic, tic%BACKUPTICS));
425 }
426
D_ResetTiccmds(void)427 void D_ResetTiccmds(void)
428 {
429 INT32 i;
430
431 memset(&localcmds, 0, sizeof(ticcmd_t));
432 memset(&localcmds2, 0, sizeof(ticcmd_t));
433
434 // Reset the net command list
435 for (i = 0; i < TEXTCMD_HASH_SIZE; i++)
436 while (textcmds[i])
437 D_Clearticcmd(textcmds[i]->tic);
438 }
439
SendKick(UINT8 playernum,UINT8 msg)440 void SendKick(UINT8 playernum, UINT8 msg)
441 {
442 UINT8 buf[2];
443
444 if (!(server && cv_rejointimeout.value))
445 msg &= ~KICK_MSG_KEEP_BODY;
446
447 buf[0] = playernum;
448 buf[1] = msg;
449 SendNetXCmd(XD_KICK, &buf, 2);
450 }
451
452 // -----------------------------------------------------------------
453 // end of extra data function
454 // -----------------------------------------------------------------
455
456 // -----------------------------------------------------------------
457 // extra data function for lmps
458 // -----------------------------------------------------------------
459
460 // if extradatabit is set, after the ziped tic you find this:
461 //
462 // type | description
463 // ---------+--------------
464 // byte | size of the extradata
465 // byte | the extradata (xd) bits: see XD_...
466 // with this byte you know what parameter folow
467 // if (xd & XDNAMEANDCOLOR)
468 // byte | color
469 // char[MAXPLAYERNAME] | name of the player
470 // endif
471 // if (xd & XD_WEAPON_PREF)
472 // byte | original weapon switch: boolean, true if use the old
473 // | weapon switch methode
474 // char[NUMWEAPONS] | the weapon switch priority
475 // byte | autoaim: true if use the old autoaim system
476 // endif
477 /*boolean AddLmpExtradata(UINT8 **demo_point, INT32 playernum)
478 {
479 UINT8 *textcmd = D_GetExistingTextcmd(gametic, playernum);
480
481 if (!textcmd)
482 return false;
483
484 M_Memcpy(*demo_point, textcmd, textcmd[0]+1);
485 *demo_point += textcmd[0]+1;
486 return true;
487 }
488
489 void ReadLmpExtraData(UINT8 **demo_pointer, INT32 playernum)
490 {
491 UINT8 nextra;
492 UINT8 *textcmd;
493
494 if (!demo_pointer)
495 return;
496
497 textcmd = D_GetTextcmd(gametic, playernum);
498 nextra = **demo_pointer;
499 M_Memcpy(textcmd, *demo_pointer, nextra + 1);
500 // increment demo pointer
501 *demo_pointer += nextra + 1;
502 }*/
503
504 // -----------------------------------------------------------------
505 // end extra data function for lmps
506 // -----------------------------------------------------------------
507
508 static INT16 Consistancy(void);
509
510 typedef enum
511 {
512 CL_SEARCHING,
513 CL_DOWNLOADFILES,
514 CL_ASKJOIN,
515 CL_WAITJOINRESPONSE,
516 CL_DOWNLOADSAVEGAME,
517 CL_CONNECTED,
518 CL_ABORTED
519 } cl_mode_t;
520
521 static void GetPackets(void);
522
523 static cl_mode_t cl_mode = CL_SEARCHING;
524
525 #ifndef NONET
526 #define SNAKE_SPEED 5
527
528 #define SNAKE_NUM_BLOCKS_X 20
529 #define SNAKE_NUM_BLOCKS_Y 10
530 #define SNAKE_BLOCK_SIZE 12
531 #define SNAKE_BORDER_SIZE 12
532
533 #define SNAKE_MAP_WIDTH (SNAKE_NUM_BLOCKS_X * SNAKE_BLOCK_SIZE)
534 #define SNAKE_MAP_HEIGHT (SNAKE_NUM_BLOCKS_Y * SNAKE_BLOCK_SIZE)
535
536 #define SNAKE_LEFT_X ((BASEVIDWIDTH - SNAKE_MAP_WIDTH) / 2 - SNAKE_BORDER_SIZE)
537 #define SNAKE_RIGHT_X (SNAKE_LEFT_X + SNAKE_MAP_WIDTH + SNAKE_BORDER_SIZE * 2 - 1)
538 #define SNAKE_BOTTOM_Y (BASEVIDHEIGHT - 48)
539 #define SNAKE_TOP_Y (SNAKE_BOTTOM_Y - SNAKE_MAP_HEIGHT - SNAKE_BORDER_SIZE * 2 + 1)
540
541 enum snake_bonustype_s {
542 SNAKE_BONUS_NONE = 0,
543 SNAKE_BONUS_SLOW,
544 SNAKE_BONUS_FAST,
545 SNAKE_BONUS_GHOST,
546 SNAKE_BONUS_NUKE,
547 SNAKE_BONUS_SCISSORS,
548 SNAKE_BONUS_REVERSE,
549 SNAKE_BONUS_EGGMAN,
550 SNAKE_NUM_BONUSES,
551 };
552
553 static const char *snake_bonuspatches[] = {
554 NULL,
555 "DL_SLOW",
556 "TVSSC0",
557 "TVIVC0",
558 "TVARC0",
559 "DL_SCISSORS",
560 "TVRCC0",
561 "TVEGC0",
562 };
563
564 static const char *snake_backgrounds[] = {
565 "RVPUMICF",
566 "FRSTRCKF",
567 "TAR",
568 "MMFLRB4",
569 "RVDARKF1",
570 "RVZWALF1",
571 "RVZWALF4",
572 "RVZWALF5",
573 "RVZGRS02",
574 "RVZGRS04",
575 };
576
577 typedef struct snake_s
578 {
579 boolean paused;
580 boolean pausepressed;
581 tic_t time;
582 tic_t nextupdate;
583 boolean gameover;
584 UINT8 background;
585
586 UINT16 snakelength;
587 enum snake_bonustype_s snakebonus;
588 tic_t snakebonustime;
589 UINT8 snakex[SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y];
590 UINT8 snakey[SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y];
591 UINT8 snakedir[SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y];
592
593 UINT8 applex;
594 UINT8 appley;
595
596 enum snake_bonustype_s bonustype;
597 UINT8 bonusx;
598 UINT8 bonusy;
599 } snake_t;
600
601 static snake_t *snake = NULL;
602
Snake_Initialise(void)603 static void Snake_Initialise(void)
604 {
605 if (!snake)
606 snake = malloc(sizeof(snake_t));
607
608 snake->paused = false;
609 snake->pausepressed = false;
610 snake->time = 0;
611 snake->nextupdate = SNAKE_SPEED;
612 snake->gameover = false;
613 snake->background = M_RandomKey(sizeof(snake_backgrounds) / sizeof(*snake_backgrounds));
614
615 snake->snakelength = 1;
616 snake->snakebonus = SNAKE_BONUS_NONE;
617 snake->snakex[0] = M_RandomKey(SNAKE_NUM_BLOCKS_X);
618 snake->snakey[0] = M_RandomKey(SNAKE_NUM_BLOCKS_Y);
619 snake->snakedir[0] = 0;
620 snake->snakedir[1] = 0;
621
622 snake->applex = M_RandomKey(SNAKE_NUM_BLOCKS_X);
623 snake->appley = M_RandomKey(SNAKE_NUM_BLOCKS_Y);
624
625 snake->bonustype = SNAKE_BONUS_NONE;
626 }
627
Snake_GetOppositeDir(UINT8 dir)628 static UINT8 Snake_GetOppositeDir(UINT8 dir)
629 {
630 if (dir == 1 || dir == 3)
631 return dir + 1;
632 else if (dir == 2 || dir == 4)
633 return dir - 1;
634 else
635 return 12 + 5 - dir;
636 }
637
Snake_FindFreeSlot(UINT8 * freex,UINT8 * freey,UINT8 headx,UINT8 heady)638 static void Snake_FindFreeSlot(UINT8 *freex, UINT8 *freey, UINT8 headx, UINT8 heady)
639 {
640 UINT8 x, y;
641 UINT16 i;
642
643 do
644 {
645 x = M_RandomKey(SNAKE_NUM_BLOCKS_X);
646 y = M_RandomKey(SNAKE_NUM_BLOCKS_Y);
647
648 for (i = 0; i < snake->snakelength; i++)
649 if (x == snake->snakex[i] && y == snake->snakey[i])
650 break;
651 } while (i < snake->snakelength || (x == headx && y == heady)
652 || (x == snake->applex && y == snake->appley)
653 || (snake->bonustype != SNAKE_BONUS_NONE && x == snake->bonusx && y == snake->bonusy));
654
655 *freex = x;
656 *freey = y;
657 }
658
Snake_Handle(void)659 static void Snake_Handle(void)
660 {
661 UINT8 x, y;
662 UINT8 oldx, oldy;
663 UINT16 i;
664
665 // Handle retry
666 if (snake->gameover && (PLAYER1INPUTDOWN(gc_jump) || gamekeydown[KEY_ENTER]))
667 {
668 Snake_Initialise();
669 snake->pausepressed = true; // Avoid accidental pause on respawn
670 }
671
672 // Handle pause
673 if (PLAYER1INPUTDOWN(gc_pause) || gamekeydown[KEY_ENTER])
674 {
675 if (!snake->pausepressed)
676 snake->paused = !snake->paused;
677 snake->pausepressed = true;
678 }
679 else
680 snake->pausepressed = false;
681
682 if (snake->paused)
683 return;
684
685 snake->time++;
686
687 x = snake->snakex[0];
688 y = snake->snakey[0];
689 oldx = snake->snakex[1];
690 oldy = snake->snakey[1];
691
692 // Update direction
693 if (gamekeydown[KEY_LEFTARROW])
694 {
695 if (snake->snakelength < 2 || x <= oldx)
696 snake->snakedir[0] = 1;
697 }
698 else if (gamekeydown[KEY_RIGHTARROW])
699 {
700 if (snake->snakelength < 2 || x >= oldx)
701 snake->snakedir[0] = 2;
702 }
703 else if (gamekeydown[KEY_UPARROW])
704 {
705 if (snake->snakelength < 2 || y <= oldy)
706 snake->snakedir[0] = 3;
707 }
708 else if (gamekeydown[KEY_DOWNARROW])
709 {
710 if (snake->snakelength < 2 || y >= oldy)
711 snake->snakedir[0] = 4;
712 }
713
714 if (snake->snakebonustime)
715 {
716 snake->snakebonustime--;
717 if (!snake->snakebonustime)
718 snake->snakebonus = SNAKE_BONUS_NONE;
719 }
720
721 snake->nextupdate--;
722 if (snake->nextupdate)
723 return;
724 if (snake->snakebonus == SNAKE_BONUS_SLOW)
725 snake->nextupdate = SNAKE_SPEED * 2;
726 else if (snake->snakebonus == SNAKE_BONUS_FAST)
727 snake->nextupdate = SNAKE_SPEED * 2 / 3;
728 else
729 snake->nextupdate = SNAKE_SPEED;
730
731 if (snake->gameover)
732 return;
733
734 // Find new position
735 switch (snake->snakedir[0])
736 {
737 case 1:
738 if (x > 0)
739 x--;
740 else
741 snake->gameover = true;
742 break;
743 case 2:
744 if (x < SNAKE_NUM_BLOCKS_X - 1)
745 x++;
746 else
747 snake->gameover = true;
748 break;
749 case 3:
750 if (y > 0)
751 y--;
752 else
753 snake->gameover = true;
754 break;
755 case 4:
756 if (y < SNAKE_NUM_BLOCKS_Y - 1)
757 y++;
758 else
759 snake->gameover = true;
760 break;
761 }
762
763 // Check collision with snake
764 if (snake->snakebonus != SNAKE_BONUS_GHOST)
765 for (i = 1; i < snake->snakelength - 1; i++)
766 if (x == snake->snakex[i] && y == snake->snakey[i])
767 {
768 if (snake->snakebonus == SNAKE_BONUS_SCISSORS)
769 {
770 snake->snakebonus = SNAKE_BONUS_NONE;
771 snake->snakelength = i;
772 S_StartSound(NULL, sfx_adderr);
773 }
774 else
775 snake->gameover = true;
776 }
777
778 if (snake->gameover)
779 {
780 S_StartSound(NULL, sfx_lose);
781 return;
782 }
783
784 // Check collision with apple
785 if (x == snake->applex && y == snake->appley)
786 {
787 if (snake->snakelength + 3 < SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y)
788 {
789 snake->snakelength++;
790 snake->snakex [snake->snakelength - 1] = snake->snakex [snake->snakelength - 2];
791 snake->snakey [snake->snakelength - 1] = snake->snakey [snake->snakelength - 2];
792 snake->snakedir[snake->snakelength - 1] = snake->snakedir[snake->snakelength - 2];
793 }
794
795 // Spawn new apple
796 Snake_FindFreeSlot(&snake->applex, &snake->appley, x, y);
797
798 // Spawn new bonus
799 if (!(snake->snakelength % 5))
800 {
801 do
802 {
803 snake->bonustype = M_RandomKey(SNAKE_NUM_BONUSES - 1) + 1;
804 } while (snake->snakelength > SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y * 3 / 4
805 && (snake->bonustype == SNAKE_BONUS_EGGMAN || snake->bonustype == SNAKE_BONUS_FAST || snake->bonustype == SNAKE_BONUS_REVERSE));
806
807 Snake_FindFreeSlot(&snake->bonusx, &snake->bonusy, x, y);
808 }
809
810 S_StartSound(NULL, sfx_s3k6b);
811 }
812
813 if (snake->snakelength > 1 && snake->snakedir[0])
814 {
815 UINT8 dir = snake->snakedir[0];
816
817 oldx = snake->snakex[1];
818 oldy = snake->snakey[1];
819
820 // Move
821 for (i = snake->snakelength - 1; i > 0; i--)
822 {
823 snake->snakex[i] = snake->snakex[i - 1];
824 snake->snakey[i] = snake->snakey[i - 1];
825 snake->snakedir[i] = snake->snakedir[i - 1];
826 }
827
828 // Handle corners
829 if (x < oldx && dir == 3)
830 dir = 5;
831 else if (x > oldx && dir == 3)
832 dir = 6;
833 else if (x < oldx && dir == 4)
834 dir = 7;
835 else if (x > oldx && dir == 4)
836 dir = 8;
837 else if (y < oldy && dir == 1)
838 dir = 9;
839 else if (y < oldy && dir == 2)
840 dir = 10;
841 else if (y > oldy && dir == 1)
842 dir = 11;
843 else if (y > oldy && dir == 2)
844 dir = 12;
845 snake->snakedir[1] = dir;
846 }
847
848 snake->snakex[0] = x;
849 snake->snakey[0] = y;
850
851 // Check collision with bonus
852 if (snake->bonustype != SNAKE_BONUS_NONE && x == snake->bonusx && y == snake->bonusy)
853 {
854 S_StartSound(NULL, sfx_ncchip);
855
856 switch (snake->bonustype)
857 {
858 case SNAKE_BONUS_SLOW:
859 snake->snakebonus = SNAKE_BONUS_SLOW;
860 snake->snakebonustime = 20 * TICRATE;
861 break;
862 case SNAKE_BONUS_FAST:
863 snake->snakebonus = SNAKE_BONUS_FAST;
864 snake->snakebonustime = 20 * TICRATE;
865 break;
866 case SNAKE_BONUS_GHOST:
867 snake->snakebonus = SNAKE_BONUS_GHOST;
868 snake->snakebonustime = 10 * TICRATE;
869 break;
870 case SNAKE_BONUS_NUKE:
871 for (i = 0; i < snake->snakelength; i++)
872 {
873 snake->snakex [i] = snake->snakex [0];
874 snake->snakey [i] = snake->snakey [0];
875 snake->snakedir[i] = snake->snakedir[0];
876 }
877
878 S_StartSound(NULL, sfx_bkpoof);
879 break;
880 case SNAKE_BONUS_SCISSORS:
881 snake->snakebonus = SNAKE_BONUS_SCISSORS;
882 snake->snakebonustime = 60 * TICRATE;
883 break;
884 case SNAKE_BONUS_REVERSE:
885 for (i = 0; i < (snake->snakelength + 1) / 2; i++)
886 {
887 UINT16 i2 = snake->snakelength - 1 - i;
888 UINT8 tmpx = snake->snakex [i];
889 UINT8 tmpy = snake->snakey [i];
890 UINT8 tmpdir = snake->snakedir[i];
891
892 // Swap first segment with last segment
893 snake->snakex [i] = snake->snakex [i2];
894 snake->snakey [i] = snake->snakey [i2];
895 snake->snakedir[i] = Snake_GetOppositeDir(snake->snakedir[i2]);
896 snake->snakex [i2] = tmpx;
897 snake->snakey [i2] = tmpy;
898 snake->snakedir[i2] = Snake_GetOppositeDir(tmpdir);
899 }
900
901 snake->snakedir[0] = 0;
902
903 S_StartSound(NULL, sfx_gravch);
904 break;
905 default:
906 if (snake->snakebonus != SNAKE_BONUS_GHOST)
907 {
908 snake->gameover = true;
909 S_StartSound(NULL, sfx_lose);
910 }
911 }
912
913 snake->bonustype = SNAKE_BONUS_NONE;
914 }
915 }
916
Snake_Draw(void)917 static void Snake_Draw(void)
918 {
919 INT16 i;
920
921 // Background
922 V_DrawFlatFill(
923 SNAKE_LEFT_X + SNAKE_BORDER_SIZE,
924 SNAKE_TOP_Y + SNAKE_BORDER_SIZE,
925 SNAKE_MAP_WIDTH,
926 SNAKE_MAP_HEIGHT,
927 W_GetNumForName(snake_backgrounds[snake->background])
928 );
929
930 // Borders
931 V_DrawFill(SNAKE_LEFT_X, SNAKE_TOP_Y, SNAKE_BORDER_SIZE + SNAKE_MAP_WIDTH, SNAKE_BORDER_SIZE, 242); // Top
932 V_DrawFill(SNAKE_LEFT_X + SNAKE_BORDER_SIZE + SNAKE_MAP_WIDTH, SNAKE_TOP_Y, SNAKE_BORDER_SIZE, SNAKE_BORDER_SIZE + SNAKE_MAP_HEIGHT, 242); // Right
933 V_DrawFill(SNAKE_LEFT_X + SNAKE_BORDER_SIZE, SNAKE_TOP_Y + SNAKE_BORDER_SIZE + SNAKE_MAP_HEIGHT, SNAKE_BORDER_SIZE + SNAKE_MAP_WIDTH, SNAKE_BORDER_SIZE, 242); // Bottom
934 V_DrawFill(SNAKE_LEFT_X, SNAKE_TOP_Y + SNAKE_BORDER_SIZE, SNAKE_BORDER_SIZE, SNAKE_BORDER_SIZE + SNAKE_MAP_HEIGHT, 242); // Left
935
936 // Apple
937 V_DrawFixedPatch(
938 (SNAKE_LEFT_X + SNAKE_BORDER_SIZE + snake->applex * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2) * FRACUNIT,
939 (SNAKE_TOP_Y + SNAKE_BORDER_SIZE + snake->appley * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2) * FRACUNIT,
940 FRACUNIT / 4,
941 0,
942 W_CachePatchLongName("DL_APPLE", PU_HUDGFX),
943 NULL
944 );
945
946 // Bonus
947 if (snake->bonustype != SNAKE_BONUS_NONE)
948 V_DrawFixedPatch(
949 (SNAKE_LEFT_X + SNAKE_BORDER_SIZE + snake->bonusx * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2 ) * FRACUNIT,
950 (SNAKE_TOP_Y + SNAKE_BORDER_SIZE + snake->bonusy * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2 + 4) * FRACUNIT,
951 FRACUNIT / 2,
952 0,
953 W_CachePatchLongName(snake_bonuspatches[snake->bonustype], PU_HUDGFX),
954 NULL
955 );
956
957 // Snake
958 if (!snake->gameover || snake->time % 8 < 8 / 2) // Blink if game over
959 {
960 for (i = snake->snakelength - 1; i >= 0; i--)
961 {
962 const char *patchname;
963 UINT8 dir = snake->snakedir[i];
964
965 if (i == 0) // Head
966 {
967 switch (dir)
968 {
969 case 1: patchname = "DL_SNAKEHEAD_L"; break;
970 case 2: patchname = "DL_SNAKEHEAD_R"; break;
971 case 3: patchname = "DL_SNAKEHEAD_T"; break;
972 case 4: patchname = "DL_SNAKEHEAD_B"; break;
973 default: patchname = "DL_SNAKEHEAD_M";
974 }
975 }
976 else // Body
977 {
978 switch (dir)
979 {
980 case 1: patchname = "DL_SNAKEBODY_L"; break;
981 case 2: patchname = "DL_SNAKEBODY_R"; break;
982 case 3: patchname = "DL_SNAKEBODY_T"; break;
983 case 4: patchname = "DL_SNAKEBODY_B"; break;
984 case 5: patchname = "DL_SNAKEBODY_LT"; break;
985 case 6: patchname = "DL_SNAKEBODY_RT"; break;
986 case 7: patchname = "DL_SNAKEBODY_LB"; break;
987 case 8: patchname = "DL_SNAKEBODY_RB"; break;
988 case 9: patchname = "DL_SNAKEBODY_TL"; break;
989 case 10: patchname = "DL_SNAKEBODY_TR"; break;
990 case 11: patchname = "DL_SNAKEBODY_BL"; break;
991 case 12: patchname = "DL_SNAKEBODY_BR"; break;
992 default: patchname = "DL_SNAKEBODY_B";
993 }
994 }
995
996 V_DrawFixedPatch(
997 (SNAKE_LEFT_X + SNAKE_BORDER_SIZE + snake->snakex[i] * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2) * FRACUNIT,
998 (SNAKE_TOP_Y + SNAKE_BORDER_SIZE + snake->snakey[i] * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2) * FRACUNIT,
999 i == 0 && dir == 0 ? FRACUNIT / 5 : FRACUNIT / 2,
1000 snake->snakebonus == SNAKE_BONUS_GHOST ? V_TRANSLUCENT : 0,
1001 W_CachePatchLongName(patchname, PU_HUDGFX),
1002 NULL
1003 );
1004 }
1005 }
1006
1007 // Length
1008 V_DrawString(SNAKE_RIGHT_X + 4, SNAKE_TOP_Y, V_MONOSPACE, va("%u", snake->snakelength));
1009
1010 // Bonus
1011 if (snake->snakebonus != SNAKE_BONUS_NONE
1012 && (snake->snakebonustime >= 3 * TICRATE || snake->time % 4 < 4 / 2))
1013 V_DrawFixedPatch(
1014 (SNAKE_RIGHT_X + 10) * FRACUNIT,
1015 (SNAKE_TOP_Y + 24) * FRACUNIT,
1016 FRACUNIT / 2,
1017 0,
1018 W_CachePatchLongName(snake_bonuspatches[snake->snakebonus], PU_HUDGFX),
1019 NULL
1020 );
1021 }
1022
1023 //
1024 // CL_DrawConnectionStatus
1025 //
1026 // Keep the local client informed of our status.
1027 //
CL_DrawConnectionStatus(void)1028 static inline void CL_DrawConnectionStatus(void)
1029 {
1030 INT32 ccstime = I_GetTime();
1031
1032 // Draw background fade
1033 if (!menuactive) // menu already draws its own fade
1034 V_DrawFadeScreen(0xFF00, 16); // force default
1035
1036 // Draw the bottom box.
1037 M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1);
1038 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort");
1039
1040 if (cl_mode != CL_DOWNLOADFILES)
1041 {
1042 INT32 i, animtime = ((ccstime / 4) & 15) + 16;
1043 UINT8 palstart = (cl_mode == CL_SEARCHING) ? 32 : 96;
1044 // 15 pal entries total.
1045 const char *cltext;
1046
1047 if (!(cl_mode == CL_DOWNLOADSAVEGAME && lastfilenum != -1))
1048 for (i = 0; i < 16; ++i)
1049 V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-16, 16, 8, palstart + ((animtime - i) & 15));
1050
1051 switch (cl_mode)
1052 {
1053 case CL_DOWNLOADSAVEGAME:
1054 if (lastfilenum != -1)
1055 {
1056 UINT32 currentsize = fileneeded[lastfilenum].currentsize;
1057 UINT32 totalsize = fileneeded[lastfilenum].totalsize;
1058 INT32 dldlength;
1059
1060 cltext = M_GetText("Downloading game state...");
1061 Net_GetNetStat();
1062
1063 dldlength = (INT32)((currentsize/(double)totalsize) * 256);
1064 if (dldlength > 256)
1065 dldlength = 256;
1066 V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111);
1067 V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, dldlength, 8, 96);
1068
1069 V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
1070 va(" %4uK/%4uK",currentsize>>10,totalsize>>10));
1071
1072 V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
1073 va("%3.1fK/s ", ((double)getbps)/1024));
1074 }
1075 else
1076 cltext = M_GetText("Waiting to download game state...");
1077 break;
1078 case CL_ASKJOIN:
1079 case CL_WAITJOINRESPONSE:
1080 cltext = M_GetText("Requesting to join...");
1081 break;
1082 default:
1083 cltext = M_GetText("Connecting to server...");
1084 break;
1085 }
1086 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, cltext);
1087 }
1088 else
1089 {
1090 if (lastfilenum != -1)
1091 {
1092 INT32 dldlength;
1093 static char tempname[28];
1094 fileneeded_t *file = &fileneeded[lastfilenum];
1095 char *filename = file->filename;
1096
1097 Snake_Draw();
1098
1099 Net_GetNetStat();
1100 dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256);
1101 if (dldlength > 256)
1102 dldlength = 256;
1103 V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111);
1104 V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, dldlength, 8, 96);
1105
1106 memset(tempname, 0, sizeof(tempname));
1107 // offset filename to just the name only part
1108 filename += strlen(filename) - nameonlylength(filename);
1109
1110 if (strlen(filename) > sizeof(tempname)-1) // too long to display fully
1111 {
1112 size_t endhalfpos = strlen(filename)-10;
1113 // display as first 14 chars + ... + last 10 chars
1114 // which should add up to 27 if our math(s) is correct
1115 snprintf(tempname, sizeof(tempname), "%.14s...%.10s", filename, filename+endhalfpos);
1116 }
1117 else // we can copy the whole thing in safely
1118 {
1119 strncpy(tempname, filename, sizeof(tempname)-1);
1120 }
1121
1122 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP,
1123 va(M_GetText("Downloading \"%s\""), tempname));
1124 V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
1125 va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,file->totalsize>>10));
1126 V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
1127 va("%3.1fK/s ", ((double)getbps)/1024));
1128 }
1129 else
1130 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP,
1131 M_GetText("Waiting to download files..."));
1132 }
1133 }
1134 #endif
1135
1136 /** Sends a special packet to declare how many players in local
1137 * Used only in arbitratrenetstart()
1138 * Sends a PT_CLIENTJOIN packet to the server
1139 *
1140 * \return True if the packet was successfully sent
1141 * \todo Improve the description...
1142 * Because to be honest, I have no idea what arbitratrenetstart is...
1143 * Is it even used...?
1144 *
1145 */
CL_SendJoin(void)1146 static boolean CL_SendJoin(void)
1147 {
1148 UINT8 localplayers = 1;
1149 if (netgame)
1150 CONS_Printf(M_GetText("Sending join request...\n"));
1151 netbuffer->packettype = PT_CLIENTJOIN;
1152
1153 if (splitscreen || botingame)
1154 localplayers++;
1155 netbuffer->u.clientcfg.localplayers = localplayers;
1156 netbuffer->u.clientcfg._255 = 255;
1157 netbuffer->u.clientcfg.packetversion = PACKETVERSION;
1158 netbuffer->u.clientcfg.version = VERSION;
1159 netbuffer->u.clientcfg.subversion = SUBVERSION;
1160 strncpy(netbuffer->u.clientcfg.application, SRB2APPLICATION,
1161 sizeof netbuffer->u.clientcfg.application);
1162
1163 CleanupPlayerName(consoleplayer, cv_playername.zstring);
1164 if (splitscreen)
1165 CleanupPlayerName(1, cv_playername2.zstring);/* 1 is a HACK? oh no */
1166
1167 strncpy(netbuffer->u.clientcfg.names[0], cv_playername.zstring, MAXPLAYERNAME);
1168 strncpy(netbuffer->u.clientcfg.names[1], cv_playername2.zstring, MAXPLAYERNAME);
1169
1170 return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak));
1171 }
1172
FindRejoinerNum(SINT8 node)1173 static INT32 FindRejoinerNum(SINT8 node)
1174 {
1175 char strippednodeaddress[64];
1176 const char *nodeaddress;
1177 char *port;
1178 INT32 i;
1179
1180 // Make sure there is no dead dress before proceeding to the stripping
1181 if (!I_GetNodeAddress)
1182 return -1;
1183 nodeaddress = I_GetNodeAddress(node);
1184 if (!nodeaddress)
1185 return -1;
1186
1187 // Strip the address of its port
1188 strcpy(strippednodeaddress, nodeaddress);
1189 port = strchr(strippednodeaddress, ':');
1190 if (port)
1191 *port = '\0';
1192
1193 // Check if any player matches the stripped address
1194 for (i = 0; i < MAXPLAYERS; i++)
1195 {
1196 if (playeringame[i] && playeraddress[i][0] && playernode[i] == UINT8_MAX
1197 && !strcmp(playeraddress[i], strippednodeaddress))
1198 return i;
1199 }
1200
1201 return -1;
1202 }
1203
SV_SendServerInfo(INT32 node,tic_t servertime)1204 static void SV_SendServerInfo(INT32 node, tic_t servertime)
1205 {
1206 UINT8 *p;
1207
1208 netbuffer->packettype = PT_SERVERINFO;
1209 netbuffer->u.serverinfo._255 = 255;
1210 netbuffer->u.serverinfo.packetversion = PACKETVERSION;
1211 netbuffer->u.serverinfo.version = VERSION;
1212 netbuffer->u.serverinfo.subversion = SUBVERSION;
1213 strncpy(netbuffer->u.serverinfo.application, SRB2APPLICATION,
1214 sizeof netbuffer->u.serverinfo.application);
1215 // return back the time value so client can compute their ping
1216 netbuffer->u.serverinfo.time = (tic_t)LONG(servertime);
1217 netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime);
1218
1219 netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumPlayers();
1220 netbuffer->u.serverinfo.maxplayer = (UINT8)cv_maxplayers.value;
1221
1222 if (!node || FindRejoinerNum(node) != -1)
1223 netbuffer->u.serverinfo.refusereason = 0;
1224 else if (!cv_allownewplayer.value)
1225 netbuffer->u.serverinfo.refusereason = 1;
1226 else if (D_NumPlayers() >= cv_maxplayers.value)
1227 netbuffer->u.serverinfo.refusereason = 2;
1228 else
1229 netbuffer->u.serverinfo.refusereason = 0;
1230
1231 strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype],
1232 sizeof netbuffer->u.serverinfo.gametypename);
1233 netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame;
1234 netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled();
1235 netbuffer->u.serverinfo.isdedicated = (UINT8)dedicated;
1236 strncpy(netbuffer->u.serverinfo.servername, cv_servername.string,
1237 MAXSERVERNAME);
1238 strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7);
1239
1240 M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16);
1241
1242 memset(netbuffer->u.serverinfo.maptitle, 0, sizeof netbuffer->u.serverinfo.maptitle);
1243
1244 if (mapheaderinfo[gamemap-1] && *mapheaderinfo[gamemap-1]->lvlttl)
1245 {
1246 char *read = mapheaderinfo[gamemap-1]->lvlttl, *writ = netbuffer->u.serverinfo.maptitle;
1247 while (writ < (netbuffer->u.serverinfo.maptitle+32) && *read != '\0')
1248 {
1249 if (!(*read & 0x80))
1250 {
1251 *writ = toupper(*read);
1252 writ++;
1253 }
1254 read++;
1255 }
1256 *writ = '\0';
1257 //strncpy(netbuffer->u.serverinfo.maptitle, (char *)mapheaderinfo[gamemap-1]->lvlttl, 33);
1258 }
1259 else
1260 strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 32);
1261
1262 if (mapheaderinfo[gamemap-1] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
1263 netbuffer->u.serverinfo.iszone = 1;
1264 else
1265 netbuffer->u.serverinfo.iszone = 0;
1266
1267 if (mapheaderinfo[gamemap-1])
1268 netbuffer->u.serverinfo.actnum = mapheaderinfo[gamemap-1]->actnum;
1269
1270 p = PutFileNeeded();
1271
1272 HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u));
1273 }
1274
SV_SendPlayerInfo(INT32 node)1275 static void SV_SendPlayerInfo(INT32 node)
1276 {
1277 UINT8 i;
1278 netbuffer->packettype = PT_PLAYERINFO;
1279
1280 for (i = 0; i < MAXPLAYERS; i++)
1281 {
1282 if (!playeringame[i])
1283 {
1284 netbuffer->u.playerinfo[i].num = 255; // This slot is empty.
1285 continue;
1286 }
1287
1288 netbuffer->u.playerinfo[i].num = i;
1289 strncpy(netbuffer->u.playerinfo[i].name, (const char *)&player_names[i], MAXPLAYERNAME+1);
1290 netbuffer->u.playerinfo[i].name[MAXPLAYERNAME] = '\0';
1291
1292 //fetch IP address
1293 //No, don't do that, you fuckface.
1294 memset(netbuffer->u.playerinfo[i].address, 0, 4);
1295
1296 if (G_GametypeHasTeams())
1297 {
1298 if (!players[i].ctfteam)
1299 netbuffer->u.playerinfo[i].team = 255;
1300 else
1301 netbuffer->u.playerinfo[i].team = (UINT8)players[i].ctfteam;
1302 }
1303 else
1304 {
1305 if (players[i].spectator)
1306 netbuffer->u.playerinfo[i].team = 255;
1307 else
1308 netbuffer->u.playerinfo[i].team = 0;
1309 }
1310
1311 netbuffer->u.playerinfo[i].score = LONG(players[i].score);
1312 netbuffer->u.playerinfo[i].timeinserver = SHORT((UINT16)(players[i].jointime / TICRATE));
1313 netbuffer->u.playerinfo[i].skin = (UINT8)(players[i].skin
1314 #ifdef DEVELOP // it's safe to do this only because PLAYERINFO isn't read by the game itself
1315 % 3
1316 #endif
1317 );
1318
1319 // Extra data
1320 netbuffer->u.playerinfo[i].data = 0; //players[i].skincolor;
1321
1322 if (players[i].pflags & PF_TAGIT)
1323 netbuffer->u.playerinfo[i].data |= 0x20;
1324
1325 if (players[i].gotflag)
1326 netbuffer->u.playerinfo[i].data |= 0x40;
1327
1328 if (players[i].powers[pw_super])
1329 netbuffer->u.playerinfo[i].data |= 0x80;
1330 }
1331
1332 HSendPacket(node, false, 0, sizeof(plrinfo) * MAXPLAYERS);
1333 }
1334
1335 /** Sends a PT_SERVERCFG packet
1336 *
1337 * \param node The destination
1338 * \return True if the packet was successfully sent
1339 *
1340 */
SV_SendServerConfig(INT32 node)1341 static boolean SV_SendServerConfig(INT32 node)
1342 {
1343 boolean waspacketsent;
1344
1345 netbuffer->packettype = PT_SERVERCFG;
1346
1347 netbuffer->u.servercfg.version = VERSION;
1348 netbuffer->u.servercfg.subversion = SUBVERSION;
1349
1350 netbuffer->u.servercfg.serverplayer = (UINT8)serverplayer;
1351 netbuffer->u.servercfg.totalslotnum = (UINT8)(doomcom->numslots);
1352 netbuffer->u.servercfg.gametic = (tic_t)LONG(gametic);
1353 netbuffer->u.servercfg.clientnode = (UINT8)node;
1354 netbuffer->u.servercfg.gamestate = (UINT8)gamestate;
1355 netbuffer->u.servercfg.gametype = (UINT8)gametype;
1356 netbuffer->u.servercfg.modifiedgame = (UINT8)modifiedgame;
1357
1358 memcpy(netbuffer->u.servercfg.server_context, server_context, 8);
1359
1360 {
1361 const size_t len = sizeof (serverconfig_pak);
1362
1363 #ifdef DEBUGFILE
1364 if (debugfile)
1365 {
1366 fprintf(debugfile, "ServerConfig Packet about to be sent, size of packet:%s to node:%d\n",
1367 sizeu1(len), node);
1368 }
1369 #endif
1370
1371 waspacketsent = HSendPacket(node, true, 0, len);
1372 }
1373
1374 #ifdef DEBUGFILE
1375 if (debugfile)
1376 {
1377 if (waspacketsent)
1378 {
1379 fprintf(debugfile, "ServerConfig Packet was sent\n");
1380 }
1381 else
1382 {
1383 fprintf(debugfile, "ServerConfig Packet could not be sent right now\n");
1384 }
1385 }
1386 #endif
1387
1388 return waspacketsent;
1389 }
1390
1391 #ifndef NONET
1392 #define SAVEGAMESIZE (768*1024)
1393
SV_ResendingSavegameToAnyone(void)1394 static boolean SV_ResendingSavegameToAnyone(void)
1395 {
1396 INT32 i;
1397
1398 for (i = 0; i < MAXNETNODES; i++)
1399 if (resendingsavegame[i])
1400 return true;
1401 return false;
1402 }
1403
SV_SendSaveGame(INT32 node,boolean resending)1404 static void SV_SendSaveGame(INT32 node, boolean resending)
1405 {
1406 size_t length, compressedlen;
1407 UINT8 *savebuffer;
1408 UINT8 *compressedsave;
1409 UINT8 *buffertosend;
1410
1411 // first save it in a malloced buffer
1412 savebuffer = (UINT8 *)malloc(SAVEGAMESIZE);
1413 if (!savebuffer)
1414 {
1415 CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
1416 return;
1417 }
1418
1419 // Leave room for the uncompressed length.
1420 save_p = savebuffer + sizeof(UINT32);
1421
1422 P_SaveNetGame(resending);
1423
1424 length = save_p - savebuffer;
1425 if (length > SAVEGAMESIZE)
1426 {
1427 free(savebuffer);
1428 save_p = NULL;
1429 I_Error("Savegame buffer overrun");
1430 }
1431
1432 // Allocate space for compressed save: one byte fewer than for the
1433 // uncompressed data to ensure that the compression is worthwhile.
1434 compressedsave = malloc(length - 1);
1435 if (!compressedsave)
1436 {
1437 CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
1438 return;
1439 }
1440
1441 // Attempt to compress it.
1442 if((compressedlen = lzf_compress(savebuffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1)))
1443 {
1444 // Compressing succeeded; send compressed data
1445
1446 free(savebuffer);
1447
1448 // State that we're compressed.
1449 buffertosend = compressedsave;
1450 WRITEUINT32(compressedsave, length - sizeof(UINT32));
1451 length = compressedlen + sizeof(UINT32);
1452 }
1453 else
1454 {
1455 // Compression failed to make it smaller; send original
1456
1457 free(compressedsave);
1458
1459 // State that we're not compressed
1460 buffertosend = savebuffer;
1461 WRITEUINT32(savebuffer, 0);
1462 }
1463
1464 AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0);
1465 save_p = NULL;
1466
1467 // Remember when we started sending the savegame so we can handle timeouts
1468 sendingsavegame[node] = true;
1469 freezetimeout[node] = I_GetTime() + jointimeout + length / 1024; // 1 extra tic for each kilobyte
1470 }
1471
1472 #ifdef DUMPCONSISTENCY
1473 #define TMPSAVENAME "badmath.sav"
1474 static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
1475
SV_SavedGame(void)1476 static void SV_SavedGame(void)
1477 {
1478 size_t length;
1479 UINT8 *savebuffer;
1480 char tmpsave[256];
1481
1482 if (!cv_dumpconsistency.value)
1483 return;
1484
1485 sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
1486
1487 // first save it in a malloced buffer
1488 save_p = savebuffer = (UINT8 *)malloc(SAVEGAMESIZE);
1489 if (!save_p)
1490 {
1491 CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
1492 return;
1493 }
1494
1495 P_SaveNetGame(false);
1496
1497 length = save_p - savebuffer;
1498 if (length > SAVEGAMESIZE)
1499 {
1500 free(savebuffer);
1501 save_p = NULL;
1502 I_Error("Savegame buffer overrun");
1503 }
1504
1505 // then save it!
1506 if (!FIL_WriteFile(tmpsave, savebuffer, length))
1507 CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave);
1508
1509 free(savebuffer);
1510 save_p = NULL;
1511 }
1512
1513 #undef TMPSAVENAME
1514 #endif
1515 #define TMPSAVENAME "$$$.sav"
1516
1517
CL_LoadReceivedSavegame(boolean reloading)1518 static void CL_LoadReceivedSavegame(boolean reloading)
1519 {
1520 UINT8 *savebuffer = NULL;
1521 size_t length, decompressedlen;
1522 char tmpsave[256];
1523
1524 sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
1525
1526 length = FIL_ReadFile(tmpsave, &savebuffer);
1527
1528 CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length));
1529 if (!length)
1530 {
1531 I_Error("Can't read savegame sent");
1532 return;
1533 }
1534
1535 save_p = savebuffer;
1536
1537 // Decompress saved game if necessary.
1538 decompressedlen = READUINT32(save_p);
1539 if(decompressedlen > 0)
1540 {
1541 UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL);
1542 lzf_decompress(save_p, length - sizeof(UINT32), decompressedbuffer, decompressedlen);
1543 Z_Free(savebuffer);
1544 save_p = savebuffer = decompressedbuffer;
1545 }
1546
1547 paused = false;
1548 demoplayback = false;
1549 titlemapinaction = TITLEMAP_OFF;
1550 titledemo = false;
1551 automapactive = false;
1552
1553 // load a base level
1554 if (P_LoadNetGame(reloading))
1555 {
1556 const UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
1557 CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap));
1558 if (strcmp(mapheaderinfo[gamemap-1]->lvlttl, ""))
1559 {
1560 CONS_Printf(": %s", mapheaderinfo[gamemap-1]->lvlttl);
1561 if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
1562 CONS_Printf(M_GetText(" Zone"));
1563 if (actnum > 0)
1564 CONS_Printf(" %2d", actnum);
1565 }
1566 CONS_Printf("\"\n");
1567 }
1568 else
1569 {
1570 CONS_Alert(CONS_ERROR, M_GetText("Can't load the level!\n"));
1571 }
1572
1573 // done
1574 Z_Free(savebuffer);
1575 save_p = NULL;
1576 if (unlink(tmpsave) == -1)
1577 CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave);
1578 consistancy[gametic%BACKUPTICS] = Consistancy();
1579 CON_ToggleOff();
1580
1581 // Tell the server we have received and reloaded the gamestate
1582 // so they know they can resume the game
1583 netbuffer->packettype = PT_RECEIVEDGAMESTATE;
1584 HSendPacket(servernode, true, 0, 0);
1585 }
1586
CL_ReloadReceivedSavegame(void)1587 static void CL_ReloadReceivedSavegame(void)
1588 {
1589 INT32 i;
1590
1591 for (i = 0; i < MAXPLAYERS; i++)
1592 {
1593 LUA_InvalidatePlayer(&players[i]);
1594 sprintf(player_names[i], "Player %d", i + 1);
1595 }
1596
1597 CL_LoadReceivedSavegame(true);
1598
1599 if (neededtic < gametic)
1600 neededtic = gametic;
1601 maketic = neededtic;
1602
1603 ticcmd_oldangleturn[0] = players[consoleplayer].oldrelangleturn;
1604 P_ForceLocalAngle(&players[consoleplayer], (angle_t)(players[consoleplayer].angleturn << 16));
1605 if (splitscreen)
1606 {
1607 ticcmd_oldangleturn[1] = players[secondarydisplayplayer].oldrelangleturn;
1608 P_ForceLocalAngle(&players[secondarydisplayplayer], (angle_t)(players[secondarydisplayplayer].angleturn << 16));
1609 }
1610
1611 camera.subsector = R_PointInSubsector(camera.x, camera.y);
1612 camera2.subsector = R_PointInSubsector(camera2.x, camera2.y);
1613
1614 cl_redownloadinggamestate = false;
1615
1616 CONS_Printf(M_GetText("Game state reloaded\n"));
1617 }
1618 #endif
1619
1620 #ifndef NONET
SendAskInfo(INT32 node)1621 static void SendAskInfo(INT32 node)
1622 {
1623 const tic_t asktime = I_GetTime();
1624 netbuffer->packettype = PT_ASKINFO;
1625 netbuffer->u.askinfo.version = VERSION;
1626 netbuffer->u.askinfo.time = (tic_t)LONG(asktime);
1627
1628 // Even if this never arrives due to the host being firewalled, we've
1629 // now allowed traffic from the host to us in, so once the MS relays
1630 // our address to the host, it'll be able to speak to us.
1631 HSendPacket(node, false, 0, sizeof (askinfo_pak));
1632 }
1633
1634 serverelem_t serverlist[MAXSERVERLIST];
1635 UINT32 serverlistcount = 0;
1636
1637 #define FORCECLOSE 0x8000
1638
SL_ClearServerList(INT32 connectedserver)1639 static void SL_ClearServerList(INT32 connectedserver)
1640 {
1641 UINT32 i;
1642
1643 for (i = 0; i < serverlistcount; i++)
1644 if (connectedserver != serverlist[i].node)
1645 {
1646 Net_CloseConnection(serverlist[i].node|FORCECLOSE);
1647 serverlist[i].node = 0;
1648 }
1649 serverlistcount = 0;
1650 }
1651
SL_SearchServer(INT32 node)1652 static UINT32 SL_SearchServer(INT32 node)
1653 {
1654 UINT32 i;
1655 for (i = 0; i < serverlistcount; i++)
1656 if (serverlist[i].node == node)
1657 return i;
1658
1659 return UINT32_MAX;
1660 }
1661
SL_InsertServer(serverinfo_pak * info,SINT8 node)1662 static void SL_InsertServer(serverinfo_pak* info, SINT8 node)
1663 {
1664 UINT32 i;
1665
1666 // search if not already on it
1667 i = SL_SearchServer(node);
1668 if (i == UINT32_MAX)
1669 {
1670 // not found add it
1671 if (serverlistcount >= MAXSERVERLIST)
1672 return; // list full
1673
1674 if (info->_255 != 255)
1675 return;/* old packet format */
1676
1677 if (info->packetversion != PACKETVERSION)
1678 return;/* old new packet format */
1679
1680 if (info->version != VERSION)
1681 return; // Not same version.
1682
1683 if (info->subversion != SUBVERSION)
1684 return; // Close, but no cigar.
1685
1686 if (strcmp(info->application, SRB2APPLICATION))
1687 return;/* that's a different mod */
1688
1689 i = serverlistcount++;
1690 }
1691
1692 serverlist[i].info = *info;
1693 serverlist[i].node = node;
1694
1695 // resort server list
1696 M_SortServerList();
1697 }
1698
1699 #if defined (MASTERSERVER) && defined (HAVE_THREADS)
1700 struct Fetch_servers_ctx
1701 {
1702 int room;
1703 int id;
1704 };
1705
1706 static void
Fetch_servers_thread(struct Fetch_servers_ctx * ctx)1707 Fetch_servers_thread (struct Fetch_servers_ctx *ctx)
1708 {
1709 msg_server_t *server_list;
1710
1711 server_list = GetShortServersList(ctx->room, ctx->id);
1712
1713 if (server_list)
1714 {
1715 I_lock_mutex(&ms_QueryId_mutex);
1716 {
1717 if (ctx->id != ms_QueryId)
1718 {
1719 free(server_list);
1720 server_list = NULL;
1721 }
1722 }
1723 I_unlock_mutex(ms_QueryId_mutex);
1724
1725 if (server_list)
1726 {
1727 I_lock_mutex(&m_menu_mutex);
1728 {
1729 if (m_waiting_mode == M_WAITING_SERVERS)
1730 m_waiting_mode = M_NOT_WAITING;
1731 }
1732 I_unlock_mutex(m_menu_mutex);
1733
1734 I_lock_mutex(&ms_ServerList_mutex);
1735 {
1736 ms_ServerList = server_list;
1737 }
1738 I_unlock_mutex(ms_ServerList_mutex);
1739 }
1740 }
1741
1742 free(ctx);
1743 }
1744 #endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/
1745
CL_QueryServerList(msg_server_t * server_list)1746 void CL_QueryServerList (msg_server_t *server_list)
1747 {
1748 INT32 i;
1749
1750 for (i = 0; server_list[i].header.buffer[0]; i++)
1751 {
1752 // Make sure MS version matches our own, to
1753 // thwart nefarious servers who lie to the MS.
1754
1755 /* lol bruh, that version COMES from the servers */
1756 //if (strcmp(version, server_list[i].version) == 0)
1757 {
1758 INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port);
1759 if (node == -1)
1760 break; // no more node free
1761 SendAskInfo(node);
1762 // Force close the connection so that servers can't eat
1763 // up nodes forever if we never get a reply back from them
1764 // (usually when they've not forwarded their ports).
1765 //
1766 // Don't worry, we'll get in contact with the working
1767 // servers again when they send SERVERINFO to us later!
1768 //
1769 // (Note: as a side effect this probably means every
1770 // server in the list will probably be using the same node (e.g. node 1),
1771 // not that it matters which nodes they use when
1772 // the connections are closed afterwards anyway)
1773 // -- Monster Iestyn 12/11/18
1774 Net_CloseConnection(node|FORCECLOSE);
1775 }
1776 }
1777 }
1778
CL_UpdateServerList(boolean internetsearch,INT32 room)1779 void CL_UpdateServerList(boolean internetsearch, INT32 room)
1780 {
1781 (void)internetsearch;
1782 (void)room;
1783
1784 SL_ClearServerList(0);
1785
1786 if (!netgame && I_NetOpenSocket)
1787 {
1788 if (I_NetOpenSocket())
1789 {
1790 netgame = true;
1791 multiplayer = true;
1792 }
1793 }
1794
1795 // search for local servers
1796 if (netgame)
1797 SendAskInfo(BROADCASTADDR);
1798
1799 #ifdef MASTERSERVER
1800 if (internetsearch)
1801 {
1802 #ifdef HAVE_THREADS
1803 struct Fetch_servers_ctx *ctx;
1804
1805 ctx = malloc(sizeof *ctx);
1806
1807 /* This called from M_Refresh so I don't use a mutex */
1808 m_waiting_mode = M_WAITING_SERVERS;
1809
1810 I_lock_mutex(&ms_QueryId_mutex);
1811 {
1812 ctx->id = ms_QueryId;
1813 }
1814 I_unlock_mutex(ms_QueryId_mutex);
1815
1816 ctx->room = room;
1817
1818 I_spawn_thread("fetch-servers", (I_thread_fn)Fetch_servers_thread, ctx);
1819 #else
1820 msg_server_t *server_list;
1821
1822 server_list = GetShortServersList(room, 0);
1823
1824 if (server_list)
1825 {
1826 CL_QueryServerList(server_list);
1827 free(server_list);
1828 }
1829 #endif
1830 }
1831 #endif/*MASTERSERVER*/
1832 }
1833
1834 #endif // ifndef NONET
1835
1836 /** Called by CL_ServerConnectionTicker
1837 *
1838 * \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit.
1839 * \return False if the connection was aborted
1840 * \sa CL_ServerConnectionTicker
1841 * \sa CL_ConnectToServer
1842 *
1843 */
CL_ServerConnectionSearchTicker(tic_t * asksent)1844 static boolean CL_ServerConnectionSearchTicker(tic_t *asksent)
1845 {
1846 #ifndef NONET
1847 INT32 i;
1848
1849 // serverlist is updated by GetPacket function
1850 if (serverlistcount > 0)
1851 {
1852 // this can be a responce to our broadcast request
1853 if (servernode == -1 || servernode >= MAXNETNODES)
1854 {
1855 i = 0;
1856 servernode = serverlist[i].node;
1857 CONS_Printf(M_GetText("Found, "));
1858 }
1859 else
1860 {
1861 i = SL_SearchServer(servernode);
1862 if (i < 0)
1863 return true;
1864 }
1865
1866 // Quit here rather than downloading files and being refused later.
1867 if (serverlist[i].info.refusereason)
1868 {
1869 D_QuitNetGame();
1870 CL_Reset();
1871 D_StartTitle();
1872 if (serverlist[i].info.refusereason == 1)
1873 M_StartMessage(M_GetText("The server is not accepting\njoins for the moment.\n\nPress ESC\n"), NULL, MM_NOTHING);
1874 else if (serverlist[i].info.refusereason == 2)
1875 M_StartMessage(va(M_GetText("Maximum players reached: %d\n\nPress ESC\n"), serverlist[i].info.maxplayer), NULL, MM_NOTHING);
1876 else
1877 M_StartMessage(M_GetText("You can't join.\nI don't know why,\nbut you can't join.\n\nPress ESC\n"), NULL, MM_NOTHING);
1878 return false;
1879 }
1880
1881 if (client)
1882 {
1883 D_ParseFileneeded(serverlist[i].info.fileneedednum,
1884 serverlist[i].info.fileneeded);
1885 CONS_Printf(M_GetText("Checking files...\n"));
1886 i = CL_CheckFiles();
1887 if (i == 3) // too many files
1888 {
1889 D_QuitNetGame();
1890 CL_Reset();
1891 D_StartTitle();
1892 M_StartMessage(M_GetText(
1893 "You have too many WAD files loaded\n"
1894 "to add ones the server is using.\n"
1895 "Please restart SRB2 before connecting.\n\n"
1896 "Press ESC\n"
1897 ), NULL, MM_NOTHING);
1898 return false;
1899 }
1900 else if (i == 2) // cannot join for some reason
1901 {
1902 D_QuitNetGame();
1903 CL_Reset();
1904 D_StartTitle();
1905 M_StartMessage(M_GetText(
1906 "You have the wrong addons loaded.\n\n"
1907 "To play on this server, restart\n"
1908 "the game and don't load any addons.\n"
1909 "SRB2 will automatically add\n"
1910 "everything you need when you join.\n\n"
1911 "Press ESC\n"
1912 ), NULL, MM_NOTHING);
1913 return false;
1914 }
1915 else if (i == 1)
1916 cl_mode = CL_ASKJOIN;
1917 else
1918 {
1919 // must download something
1920 // can we, though?
1921 if (!CL_CheckDownloadable()) // nope!
1922 {
1923 D_QuitNetGame();
1924 CL_Reset();
1925 D_StartTitle();
1926 M_StartMessage(M_GetText(
1927 "You cannot connect to this server\n"
1928 "because you cannot download the files\n"
1929 "that you are missing from the server.\n\n"
1930 "See the console or log file for\n"
1931 "more details.\n\n"
1932 "Press ESC\n"
1933 ), NULL, MM_NOTHING);
1934 return false;
1935 }
1936 // no problem if can't send packet, we will retry later
1937 if (CL_SendFileRequest())
1938 {
1939 cl_mode = CL_DOWNLOADFILES;
1940 #ifndef NONET
1941 Snake_Initialise();
1942 #endif
1943 }
1944 }
1945 }
1946 else
1947 cl_mode = CL_ASKJOIN; // files need not be checked for the server.
1948
1949 return true;
1950 }
1951
1952 // Ask the info to the server (askinfo packet)
1953 if (*asksent + NEWTICRATE < I_GetTime())
1954 {
1955 SendAskInfo(servernode);
1956 *asksent = I_GetTime();
1957 }
1958 #else
1959 (void)asksent;
1960 // No netgames, so we skip this state.
1961 cl_mode = CL_ASKJOIN;
1962 #endif // ifndef NONET/else
1963
1964 return true;
1965 }
1966
1967 /** Called by CL_ConnectToServer
1968 *
1969 * \param tmpsave The name of the gamestate file???
1970 * \param oldtic Used for knowing when to poll events and redraw
1971 * \param asksent ???
1972 * \return False if the connection was aborted
1973 * \sa CL_ServerConnectionSearchTicker
1974 * \sa CL_ConnectToServer
1975 *
1976 */
CL_ServerConnectionTicker(const char * tmpsave,tic_t * oldtic,tic_t * asksent)1977 static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic_t *asksent)
1978 {
1979 boolean waitmore;
1980 INT32 i;
1981
1982 #ifdef NONET
1983 (void)tmpsave;
1984 #endif
1985
1986 switch (cl_mode)
1987 {
1988 case CL_SEARCHING:
1989 if (!CL_ServerConnectionSearchTicker(asksent))
1990 return false;
1991 break;
1992
1993 case CL_DOWNLOADFILES:
1994 waitmore = false;
1995 for (i = 0; i < fileneedednum; i++)
1996 if (fileneeded[i].status == FS_DOWNLOADING
1997 || fileneeded[i].status == FS_REQUESTED)
1998 {
1999 waitmore = true;
2000 break;
2001 }
2002 if (waitmore)
2003 break; // exit the case
2004
2005 #ifndef NONET
2006 if (snake)
2007 {
2008 free(snake);
2009 snake = NULL;
2010 }
2011 #endif
2012
2013 cl_mode = CL_ASKJOIN; // don't break case continue to cljoin request now
2014 /* FALLTHRU */
2015
2016 case CL_ASKJOIN:
2017 CL_LoadServerFiles();
2018 #ifndef NONET
2019 // prepare structures to save the file
2020 // WARNING: this can be useless in case of server not in GS_LEVEL
2021 // but since the network layer doesn't provide ordered packets...
2022 CL_PrepareDownloadSaveGame(tmpsave);
2023 #endif
2024 if (CL_SendJoin())
2025 cl_mode = CL_WAITJOINRESPONSE;
2026 break;
2027
2028 #ifndef NONET
2029 case CL_DOWNLOADSAVEGAME:
2030 // At this state, the first (and only) needed file is the gamestate
2031 if (fileneeded[0].status == FS_FOUND)
2032 {
2033 // Gamestate is now handled within CL_LoadReceivedSavegame()
2034 CL_LoadReceivedSavegame(false);
2035 cl_mode = CL_CONNECTED;
2036 } // don't break case continue to CL_CONNECTED
2037 else
2038 break;
2039 #endif
2040
2041 case CL_WAITJOINRESPONSE:
2042 case CL_CONNECTED:
2043 default:
2044 break;
2045
2046 // Connection closed by cancel, timeout or refusal.
2047 case CL_ABORTED:
2048 cl_mode = CL_SEARCHING;
2049 return false;
2050
2051 }
2052
2053 GetPackets();
2054 Net_AckTicker();
2055
2056 // Call it only once by tic
2057 if (*oldtic != I_GetTime())
2058 {
2059 I_OsPolling();
2060 for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1))
2061 G_MapEventsToControls(&events[eventtail]);
2062
2063 if (gamekeydown[KEY_ESCAPE] || gamekeydown[KEY_JOY1+1])
2064 {
2065 CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
2066 // M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
2067
2068 #ifndef NONET
2069 if (snake)
2070 {
2071 free(snake);
2072 snake = NULL;
2073 }
2074 #endif
2075
2076 D_QuitNetGame();
2077 CL_Reset();
2078 D_StartTitle();
2079 memset(gamekeydown, 0, NUMKEYS);
2080 return false;
2081 }
2082 #ifndef NONET
2083 else if (cl_mode == CL_DOWNLOADFILES && snake)
2084 Snake_Handle();
2085 #endif
2086
2087 if (client && (cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADSAVEGAME))
2088 FileReceiveTicker();
2089
2090 // why are these here? this is for servers, we're a client
2091 //if (key == 's' && server)
2092 // doomcom->numnodes = (INT16)pnumnodes;
2093 //FileSendTicker();
2094 *oldtic = I_GetTime();
2095
2096 #ifndef NONET
2097 if (client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED)
2098 {
2099 if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_DOWNLOADSAVEGAME)
2100 {
2101 F_MenuPresTicker(true); // title sky
2102 F_TitleScreenTicker(true);
2103 F_TitleScreenDrawer();
2104 }
2105 CL_DrawConnectionStatus();
2106 I_UpdateNoVsync(); // page flip or blit buffer
2107 if (moviemode)
2108 M_SaveFrame();
2109 S_UpdateSounds();
2110 S_UpdateClosedCaptions();
2111 }
2112 #else
2113 CON_Drawer();
2114 I_UpdateNoVsync();
2115 #endif
2116 }
2117 else
2118 I_Sleep();
2119
2120 return true;
2121 }
2122
2123 /** Use adaptive send using net_bandwidth and stat.sendbytes
2124 *
2125 * \todo Better description...
2126 *
2127 */
CL_ConnectToServer(void)2128 static void CL_ConnectToServer(void)
2129 {
2130 INT32 pnumnodes, nodewaited = doomcom->numnodes, i;
2131 tic_t oldtic;
2132 #ifndef NONET
2133 tic_t asksent;
2134 char tmpsave[256];
2135
2136 sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
2137
2138 lastfilenum = -1;
2139 #endif
2140
2141 cl_mode = CL_SEARCHING;
2142
2143 #ifndef NONET
2144 // Don't get a corrupt savegame error because tmpsave already exists
2145 if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1)
2146 I_Error("Can't delete %s\n", tmpsave);
2147 #endif
2148
2149 if (netgame)
2150 {
2151 if (servernode < 0 || servernode >= MAXNETNODES)
2152 CONS_Printf(M_GetText("Searching for a server...\n"));
2153 else
2154 CONS_Printf(M_GetText("Contacting the server...\n"));
2155 }
2156
2157 if (gamestate == GS_INTERMISSION)
2158 Y_EndIntermission(); // clean up intermission graphics etc
2159
2160 DEBFILE(va("waiting %d nodes\n", doomcom->numnodes));
2161 G_SetGamestate(GS_WAITINGPLAYERS);
2162 wipegamestate = GS_WAITINGPLAYERS;
2163
2164 ClearAdminPlayers();
2165 pnumnodes = 1;
2166 oldtic = I_GetTime() - 1;
2167 #ifndef NONET
2168 asksent = (tic_t) - TICRATE;
2169
2170 i = SL_SearchServer(servernode);
2171
2172 if (i != -1)
2173 {
2174 char *gametypestr = serverlist[i].info.gametypename;
2175 CONS_Printf(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername);
2176 gametypestr[sizeof serverlist[i].info.gametypename - 1] = '\0';
2177 CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr);
2178 CONS_Printf(M_GetText("Version: %d.%d.%u\n"), serverlist[i].info.version/100,
2179 serverlist[i].info.version%100, serverlist[i].info.subversion);
2180 }
2181 SL_ClearServerList(servernode);
2182 #endif
2183
2184 do
2185 {
2186 // If the connection was aborted for some reason, leave
2187 #ifndef NONET
2188 if (!CL_ServerConnectionTicker(tmpsave, &oldtic, &asksent))
2189 #else
2190 if (!CL_ServerConnectionTicker((char*)NULL, &oldtic, (tic_t *)NULL))
2191 #endif
2192 return;
2193
2194 if (server)
2195 {
2196 pnumnodes = 0;
2197 for (i = 0; i < MAXNETNODES; i++)
2198 if (nodeingame[i])
2199 pnumnodes++;
2200 }
2201 }
2202 while (!(cl_mode == CL_CONNECTED && (client || (server && nodewaited <= pnumnodes))));
2203
2204 DEBFILE(va("Synchronisation Finished\n"));
2205
2206 displayplayer = consoleplayer;
2207 }
2208
2209 #ifndef NONET
2210 typedef struct banreason_s
2211 {
2212 char *reason;
2213 struct banreason_s *prev; //-1
2214 struct banreason_s *next; //+1
2215 } banreason_t;
2216
2217 static banreason_t *reasontail = NULL; //last entry, use prev
2218 static banreason_t *reasonhead = NULL; //1st entry, use next
2219
Command_ShowBan(void)2220 static void Command_ShowBan(void) //Print out ban list
2221 {
2222 size_t i;
2223 const char *address, *mask;
2224 banreason_t *reasonlist = reasonhead;
2225
2226 if (I_GetBanAddress)
2227 CONS_Printf(M_GetText("Ban List:\n"));
2228 else
2229 return;
2230
2231 for (i = 0;(address = I_GetBanAddress(i)) != NULL;i++)
2232 {
2233 if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL)
2234 CONS_Printf("%s: %s ", sizeu1(i+1), address);
2235 else
2236 CONS_Printf("%s: %s/%s ", sizeu1(i+1), address, mask);
2237
2238 if (reasonlist && reasonlist->reason)
2239 CONS_Printf("(%s)\n", reasonlist->reason);
2240 else
2241 CONS_Printf("\n");
2242
2243 if (reasonlist) reasonlist = reasonlist->next;
2244 }
2245
2246 if (i == 0 && !address)
2247 CONS_Printf(M_GetText("(empty)\n"));
2248 }
2249
D_SaveBan(void)2250 void D_SaveBan(void)
2251 {
2252 FILE *f;
2253 size_t i;
2254 banreason_t *reasonlist = reasonhead;
2255 const char *address, *mask;
2256 const char *path = va("%s"PATHSEP"%s", srb2home, "ban.txt");
2257
2258 if (!reasonhead)
2259 {
2260 remove(path);
2261 return;
2262 }
2263
2264 f = fopen(path, "w");
2265
2266 if (!f)
2267 {
2268 CONS_Alert(CONS_WARNING, M_GetText("Could not save ban list into ban.txt\n"));
2269 return;
2270 }
2271
2272 for (i = 0;(address = I_GetBanAddress(i)) != NULL;i++)
2273 {
2274 if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL)
2275 fprintf(f, "%s 0", address);
2276 else
2277 fprintf(f, "%s %s", address, mask);
2278
2279 if (reasonlist && reasonlist->reason)
2280 fprintf(f, " %s\n", reasonlist->reason);
2281 else
2282 fprintf(f, " %s\n", "NA");
2283
2284 if (reasonlist) reasonlist = reasonlist->next;
2285 }
2286
2287 fclose(f);
2288 }
2289
Ban_Add(const char * reason)2290 static void Ban_Add(const char *reason)
2291 {
2292 banreason_t *reasonlist = malloc(sizeof(*reasonlist));
2293
2294 if (!reasonlist)
2295 return;
2296 if (!reason)
2297 reason = "NA";
2298
2299 reasonlist->next = NULL;
2300 reasonlist->reason = Z_StrDup(reason);
2301 if ((reasonlist->prev = reasontail) == NULL)
2302 reasonhead = reasonlist;
2303 else
2304 reasontail->next = reasonlist;
2305 reasontail = reasonlist;
2306 }
2307
Ban_Clear(void)2308 static void Ban_Clear(void)
2309 {
2310 banreason_t *temp;
2311
2312 I_ClearBans();
2313
2314 reasontail = NULL;
2315
2316 while (reasonhead)
2317 {
2318 temp = reasonhead->next;
2319 Z_Free(reasonhead->reason);
2320 free(reasonhead);
2321 reasonhead = temp;
2322 }
2323 }
2324
Command_ClearBans(void)2325 static void Command_ClearBans(void)
2326 {
2327 if (!I_ClearBans)
2328 return;
2329
2330 Ban_Clear();
2331 D_SaveBan();
2332 }
2333
Ban_Load_File(boolean warning)2334 static void Ban_Load_File(boolean warning)
2335 {
2336 FILE *f;
2337 size_t i;
2338 const char *address, *mask;
2339 char buffer[MAX_WADPATH];
2340
2341 if (!I_ClearBans)
2342 return;
2343
2344 f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "r");
2345
2346 if (!f)
2347 {
2348 if (warning)
2349 CONS_Alert(CONS_WARNING, M_GetText("Could not open ban.txt for ban list\n"));
2350 return;
2351 }
2352
2353 Ban_Clear();
2354
2355 for (i=0; fgets(buffer, (int)sizeof(buffer), f); i++)
2356 {
2357 address = strtok(buffer, " \t\r\n");
2358 mask = strtok(NULL, " \t\r\n");
2359
2360 I_SetBanAddress(address, mask);
2361
2362 Ban_Add(strtok(NULL, "\r\n"));
2363 }
2364
2365 fclose(f);
2366 }
2367
Command_ReloadBan(void)2368 static void Command_ReloadBan(void) //recheck ban.txt
2369 {
2370 Ban_Load_File(true);
2371 }
2372
Command_connect(void)2373 static void Command_connect(void)
2374 {
2375 if (COM_Argc() < 2 || *COM_Argv(1) == 0)
2376 {
2377 CONS_Printf(M_GetText(
2378 "Connect <serveraddress> (port): connect to a server\n"
2379 "Connect ANY: connect to the first lan server found\n"
2380 //"Connect SELF: connect to your own server.\n"
2381 ));
2382 return;
2383 }
2384
2385 if (Playing() || titledemo)
2386 {
2387 CONS_Printf(M_GetText("You cannot connect while in a game. End this game first.\n"));
2388 return;
2389 }
2390
2391 // modified game check: no longer handled
2392 // we don't request a restart unless the filelist differs
2393
2394 server = false;
2395 /*
2396 if (!stricmp(COM_Argv(1), "self"))
2397 {
2398 servernode = 0;
2399 server = true;
2400 /// \bug should be but...
2401 //SV_SpawnServer();
2402 }
2403 else
2404 */
2405 {
2406 // used in menu to connect to a server in the list
2407 if (netgame && !stricmp(COM_Argv(1), "node"))
2408 {
2409 servernode = (SINT8)atoi(COM_Argv(2));
2410 }
2411 else if (netgame)
2412 {
2413 CONS_Printf(M_GetText("You cannot connect while in a game. End this game first.\n"));
2414 return;
2415 }
2416 else if (I_NetOpenSocket)
2417 {
2418 I_NetOpenSocket();
2419 netgame = true;
2420 multiplayer = true;
2421
2422 if (!stricmp(COM_Argv(1), "any"))
2423 servernode = BROADCASTADDR;
2424 else if (I_NetMakeNodewPort)
2425 {
2426 if (COM_Argc() >= 3) // address AND port
2427 servernode = I_NetMakeNodewPort(COM_Argv(1), COM_Argv(2));
2428 else // address only, or address:port
2429 servernode = I_NetMakeNode(COM_Argv(1));
2430 }
2431 else
2432 {
2433 CONS_Alert(CONS_ERROR, M_GetText("There is no server identification with this network driver\n"));
2434 D_CloseConnection();
2435 return;
2436 }
2437 }
2438 else
2439 CONS_Alert(CONS_ERROR, M_GetText("There is no network driver\n"));
2440 }
2441
2442 splitscreen = false;
2443 SplitScreen_OnChange();
2444 botingame = false;
2445 botskin = 0;
2446 CL_ConnectToServer();
2447 }
2448 #endif
2449
2450 static void ResetNode(INT32 node);
2451
2452 //
2453 // CL_ClearPlayer
2454 //
2455 // Clears the player data so that a future client can use this slot
2456 //
CL_ClearPlayer(INT32 playernum)2457 void CL_ClearPlayer(INT32 playernum)
2458 {
2459 if (players[playernum].mo)
2460 P_RemoveMobj(players[playernum].mo);
2461 memset(&players[playernum], 0, sizeof (player_t));
2462 memset(playeraddress[playernum], 0, sizeof(*playeraddress));
2463 }
2464
2465 //
2466 // CL_RemovePlayer
2467 //
2468 // Removes a player from the current game
2469 //
CL_RemovePlayer(INT32 playernum,kickreason_t reason)2470 static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
2471 {
2472 // Sanity check: exceptional cases (i.e. c-fails) can cause multiple
2473 // kick commands to be issued for the same player.
2474 if (!playeringame[playernum])
2475 return;
2476
2477 if (server && !demoplayback && playernode[playernum] != UINT8_MAX)
2478 {
2479 INT32 node = playernode[playernum];
2480 playerpernode[node]--;
2481 if (playerpernode[node] <= 0)
2482 {
2483 nodeingame[node] = false;
2484 Net_CloseConnection(node);
2485 ResetNode(node);
2486 }
2487 }
2488
2489 if (gametyperules & GTR_TEAMFLAGS)
2490 P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you!
2491
2492 // If in a special stage, redistribute the player's spheres across
2493 // the remaining players.
2494 if (G_IsSpecialStage(gamemap))
2495 {
2496 INT32 i, count, sincrement, spheres, rincrement, rings;
2497
2498 for (i = 0, count = 0; i < MAXPLAYERS; i++)
2499 {
2500 if (playeringame[i])
2501 count++;
2502 }
2503
2504 count--;
2505 sincrement = spheres = players[playernum].spheres;
2506 rincrement = rings = players[playernum].rings;
2507
2508 if (count)
2509 {
2510 sincrement /= count;
2511 rincrement /= count;
2512 }
2513
2514 for (i = 0; i < MAXPLAYERS; i++)
2515 {
2516 if (playeringame[i] && i != playernum)
2517 {
2518 if (spheres < 2*sincrement)
2519 {
2520 P_GivePlayerSpheres(&players[i], spheres);
2521 spheres = 0;
2522 }
2523 else
2524 {
2525 P_GivePlayerSpheres(&players[i], sincrement);
2526 spheres -= sincrement;
2527 }
2528
2529 if (rings < 2*rincrement)
2530 {
2531 P_GivePlayerRings(&players[i], rings);
2532 rings = 0;
2533 }
2534 else
2535 {
2536 P_GivePlayerRings(&players[i], rincrement);
2537 rings -= rincrement;
2538 }
2539 }
2540 }
2541 }
2542
2543 LUAh_PlayerQuit(&players[playernum], reason); // Lua hook for player quitting
2544
2545 // don't look through someone's view who isn't there
2546 if (playernum == displayplayer)
2547 {
2548 // Call ViewpointSwitch hooks here.
2549 // The viewpoint was forcibly changed.
2550 LUAh_ViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true);
2551 displayplayer = consoleplayer;
2552 }
2553
2554 // Reset player data
2555 CL_ClearPlayer(playernum);
2556
2557 // remove avatar of player
2558 playeringame[playernum] = false;
2559 playernode[playernum] = UINT8_MAX;
2560 while (!playeringame[doomcom->numslots-1] && doomcom->numslots > 1)
2561 doomcom->numslots--;
2562
2563 // Reset the name
2564 sprintf(player_names[playernum], "Player %d", playernum+1);
2565
2566 player_name_changes[playernum] = 0;
2567
2568 if (IsPlayerAdmin(playernum))
2569 {
2570 RemoveAdminPlayer(playernum); // don't stay admin after you're gone
2571 }
2572
2573 LUA_InvalidatePlayer(&players[playernum]);
2574
2575 if (G_TagGametype()) //Check if you still have a game. Location flexible. =P
2576 P_CheckSurvivors();
2577 else if (gametyperules & GTR_RACE)
2578 P_CheckRacers();
2579 }
2580
CL_Reset(void)2581 void CL_Reset(void)
2582 {
2583 if (metalrecording)
2584 G_StopMetalRecording(false);
2585 if (metalplayback)
2586 G_StopMetalDemo();
2587 if (demorecording)
2588 G_CheckDemoStatus();
2589
2590 // reset client/server code
2591 DEBFILE(va("\n-=-=-=-=-=-=-= Client reset =-=-=-=-=-=-=-\n\n"));
2592
2593 if (servernode > 0 && servernode < MAXNETNODES)
2594 {
2595 nodeingame[(UINT8)servernode] = false;
2596 Net_CloseConnection(servernode);
2597 }
2598 D_CloseConnection(); // netgame = false
2599 multiplayer = false;
2600 servernode = 0;
2601 server = true;
2602 doomcom->numnodes = 1;
2603 doomcom->numslots = 1;
2604 SV_StopServer();
2605 SV_ResetServer();
2606 CV_RevertNetVars();
2607
2608 // make sure we don't leave any fileneeded gunk over from a failed join
2609 fileneedednum = 0;
2610 memset(fileneeded, 0, sizeof(fileneeded));
2611
2612 // D_StartTitle should get done now, but the calling function will handle it
2613 }
2614
2615 #ifndef NONET
Command_GetPlayerNum(void)2616 static void Command_GetPlayerNum(void)
2617 {
2618 INT32 i;
2619
2620 for (i = 0; i < MAXPLAYERS; i++)
2621 if (playeringame[i])
2622 {
2623 if (serverplayer == i)
2624 CONS_Printf(M_GetText("num:%2d node:%2d %s\n"), i, playernode[i], player_names[i]);
2625 else
2626 CONS_Printf(M_GetText("\x82num:%2d node:%2d %s\n"), i, playernode[i], player_names[i]);
2627 }
2628 }
2629
nametonum(const char * name)2630 SINT8 nametonum(const char *name)
2631 {
2632 INT32 playernum, i;
2633
2634 if (!strcmp(name, "0"))
2635 return 0;
2636
2637 playernum = (SINT8)atoi(name);
2638
2639 if (playernum < 0 || playernum >= MAXPLAYERS)
2640 return -1;
2641
2642 if (playernum)
2643 {
2644 if (playeringame[playernum])
2645 return (SINT8)playernum;
2646 else
2647 return -1;
2648 }
2649
2650 for (i = 0; i < MAXPLAYERS; i++)
2651 if (playeringame[i] && !stricmp(player_names[i], name))
2652 return (SINT8)i;
2653
2654 CONS_Printf(M_GetText("There is no player named \"%s\"\n"), name);
2655
2656 return -1;
2657 }
2658
2659 /** Lists all players and their player numbers.
2660 *
2661 * \sa Command_GetPlayerNum
2662 */
Command_Nodes(void)2663 static void Command_Nodes(void)
2664 {
2665 INT32 i;
2666 size_t maxlen = 0;
2667 const char *address;
2668
2669 for (i = 0; i < MAXPLAYERS; i++)
2670 {
2671 const size_t plen = strlen(player_names[i]);
2672 if (playeringame[i] && plen > maxlen)
2673 maxlen = plen;
2674 }
2675
2676 for (i = 0; i < MAXPLAYERS; i++)
2677 {
2678 if (playeringame[i])
2679 {
2680 CONS_Printf("%.2u: %*s", i, (int)maxlen, player_names[i]);
2681
2682 if (playernode[i] != UINT8_MAX)
2683 {
2684 CONS_Printf(" - node %.2d", playernode[i]);
2685 if (I_GetNodeAddress && (address = I_GetNodeAddress(playernode[i])) != NULL)
2686 CONS_Printf(" - %s", address);
2687 }
2688
2689 if (IsPlayerAdmin(i))
2690 CONS_Printf(M_GetText(" (verified admin)"));
2691
2692 if (players[i].spectator)
2693 CONS_Printf(M_GetText(" (spectator)"));
2694
2695 CONS_Printf("\n");
2696 }
2697 }
2698 }
2699
Command_Ban(void)2700 static void Command_Ban(void)
2701 {
2702 if (COM_Argc() < 2)
2703 {
2704 CONS_Printf(M_GetText("Ban <playername/playernum> <reason>: ban and kick a player\n"));
2705 return;
2706 }
2707
2708 if (!netgame) // Don't kick Tails in splitscreen!
2709 {
2710 CONS_Printf(M_GetText("This only works in a netgame.\n"));
2711 return;
2712 }
2713
2714 if (server || IsPlayerAdmin(consoleplayer))
2715 {
2716 UINT8 buf[3 + MAX_REASONLENGTH];
2717 UINT8 *p = buf;
2718 const SINT8 pn = nametonum(COM_Argv(1));
2719 const INT32 node = playernode[(INT32)pn];
2720
2721 if (pn == -1 || pn == 0)
2722 return;
2723
2724 WRITEUINT8(p, pn);
2725
2726 if (server && I_Ban && !I_Ban(node)) // only the server is allowed to do this right now
2727 {
2728 CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n"));
2729 WRITEUINT8(p, KICK_MSG_GO_AWAY);
2730 SendNetXCmd(XD_KICK, &buf, 2);
2731 }
2732 else
2733 {
2734 if (server) // only the server is allowed to do this right now
2735 {
2736 Ban_Add(COM_Argv(2));
2737 D_SaveBan(); // save the ban list
2738 }
2739
2740 if (COM_Argc() == 2)
2741 {
2742 WRITEUINT8(p, KICK_MSG_BANNED);
2743 SendNetXCmd(XD_KICK, &buf, 2);
2744 }
2745 else
2746 {
2747 size_t i, j = COM_Argc();
2748 char message[MAX_REASONLENGTH];
2749
2750 //Steal from the motd code so you don't have to put the reason in quotes.
2751 strlcpy(message, COM_Argv(2), sizeof message);
2752 for (i = 3; i < j; i++)
2753 {
2754 strlcat(message, " ", sizeof message);
2755 strlcat(message, COM_Argv(i), sizeof message);
2756 }
2757
2758 WRITEUINT8(p, KICK_MSG_CUSTOM_BAN);
2759 WRITESTRINGN(p, message, MAX_REASONLENGTH);
2760 SendNetXCmd(XD_KICK, &buf, p - buf);
2761 }
2762 }
2763 }
2764 else
2765 CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
2766
2767 }
2768
Command_BanIP(void)2769 static void Command_BanIP(void)
2770 {
2771 if (COM_Argc() < 2)
2772 {
2773 CONS_Printf(M_GetText("banip <ip> <reason>: ban an ip address\n"));
2774 return;
2775 }
2776
2777 if (server) // Only the server can use this, otherwise does nothing.
2778 {
2779 const char *address = (COM_Argv(1));
2780 const char *reason;
2781
2782 if (COM_Argc() == 2)
2783 reason = NULL;
2784 else
2785 reason = COM_Argv(2);
2786
2787
2788 if (I_SetBanAddress && I_SetBanAddress(address, NULL))
2789 {
2790 if (reason)
2791 CONS_Printf("Banned IP address %s for: %s\n", address, reason);
2792 else
2793 CONS_Printf("Banned IP address %s\n", address);
2794
2795 Ban_Add(reason);
2796 D_SaveBan();
2797 }
2798 else
2799 {
2800 return;
2801 }
2802 }
2803 }
2804
Command_Kick(void)2805 static void Command_Kick(void)
2806 {
2807 if (COM_Argc() < 2)
2808 {
2809 CONS_Printf(M_GetText("kick <playername/playernum> <reason>: kick a player\n"));
2810 return;
2811 }
2812
2813 if (!netgame) // Don't kick Tails in splitscreen!
2814 {
2815 CONS_Printf(M_GetText("This only works in a netgame.\n"));
2816 return;
2817 }
2818
2819 if (server || IsPlayerAdmin(consoleplayer))
2820 {
2821 UINT8 buf[3 + MAX_REASONLENGTH];
2822 UINT8 *p = buf;
2823 const SINT8 pn = nametonum(COM_Argv(1));
2824
2825 if (pn == -1 || pn == 0)
2826 return;
2827
2828 // Special case if we are trying to kick a player who is downloading the game state:
2829 // trigger a timeout instead of kicking them, because a kick would only
2830 // take effect after they have finished downloading
2831 if (server && playernode[pn] != UINT8_MAX && sendingsavegame[playernode[pn]])
2832 {
2833 Net_ConnectionTimeout(playernode[pn]);
2834 return;
2835 }
2836
2837 WRITESINT8(p, pn);
2838
2839 if (COM_Argc() == 2)
2840 {
2841 WRITEUINT8(p, KICK_MSG_GO_AWAY);
2842 SendNetXCmd(XD_KICK, &buf, 2);
2843 }
2844 else
2845 {
2846 size_t i, j = COM_Argc();
2847 char message[MAX_REASONLENGTH];
2848
2849 //Steal from the motd code so you don't have to put the reason in quotes.
2850 strlcpy(message, COM_Argv(2), sizeof message);
2851 for (i = 3; i < j; i++)
2852 {
2853 strlcat(message, " ", sizeof message);
2854 strlcat(message, COM_Argv(i), sizeof message);
2855 }
2856
2857 WRITEUINT8(p, KICK_MSG_CUSTOM_KICK);
2858 WRITESTRINGN(p, message, MAX_REASONLENGTH);
2859 SendNetXCmd(XD_KICK, &buf, p - buf);
2860 }
2861 }
2862 else
2863 CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
2864 }
2865 #endif
2866
Got_KickCmd(UINT8 ** p,INT32 playernum)2867 static void Got_KickCmd(UINT8 **p, INT32 playernum)
2868 {
2869 INT32 pnum, msg;
2870 char buf[3 + MAX_REASONLENGTH];
2871 char *reason = buf;
2872 kickreason_t kickreason = KR_KICK;
2873 boolean keepbody;
2874
2875 pnum = READUINT8(*p);
2876 msg = READUINT8(*p);
2877 keepbody = (msg & KICK_MSG_KEEP_BODY) != 0;
2878 msg &= ~KICK_MSG_KEEP_BODY;
2879
2880 if (pnum == serverplayer && IsPlayerAdmin(playernum))
2881 {
2882 CONS_Printf(M_GetText("Server is being shut down remotely. Goodbye!\n"));
2883
2884 if (server)
2885 COM_BufAddText("quit\n");
2886
2887 return;
2888 }
2889
2890 // Is playernum authorized to make this kick?
2891 if (playernum != serverplayer && !IsPlayerAdmin(playernum)
2892 && !(playernode[playernum] != UINT8_MAX && playerpernode[playernode[playernum]] == 2
2893 && nodetoplayer2[playernode[playernum]] == pnum))
2894 {
2895 // We received a kick command from someone who isn't the
2896 // server or admin, and who isn't in splitscreen removing
2897 // player 2. Thus, it must be someone with a modified
2898 // binary, trying to kick someone but without having
2899 // authorization.
2900
2901 // We deal with this by changing the kick reason to
2902 // "consistency failure" and kicking the offending user
2903 // instead.
2904
2905 // Note: Splitscreen in netgames is broken because of
2906 // this. Only the server has any idea of which players
2907 // are using splitscreen on the same computer, so
2908 // clients cannot always determine if a kick is
2909 // legitimate.
2910
2911 CONS_Alert(CONS_WARNING, M_GetText("Illegal kick command received from %s for player %d\n"), player_names[playernum], pnum);
2912
2913 // In debug, print a longer message with more details.
2914 // TODO Callum: Should we translate this?
2915 /*
2916 CONS_Debug(DBG_NETPLAY,
2917 "So, you must be asking, why is this an illegal kick?\n"
2918 "Well, let's take a look at the facts, shall we?\n"
2919 "\n"
2920 "playernum (this is the guy who did it), he's %d.\n"
2921 "pnum (the guy he's trying to kick) is %d.\n"
2922 "playernum's node is %d.\n"
2923 "That node has %d players.\n"
2924 "Player 2 on that node is %d.\n"
2925 "pnum's node is %d.\n"
2926 "That node has %d players.\n"
2927 "Player 2 on that node is %d.\n"
2928 "\n"
2929 "If you think this is a bug, please report it, including all of the details above.\n",
2930 playernum, pnum,
2931 playernode[playernum], playerpernode[playernode[playernum]],
2932 nodetoplayer2[playernode[playernum]],
2933 playernode[pnum], playerpernode[playernode[pnum]],
2934 nodetoplayer2[playernode[pnum]]);
2935 */
2936 pnum = playernum;
2937 msg = KICK_MSG_CON_FAIL;
2938 keepbody = true;
2939 }
2940
2941 //CONS_Printf("\x82%s ", player_names[pnum]);
2942
2943 // If a verified admin banned someone, the server needs to know about it.
2944 // If the playernum isn't zero (the server) then the server needs to record the ban.
2945 if (server && playernum && (msg == KICK_MSG_BANNED || msg == KICK_MSG_CUSTOM_BAN))
2946 {
2947 if (I_Ban && !I_Ban(playernode[(INT32)pnum]))
2948 CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n"));
2949 #ifndef NONET
2950 else
2951 Ban_Add(reason);
2952 #endif
2953 }
2954
2955 switch (msg)
2956 {
2957 case KICK_MSG_GO_AWAY:
2958 if (!players[pnum].quittime)
2959 HU_AddChatText(va("\x82*%s has been kicked (Go away)", player_names[pnum]), false);
2960 kickreason = KR_KICK;
2961 break;
2962 case KICK_MSG_PING_HIGH:
2963 HU_AddChatText(va("\x82*%s left the game (Broke ping limit)", player_names[pnum]), false);
2964 kickreason = KR_PINGLIMIT;
2965 break;
2966 case KICK_MSG_CON_FAIL:
2967 HU_AddChatText(va("\x82*%s left the game (Synch Failure)", player_names[pnum]), false);
2968 kickreason = KR_SYNCH;
2969
2970 if (M_CheckParm("-consisdump")) // Helps debugging some problems
2971 {
2972 INT32 i;
2973
2974 CONS_Printf(M_GetText("Player kicked is #%d, dumping consistency...\n"), pnum);
2975
2976 for (i = 0; i < MAXPLAYERS; i++)
2977 {
2978 if (!playeringame[i])
2979 continue;
2980 CONS_Printf("-------------------------------------\n");
2981 CONS_Printf("Player %d: %s\n", i, player_names[i]);
2982 CONS_Printf("Skin: %d\n", players[i].skin);
2983 CONS_Printf("Color: %d\n", players[i].skincolor);
2984 CONS_Printf("Speed: %d\n",players[i].speed>>FRACBITS);
2985 if (players[i].mo)
2986 {
2987 if (!players[i].mo->skin)
2988 CONS_Printf("Mobj skin: NULL!\n");
2989 else
2990 CONS_Printf("Mobj skin: %s\n", ((skin_t *)players[i].mo->skin)->name);
2991 CONS_Printf("Position: %d, %d, %d\n", players[i].mo->x, players[i].mo->y, players[i].mo->z);
2992 if (!players[i].mo->state)
2993 CONS_Printf("State: S_NULL\n");
2994 else
2995 CONS_Printf("State: %d\n", (statenum_t)(players[i].mo->state-states));
2996 }
2997 else
2998 CONS_Printf("Mobj: NULL\n");
2999 CONS_Printf("-------------------------------------\n");
3000 }
3001 }
3002 break;
3003 case KICK_MSG_TIMEOUT:
3004 HU_AddChatText(va("\x82*%s left the game (Connection timeout)", player_names[pnum]), false);
3005 kickreason = KR_TIMEOUT;
3006 break;
3007 case KICK_MSG_PLAYER_QUIT:
3008 if (netgame && !players[pnum].quittime) // not splitscreen/bots or soulless body
3009 HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false);
3010 kickreason = KR_LEAVE;
3011 break;
3012 case KICK_MSG_BANNED:
3013 HU_AddChatText(va("\x82*%s has been banned (Don't come back)", player_names[pnum]), false);
3014 kickreason = KR_BAN;
3015 break;
3016 case KICK_MSG_CUSTOM_KICK:
3017 READSTRINGN(*p, reason, MAX_REASONLENGTH+1);
3018 HU_AddChatText(va("\x82*%s has been kicked (%s)", player_names[pnum], reason), false);
3019 kickreason = KR_KICK;
3020 break;
3021 case KICK_MSG_CUSTOM_BAN:
3022 READSTRINGN(*p, reason, MAX_REASONLENGTH+1);
3023 HU_AddChatText(va("\x82*%s has been banned (%s)", player_names[pnum], reason), false);
3024 kickreason = KR_BAN;
3025 break;
3026 }
3027
3028 if (pnum == consoleplayer)
3029 {
3030 LUAh_GameQuit(false);
3031 #ifdef DUMPCONSISTENCY
3032 if (msg == KICK_MSG_CON_FAIL) SV_SavedGame();
3033 #endif
3034 D_QuitNetGame();
3035 CL_Reset();
3036 D_StartTitle();
3037 if (msg == KICK_MSG_CON_FAIL)
3038 M_StartMessage(M_GetText("Server closed connection\n(synch failure)\nPress ESC\n"), NULL, MM_NOTHING);
3039 else if (msg == KICK_MSG_PING_HIGH)
3040 M_StartMessage(M_GetText("Server closed connection\n(Broke ping limit)\nPress ESC\n"), NULL, MM_NOTHING);
3041 else if (msg == KICK_MSG_BANNED)
3042 M_StartMessage(M_GetText("You have been banned by the server\n\nPress ESC\n"), NULL, MM_NOTHING);
3043 else if (msg == KICK_MSG_CUSTOM_KICK)
3044 M_StartMessage(va(M_GetText("You have been kicked\n(%s)\nPress ESC\n"), reason), NULL, MM_NOTHING);
3045 else if (msg == KICK_MSG_CUSTOM_BAN)
3046 M_StartMessage(va(M_GetText("You have been banned\n(%s)\nPress ESC\n"), reason), NULL, MM_NOTHING);
3047 else
3048 M_StartMessage(M_GetText("You have been kicked by the server\n\nPress ESC\n"), NULL, MM_NOTHING);
3049 }
3050 else if (keepbody)
3051 {
3052 if (server && !demoplayback && playernode[pnum] != UINT8_MAX)
3053 {
3054 INT32 node = playernode[pnum];
3055 playerpernode[node]--;
3056 if (playerpernode[node] <= 0)
3057 {
3058 nodeingame[node] = false;
3059 Net_CloseConnection(node);
3060 ResetNode(node);
3061 }
3062 }
3063
3064 playernode[pnum] = UINT8_MAX;
3065
3066 players[pnum].quittime = 1;
3067 }
3068 else
3069 CL_RemovePlayer(pnum, kickreason);
3070 }
3071
Command_ResendGamestate(void)3072 static void Command_ResendGamestate(void)
3073 {
3074 SINT8 playernum;
3075
3076 if (COM_Argc() == 1)
3077 {
3078 CONS_Printf(M_GetText("resendgamestate <playername/playernum>: resend the game state to a player\n"));
3079 return;
3080 }
3081 else if (client)
3082 {
3083 CONS_Printf(M_GetText("Only the server can use this.\n"));
3084 return;
3085 }
3086
3087 playernum = nametonum(COM_Argv(1));
3088 if (playernum == -1 || playernum == 0)
3089 return;
3090
3091 // Send a PT_WILLRESENDGAMESTATE packet to the client so they know what's going on
3092 netbuffer->packettype = PT_WILLRESENDGAMESTATE;
3093 if (!HSendPacket(playernode[playernum], true, 0, 0))
3094 {
3095 CONS_Alert(CONS_ERROR, M_GetText("A problem occured, please try again.\n"));
3096 return;
3097 }
3098 }
3099
3100 static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}};
3101 consvar_t cv_netticbuffer = CVAR_INIT ("netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL);
3102
3103 consvar_t cv_allownewplayer = CVAR_INIT ("allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
3104 consvar_t cv_joinnextround = CVAR_INIT ("joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); /// \todo not done
3105 static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
3106 consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_SAVE|CV_NETVAR, maxplayers_cons_t, NULL);
3107 static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}};
3108 consvar_t cv_joindelay = CVAR_INIT ("joindelay", "10", CV_SAVE|CV_NETVAR, joindelay_cons_t, NULL);
3109 static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}};
3110 consvar_t cv_rejointimeout = CVAR_INIT ("rejointimeout", "2", CV_SAVE|CV_NETVAR|CV_FLOAT, rejointimeout_cons_t, NULL);
3111
3112 static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}};
3113 consvar_t cv_resynchattempts = CVAR_INIT ("resynchattempts", "10", CV_SAVE|CV_NETVAR, resynchattempts_cons_t, NULL);
3114 consvar_t cv_blamecfail = CVAR_INIT ("blamecfail", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
3115
3116 // max file size to send to a player (in kilobytes)
3117 static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {51200, "MAX"}, {0, NULL}};
3118 consvar_t cv_maxsend = CVAR_INIT ("maxsend", "4096", CV_SAVE|CV_NETVAR, maxsend_cons_t, NULL);
3119 consvar_t cv_noticedownload = CVAR_INIT ("noticedownload", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
3120
3121 // Speed of file downloading (in packets per tic)
3122 static CV_PossibleValue_t downloadspeed_cons_t[] = {{0, "MIN"}, {32, "MAX"}, {0, NULL}};
3123 consvar_t cv_downloadspeed = CVAR_INIT ("downloadspeed", "16", CV_SAVE|CV_NETVAR, downloadspeed_cons_t, NULL);
3124
3125 static void Got_AddPlayer(UINT8 **p, INT32 playernum);
3126
3127 // called one time at init
D_ClientServerInit(void)3128 void D_ClientServerInit(void)
3129 {
3130 DEBFILE(va("- - -== SRB2 v%d.%.2d.%d "VERSIONSTRING" debugfile ==- - -\n",
3131 VERSION/100, VERSION%100, SUBVERSION));
3132
3133 #ifndef NONET
3134 COM_AddCommand("getplayernum", Command_GetPlayerNum);
3135 COM_AddCommand("kick", Command_Kick);
3136 COM_AddCommand("ban", Command_Ban);
3137 COM_AddCommand("banip", Command_BanIP);
3138 COM_AddCommand("clearbans", Command_ClearBans);
3139 COM_AddCommand("showbanlist", Command_ShowBan);
3140 COM_AddCommand("reloadbans", Command_ReloadBan);
3141 COM_AddCommand("connect", Command_connect);
3142 COM_AddCommand("nodes", Command_Nodes);
3143 COM_AddCommand("resendgamestate", Command_ResendGamestate);
3144 #ifdef PACKETDROP
3145 COM_AddCommand("drop", Command_Drop);
3146 COM_AddCommand("droprate", Command_Droprate);
3147 #endif
3148 #ifdef _DEBUG
3149 COM_AddCommand("numnodes", Command_Numnodes);
3150 #endif
3151 #endif
3152
3153 RegisterNetXCmd(XD_KICK, Got_KickCmd);
3154 RegisterNetXCmd(XD_ADDPLAYER, Got_AddPlayer);
3155 #ifndef NONET
3156 #ifdef DUMPCONSISTENCY
3157 CV_RegisterVar(&cv_dumpconsistency);
3158 #endif
3159 Ban_Load_File(false);
3160 #endif
3161
3162 gametic = 0;
3163 localgametic = 0;
3164
3165 // do not send anything before the real begin
3166 SV_StopServer();
3167 SV_ResetServer();
3168 if (dedicated)
3169 SV_SpawnServer();
3170 }
3171
ResetNode(INT32 node)3172 static void ResetNode(INT32 node)
3173 {
3174 nodeingame[node] = false;
3175 nodewaiting[node] = 0;
3176
3177 nettics[node] = gametic;
3178 supposedtics[node] = gametic;
3179
3180 nodetoplayer[node] = -1;
3181 nodetoplayer2[node] = -1;
3182 playerpernode[node] = 0;
3183
3184 sendingsavegame[node] = false;
3185 resendingsavegame[node] = false;
3186 savegameresendcooldown[node] = 0;
3187 }
3188
SV_ResetServer(void)3189 void SV_ResetServer(void)
3190 {
3191 INT32 i;
3192
3193 // +1 because this command will be executed in com_executebuffer in
3194 // tryruntic so gametic will be incremented, anyway maketic > gametic
3195 // is not an issue
3196
3197 maketic = gametic + 1;
3198 neededtic = maketic;
3199 tictoclear = maketic;
3200
3201 joindelay = 0;
3202
3203 for (i = 0; i < MAXNETNODES; i++)
3204 ResetNode(i);
3205
3206 for (i = 0; i < MAXPLAYERS; i++)
3207 {
3208 LUA_InvalidatePlayer(&players[i]);
3209 playeringame[i] = false;
3210 playernode[i] = UINT8_MAX;
3211 memset(playeraddress[i], 0, sizeof(*playeraddress));
3212 sprintf(player_names[i], "Player %d", i + 1);
3213 adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
3214 }
3215
3216 memset(player_name_changes, 0, sizeof player_name_changes);
3217
3218 mynode = 0;
3219 cl_packetmissed = false;
3220 cl_redownloadinggamestate = false;
3221
3222 if (dedicated)
3223 {
3224 nodeingame[0] = true;
3225 serverplayer = 0;
3226 }
3227 else
3228 serverplayer = consoleplayer;
3229
3230 if (server)
3231 servernode = 0;
3232
3233 doomcom->numslots = 0;
3234
3235 // clear server_context
3236 memset(server_context, '-', 8);
3237
3238 DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n");
3239 }
3240
SV_GenContext(void)3241 static inline void SV_GenContext(void)
3242 {
3243 UINT8 i;
3244 // generate server_context, as exactly 8 bytes of randomly mixed A-Z and a-z
3245 // (hopefully M_Random is initialized!! if not this will be awfully silly!)
3246 for (i = 0; i < 8; i++)
3247 {
3248 const char a = M_RandomKey(26*2);
3249 if (a < 26) // uppercase
3250 server_context[i] = 'A'+a;
3251 else // lowercase
3252 server_context[i] = 'a'+(a-26);
3253 }
3254 }
3255
3256 //
3257 // D_QuitNetGame
3258 // Called before quitting to leave a net game
3259 // without hanging the other players
3260 //
D_QuitNetGame(void)3261 void D_QuitNetGame(void)
3262 {
3263 if (!netgame || !netbuffer)
3264 return;
3265
3266 DEBFILE("===========================================================================\n"
3267 " Quitting Game, closing connection\n"
3268 "===========================================================================\n");
3269
3270 // abort send/receive of files
3271 CloseNetFile();
3272 RemoveAllLuaFileTransfers();
3273 waitingforluafiletransfer = false;
3274 waitingforluafilecommand = false;
3275
3276 if (server)
3277 {
3278 INT32 i;
3279
3280 netbuffer->packettype = PT_SERVERSHUTDOWN;
3281 for (i = 0; i < MAXNETNODES; i++)
3282 if (nodeingame[i])
3283 HSendPacket(i, true, 0, 0);
3284 #ifdef MASTERSERVER
3285 if (serverrunning && ms_RoomId > 0)
3286 UnregisterServer();
3287 #endif
3288 }
3289 else if (servernode > 0 && servernode < MAXNETNODES && nodeingame[(UINT8)servernode])
3290 {
3291 netbuffer->packettype = PT_CLIENTQUIT;
3292 HSendPacket(servernode, true, 0, 0);
3293 }
3294
3295 D_CloseConnection();
3296 ClearAdminPlayers();
3297
3298 DEBFILE("===========================================================================\n"
3299 " Log finish\n"
3300 "===========================================================================\n");
3301 #ifdef DEBUGFILE
3302 if (debugfile)
3303 {
3304 fclose(debugfile);
3305 debugfile = NULL;
3306 }
3307 #endif
3308 }
3309
3310 // Adds a node to the game (player will follow at map change or at savegame....)
SV_AddNode(INT32 node)3311 static inline void SV_AddNode(INT32 node)
3312 {
3313 nettics[node] = gametic;
3314 supposedtics[node] = gametic;
3315 // little hack because the server connects to itself and puts
3316 // nodeingame when connected not here
3317 if (node)
3318 nodeingame[node] = true;
3319 }
3320
3321 // Xcmd XD_ADDPLAYER
Got_AddPlayer(UINT8 ** p,INT32 playernum)3322 static void Got_AddPlayer(UINT8 **p, INT32 playernum)
3323 {
3324 INT16 node, newplayernum;
3325 boolean splitscreenplayer;
3326 boolean rejoined;
3327 player_t *newplayer;
3328
3329 if (playernum != serverplayer && !IsPlayerAdmin(playernum))
3330 {
3331 // protect against hacked/buggy client
3332 CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]);
3333 if (server)
3334 SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
3335 return;
3336 }
3337
3338 node = READUINT8(*p);
3339 newplayernum = READUINT8(*p);
3340 splitscreenplayer = newplayernum & 0x80;
3341 newplayernum &= ~0x80;
3342
3343 rejoined = playeringame[newplayernum];
3344
3345 if (!rejoined)
3346 {
3347 // Clear player before joining, lest some things get set incorrectly
3348 // HACK: don't do this for splitscreen, it relies on preset values
3349 if (!splitscreen && !botingame)
3350 CL_ClearPlayer(newplayernum);
3351 playeringame[newplayernum] = true;
3352 G_AddPlayer(newplayernum);
3353 if (newplayernum+1 > doomcom->numslots)
3354 doomcom->numslots = (INT16)(newplayernum+1);
3355
3356 if (server && I_GetNodeAddress)
3357 {
3358 const char *address = I_GetNodeAddress(node);
3359 char *port = NULL;
3360 if (address) // MI: fix msvcrt.dll!_mbscat crash?
3361 {
3362 strcpy(playeraddress[newplayernum], address);
3363 port = strchr(playeraddress[newplayernum], ':');
3364 if (port)
3365 *port = '\0';
3366 }
3367 }
3368 }
3369
3370 newplayer = &players[newplayernum];
3371
3372 newplayer->jointime = 0;
3373 newplayer->quittime = 0;
3374
3375 READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
3376
3377 // the server is creating my player
3378 if (node == mynode)
3379 {
3380 playernode[newplayernum] = 0; // for information only
3381 if (!splitscreenplayer)
3382 {
3383 consoleplayer = newplayernum;
3384 displayplayer = newplayernum;
3385 secondarydisplayplayer = newplayernum;
3386 DEBFILE("spawning me\n");
3387 ticcmd_oldangleturn[0] = newplayer->oldrelangleturn;
3388 }
3389 else
3390 {
3391 secondarydisplayplayer = newplayernum;
3392 DEBFILE("spawning my brother\n");
3393 if (botingame)
3394 newplayer->bot = 1;
3395 ticcmd_oldangleturn[1] = newplayer->oldrelangleturn;
3396 }
3397 P_ForceLocalAngle(newplayer, (angle_t)(newplayer->angleturn << 16));
3398 D_SendPlayerConfig();
3399 addedtogame = true;
3400
3401 if (rejoined)
3402 {
3403 if (newplayer->mo)
3404 {
3405 newplayer->viewheight = 41*newplayer->height/48;
3406
3407 if (newplayer->mo->eflags & MFE_VERTICALFLIP)
3408 newplayer->viewz = newplayer->mo->z + newplayer->mo->height - newplayer->viewheight;
3409 else
3410 newplayer->viewz = newplayer->mo->z + newplayer->viewheight;
3411 }
3412
3413 // wake up the status bar
3414 ST_Start();
3415 // wake up the heads up text
3416 HU_Start();
3417
3418 if (camera.chase && !splitscreenplayer)
3419 P_ResetCamera(newplayer, &camera);
3420 if (camera2.chase && splitscreenplayer)
3421 P_ResetCamera(newplayer, &camera2);
3422 }
3423 }
3424
3425 if (netgame)
3426 {
3427 char joinmsg[256];
3428
3429 if (rejoined)
3430 strcpy(joinmsg, M_GetText("\x82*%s has rejoined the game (player %d)"));
3431 else
3432 strcpy(joinmsg, M_GetText("\x82*%s has joined the game (player %d)"));
3433 strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum));
3434
3435 // Merge join notification + IP to avoid clogging console/chat
3436 if (server && cv_showjoinaddress.value && I_GetNodeAddress)
3437 {
3438 const char *address = I_GetNodeAddress(node);
3439 if (address)
3440 strcat(joinmsg, va(" (%s)", address));
3441 }
3442
3443 HU_AddChatText(joinmsg, false);
3444 }
3445
3446 if (server && multiplayer && motd[0] != '\0')
3447 COM_BufAddText(va("sayto %d %s\n", newplayernum, motd));
3448
3449 if (!rejoined)
3450 LUAh_PlayerJoin(newplayernum);
3451 }
3452
SV_AddWaitingPlayers(const char * name,const char * name2)3453 static boolean SV_AddWaitingPlayers(const char *name, const char *name2)
3454 {
3455 INT32 node, n, newplayer = false;
3456 UINT8 buf[2 + MAXPLAYERNAME];
3457 UINT8 *p;
3458 INT32 newplayernum;
3459
3460 for (node = 0; node < MAXNETNODES; node++)
3461 {
3462 // splitscreen can allow 2 player in one node
3463 for (; nodewaiting[node] > 0; nodewaiting[node]--)
3464 {
3465 newplayer = true;
3466
3467 newplayernum = FindRejoinerNum(node);
3468 if (newplayernum == -1)
3469 {
3470 // search for a free playernum
3471 // we can't use playeringame since it is not updated here
3472 for (newplayernum = dedicated ? 1 : 0; newplayernum < MAXPLAYERS; newplayernum++)
3473 {
3474 if (playeringame[newplayernum])
3475 continue;
3476 for (n = 0; n < MAXNETNODES; n++)
3477 if (nodetoplayer[n] == newplayernum || nodetoplayer2[n] == newplayernum)
3478 break;
3479 if (n == MAXNETNODES)
3480 break;
3481 }
3482 }
3483
3484 // should never happen since we check the playernum
3485 // before accepting the join
3486 I_Assert(newplayernum < MAXPLAYERS);
3487
3488 playernode[newplayernum] = (UINT8)node;
3489
3490 p = buf + 2;
3491 buf[0] = (UINT8)node;
3492 buf[1] = newplayernum;
3493 if (playerpernode[node] < 1)
3494 {
3495 nodetoplayer[node] = newplayernum;
3496 WRITESTRINGN(p, name, MAXPLAYERNAME);
3497 }
3498 else
3499 {
3500 nodetoplayer2[node] = newplayernum;
3501 buf[1] |= 0x80;
3502 WRITESTRINGN(p, name2, MAXPLAYERNAME);
3503 }
3504 playerpernode[node]++;
3505
3506 SendNetXCmd(XD_ADDPLAYER, &buf, p - buf);
3507
3508 DEBFILE(va("Server added player %d node %d\n", newplayernum, node));
3509 }
3510 }
3511
3512 return newplayer;
3513 }
3514
CL_AddSplitscreenPlayer(void)3515 void CL_AddSplitscreenPlayer(void)
3516 {
3517 if (cl_mode == CL_CONNECTED)
3518 CL_SendJoin();
3519 }
3520
CL_RemoveSplitscreenPlayer(void)3521 void CL_RemoveSplitscreenPlayer(void)
3522 {
3523 if (cl_mode != CL_CONNECTED)
3524 return;
3525
3526 SendKick(secondarydisplayplayer, KICK_MSG_PLAYER_QUIT);
3527 }
3528
3529 // is there a game running
Playing(void)3530 boolean Playing(void)
3531 {
3532 return (server && serverrunning) || (client && cl_mode == CL_CONNECTED);
3533 }
3534
SV_SpawnServer(void)3535 boolean SV_SpawnServer(void)
3536 {
3537 if (demoplayback)
3538 G_StopDemo(); // reset engine parameter
3539 if (metalplayback)
3540 G_StopMetalDemo();
3541
3542 if (!serverrunning)
3543 {
3544 CONS_Printf(M_GetText("Starting Server....\n"));
3545 serverrunning = true;
3546 SV_ResetServer();
3547 SV_GenContext();
3548 if (netgame && I_NetOpenSocket)
3549 {
3550 I_NetOpenSocket();
3551 #ifdef MASTERSERVER
3552 if (ms_RoomId > 0)
3553 RegisterServer();
3554 #endif
3555 }
3556
3557 // non dedicated server just connect to itself
3558 if (!dedicated)
3559 CL_ConnectToServer();
3560 else doomcom->numslots = 1;
3561 }
3562
3563 return SV_AddWaitingPlayers(cv_playername.zstring, cv_playername2.zstring);
3564 }
3565
SV_StopServer(void)3566 void SV_StopServer(void)
3567 {
3568 tic_t i;
3569
3570 if (gamestate == GS_INTERMISSION)
3571 Y_EndIntermission();
3572 gamestate = wipegamestate = GS_NULL;
3573
3574 localtextcmd[0] = 0;
3575 localtextcmd2[0] = 0;
3576
3577 for (i = firstticstosend; i < firstticstosend + BACKUPTICS; i++)
3578 D_Clearticcmd(i);
3579
3580 consoleplayer = 0;
3581 cl_mode = CL_SEARCHING;
3582 maketic = gametic+1;
3583 neededtic = maketic;
3584 serverrunning = false;
3585 }
3586
3587 // called at singleplayer start and stopdemo
SV_StartSinglePlayerServer(void)3588 void SV_StartSinglePlayerServer(void)
3589 {
3590 server = true;
3591 netgame = false;
3592 multiplayer = false;
3593 G_SetGametype(GT_COOP);
3594
3595 // no more tic the game with this settings!
3596 SV_StopServer();
3597
3598 if (splitscreen)
3599 multiplayer = true;
3600 }
3601
SV_SendRefuse(INT32 node,const char * reason)3602 static void SV_SendRefuse(INT32 node, const char *reason)
3603 {
3604 strcpy(netbuffer->u.serverrefuse.reason, reason);
3605
3606 netbuffer->packettype = PT_SERVERREFUSE;
3607 HSendPacket(node, true, 0, strlen(netbuffer->u.serverrefuse.reason) + 1);
3608 Net_CloseConnection(node);
3609 }
3610
3611 // used at txtcmds received to check packetsize bound
TotalTextCmdPerTic(tic_t tic)3612 static size_t TotalTextCmdPerTic(tic_t tic)
3613 {
3614 INT32 i;
3615 size_t total = 1; // num of textcmds in the tic (ntextcmd byte)
3616
3617 for (i = 0; i < MAXPLAYERS; i++)
3618 {
3619 UINT8 *textcmd = D_GetExistingTextcmd(tic, i);
3620 if ((!i || playeringame[i]) && textcmd)
3621 total += 2 + textcmd[0]; // "+2" for size and playernum
3622 }
3623
3624 return total;
3625 }
3626
3627 /** Called when a PT_CLIENTJOIN packet is received
3628 *
3629 * \param node The packet sender
3630 *
3631 */
HandleConnect(SINT8 node)3632 static void HandleConnect(SINT8 node)
3633 {
3634 char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1];
3635 INT32 rejoinernum;
3636 INT32 i;
3637
3638 rejoinernum = FindRejoinerNum(node);
3639
3640 if (bannednode && bannednode[node])
3641 SV_SendRefuse(node, M_GetText("You have been banned\nfrom the server."));
3642 else if (netbuffer->u.clientcfg._255 != 255 ||
3643 netbuffer->u.clientcfg.packetversion != PACKETVERSION)
3644 SV_SendRefuse(node, "Incompatible packet formats.");
3645 else if (strncmp(netbuffer->u.clientcfg.application, SRB2APPLICATION,
3646 sizeof netbuffer->u.clientcfg.application))
3647 SV_SendRefuse(node, "Different SRB2 modifications\nare not compatible.");
3648 else if (netbuffer->u.clientcfg.version != VERSION
3649 || netbuffer->u.clientcfg.subversion != SUBVERSION)
3650 SV_SendRefuse(node, va(M_GetText("Different SRB2 versions cannot\nplay a netgame!\n(server version %d.%d.%d)"), VERSION/100, VERSION%100, SUBVERSION));
3651 else if (!cv_allownewplayer.value && node && rejoinernum == -1)
3652 SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment."));
3653 else if (D_NumPlayers() >= cv_maxplayers.value && rejoinernum == -1)
3654 SV_SendRefuse(node, va(M_GetText("Maximum players reached: %d"), cv_maxplayers.value));
3655 else if (netgame && netbuffer->u.clientcfg.localplayers > 1) // Hacked client?
3656 SV_SendRefuse(node, M_GetText("Too many players from\nthis node."));
3657 else if (netgame && !netbuffer->u.clientcfg.localplayers) // Stealth join?
3658 SV_SendRefuse(node, M_GetText("No players from\nthis node."));
3659 else if (luafiletransfers)
3660 SV_SendRefuse(node, M_GetText("The server is broadcasting a file\nrequested by a Lua script.\nPlease wait a bit and then\ntry rejoining."));
3661 else if (netgame && joindelay > 2 * (tic_t)cv_joindelay.value * TICRATE)
3662 SV_SendRefuse(node, va(M_GetText("Too many people are connecting.\nPlease wait %d seconds and then\ntry rejoining."),
3663 (joindelay - 2 * cv_joindelay.value * TICRATE) / TICRATE));
3664 else
3665 {
3666 #ifndef NONET
3667 boolean newnode = false;
3668 #endif
3669
3670 for (i = 0; i < netbuffer->u.clientcfg.localplayers - playerpernode[node]; i++)
3671 {
3672 strlcpy(names[i], netbuffer->u.clientcfg.names[i], MAXPLAYERNAME + 1);
3673 if (!EnsurePlayerNameIsGood(names[i], rejoinernum))
3674 {
3675 SV_SendRefuse(node, "Bad player name");
3676 return;
3677 }
3678 }
3679
3680 // client authorised to join
3681 nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]);
3682 if (!nodeingame[node])
3683 {
3684 gamestate_t backupstate = gamestate;
3685 #ifndef NONET
3686 newnode = true;
3687 #endif
3688 SV_AddNode(node);
3689
3690 if (cv_joinnextround.value && gameaction == ga_nothing)
3691 G_SetGamestate(GS_WAITINGPLAYERS);
3692 if (!SV_SendServerConfig(node))
3693 {
3694 G_SetGamestate(backupstate);
3695 /// \note Shouldn't SV_SendRefuse be called before ResetNode?
3696 ResetNode(node);
3697 SV_SendRefuse(node, M_GetText("Server couldn't send info, please try again"));
3698 /// \todo fix this !!!
3699 return; // restart the while
3700 }
3701 //if (gamestate != GS_LEVEL) // GS_INTERMISSION, etc?
3702 // SV_SendPlayerConfigs(node); // send bare minimum player info
3703 G_SetGamestate(backupstate);
3704 DEBFILE("new node joined\n");
3705 }
3706 #ifndef NONET
3707 if (nodewaiting[node])
3708 {
3709 if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) && newnode)
3710 {
3711 SV_SendSaveGame(node, false); // send a complete game state
3712 DEBFILE("send savegame\n");
3713 }
3714 SV_AddWaitingPlayers(names[0], names[1]);
3715 joindelay += cv_joindelay.value * TICRATE;
3716 player_joining = true;
3717 }
3718 #endif
3719 }
3720 }
3721
3722 /** Called when a PT_SERVERSHUTDOWN packet is received
3723 *
3724 * \param node The packet sender (should be the server)
3725 *
3726 */
HandleShutdown(SINT8 node)3727 static void HandleShutdown(SINT8 node)
3728 {
3729 (void)node;
3730 LUAh_GameQuit(false);
3731 D_QuitNetGame();
3732 CL_Reset();
3733 D_StartTitle();
3734 M_StartMessage(M_GetText("Server has shutdown\n\nPress Esc\n"), NULL, MM_NOTHING);
3735 }
3736
3737 /** Called when a PT_NODETIMEOUT packet is received
3738 *
3739 * \param node The packet sender (should be the server)
3740 *
3741 */
HandleTimeout(SINT8 node)3742 static void HandleTimeout(SINT8 node)
3743 {
3744 (void)node;
3745 LUAh_GameQuit(false);
3746 D_QuitNetGame();
3747 CL_Reset();
3748 D_StartTitle();
3749 M_StartMessage(M_GetText("Server Timeout\n\nPress Esc\n"), NULL, MM_NOTHING);
3750 }
3751
3752 #ifndef NONET
3753 /** Called when a PT_SERVERINFO packet is received
3754 *
3755 * \param node The packet sender
3756 * \note What happens if the packet comes from a client or something like that?
3757 *
3758 */
HandleServerInfo(SINT8 node)3759 static void HandleServerInfo(SINT8 node)
3760 {
3761 // compute ping in ms
3762 const tic_t ticnow = I_GetTime();
3763 const tic_t ticthen = (tic_t)LONG(netbuffer->u.serverinfo.time);
3764 const tic_t ticdiff = (ticnow - ticthen)*1000/NEWTICRATE;
3765 netbuffer->u.serverinfo.time = (tic_t)LONG(ticdiff);
3766 netbuffer->u.serverinfo.servername[MAXSERVERNAME-1] = 0;
3767 netbuffer->u.serverinfo.application
3768 [sizeof netbuffer->u.serverinfo.application - 1] = '\0';
3769 netbuffer->u.serverinfo.gametypename
3770 [sizeof netbuffer->u.serverinfo.gametypename - 1] = '\0';
3771
3772 SL_InsertServer(&netbuffer->u.serverinfo, node);
3773 }
3774 #endif
3775
PT_WillResendGamestate(void)3776 static void PT_WillResendGamestate(void)
3777 {
3778 char tmpsave[256];
3779
3780 if (server || cl_redownloadinggamestate)
3781 return;
3782
3783 // Send back a PT_CANRECEIVEGAMESTATE packet to the server
3784 // so they know they can start sending the game state
3785 netbuffer->packettype = PT_CANRECEIVEGAMESTATE;
3786 if (!HSendPacket(servernode, true, 0, 0))
3787 return;
3788
3789 CONS_Printf(M_GetText("Reloading game state...\n"));
3790
3791 sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
3792
3793 // Don't get a corrupt savegame error because tmpsave already exists
3794 if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1)
3795 I_Error("Can't delete %s\n", tmpsave);
3796
3797 CL_PrepareDownloadSaveGame(tmpsave);
3798
3799 cl_redownloadinggamestate = true;
3800 }
3801
PT_CanReceiveGamestate(SINT8 node)3802 static void PT_CanReceiveGamestate(SINT8 node)
3803 {
3804 if (client || sendingsavegame[node])
3805 return;
3806
3807 CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[nodetoplayer[node]]);
3808
3809 SV_SendSaveGame(node, true); // Resend a complete game state
3810 resendingsavegame[node] = true;
3811 }
3812
3813 /** Handles a packet received from a node that isn't in game
3814 *
3815 * \param node The packet sender
3816 * \todo Choose a better name, as the packet can also come from the server apparently?
3817 * \sa HandlePacketFromPlayer
3818 * \sa GetPackets
3819 *
3820 */
HandlePacketFromAwayNode(SINT8 node)3821 static void HandlePacketFromAwayNode(SINT8 node)
3822 {
3823 if (node != servernode)
3824 DEBFILE(va("Received packet from unknown host %d\n", node));
3825
3826 // macro for packets that should only be sent by the server
3827 // if it is NOT from the server, bail out and close the connection!
3828 #define SERVERONLY \
3829 if (node != servernode) \
3830 { \
3831 Net_CloseConnection(node); \
3832 break; \
3833 }
3834 switch (netbuffer->packettype)
3835 {
3836 case PT_ASKINFOVIAMS:
3837 #if 0
3838 if (server && serverrunning)
3839 {
3840 INT32 clientnode;
3841 if (ms_RoomId < 0) // ignore if we're not actually on the MS right now
3842 {
3843 Net_CloseConnection(node); // and yes, close connection
3844 return;
3845 }
3846 clientnode = I_NetMakeNode(netbuffer->u.msaskinfo.clientaddr);
3847 if (clientnode != -1)
3848 {
3849 SV_SendServerInfo(clientnode, (tic_t)LONG(netbuffer->u.msaskinfo.time));
3850 SV_SendPlayerInfo(clientnode); // Send extra info
3851 Net_CloseConnection(clientnode);
3852 // Don't close connection to MS...
3853 }
3854 else
3855 Net_CloseConnection(node); // ...unless the IP address is not valid
3856 }
3857 else
3858 Net_CloseConnection(node); // you're not supposed to get it, so ignore it
3859 #else
3860 Net_CloseConnection(node);
3861 #endif
3862 break;
3863
3864 case PT_ASKINFO:
3865 if (server && serverrunning)
3866 {
3867 SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time));
3868 SV_SendPlayerInfo(node); // Send extra info
3869 }
3870 Net_CloseConnection(node);
3871 break;
3872
3873 case PT_SERVERREFUSE: // Negative response of client join request
3874 if (server && serverrunning)
3875 { // But wait I thought I'm the server?
3876 Net_CloseConnection(node);
3877 break;
3878 }
3879 SERVERONLY
3880 if (cl_mode == CL_WAITJOINRESPONSE)
3881 {
3882 // Save the reason so it can be displayed after quitting the netgame
3883 char *reason = strdup(netbuffer->u.serverrefuse.reason);
3884 if (!reason)
3885 I_Error("Out of memory!\n");
3886
3887 D_QuitNetGame();
3888 CL_Reset();
3889 D_StartTitle();
3890
3891 M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
3892 reason), NULL, MM_NOTHING);
3893
3894 free(reason);
3895
3896 // Will be reset by caller. Signals refusal.
3897 cl_mode = CL_ABORTED;
3898 }
3899 break;
3900
3901 case PT_SERVERCFG: // Positive response of client join request
3902 {
3903 if (server && serverrunning && node != servernode)
3904 { // but wait I thought I'm the server?
3905 Net_CloseConnection(node);
3906 break;
3907 }
3908 SERVERONLY
3909 /// \note how would this happen? and is it doing the right thing if it does?
3910 if (cl_mode != CL_WAITJOINRESPONSE)
3911 break;
3912
3913 if (client)
3914 {
3915 maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
3916 G_SetGametype(netbuffer->u.servercfg.gametype);
3917 modifiedgame = netbuffer->u.servercfg.modifiedgame;
3918 memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
3919 }
3920
3921 nodeingame[(UINT8)servernode] = true;
3922 serverplayer = netbuffer->u.servercfg.serverplayer;
3923 doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum);
3924 mynode = netbuffer->u.servercfg.clientnode;
3925 if (serverplayer >= 0)
3926 playernode[(UINT8)serverplayer] = servernode;
3927
3928 if (netgame)
3929 #ifndef NONET
3930 CONS_Printf(M_GetText("Join accepted, waiting for complete game state...\n"));
3931 #else
3932 CONS_Printf(M_GetText("Join accepted, waiting for next level change...\n"));
3933 #endif
3934 DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode));
3935
3936 #ifndef NONET
3937 /// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook?
3938 /// Shouldn't them be downloaded even at intermission time?
3939 /// Also, according to HandleConnect, the server will send the savegame even during intermission...
3940 if (netbuffer->u.servercfg.gamestate == GS_LEVEL/* ||
3941 netbuffer->u.servercfg.gamestate == GS_INTERMISSION*/)
3942 cl_mode = CL_DOWNLOADSAVEGAME;
3943 else
3944 #endif
3945 cl_mode = CL_CONNECTED;
3946 break;
3947 }
3948
3949 // Handled in d_netfil.c
3950 case PT_FILEFRAGMENT:
3951 if (server)
3952 { // But wait I thought I'm the server?
3953 Net_CloseConnection(node);
3954 break;
3955 }
3956 SERVERONLY
3957 PT_FileFragment();
3958 break;
3959
3960 case PT_FILEACK:
3961 if (server)
3962 PT_FileAck();
3963 break;
3964
3965 case PT_FILERECEIVED:
3966 if (server)
3967 PT_FileReceived();
3968 break;
3969
3970 case PT_REQUESTFILE:
3971 if (server)
3972 {
3973 if (!cv_downloading.value || !PT_RequestFile(node))
3974 Net_CloseConnection(node); // close connection if one of the requested files could not be sent, or you disabled downloading anyway
3975 }
3976 else
3977 Net_CloseConnection(node); // nope
3978 break;
3979
3980 case PT_NODETIMEOUT:
3981 case PT_CLIENTQUIT:
3982 if (server)
3983 Net_CloseConnection(node);
3984 break;
3985
3986 case PT_CLIENTCMD:
3987 break; // This is not an "unknown packet"
3988
3989 case PT_SERVERTICS:
3990 // Do not remove my own server (we have just get a out of order packet)
3991 if (node == servernode)
3992 break;
3993 /* FALLTHRU */
3994
3995 default:
3996 DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype));
3997 Net_CloseConnection(node);
3998 break; // Ignore it
3999
4000 }
4001 #undef SERVERONLY
4002 }
4003
4004 /** Handles a packet received from a node that is in game
4005 *
4006 * \param node The packet sender
4007 * \todo Choose a better name
4008 * \sa HandlePacketFromAwayNode
4009 * \sa GetPackets
4010 *
4011 */
HandlePacketFromPlayer(SINT8 node)4012 static void HandlePacketFromPlayer(SINT8 node)
4013 {
4014 INT32 netconsole;
4015 tic_t realend, realstart;
4016 UINT8 *pak, *txtpak, numtxtpak;
4017 #ifndef NOMD5
4018 UINT8 finalmd5[16];/* Well, it's the cool thing to do? */
4019 #endif
4020
4021 txtpak = NULL;
4022
4023 if (dedicated && node == 0)
4024 netconsole = 0;
4025 else
4026 netconsole = nodetoplayer[node];
4027 #ifdef PARANOIA
4028 if (netconsole >= MAXPLAYERS)
4029 I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole);
4030 #endif
4031
4032 switch (netbuffer->packettype)
4033 {
4034 // -------------------------------------------- SERVER RECEIVE ----------
4035 case PT_CLIENTCMD:
4036 case PT_CLIENT2CMD:
4037 case PT_CLIENTMIS:
4038 case PT_CLIENT2MIS:
4039 case PT_NODEKEEPALIVE:
4040 case PT_NODEKEEPALIVEMIS:
4041 if (client)
4042 break;
4043
4044 // To save bytes, only the low byte of tic numbers are sent
4045 // Use ExpandTics to figure out what the rest of the bytes are
4046 realstart = ExpandTics(netbuffer->u.clientpak.client_tic, node);
4047 realend = ExpandTics(netbuffer->u.clientpak.resendfrom, node);
4048
4049 if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS
4050 || netbuffer->packettype == PT_NODEKEEPALIVEMIS
4051 || supposedtics[node] < realend)
4052 {
4053 supposedtics[node] = realend;
4054 }
4055 // Discard out of order packet
4056 if (nettics[node] > realend)
4057 {
4058 DEBFILE(va("out of order ticcmd discarded nettics = %u\n", nettics[node]));
4059 break;
4060 }
4061
4062 // Update the nettics
4063 nettics[node] = realend;
4064
4065 // Don't do anything for packets of type NODEKEEPALIVE?
4066 if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
4067 || netbuffer->packettype == PT_NODEKEEPALIVEMIS)
4068 break;
4069
4070 // As long as clients send valid ticcmds, the server can keep running, so reset the timeout
4071 /// \todo Use a separate cvar for that kind of timeout?
4072 freezetimeout[node] = I_GetTime() + connectiontimeout;
4073
4074 // Copy ticcmd
4075 G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
4076
4077 // Check ticcmd for "speed hacks"
4078 if (netcmds[maketic%BACKUPTICS][netconsole].forwardmove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].forwardmove < -MAXPLMOVE
4079 || netcmds[maketic%BACKUPTICS][netconsole].sidemove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].sidemove < -MAXPLMOVE)
4080 {
4081 CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), netconsole);
4082 //D_Clearticcmd(k);
4083
4084 SendKick(netconsole, KICK_MSG_CON_FAIL);
4085 break;
4086 }
4087
4088 // Splitscreen cmd
4089 if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
4090 && nodetoplayer2[node] >= 0)
4091 G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
4092 &netbuffer->u.client2pak.cmd2, 1);
4093
4094 // Check player consistancy during the level
4095 if (realstart <= gametic && realstart + BACKUPTICS - 1 > gametic && gamestate == GS_LEVEL
4096 && consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy)
4097 && !resendingsavegame[node] && savegameresendcooldown[node] <= I_GetTime()
4098 && !SV_ResendingSavegameToAnyone())
4099 {
4100 if (cv_resynchattempts.value)
4101 {
4102 // Tell the client we are about to resend them the gamestate
4103 netbuffer->packettype = PT_WILLRESENDGAMESTATE;
4104 HSendPacket(node, true, 0, 0);
4105
4106 resendingsavegame[node] = true;
4107
4108 if (cv_blamecfail.value)
4109 CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
4110 netconsole+1, player_names[netconsole],
4111 consistancy[realstart%BACKUPTICS],
4112 SHORT(netbuffer->u.clientpak.consistancy));
4113 DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n",
4114 netconsole, realstart, consistancy[realstart%BACKUPTICS],
4115 SHORT(netbuffer->u.clientpak.consistancy)));
4116 break;
4117 }
4118 else
4119 {
4120 SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
4121 DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
4122 netconsole, realstart, consistancy[realstart%BACKUPTICS],
4123 SHORT(netbuffer->u.clientpak.consistancy)));
4124 break;
4125 }
4126 }
4127 break;
4128 case PT_TEXTCMD2: // splitscreen special
4129 netconsole = nodetoplayer2[node];
4130 /* FALLTHRU */
4131 case PT_TEXTCMD:
4132 if (client)
4133 break;
4134
4135 if (netconsole < 0 || netconsole >= MAXPLAYERS)
4136 Net_UnAcknowledgePacket(node);
4137 else
4138 {
4139 size_t j;
4140 tic_t tic = maketic;
4141 UINT8 *textcmd;
4142
4143 // ignore if the textcmd has a reported size of zero
4144 // this shouldn't be sent at all
4145 if (!netbuffer->u.textcmd[0])
4146 {
4147 DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n",
4148 node, netconsole));
4149 Net_UnAcknowledgePacket(node);
4150 break;
4151 }
4152
4153 // ignore if the textcmd size var is actually larger than it should be
4154 // BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength
4155 if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1)
4156 {
4157 DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n",
4158 netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1),
4159 node, netconsole));
4160 Net_UnAcknowledgePacket(node);
4161 break;
4162 }
4163
4164 // check if tic that we are making isn't too large else we cannot send it :(
4165 // doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time
4166 j = software_MAXPACKETLENGTH
4167 - (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE
4168 + (doomcom->numslots+1)*sizeof(ticcmd_t));
4169
4170 // search a tic that have enougth space in the ticcmd
4171 while ((textcmd = D_GetExistingTextcmd(tic, netconsole)),
4172 (TotalTextCmdPerTic(tic) > j || netbuffer->u.textcmd[0] + (textcmd ? textcmd[0] : 0) > MAXTEXTCMD)
4173 && tic < firstticstosend + BACKUPTICS)
4174 tic++;
4175
4176 if (tic >= firstticstosend + BACKUPTICS)
4177 {
4178 DEBFILE(va("GetPacket: Textcmd too long (max %s, used %s, mak %d, "
4179 "tosend %u, node %u, player %d)\n", sizeu1(j), sizeu2(TotalTextCmdPerTic(maketic)),
4180 maketic, firstticstosend, node, netconsole));
4181 Net_UnAcknowledgePacket(node);
4182 break;
4183 }
4184
4185 // Make sure we have a buffer
4186 if (!textcmd) textcmd = D_GetTextcmd(tic, netconsole);
4187
4188 DEBFILE(va("textcmd put in tic %u at position %d (player %d) ftts %u mk %u\n",
4189 tic, textcmd[0]+1, netconsole, firstticstosend, maketic));
4190
4191 M_Memcpy(&textcmd[textcmd[0]+1], netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]);
4192 textcmd[0] += (UINT8)netbuffer->u.textcmd[0];
4193 }
4194 break;
4195 case PT_LOGIN:
4196 if (client)
4197 break;
4198
4199 #ifndef NOMD5
4200 if (doomcom->datalength < 16)/* ignore partial sends */
4201 break;
4202
4203 if (!adminpasswordset)
4204 {
4205 CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[netconsole]);
4206 break;
4207 }
4208
4209 // Do the final pass to compare with the sent md5
4210 D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5);
4211
4212 if (!memcmp(netbuffer->u.md5sum, finalmd5, 16))
4213 {
4214 CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]);
4215 COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately
4216 }
4217 else
4218 CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]);
4219 #endif
4220 break;
4221 case PT_NODETIMEOUT:
4222 case PT_CLIENTQUIT:
4223 if (client)
4224 break;
4225
4226 // nodeingame will be put false in the execution of kick command
4227 // this allow to send some packets to the quitting client to have their ack back
4228 nodewaiting[node] = 0;
4229 if (netconsole != -1 && playeringame[netconsole])
4230 {
4231 UINT8 kickmsg;
4232
4233 if (netbuffer->packettype == PT_NODETIMEOUT)
4234 kickmsg = KICK_MSG_TIMEOUT;
4235 else
4236 kickmsg = KICK_MSG_PLAYER_QUIT;
4237 kickmsg |= KICK_MSG_KEEP_BODY;
4238
4239 SendKick(netconsole, kickmsg);
4240 nodetoplayer[node] = -1;
4241
4242 if (nodetoplayer2[node] != -1 && nodetoplayer2[node] >= 0
4243 && playeringame[(UINT8)nodetoplayer2[node]])
4244 {
4245 SendKick(nodetoplayer2[node], kickmsg);
4246 nodetoplayer2[node] = -1;
4247 }
4248 }
4249 Net_CloseConnection(node);
4250 nodeingame[node] = false;
4251 break;
4252 case PT_CANRECEIVEGAMESTATE:
4253 PT_CanReceiveGamestate(node);
4254 break;
4255 case PT_ASKLUAFILE:
4256 if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED)
4257 AddLuaFileToSendQueue(node, luafiletransfers->realfilename);
4258 break;
4259 case PT_HASLUAFILE:
4260 if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING)
4261 SV_HandleLuaFileSent(node);
4262 break;
4263 case PT_RECEIVEDGAMESTATE:
4264 sendingsavegame[node] = false;
4265 resendingsavegame[node] = false;
4266 savegameresendcooldown[node] = I_GetTime() + 5 * TICRATE;
4267 break;
4268 // -------------------------------------------- CLIENT RECEIVE ----------
4269 case PT_SERVERTICS:
4270 // Only accept PT_SERVERTICS from the server.
4271 if (node != servernode)
4272 {
4273 CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node);
4274 if (server)
4275 SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
4276 break;
4277 }
4278
4279 realstart = netbuffer->u.serverpak.starttic;
4280 realend = realstart + netbuffer->u.serverpak.numtics;
4281
4282 if (!txtpak)
4283 txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots
4284 * netbuffer->u.serverpak.numtics];
4285
4286 if (realend > gametic + CLIENTBACKUPTICS)
4287 realend = gametic + CLIENTBACKUPTICS;
4288 cl_packetmissed = realstart > neededtic;
4289
4290 if (realstart <= neededtic && realend > neededtic)
4291 {
4292 tic_t i, j;
4293 pak = (UINT8 *)&netbuffer->u.serverpak.cmds;
4294
4295 for (i = realstart; i < realend; i++)
4296 {
4297 // clear first
4298 D_Clearticcmd(i);
4299
4300 // copy the tics
4301 pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak,
4302 netbuffer->u.serverpak.numslots*sizeof (ticcmd_t));
4303
4304 // copy the textcmds
4305 numtxtpak = *txtpak++;
4306 for (j = 0; j < numtxtpak; j++)
4307 {
4308 INT32 k = *txtpak++; // playernum
4309 const size_t txtsize = txtpak[0]+1;
4310
4311 if (i >= gametic) // Don't copy old net commands
4312 M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize);
4313 txtpak += txtsize;
4314 }
4315 }
4316
4317 neededtic = realend;
4318 }
4319 else
4320 {
4321 DEBFILE(va("frame not in bound: %u\n", neededtic));
4322 /*if (realend < neededtic - 2 * TICRATE || neededtic + 2 * TICRATE < realstart)
4323 I_Error("Received an out of order PT_SERVERTICS packet!\n"
4324 "Got tics %d-%d, needed tic %d\n\n"
4325 "Please report this crash on the Master Board,\n"
4326 "IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
4327 }
4328 break;
4329 case PT_PING:
4330 // Only accept PT_PING from the server.
4331 if (node != servernode)
4332 {
4333 CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node);
4334 if (server)
4335 SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
4336 break;
4337 }
4338
4339 //Update client ping table from the server.
4340 if (client)
4341 {
4342 UINT8 i;
4343 for (i = 0; i < MAXPLAYERS; i++)
4344 if (playeringame[i])
4345 playerpingtable[i] = (tic_t)netbuffer->u.pingtable[i];
4346
4347 servermaxping = (tic_t)netbuffer->u.pingtable[MAXPLAYERS];
4348 }
4349
4350 break;
4351 case PT_SERVERCFG:
4352 break;
4353 case PT_FILEFRAGMENT:
4354 // Only accept PT_FILEFRAGMENT from the server.
4355 if (node != servernode)
4356 {
4357 CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node);
4358 if (server)
4359 SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
4360 break;
4361 }
4362 if (client)
4363 PT_FileFragment();
4364 break;
4365 case PT_FILEACK:
4366 if (server)
4367 PT_FileAck();
4368 break;
4369 case PT_FILERECEIVED:
4370 if (server)
4371 PT_FileReceived();
4372 break;
4373 case PT_WILLRESENDGAMESTATE:
4374 PT_WillResendGamestate();
4375 break;
4376 case PT_SENDINGLUAFILE:
4377 if (client)
4378 CL_PrepareDownloadLuaFile();
4379 break;
4380 default:
4381 DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n",
4382 netbuffer->packettype, node));
4383 } // end switch
4384 }
4385
4386 /** Handles all received packets, if any
4387 *
4388 * \todo Add details to this description (lol)
4389 *
4390 */
GetPackets(void)4391 static void GetPackets(void)
4392 {
4393 SINT8 node; // The packet sender
4394
4395 player_joining = false;
4396
4397 while (HGetPacket())
4398 {
4399 node = (SINT8)doomcom->remotenode;
4400
4401 if (netbuffer->packettype == PT_CLIENTJOIN && server)
4402 {
4403 HandleConnect(node);
4404 continue;
4405 }
4406 if (node == servernode && client && cl_mode != CL_SEARCHING)
4407 {
4408 if (netbuffer->packettype == PT_SERVERSHUTDOWN)
4409 {
4410 HandleShutdown(node);
4411 continue;
4412 }
4413 if (netbuffer->packettype == PT_NODETIMEOUT)
4414 {
4415 HandleTimeout(node);
4416 continue;
4417 }
4418 }
4419
4420 #ifndef NONET
4421 if (netbuffer->packettype == PT_SERVERINFO)
4422 {
4423 HandleServerInfo(node);
4424 continue;
4425 }
4426 #endif
4427
4428 if (netbuffer->packettype == PT_PLAYERINFO)
4429 continue; // We do nothing with PLAYERINFO, that's for the MS browser.
4430
4431 // Packet received from someone already playing
4432 if (nodeingame[node])
4433 HandlePacketFromPlayer(node);
4434 // Packet received from someone not playing
4435 else
4436 HandlePacketFromAwayNode(node);
4437 }
4438 }
4439
4440 //
4441 // NetUpdate
4442 // Builds ticcmds for console player,
4443 // sends out a packet
4444 //
4445 // no more use random generator, because at very first tic isn't yet synchronized
4446 // Note: It is called consistAncy on purpose.
4447 //
Consistancy(void)4448 static INT16 Consistancy(void)
4449 {
4450 INT32 i;
4451 UINT32 ret = 0;
4452 #ifdef MOBJCONSISTANCY
4453 thinker_t *th;
4454 mobj_t *mo;
4455 #endif
4456
4457 DEBFILE(va("TIC %u ", gametic));
4458
4459 for (i = 0; i < MAXPLAYERS; i++)
4460 {
4461 if (!playeringame[i])
4462 ret ^= 0xCCCC;
4463 else if (!players[i].mo);
4464 else
4465 {
4466 ret += players[i].mo->x;
4467 ret -= players[i].mo->y;
4468 ret += players[i].powers[pw_shield];
4469 ret *= i+1;
4470 }
4471 }
4472 // I give up
4473 // Coop desynching enemies is painful
4474 if (!G_PlatformGametype())
4475 ret += P_GetRandSeed();
4476
4477 #ifdef MOBJCONSISTANCY
4478 if (gamestate == GS_LEVEL)
4479 {
4480 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
4481 {
4482 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
4483 continue;
4484
4485 mo = (mobj_t *)th;
4486
4487 if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY))
4488 {
4489 ret -= mo->type;
4490 ret += mo->x;
4491 ret -= mo->y;
4492 ret += mo->z;
4493 ret -= mo->momx;
4494 ret += mo->momy;
4495 ret -= mo->momz;
4496 ret += mo->angle;
4497 ret -= mo->flags;
4498 ret += mo->flags2;
4499 ret -= mo->eflags;
4500 if (mo->target)
4501 {
4502 ret += mo->target->type;
4503 ret -= mo->target->x;
4504 ret += mo->target->y;
4505 ret -= mo->target->z;
4506 ret += mo->target->momx;
4507 ret -= mo->target->momy;
4508 ret += mo->target->momz;
4509 ret -= mo->target->angle;
4510 ret += mo->target->flags;
4511 ret -= mo->target->flags2;
4512 ret += mo->target->eflags;
4513 ret -= mo->target->state - states;
4514 ret += mo->target->tics;
4515 ret -= mo->target->sprite;
4516 ret += mo->target->frame;
4517 }
4518 else
4519 ret ^= 0x3333;
4520 if (mo->tracer && mo->tracer->type != MT_OVERLAY)
4521 {
4522 ret += mo->tracer->type;
4523 ret -= mo->tracer->x;
4524 ret += mo->tracer->y;
4525 ret -= mo->tracer->z;
4526 ret += mo->tracer->momx;
4527 ret -= mo->tracer->momy;
4528 ret += mo->tracer->momz;
4529 ret -= mo->tracer->angle;
4530 ret += mo->tracer->flags;
4531 ret -= mo->tracer->flags2;
4532 ret += mo->tracer->eflags;
4533 ret -= mo->tracer->state - states;
4534 ret += mo->tracer->tics;
4535 ret -= mo->tracer->sprite;
4536 ret += mo->tracer->frame;
4537 }
4538 else
4539 ret ^= 0xAAAA;
4540 ret -= mo->state - states;
4541 ret += mo->tics;
4542 ret -= mo->sprite;
4543 ret += mo->frame;
4544 }
4545 }
4546 }
4547 #endif
4548
4549 DEBFILE(va("Consistancy = %u\n", (ret & 0xFFFF)));
4550
4551 return (INT16)(ret & 0xFFFF);
4552 }
4553
4554 // send the client packet to the server
CL_SendClientCmd(void)4555 static void CL_SendClientCmd(void)
4556 {
4557 size_t packetsize = 0;
4558
4559 netbuffer->packettype = PT_CLIENTCMD;
4560
4561 if (cl_packetmissed)
4562 netbuffer->packettype++;
4563 netbuffer->u.clientpak.resendfrom = (UINT8)(neededtic & UINT8_MAX);
4564 netbuffer->u.clientpak.client_tic = (UINT8)(gametic & UINT8_MAX);
4565
4566 if (gamestate == GS_WAITINGPLAYERS)
4567 {
4568 // Send PT_NODEKEEPALIVE packet
4569 netbuffer->packettype += 4;
4570 packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
4571 HSendPacket(servernode, false, 0, packetsize);
4572 }
4573 else if (gamestate != GS_NULL && (addedtogame || dedicated))
4574 {
4575 G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
4576 netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
4577
4578 // Send a special packet with 2 cmd for splitscreen
4579 if (splitscreen || botingame)
4580 {
4581 netbuffer->packettype += 2;
4582 G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
4583 packetsize = sizeof (client2cmd_pak);
4584 }
4585 else
4586 packetsize = sizeof (clientcmd_pak);
4587
4588 HSendPacket(servernode, false, 0, packetsize);
4589 }
4590
4591 if (cl_mode == CL_CONNECTED || dedicated)
4592 {
4593 // Send extra data if needed
4594 if (localtextcmd[0])
4595 {
4596 netbuffer->packettype = PT_TEXTCMD;
4597 M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1);
4598 // All extra data have been sent
4599 if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail...
4600 localtextcmd[0] = 0;
4601 }
4602
4603 // Send extra data if needed for player 2 (splitscreen)
4604 if (localtextcmd2[0])
4605 {
4606 netbuffer->packettype = PT_TEXTCMD2;
4607 M_Memcpy(netbuffer->u.textcmd, localtextcmd2, localtextcmd2[0]+1);
4608 // All extra data have been sent
4609 if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // Send can fail...
4610 localtextcmd2[0] = 0;
4611 }
4612 }
4613 }
4614
4615 // send the server packet
4616 // send tic from firstticstosend to maketic-1
SV_SendTics(void)4617 static void SV_SendTics(void)
4618 {
4619 tic_t realfirsttic, lasttictosend, i;
4620 UINT32 n;
4621 INT32 j;
4622 size_t packsize;
4623 UINT8 *bufpos;
4624 UINT8 *ntextcmd;
4625
4626 // send to all client but not to me
4627 // for each node create a packet with x tics and send it
4628 // x is computed using supposedtics[n], max packet size and maketic
4629 for (n = 1; n < MAXNETNODES; n++)
4630 if (nodeingame[n])
4631 {
4632 // assert supposedtics[n]>=nettics[n]
4633 realfirsttic = supposedtics[n];
4634 lasttictosend = min(maketic, nettics[n] + CLIENTBACKUPTICS);
4635
4636 if (realfirsttic >= lasttictosend)
4637 {
4638 // well we have sent all tics we will so use extrabandwidth
4639 // to resent packet that are supposed lost (this is necessary since lost
4640 // packet detection work when we have received packet with firsttic > neededtic
4641 // (getpacket servertics case)
4642 DEBFILE(va("Nothing to send node %u mak=%u sup=%u net=%u \n",
4643 n, maketic, supposedtics[n], nettics[n]));
4644 realfirsttic = nettics[n];
4645 if (realfirsttic >= lasttictosend || (I_GetTime() + n)&3)
4646 // all tic are ok
4647 continue;
4648 DEBFILE(va("Sent %d anyway\n", realfirsttic));
4649 }
4650 if (realfirsttic < firstticstosend)
4651 realfirsttic = firstticstosend;
4652
4653 // compute the length of the packet and cut it if too large
4654 packsize = BASESERVERTICSSIZE;
4655 for (i = realfirsttic; i < lasttictosend; i++)
4656 {
4657 packsize += sizeof (ticcmd_t) * doomcom->numslots;
4658 packsize += TotalTextCmdPerTic(i);
4659
4660 if (packsize > software_MAXPACKETLENGTH)
4661 {
4662 DEBFILE(va("packet too large (%s) at tic %d (should be from %d to %d)\n",
4663 sizeu1(packsize), i, realfirsttic, lasttictosend));
4664 lasttictosend = i;
4665
4666 // too bad: too much player have send extradata and there is too
4667 // much data in one tic.
4668 // To avoid it put the data on the next tic. (see getpacket
4669 // textcmd case) but when numplayer changes the computation can be different
4670 if (lasttictosend == realfirsttic)
4671 {
4672 if (packsize > MAXPACKETLENGTH)
4673 I_Error("Too many players: can't send %s data for %d players to node %d\n"
4674 "Well sorry nobody is perfect....\n",
4675 sizeu1(packsize), doomcom->numslots, n);
4676 else
4677 {
4678 lasttictosend++; // send it anyway!
4679 DEBFILE("sending it anyway\n");
4680 }
4681 }
4682 break;
4683 }
4684 }
4685
4686 // Send the tics
4687 netbuffer->packettype = PT_SERVERTICS;
4688 netbuffer->u.serverpak.starttic = realfirsttic;
4689 netbuffer->u.serverpak.numtics = (UINT8)(lasttictosend - realfirsttic);
4690 netbuffer->u.serverpak.numslots = (UINT8)SHORT(doomcom->numslots);
4691 bufpos = (UINT8 *)&netbuffer->u.serverpak.cmds;
4692
4693 for (i = realfirsttic; i < lasttictosend; i++)
4694 {
4695 bufpos = G_DcpyTiccmd(bufpos, netcmds[i%BACKUPTICS], doomcom->numslots * sizeof (ticcmd_t));
4696 }
4697
4698 // add textcmds
4699 for (i = realfirsttic; i < lasttictosend; i++)
4700 {
4701 ntextcmd = bufpos++;
4702 *ntextcmd = 0;
4703 for (j = 0; j < MAXPLAYERS; j++)
4704 {
4705 UINT8 *textcmd = D_GetExistingTextcmd(i, j);
4706 INT32 size = textcmd ? textcmd[0] : 0;
4707
4708 if ((!j || playeringame[j]) && size)
4709 {
4710 (*ntextcmd)++;
4711 WRITEUINT8(bufpos, j);
4712 M_Memcpy(bufpos, textcmd, size + 1);
4713 bufpos += size + 1;
4714 }
4715 }
4716 }
4717 packsize = bufpos - (UINT8 *)&(netbuffer->u);
4718
4719 HSendPacket(n, false, 0, packsize);
4720 // when tic are too large, only one tic is sent so don't go backward!
4721 if (lasttictosend-doomcom->extratics > realfirsttic)
4722 supposedtics[n] = lasttictosend-doomcom->extratics;
4723 else
4724 supposedtics[n] = lasttictosend;
4725 if (supposedtics[n] < nettics[n]) supposedtics[n] = nettics[n];
4726 }
4727 // node 0 is me!
4728 supposedtics[0] = maketic;
4729 }
4730
4731 //
4732 // TryRunTics
4733 //
Local_Maketic(INT32 realtics)4734 static void Local_Maketic(INT32 realtics)
4735 {
4736 I_OsPolling(); // I_Getevent
4737 D_ProcessEvents(); // menu responder, cons responder,
4738 // game responder calls HU_Responder, AM_Responder,
4739 // and G_MapEventsToControls
4740 if (!dedicated) rendergametic = gametic;
4741 // translate inputs (keyboard/mouse/joystick) into game controls
4742 G_BuildTiccmd(&localcmds, realtics, 1);
4743 if (splitscreen || botingame)
4744 G_BuildTiccmd(&localcmds2, realtics, 2);
4745
4746 localcmds.angleturn |= TICCMD_RECEIVED;
4747 localcmds2.angleturn |= TICCMD_RECEIVED;
4748 }
4749
4750 // create missed tic
SV_Maketic(void)4751 static void SV_Maketic(void)
4752 {
4753 INT32 i;
4754
4755 for (i = 0; i < MAXPLAYERS; i++)
4756 {
4757 if (!playeringame[i])
4758 continue;
4759
4760 // We didn't receive this tic
4761 if ((netcmds[maketic % BACKUPTICS][i].angleturn & TICCMD_RECEIVED) == 0)
4762 {
4763 ticcmd_t * ticcmd = &netcmds[(maketic ) % BACKUPTICS][i];
4764 ticcmd_t *prevticcmd = &netcmds[(maketic - 1) % BACKUPTICS][i];
4765
4766 if (players[i].quittime)
4767 {
4768 // Copy the angle/aiming from the previous tic
4769 // and empty the other inputs
4770 memset(ticcmd, 0, sizeof(netcmds[0][0]));
4771 ticcmd->angleturn = prevticcmd->angleturn | TICCMD_RECEIVED;
4772 ticcmd->aiming = prevticcmd->aiming;
4773 }
4774 else
4775 {
4776 DEBFILE(va("MISS tic%4d for player %d\n", maketic, i));
4777 // Copy the input from the previous tic
4778 *ticcmd = *prevticcmd;
4779 ticcmd->angleturn &= ~TICCMD_RECEIVED;
4780 }
4781 }
4782 }
4783
4784 // all tic are now proceed make the next
4785 maketic++;
4786 }
4787
TryRunTics(tic_t realtics)4788 void TryRunTics(tic_t realtics)
4789 {
4790 // the machine has lagged but it is not so bad
4791 if (realtics > TICRATE/7) // FIXME: consistency failure!!
4792 {
4793 if (server)
4794 realtics = 1;
4795 else
4796 realtics = TICRATE/7;
4797 }
4798
4799 if (singletics)
4800 realtics = 1;
4801
4802 if (realtics >= 1)
4803 {
4804 COM_BufTicker();
4805 if (mapchangepending)
4806 D_MapChange(-1, 0, ultimatemode, false, 2, false, fromlevelselect); // finish the map change
4807 }
4808
4809 NetUpdate();
4810
4811 if (demoplayback)
4812 {
4813 neededtic = gametic + (realtics * cv_playbackspeed.value);
4814 // start a game after a demo
4815 maketic += realtics;
4816 firstticstosend = maketic;
4817 tictoclear = firstticstosend;
4818 }
4819
4820 GetPackets();
4821
4822 #ifdef DEBUGFILE
4823 if (debugfile && (realtics || neededtic > gametic))
4824 {
4825 //SoM: 3/30/2000: Need long INT32 in the format string for args 4 & 5.
4826 //Shut up stupid warning!
4827 fprintf(debugfile, "------------ Tryruntic: REAL:%d NEED:%d GAME:%d LOAD: %d\n",
4828 realtics, neededtic, gametic, debugload);
4829 debugload = 100000;
4830 }
4831 #endif
4832
4833 if (player_joining)
4834 return;
4835
4836 if (neededtic > gametic)
4837 {
4838 if (advancedemo)
4839 {
4840 if (timedemo_quit)
4841 COM_ImmedExecute("quit");
4842 else
4843 D_StartTitle();
4844 }
4845 else
4846 // run the count * tics
4847 while (neededtic > gametic)
4848 {
4849 DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
4850
4851 ps_tictime = I_GetPreciseTime();
4852
4853 G_Ticker((gametic % NEWTICRATERATIO) == 0);
4854 ExtraDataTicker();
4855 gametic++;
4856 consistancy[gametic%BACKUPTICS] = Consistancy();
4857
4858 ps_tictime = I_GetPreciseTime() - ps_tictime;
4859
4860 // Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
4861 if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value)
4862 break;
4863 }
4864 }
4865 }
4866
4867 /*
4868 Ping Update except better:
4869 We call this once per second and check for people's pings. If their ping happens to be too high, we increment some timer and kick them out.
4870 If they're not lagging, decrement the timer by 1. Of course, reset all of this if they leave.
4871 */
4872
4873 static INT32 pingtimeout[MAXPLAYERS];
4874
PingUpdate(void)4875 static inline void PingUpdate(void)
4876 {
4877 INT32 i;
4878 boolean laggers[MAXPLAYERS];
4879 UINT8 numlaggers = 0;
4880 memset(laggers, 0, sizeof(boolean) * MAXPLAYERS);
4881
4882 netbuffer->packettype = PT_PING;
4883
4884 //check for ping limit breakage.
4885 if (cv_maxping.value)
4886 {
4887 for (i = 1; i < MAXPLAYERS; i++)
4888 {
4889 if (playeringame[i] && !players[i].quittime
4890 && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value))
4891 {
4892 if (players[i].jointime > 30 * TICRATE)
4893 laggers[i] = true;
4894 numlaggers++;
4895 }
4896 else
4897 pingtimeout[i] = 0;
4898 }
4899
4900 //kick lagging players... unless everyone but the server's ping sucks.
4901 //in that case, it is probably the server's fault.
4902 if (numlaggers < D_NumPlayers() - 1)
4903 {
4904 for (i = 1; i < MAXPLAYERS; i++)
4905 {
4906 if (playeringame[i] && laggers[i])
4907 {
4908 pingtimeout[i]++;
4909 // ok your net has been bad for too long, you deserve to die.
4910 if (pingtimeout[i] > cv_pingtimeout.value)
4911 {
4912 pingtimeout[i] = 0;
4913 SendKick(i, KICK_MSG_PING_HIGH | KICK_MSG_KEEP_BODY);
4914 }
4915 }
4916 /*
4917 you aren't lagging,
4918 but you aren't free yet.
4919 In case you'll keep spiking,
4920 we just make the timer go back down. (Very unstable net must still get kicked).
4921 */
4922 else
4923 pingtimeout[i] = (pingtimeout[i] == 0 ? 0 : pingtimeout[i]-1);
4924 }
4925 }
4926 }
4927
4928 //make the ping packet and clear server data for next one
4929 for (i = 0; i < MAXPLAYERS; i++)
4930 {
4931 netbuffer->u.pingtable[i] = realpingtable[i] / pingmeasurecount;
4932 //server takes a snapshot of the real ping for display.
4933 //otherwise, pings fluctuate a lot and would be odd to look at.
4934 playerpingtable[i] = realpingtable[i] / pingmeasurecount;
4935 realpingtable[i] = 0; //Reset each as we go.
4936 }
4937
4938 // send the server's maxping as last element of our ping table. This is useful to let us know when we're about to get kicked.
4939 netbuffer->u.pingtable[MAXPLAYERS] = cv_maxping.value;
4940
4941 //send out our ping packets
4942 for (i = 0; i < MAXNETNODES; i++)
4943 if (nodeingame[i])
4944 HSendPacket(i, true, 0, sizeof(INT32) * (MAXPLAYERS+1));
4945
4946 pingmeasurecount = 1; //Reset count
4947 }
4948
NetUpdate(void)4949 void NetUpdate(void)
4950 {
4951 static tic_t gametime = 0;
4952 static tic_t resptime = 0;
4953 tic_t nowtime;
4954 INT32 i;
4955 INT32 realtics;
4956
4957 nowtime = I_GetTime();
4958 realtics = nowtime - gametime;
4959
4960 if (realtics <= 0) // nothing new to update
4961 return;
4962 if (realtics > 5)
4963 {
4964 if (server)
4965 realtics = 1;
4966 else
4967 realtics = 5;
4968 }
4969
4970 gametime = nowtime;
4971
4972 if (server)
4973 {
4974 if (netgame && !(gametime % 35)) // update once per second.
4975 PingUpdate();
4976 // update node latency values so we can take an average later.
4977 for (i = 0; i < MAXPLAYERS; i++)
4978 if (playeringame[i] && playernode[i] != UINT8_MAX)
4979 realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
4980 pingmeasurecount++;
4981 }
4982
4983 if (client)
4984 maketic = neededtic;
4985
4986 Local_Maketic(realtics); // make local tic, and call menu?
4987
4988 if (server)
4989 CL_SendClientCmd(); // send it
4990
4991 GetPackets(); // get packet from client or from server
4992
4993 // client send the command after a receive of the server
4994 // the server send before because in single player is beter
4995
4996 #ifdef MASTERSERVER
4997 MasterClient_Ticker(); // Acking the Master Server
4998 #endif
4999
5000 if (client)
5001 {
5002 // If the client just finished redownloading the game state, load it
5003 if (cl_redownloadinggamestate && fileneeded[0].status == FS_FOUND)
5004 CL_ReloadReceivedSavegame();
5005
5006 CL_SendClientCmd(); // Send tic cmd
5007 hu_redownloadinggamestate = cl_redownloadinggamestate;
5008 }
5009 else
5010 {
5011 if (!demoplayback)
5012 {
5013 INT32 counts;
5014
5015 hu_redownloadinggamestate = false;
5016
5017 firstticstosend = gametic;
5018 for (i = 0; i < MAXNETNODES; i++)
5019 if (nodeingame[i] && nettics[i] < firstticstosend)
5020 {
5021 firstticstosend = nettics[i];
5022
5023 if (maketic + 1 >= nettics[i] + BACKUPTICS)
5024 Net_ConnectionTimeout(i);
5025 }
5026
5027 // Don't erase tics not acknowledged
5028 counts = realtics;
5029
5030 if (maketic + counts >= firstticstosend + BACKUPTICS)
5031 counts = firstticstosend+BACKUPTICS-maketic-1;
5032
5033 for (i = 0; i < counts; i++)
5034 SV_Maketic(); // Create missed tics and increment maketic
5035
5036 for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged
5037 D_Clearticcmd(tictoclear); // Clear the maketic the new tic
5038
5039 SV_SendTics();
5040
5041 neededtic = maketic; // The server is a client too
5042 }
5043 }
5044
5045 Net_AckTicker();
5046
5047 // Handle timeouts to prevent definitive freezes from happenning
5048 if (server)
5049 {
5050 for (i = 1; i < MAXNETNODES; i++)
5051 if (nodeingame[i] && freezetimeout[i] < I_GetTime())
5052 Net_ConnectionTimeout(i);
5053
5054 // In case the cvar value was lowered
5055 if (joindelay)
5056 joindelay = min(joindelay - 1, 3 * (tic_t)cv_joindelay.value * TICRATE);
5057 }
5058
5059 nowtime /= NEWTICRATERATIO;
5060 if (nowtime > resptime)
5061 {
5062 resptime = nowtime;
5063 #ifdef HAVE_THREADS
5064 I_lock_mutex(&m_menu_mutex);
5065 #endif
5066 M_Ticker();
5067 #ifdef HAVE_THREADS
5068 I_unlock_mutex(m_menu_mutex);
5069 #endif
5070 CON_Ticker();
5071 }
5072
5073 FileSendTicker();
5074 }
5075
5076 /** Returns the number of players playing.
5077 * \return Number of players. Can be zero if we're running a ::dedicated
5078 * server.
5079 * \author Graue <graue@oceanbase.org>
5080 */
D_NumPlayers(void)5081 INT32 D_NumPlayers(void)
5082 {
5083 INT32 num = 0, ix;
5084 for (ix = 0; ix < MAXPLAYERS; ix++)
5085 if (playeringame[ix])
5086 num++;
5087 return num;
5088 }
5089
GetLag(INT32 node)5090 tic_t GetLag(INT32 node)
5091 {
5092 return gametic - nettics[node];
5093 }
5094
D_MD5PasswordPass(const UINT8 * buffer,size_t len,const char * salt,void * dest)5095 void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest)
5096 {
5097 #ifdef NOMD5
5098 (void)buffer;
5099 (void)len;
5100 (void)salt;
5101 memset(dest, 0, 16);
5102 #else
5103 char tmpbuf[256];
5104 const size_t sl = strlen(salt);
5105
5106 if (len > 256-sl)
5107 len = 256-sl;
5108
5109 memcpy(tmpbuf, buffer, len);
5110 memmove(&tmpbuf[len], salt, sl);
5111 //strcpy(&tmpbuf[len], salt);
5112 len += strlen(salt);
5113 if (len < 256)
5114 memset(&tmpbuf[len],0,256-len);
5115
5116 // Yes, we intentionally md5 the ENTIRE buffer regardless of size...
5117 md5_buffer(tmpbuf, 256, dest);
5118 #endif
5119 }
5120