1 /** @file d_netsv.cpp  Common code related to net games (server-side).
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2014 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 2007 Jamie Jones <jamie_jones_au@yahoo.com.au>
6  *
7  * @par License
8  * GPL: http://www.gnu.org/licenses/gpl.html
9  *
10  * <small>This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by the
12  * Free Software Foundation; either version 2 of the License, or (at your
13  * option) any later version. This program is distributed in the hope that it
14  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16  * Public License for more details. You should have received a copy of the GNU
17  * General Public License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA</small>
20  */
21 
22 #include "common.h"
23 #include "d_netsv.h"
24 
25 #include <cctype>
26 #include <cstdio>
27 #include <cstring>
28 #include <de/mathutil.h>
29 
30 #include "d_net.h"
31 #include "gamesession.h"
32 #include "player.h"
33 #include "p_user.h"
34 #include "p_map.h"
35 #include "mobj.h"
36 #include "p_actor.h"
37 #include "g_common.h"
38 #include "g_defs.h"
39 #include "p_tick.h"
40 #include "p_start.h"
41 #include "p_inventory.h"
42 
43 #ifdef __JHEXEN__
44 #  include "s_sequence.h"
45 #endif
46 
47 using namespace de;
48 using namespace common;
49 
50 #if __JHEXEN__
51 #  define SOUND_COUNTDOWN       SFX_PICKUP_KEY
52 #elif __JDOOM__ || __JDOOM64__
53 #  define SOUND_COUNTDOWN       SFX_GETPOW
54 #elif __JHERETIC__
55 #  define SOUND_COUNTDOWN       SFX_KEYUP
56 #endif
57 
58 #define SOUND_VICTORY           SOUND_COUNTDOWN
59 
60 struct maprule_t
61 {
62     dd_bool     usetime, usefrags;
63     int         time;           // Minutes.
64     int         frags;          // Maximum frags for one player.
65 };
66 
67 typedef enum cyclemode_s {
68     CYCLE_IDLE,
69     CYCLE_COUNTDOWN
70 } cyclemode_t;
71 
72 void R_SetAllDoomsdayFlags();
73 
74 /**
75  * Calculates the frags of player 'pl'.
76  */
77 int NetSv_GetFrags(int pl);
78 
79 void NetSv_MapCycleTicker(void);
80 
81 void NetSv_SendPlayerClass(int pnum, char cls);
82 
83 char cyclingMaps;
84 char *mapCycle = (char *)"";
85 char mapCycleNoExit = true;
86 int netSvAllowSendMsg = true;
87 int netSvAllowCheats;
88 
89 // This is returned in *_Get(DD_GAME_CONFIG). It contains a combination
90 // of space-separated keywords.
91 char gameConfigString[128];
92 
93 static int cycleIndex;
94 static int cycleCounter = -1, cycleMode = CYCLE_IDLE;
95 static int cycleRulesCounter[MAXPLAYERS];
96 
97 #if __JHERETIC__ || __JHEXEN__ || __JSTRIFE__
98 static int oldClasses[MAXPLAYERS];
99 #endif
100 
NetSv_UpdateGameConfigDescription()101 void NetSv_UpdateGameConfigDescription()
102 {
103     if (IS_CLIENT) return;
104 
105     GameRules const &gameRules = gfw_Session()->rules();
106 
107     QByteArray str = "skill" + QByteArray::number(gameRules.values.skill + 1);
108 
109     if (gameRules.values.deathmatch > 1)
110     {
111         str += " dm" + QByteArray::number(gameRules.values.deathmatch);
112     }
113     else if (gameRules.values.deathmatch)
114     {
115         str += " dm";
116     }
117     else
118     {
119         str += " coop";
120     }
121 
122     if (gameRules.values.noMonsters)
123     {
124         str += " nomonst";
125     }
126 #if !__JHEXEN__
127     if (gameRules.values.respawnMonsters)
128     {
129         str += " respawn";
130     }
131 #endif
132 
133     if (cfg.common.jumpEnabled)
134     {
135         str += " jump";
136     }
137 
138     strcpy(gameConfigString, str);
139 }
140 
NetSv_Ticker()141 void NetSv_Ticker()
142 {
143     // Map rotation checker.
144     NetSv_MapCycleTicker();
145 
146     // This is done here for servers.
147     R_SetAllDoomsdayFlags();
148 
149     // Set the camera filters for players.
150     for(int i = 0; i < MAXPLAYERS; ++i)
151     {
152         R_UpdateViewFilter(i);
153     }
154 
155 #ifdef __JHEXEN__
156     SN_UpdateActiveSequences();
157 #endif
158 
159     // Inform clients about jumping?
160     float power = (cfg.common.jumpEnabled ? cfg.common.jumpPower : 0);
161     if(power != netJumpPower)
162     {
163         netJumpPower = power;
164         for(int i = 0; i < MAXPLAYERS; ++i)
165         {
166             if(players[i].plr->inGame)
167             {
168                 NetSv_SendJumpPower(i, power);
169             }
170         }
171     }
172 
173     // Send the player state updates.
174     for(int i = 0; i < MAXPLAYERS; ++i)
175     {
176         player_t *plr = &players[i];
177 
178         if(!plr->plr->inGame)
179             continue;
180 
181         if(plr->update)
182         {
183             // Owned weapons and player state will be sent in the v2 packet.
184             if(plr->update & (PSF_OWNED_WEAPONS | PSF_STATE))
185             {
186                 int flags =
187                     (plr->update & PSF_OWNED_WEAPONS ? PSF2_OWNED_WEAPONS : 0) |
188                     (plr->update & PSF_STATE ? PSF2_STATE : 0);
189 
190                 NetSv_SendPlayerState2(i, i, flags, true);
191 
192                 plr->update &= ~(PSF_OWNED_WEAPONS | PSF_STATE);
193                 // That was all?
194                 if(!plr->update)
195                 {
196                     continue;
197                 }
198             }
199 
200             NetSv_SendPlayerState(i, i, plr->update, true);
201             plr->update = 0;
202         }
203 
204 #if __JHERETIC__ || __JHEXEN__
205         // Keep track of player class changes (fighter, cleric, mage, pig).
206         // Notify clients accordingly. This is mostly just FYI (it'll update
207         // pl->class_ on the clientside).
208         if(oldClasses[i] != plr->class_)
209         {
210             oldClasses[i] = plr->class_;
211             NetSv_SendPlayerClass(i, plr->class_);
212         }
213 #endif
214     }
215 }
216 
NetSv_CycleToMapNum(de::Uri const & mapUri)217 static void NetSv_CycleToMapNum(de::Uri const &mapUri)
218 {
219     de::String const warpCommand = de::String("warp ") + mapUri.compose(de::Uri::DecodePath);
220     DD_Execute(false, warpCommand.toUtf8().constData());
221 
222     // In a couple of seconds, send everyone the rules of this map.
223     for(int i = 0; i < MAXPLAYERS; ++i)
224     {
225         cycleRulesCounter[i] = 3 * TICSPERSEC;
226     }
227 
228     cycleMode    = CYCLE_IDLE;
229     cycleCounter = 0;
230 }
231 
232 /**
233  * Reads through the MapCycle cvar and finds the map with the given index.
234  * Rules that apply to the map are returned in 'rules'.
235  *
236  * @pre The game session has already begun. Necessary because the cycle rules
237  * for Hexen reference maps by "warp numbers", which, can only be resolved in
238  * the context of an episode.
239  */
NetSv_ScanCycle(int index,maprule_t * rules=0)240 static de::Uri NetSv_ScanCycle(int index, maprule_t *rules = 0)
241 {
242     bool clear = false, has_random = false;
243 
244     maprule_t dummy;
245     if(!rules) rules = &dummy;
246 
247     // By default no rules apply.
248     rules->usetime = rules->usefrags = false;
249 
250     int pos = -1;
251     for(char *ptr = mapCycle; *ptr; ptr++)
252     {
253         if(isspace(*ptr)) continue;
254 
255         if(*ptr == ',' || *ptr == '+' || *ptr == ';' || *ptr == '/' ||
256            *ptr == '\\')
257         {
258             // These symbols are allowed to combine "time" and "frags".
259             // E.g. "Time:10/Frags:5" or "t:30, f:10"
260             clear = false;
261         }
262         else if(!qstrnicmp("time", ptr, 1))
263         {
264             // Find the colon.
265             while(*ptr && *ptr != ':') { ptr++; }
266 
267             if(!*ptr) break;
268 
269             if(clear)
270             {
271                 rules->usefrags = false;
272             }
273             clear = true;
274 
275             char *end;
276             rules->usetime = true;
277             rules->time    = strtol(ptr + 1, &end, 0);
278 
279             ptr = end - 1;
280         }
281         else if(!qstrnicmp("frags", ptr, 1))
282         {
283             // Find the colon.
284             while(*ptr && *ptr != ':') { ptr++; }
285 
286             if(!*ptr) break;
287 
288             if(clear)
289             {
290                 rules->usetime = false;
291             }
292             clear = true;
293 
294             char *end;
295             rules->usefrags = true;
296             rules->frags    = strtol(ptr + 1, &end, 0);
297 
298             ptr = end - 1;
299         }
300         else if(*ptr == '*' || (*ptr >= '0' && *ptr <= '9'))
301         {
302             // A map identifier is here.
303             pos++;
304 
305             // Read it.
306             char tmp[3];
307             tmp[0] = *ptr++;
308             tmp[1] = de::clamp('0', *ptr, '9');
309             tmp[2] = 0;
310 
311             if(strlen(tmp) < 2)
312             {
313                 // Assume a zero is missing.
314                 tmp[1] = tmp[0];
315                 tmp[0] = '0';
316             }
317 
318             if(index == pos)
319             {
320                 // Are there randomized components?
321                 // (If so make many passes to try to find an existing map).
322                 if(tmp[0] == '*' || tmp[1] == '*')
323                 {
324                     has_random = true;
325                 }
326 
327                 for(int pass = 0; pass < (has_random? 100 : 1); ++pass)
328                 {
329                     uint map = 0;
330 #if !__JHEXEN__
331                     uint episode = 0;
332 #endif
333 #if __JDOOM__
334                     if(!(gameModeBits & GM_ANY_DOOM2))
335                     {
336                         episode = tmp[0] == '*' ? RNG_RandByte() % 9 : tmp[0] - '0';
337                         map     = tmp[1] == '*' ? RNG_RandByte() % 9 : tmp[1] - '0';
338                     }
339                     else
340                     {
341                         int tens = tmp[0] == '*' ? RNG_RandByte() % 10 : tmp[0] - '0';
342                         int ones = tmp[1] == '*' ? RNG_RandByte() % 10 : tmp[1] - '0';
343                         map = de::String("%1%2").arg(tens).arg(ones).toInt();
344                     }
345 #elif __JHERETIC__
346                     episode = tmp[0] == '*' ? RNG_RandByte() % 9 : tmp[0] - '0';
347                     map     = tmp[1] == '*' ? RNG_RandByte() % 9 : tmp[1] - '0';
348 
349 #else // __JHEXEN__ || __JDOOM64__
350                     int tens = tmp[0] == '*' ? RNG_RandByte() % 10 : tmp[0] - '0';
351                     int ones = tmp[1] == '*' ? RNG_RandByte() % 10 : tmp[1] - '0';
352                     map = de::String("%1%2").arg(tens).arg(ones).toInt();
353 #endif
354 
355 #if __JHEXEN__
356                     // In Hexen map numbers must be translated (urgh...).
357                     de::Uri mapUri = TranslateMapWarpNumber(gfw_Session()->episodeId(), map);
358 #else
359                     de::Uri mapUri = G_ComposeMapUri(episode, map);
360 #endif
361 
362                     if(P_MapExists(mapUri.compose().toUtf8().constData()))
363                     {
364                         return mapUri;
365                     }
366                 }
367 
368                 // This was the map we were looking for...
369                 break;
370             }
371         }
372     }
373 
374     // Didn't find it.
375     return de::Uri();
376 }
377 
NetSv_TellCycleRulesToPlayerAfterTics(int destPlr,int tics)378 void NetSv_TellCycleRulesToPlayerAfterTics(int destPlr, int tics)
379 {
380     if(destPlr >= 0 && destPlr < MAXPLAYERS)
381     {
382         cycleRulesCounter[destPlr] = tics;
383     }
384     else if((unsigned)destPlr == DDSP_ALL_PLAYERS)
385     {
386         for(int i = 0; i < MAXPLAYERS; ++i)
387         {
388             cycleRulesCounter[i] = tics;
389         }
390     }
391 }
392 
393 /**
394  * Sends a message about the map cycle rules to a specific player.
395  */
NetSv_TellCycleRulesToPlayer(int destPlr)396 void NetSv_TellCycleRulesToPlayer(int destPlr)
397 {
398     if(!cyclingMaps) return;
399 
400     LOGDEV_NET_VERBOSE("NetSv_TellCycleRulesToPlayer: %i") << destPlr;
401 
402     // Get the rules of the current map.
403     maprule_t rules;
404     NetSv_ScanCycle(cycleIndex, &rules);
405 
406     char msg[100];
407     strcpy(msg, "MAP RULES: ");
408     if(!rules.usetime && !rules.usefrags)
409     {
410         strcat(msg, "NONE");
411     }
412     else
413     {
414         char tmp[100];
415         if(rules.usetime)
416         {
417             sprintf(tmp, "%i MINUTES", rules.time);
418             strcat(msg, tmp);
419         }
420         if(rules.usefrags)
421         {
422             sprintf(tmp, "%s%i FRAGS", rules.usetime ? " OR " : "", rules.frags);
423             strcat(msg, tmp);
424         }
425     }
426 
427     NetSv_SendMessage(destPlr, msg);
428 }
429 
NetSv_MapCycleTicker()430 void NetSv_MapCycleTicker()
431 {
432     if(!cyclingMaps) return;
433 
434     // Check rules broadcasting.
435     for(int i = 0; i < MAXPLAYERS; ++i)
436     {
437         if(!cycleRulesCounter[i] || !players[i].plr->inGame)
438             continue;
439 
440         if(--cycleRulesCounter[i] == 0)
441         {
442             NetSv_TellCycleRulesToPlayer(i);
443         }
444     }
445 
446     cycleCounter--;
447 
448     switch(cycleMode)
449     {
450     case CYCLE_IDLE:
451         // Check if the current map should end.
452         if(cycleCounter <= 0)
453         {
454             // Test again in ten seconds time.
455             cycleCounter = 10 * TICSPERSEC;
456 
457             maprule_t rules;
458             if(NetSv_ScanCycle(cycleIndex, &rules).path().isEmpty())
459             {
460                 if(NetSv_ScanCycle(cycleIndex = 0, &rules).path().isEmpty())
461                 {
462                     // Hmm?! Abort cycling.
463                     LOG_MAP_WARNING("All of a sudden MapCycle is invalid; stopping cycle");
464                     DD_Execute(false, "endcycle");
465                     return;
466                 }
467             }
468 
469             if(rules.usetime &&
470                mapTime > (rules.time * 60 - 29) * TICSPERSEC)
471             {
472                 // Time runs out!
473                 cycleMode = CYCLE_COUNTDOWN;
474                 cycleCounter = 31 * TICSPERSEC;
475             }
476 
477             if(rules.usefrags)
478             {
479                 for(int i = 0; i < MAXPLAYERS; i++)
480                 {
481                     if(!players[i].plr->inGame)
482                         continue;
483 
484                     int frags = NetSv_GetFrags(i);
485                     if(frags >= rules.frags)
486                     {
487                         char msg[100]; sprintf(msg, "--- %s REACHES %i FRAGS ---", Net_GetPlayerName(i), frags);
488                         NetSv_SendMessage(DDSP_ALL_PLAYERS, msg);
489                         S_StartSound(SOUND_VICTORY, NULL);
490 
491                         cycleMode = CYCLE_COUNTDOWN;
492                         cycleCounter = 15 * TICSPERSEC; // No msg for 15 secs.
493                         break;
494                     }
495                 }
496             }
497         }
498         break;
499 
500     case CYCLE_COUNTDOWN:
501         if(cycleCounter == 30 * TICSPERSEC ||
502            cycleCounter == 15 * TICSPERSEC ||
503            cycleCounter == 10 * TICSPERSEC ||
504            cycleCounter == 5 * TICSPERSEC)
505         {
506             char msg[100]; sprintf(msg, "--- WARPING IN %i SECONDS ---", cycleCounter / TICSPERSEC);
507             NetSv_SendMessage(DDSP_ALL_PLAYERS, msg);
508             // Also, a warning sound.
509             S_StartSound(SOUND_COUNTDOWN, NULL);
510         }
511         else if(cycleCounter <= 0)
512         {
513             // Next map, please!
514             de::Uri mapUri = NetSv_ScanCycle(++cycleIndex);
515             if(mapUri.path().isEmpty())
516             {
517                 // Must be past the end?
518                 mapUri = NetSv_ScanCycle(cycleIndex = 0);
519                 if(mapUri.path().isEmpty())
520                 {
521                     // Hmm?! Abort cycling.
522                     LOG_MAP_WARNING("All of a sudden MapCycle is invalid; stopping cycle");
523                     DD_Execute(false, "endcycle");
524                     return;
525                 }
526             }
527 
528             // Warp to the next map. Don't bother with the intermission.
529             NetSv_CycleToMapNum(mapUri);
530         }
531         break;
532     }
533 }
534 
NetSv_ResetPlayerFrags(int plrNum)535 void NetSv_ResetPlayerFrags(int plrNum)
536 {
537     LOGDEV_NET_VERBOSE("NetSv_ResetPlayerFrags: Player %i") << plrNum;
538 
539     player_t *plr = &players[plrNum];
540     de::zap(plr->frags);
541 
542     // The frag count is dependent on the others' frags.
543     for(int i = 0; i < MAXPLAYERS; ++i)
544     {
545         players[i].frags[plrNum] = 0;
546 
547         // Everybody will get their frags updated.
548         players[i].update |= PSF_FRAGS;
549     }
550 }
551 
NetSv_NewPlayerEnters(int plrNum)552 void NetSv_NewPlayerEnters(int plrNum)
553 {
554     LOGDEV_MSG("NetSv_NewPlayerEnters: player %i") << plrNum;
555 
556     player_t *plr = &players[plrNum];
557     plr->playerState = PST_REBORN;  // Force an init.
558 
559     // Re-deal player starts.
560     P_DealPlayerStarts(0);
561 
562     // Reset the player's frags.
563     NetSv_ResetPlayerFrags(plrNum);
564 
565     // Spawn the player into the world.
566     if(gfw_Rule(deathmatch))
567     {
568         G_DeathMatchSpawnPlayer(plrNum);
569     }
570     else
571     {
572         playerclass_t pClass = P_ClassForPlayerWhenRespawning(plrNum, false);
573         playerstart_t const *start;
574 
575         if((start = P_GetPlayerStart(gfw_Session()->mapEntryPoint(), plrNum, false)))
576         {
577             mapspot_t const *spot = &mapSpots[start->spot];
578 
579             LOGDEV_MAP_MSG("NetSv_NewPlayerEnters: Spawning player with angle:%x") << spot->angle;
580 
581             P_SpawnPlayer(plrNum, pClass, spot->origin[VX], spot->origin[VY],
582                           spot->origin[VZ], spot->angle, spot->flags,
583                           false, true);
584         }
585         else
586         {
587             P_SpawnPlayer(plrNum, pClass, 0, 0, 0, 0, MSF_Z_FLOOR, true, true);
588         }
589 
590         /// @todo Spawn a telefog in front of the player.
591     }
592 
593     // Get rid of anybody at the starting spot.
594     P_Telefrag(plr->plr->mo);
595 
596     NetSv_TellCycleRulesToPlayerAfterTics(plrNum, 5 * TICSPERSEC);
597     NetSv_SendTotalCounts(plrNum);
598 }
599 
NetSv_Intermission(int flags,int state,int time)600 void NetSv_Intermission(int flags, int state, int time)
601 {
602     if(IS_CLIENT) return;
603 
604     writer_s *msg = D_NetWrite();
605     Writer_WriteByte(msg, flags);
606 
607     /// @todo jHeretic does not transmit the intermission info!
608 #if !defined(__JHERETIC__)
609     if(flags & IMF_BEGIN)
610     {
611         // Only include the necessary information.
612 #  if __JDOOM__ || __JDOOM64__
613         Writer_WriteUInt16(msg, ::wmInfo.maxKills);
614         Writer_WriteUInt16(msg, ::wmInfo.maxItems);
615         Writer_WriteUInt16(msg, ::wmInfo.maxSecret);
616 #  endif
617         Uri_Write(reinterpret_cast<uri_s *>(&::wmInfo.nextMap), msg);
618 #  if __JHEXEN__
619         Writer_WriteByte(msg, ::wmInfo.nextMapEntryPoint);
620 #  else
621         Uri_Write(reinterpret_cast<uri_s *>(&::wmInfo.currentMap), msg);
622 #  endif
623 #  if __JDOOM__ || __JDOOM64__
624         Writer_WriteByte(msg, ::wmInfo.didSecret);
625 #  endif
626     }
627 #  endif
628 
629     if(flags & IMF_STATE)
630     {
631         Writer_WriteInt16(msg, state);
632     }
633 
634     if(flags & IMF_TIME)
635     {
636         Writer_WriteInt16(msg, time);
637     }
638 
639     Net_SendPacket(DDSP_ALL_PLAYERS, GPT_INTERMISSION, Writer_Data(msg), Writer_Size(msg));
640 }
641 
NetSv_SendTotalCounts(int to)642 void NetSv_SendTotalCounts(int to)
643 {
644     // Hexen does not have total counts.
645 #ifndef __JHEXEN__
646     if(IS_CLIENT) return;
647 
648     writer_s *writer = D_NetWrite();
649     Writer_WriteInt32(writer, totalKills);
650     Writer_WriteInt32(writer, totalItems);
651     Writer_WriteInt32(writer, totalSecret);
652 
653     // Send the packet.
654     Net_SendPacket(to, GPT_TOTAL_COUNTS, Writer_Data(writer), Writer_Size(writer));
655 #else
656     DENG2_UNUSED(to);
657 #endif
658 }
659 
NetSv_SendGameState(int flags,int to)660 void NetSv_SendGameState(int flags, int to)
661 {
662     if(!IS_NETWORK_SERVER) return;
663 
664     AutoStr *gameId    = AutoStr_FromTextStd(gfw_GameId().toLatin1().constData());
665     AutoStr *episodeId = AutoStr_FromTextStd(gfw_Session()->episodeId().toLatin1().constData());
666     de::Uri mapUri     = gfw_Session()->mapUri();
667 
668     // Print a short message that describes the game state.
669     LOG_NET_NOTE("Sending game setup: %s %s %s %s")
670             << Str_Text(gameId)
671             << Str_Text(episodeId)
672             << mapUri.resolved()
673             << gameConfigString;
674 
675     // Send an update to all the players in the game.
676     for(int i = 0; i < MAXPLAYERS; ++i)
677     {
678         if(!players[i].plr->inGame) continue;
679         if((unsigned)to != DDSP_ALL_PLAYERS && to != i) continue;
680 
681         writer_s *writer = D_NetWrite();
682         Writer_WriteByte(writer, flags);
683 
684         // Game identity key.
685         Str_Write(gameId, writer);
686 
687         // Current map.
688         Uri_Write(reinterpret_cast<uri_s *>(&mapUri), writer);
689 
690         // Current episode.
691         Str_Write(episodeId, writer);
692 
693         // Old map number. Presently unused.
694         Writer_WriteByte(writer, 0);
695 
696         Writer_WriteByte(writer, (gfw_Rule(deathmatch) & 0x3)
697             | (!gfw_Rule(noMonsters)? 0x4 : 0)
698 #if !__JHEXEN__
699             | (gfw_Rule(respawnMonsters)? 0x8 : 0)
700 #else
701             | 0
702 #endif
703             | (cfg.common.jumpEnabled? 0x10 : 0));
704 
705         // Note that SM_NOTHINGS will result in a value of '7'.
706         Writer_WriteByte(writer, gfw_Rule(skill) & 0x7);
707         Writer_WriteFloat(writer, (float)P_GetGravity());
708 
709         if(flags & GSF_CAMERA_INIT)
710         {
711             mobj_t *mo = players[i].plr->mo;
712             Writer_WriteFloat(writer, mo->origin[VX]);
713             Writer_WriteFloat(writer, mo->origin[VY]);
714             Writer_WriteFloat(writer, mo->origin[VZ]);
715             Writer_WriteUInt32(writer, mo->angle);
716         }
717 
718         // Send the packet.
719         Net_SendPacket(i, GPT_GAME_STATE, Writer_Data(writer), Writer_Size(writer));
720     }
721 }
722 
NetSv_PlayerMobjImpulse(mobj_t * mobj,float mx,float my,float mz)723 void NetSv_PlayerMobjImpulse(mobj_t *mobj, float mx, float my, float mz)
724 {
725     if(!IS_SERVER) return;
726 
727     if(!mobj || !mobj->player) return;
728 
729     // Which player?
730     int plrNum = mobj->player - players;
731 
732     writer_s *writer = D_NetWrite();
733     Writer_WriteUInt16(writer, mobj->thinker.id);
734     Writer_WriteFloat(writer, mx);
735     Writer_WriteFloat(writer, my);
736     Writer_WriteFloat(writer, mz);
737 
738     Net_SendPacket(plrNum, GPT_MOBJ_IMPULSE, Writer_Data(writer), Writer_Size(writer));
739 }
740 
NetSv_DismissHUDs(int plrNum,dd_bool fast)741 void NetSv_DismissHUDs(int plrNum, dd_bool fast)
742 {
743     if(!IS_SERVER) return;
744     if(plrNum < 1 || plrNum >= DDMAXPLAYERS) return;
745 
746     writer_s *writer = D_NetWrite();
747     Writer_WriteByte(writer, fast? 1 : 0);
748 
749     Net_SendPacket(plrNum, GPT_DISMISS_HUDS, Writer_Data(writer), Writer_Size(writer));
750 }
751 
NetSv_SendPlayerSpawnPosition(int plrNum,float x,float y,float z,int angle)752 void NetSv_SendPlayerSpawnPosition(int plrNum, float x, float y, float z, int angle)
753 {
754     if(!IS_SERVER) return;
755 
756     LOGDEV_NET_MSG("NetSv_SendPlayerSpawnPosition: Player #%i pos:%s angle:%x")
757             << plrNum << de::Vector3f(x, y, z).asText() << angle;
758 
759     writer_s *writer = D_NetWrite();
760     Writer_WriteFloat(writer, x);
761     Writer_WriteFloat(writer, y);
762     Writer_WriteFloat(writer, z);
763     Writer_WriteUInt32(writer, angle);
764 
765     Net_SendPacket(plrNum, GPT_PLAYER_SPAWN_POSITION,
766                    Writer_Data(writer), Writer_Size(writer));
767 }
768 
NetSv_SendPlayerState2(int srcPlrNum,int destPlrNum,int flags,dd_bool)769 void NetSv_SendPlayerState2(int srcPlrNum, int destPlrNum, int flags, dd_bool /*reliable*/)
770 {
771     int pType = (srcPlrNum == destPlrNum ? GPT_CONSOLEPLAYER_STATE2 : GPT_PLAYER_STATE2);
772     player_t *pl = &players[srcPlrNum];
773 
774     // Check that this is a valid call.
775     if(IS_CLIENT || !pl->plr->inGame ||
776        (destPlrNum >= 0 && destPlrNum < MAXPLAYERS &&
777         !players[destPlrNum].plr->inGame))
778         return;
779 
780     writer_s *writer = D_NetWrite();
781 
782     // Include the player number if necessary.
783     if(pType == GPT_PLAYER_STATE2)
784     {
785         Writer_WriteByte(writer, srcPlrNum);
786     }
787     Writer_WriteUInt32(writer, flags);
788 
789     if(flags & PSF2_OWNED_WEAPONS)
790     {
791         // This supports up to 16 weapons.
792         int fl = 0;
793         for(int i = 0; i < NUM_WEAPON_TYPES; ++i)
794         {
795             if(pl->weapons[i].owned)
796                 fl |= 1 << i;
797         }
798         Writer_WriteUInt16(writer, fl);
799     }
800 
801     if(flags & PSF2_STATE)
802     {
803         Writer_WriteByte(writer, pl->playerState |
804 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__ // Hexen doesn't have armortype.
805             (pl->armorType << 4));
806 #else
807             0);
808 #endif
809         Writer_WriteByte(writer, pl->cheats);
810     }
811 
812     // Finally, send the packet.
813     Net_SendPacket(destPlrNum, pType, Writer_Data(writer), Writer_Size(writer));
814 }
815 
NetSv_SendPlayerState(int srcPlrNum,int destPlrNum,int flags,dd_bool)816 void NetSv_SendPlayerState(int srcPlrNum, int destPlrNum, int flags, dd_bool /*reliable*/)
817 {
818     int pType = (srcPlrNum == destPlrNum ? GPT_CONSOLEPLAYER_STATE : GPT_PLAYER_STATE);
819     player_t *pl = &players[srcPlrNum];
820 
821     if(!IS_NETWORK_SERVER || !pl->plr->inGame ||
822        (destPlrNum >= 0 && destPlrNum < MAXPLAYERS && !players[destPlrNum].plr->inGame))
823         return;
824 
825     LOGDEV_NET_MSG("NetSv_SendPlayerState: src=%i, dest=%i, flags=%x") << srcPlrNum << destPlrNum << flags;
826 
827     writer_s *writer = D_NetWrite();
828 
829     // Include the player number if necessary.
830     if(pType == GPT_PLAYER_STATE)
831     {
832         Writer_WriteByte(writer, srcPlrNum);
833     }
834 
835     // The first bytes contain the flags.
836     Writer_WriteUInt16(writer, flags);
837     if(flags & PSF_STATE)
838     {
839         Writer_WriteByte(writer, pl->playerState |
840 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__ // Hexen doesn't have armortype.
841             (pl->armorType << 4));
842 #else
843             0);
844 #endif
845     }
846 
847     if(flags & PSF_HEALTH)
848     {
849         Writer_WriteByte(writer, pl->health);
850     }
851 
852     if(flags & PSF_ARMOR_POINTS)
853     {
854 #if __JHEXEN__
855         // Hexen has many types of armor points, send them all.
856         for(int i = 0; i < NUMARMOR; ++i)
857         {
858             Writer_WriteByte(writer, pl->armorPoints[i]);
859         }
860 #else
861         Writer_WriteByte(writer, pl->armorPoints);
862 #endif
863     }
864 
865 #if __JHERETIC__ || __JHEXEN__
866     if(flags & PSF_INVENTORY)
867     {
868         uint count = 0;
869         for(int i = 0; i < NUM_INVENTORYITEM_TYPES; ++i)
870         {
871             count += P_InventoryCount(srcPlrNum, inventoryitemtype_t(IIT_FIRST + i))? 1 : 0;
872         }
873 
874         Writer_WriteByte(writer, count);
875         if(count)
876         {
877             for(int i = 0; i < NUM_INVENTORYITEM_TYPES; ++i)
878             {
879                 inventoryitemtype_t type = inventoryitemtype_t(IIT_FIRST + i);
880                 uint num = P_InventoryCount(srcPlrNum, type);
881 
882                 if(num)
883                 {
884                     Writer_WriteUInt16(writer, (type & 0xff) | ((num & 0xff) << 8));
885                 }
886             }
887         }
888     }
889 #endif
890 
891     if(flags & PSF_POWERS)
892     {
893         byte powers = 0;
894 
895         // First see which powers should be sent.
896 #if __JHEXEN__ || __JSTRIFE__
897         for(int i = 1; i < NUM_POWER_TYPES; ++i)
898         {
899             if(pl->powers[i])
900             {
901                 powers |= 1 << (i - 1);
902             }
903         }
904 #else
905         for(int i = 0; i < NUM_POWER_TYPES; ++i)
906         {
907 #  if __JDOOM__ || __JDOOM64__
908             if(i == PT_IRONFEET || i == PT_STRENGTH)
909                 continue;
910 #  endif
911             if(pl->powers[i])
912             {
913                 powers |= 1 << i;
914             }
915         }
916 #endif
917         Writer_WriteByte(writer, powers);
918 
919         // Send the non-zero powers.
920 #if __JHEXEN__ || __JSTRIFE__
921         for(int i = 1; i < NUM_POWER_TYPES; ++i)
922         {
923             if(pl->powers[i])
924             {
925                 Writer_WriteByte(writer, (pl->powers[i] + 34) / 35);
926             }
927         }
928 #else
929         for(int i = 0; i < NUM_POWER_TYPES; ++i)
930         {
931 #  if __JDOOM__ || __JDOOM64__
932             if(i == PT_IRONFEET || i == PT_STRENGTH)
933                 continue;
934 #  endif
935             if(pl->powers[i])
936             {
937                 Writer_WriteByte(writer, (pl->powers[i] + 34) / 35); // Send as seconds.
938             }
939         }
940 #endif
941     }
942 
943     if(flags & PSF_KEYS)
944     {
945         byte keys = 0;
946 
947 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
948         for(int i = 0; i < NUM_KEY_TYPES; ++i)
949         {
950             if(pl->keys[i])
951                 keys |= 1 << i;
952         }
953 #endif
954 #if __JHEXEN__
955         keys = pl->keys;
956 #endif
957 
958         Writer_WriteByte(writer, keys);
959     }
960 
961     if(flags & PSF_FRAGS)
962     {
963         // How many are there?
964         byte count = 0;
965         for(int i = 0; i < MAXPLAYERS; ++i)
966         {
967             if(pl->frags[i] > 0) count++;
968         }
969         Writer_WriteByte(writer, count);
970 
971         // We'll send all non-zero frags. The topmost four bits of
972         // the word define the player number.
973         for(int i = 0; i < MAXPLAYERS; ++i)
974         {
975             if(pl->frags[i] > 0)
976             {
977                 Writer_WriteUInt16(writer, (i << 12) | pl->frags[i]);
978             }
979         }
980     }
981 
982     if(flags & PSF_OWNED_WEAPONS)
983     {
984         int ownedBits = 0;
985         for(int i = 0; i < NUM_WEAPON_TYPES; ++i)
986         {
987             if(pl->weapons[i].owned)
988                 ownedBits |= 1 << i;
989         }
990         Writer_WriteByte(writer, ownedBits);
991     }
992 
993     if(flags & PSF_AMMO)
994     {
995         for(int i = 0; i < NUM_AMMO_TYPES; ++i)
996         {
997             Writer_WriteInt16(writer, pl->ammo[i].owned);
998         }
999     }
1000 
1001     if(flags & PSF_MAX_AMMO)
1002     {
1003 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__ // Hexen has no use for max ammo.
1004         for(int i = 0; i < NUM_AMMO_TYPES; ++i)
1005         {
1006             Writer_WriteInt16(writer, pl->ammo[i].max);
1007         }
1008 #endif
1009     }
1010 
1011     if(flags & PSF_COUNTERS)
1012     {
1013         Writer_WriteInt16(writer, pl->killCount);
1014         Writer_WriteByte(writer, pl->itemCount);
1015         Writer_WriteByte(writer, pl->secretCount);
1016     }
1017 
1018     if((flags & PSF_PENDING_WEAPON) || (flags & PSF_READY_WEAPON))
1019     {
1020         // These two will be in the same byte.
1021         byte fl = 0;
1022         if(flags & PSF_PENDING_WEAPON)
1023             fl |= pl->pendingWeapon & 0xf;
1024         if(flags & PSF_READY_WEAPON)
1025             fl |= (pl->readyWeapon & 0xf) << 4;
1026         Writer_WriteByte(writer, fl);
1027     }
1028 
1029     if(flags & PSF_VIEW_HEIGHT)
1030     {
1031         // @todo Do clients really need to know this?
1032         Writer_WriteByte(writer, (byte) pl->viewHeight);
1033     }
1034 
1035 #if __JHERETIC__ || __JHEXEN__ || __JSTRIFE__
1036     if(flags & PSF_MORPH_TIME)
1037     {
1038         App_Log(DE2_DEV_NET_MSG,
1039                 "NetSv_SendPlayerState: Player %i, sending morph tics as %i seconds",
1040                 srcPlrNum, (pl->morphTics + 34) / 35);
1041 
1042         // Send as seconds.
1043         Writer_WriteByte(writer, (pl->morphTics + 34) / 35);
1044     }
1045 #endif
1046 
1047 #if defined(HAVE_EARTHQUAKE) || __JSTRIFE__
1048     if(flags & PSF_LOCAL_QUAKE)
1049     {
1050         // Send the "quaking" state.
1051         Writer_WriteByte(writer, localQuakeHappening[srcPlrNum]);
1052     }
1053 #endif
1054 
1055     // Finally, send the packet.
1056     Net_SendPacket(destPlrNum, pType,
1057                    Writer_Data(writer), Writer_Size(writer));
1058 }
1059 
NetSv_SendPlayerInfo(int whose,int toWhom)1060 void NetSv_SendPlayerInfo(int whose, int toWhom)
1061 {
1062     if(IS_CLIENT) return;
1063 
1064     writer_s *writer = D_NetWrite();
1065     Writer_WriteByte(writer, whose);
1066     Writer_WriteByte(writer, cfg.playerColor[whose]);
1067 
1068 #if __JHERETIC__ || __JHEXEN__
1069     Writer_WriteByte(writer, cfg.playerClass[whose]); // current class
1070 #endif
1071     Net_SendPacket(toWhom, GPT_PLAYER_INFO, Writer_Data(writer), Writer_Size(writer));
1072 }
1073 
NetSv_ChangePlayerInfo(int from,reader_s * msg)1074 void NetSv_ChangePlayerInfo(int from, reader_s *msg)
1075 {
1076     player_t *pl = &players[from];
1077 
1078     // Color is first.
1079     int col = Reader_ReadByte(msg);
1080     cfg.playerColor[from] = PLR_COLOR(from, col);
1081 
1082     // Player class.
1083     playerclass_t newClass = playerclass_t(Reader_ReadByte(msg));
1084     P_SetPlayerRespawnClass(from, newClass); // requesting class change?
1085 
1086     App_Log(DE2_DEV_NET_NOTE,
1087             "NetSv_ChangePlayerInfo: pl%i, col=%i, requested class=%i",
1088             from, cfg.playerColor[from], newClass);
1089 
1090     // The 'colorMap' variable controls the setting of the color
1091     // translation flags when the player is (re)spawned.
1092     pl->colorMap = cfg.playerColor[from];
1093 
1094     if(pl->plr->mo)
1095     {
1096         // Change the player's mobj's color translation flags.
1097         pl->plr->mo->flags &= ~MF_TRANSLATION;
1098         pl->plr->mo->flags |= cfg.playerColor[from] << MF_TRANSSHIFT;
1099     }
1100 
1101     if(pl->plr->mo)
1102     {
1103         App_Log(DE2_DEV_NET_XVERBOSE,
1104                 "Player %i mo %i translation flags %x", from, pl->plr->mo->thinker.id,
1105                 (pl->plr->mo->flags & MF_TRANSLATION) >> MF_TRANSSHIFT);
1106     }
1107 
1108     // Re-deal start spots.
1109     P_DealPlayerStarts(0);
1110 
1111     // Tell the other clients about the change.
1112     NetSv_SendPlayerInfo(from, DDSP_ALL_PLAYERS);
1113 }
1114 
NetSv_FragsForAll(player_t * player)1115 void NetSv_FragsForAll(player_t *player)
1116 {
1117     DENG2_ASSERT(player != 0);
1118     NetSv_SendPlayerState(player - players, DDSP_ALL_PLAYERS, PSF_FRAGS, true);
1119 }
1120 
NetSv_GetFrags(int pl)1121 int NetSv_GetFrags(int pl)
1122 {
1123     int count = 0;
1124     for(int i = 0; i < MAXPLAYERS; ++i)
1125     {
1126 #if __JDOOM__ || __JDOOM64__
1127         count += players[pl].frags[i] * (i == pl ? -1 : 1);
1128 #else
1129         count += players[pl].frags[i];
1130 #endif
1131     }
1132     return count;
1133 }
1134 
NetSv_KillMessage(player_t * killer,player_t * fragged,dd_bool stomping)1135 void NetSv_KillMessage(player_t *killer, player_t *fragged, dd_bool stomping)
1136 {
1137 #if __JDOOM__ || __JDOOM64__
1138     if(!cfg.killMessages) return;
1139     if(!gfw_Rule(deathmatch)) return;
1140 
1141     char buf[500];
1142     buf[0] = 0;
1143 
1144     char tmp[2];
1145     tmp[1] = 0;
1146 
1147     // Choose the right kill message template.
1148     char const *in = GET_TXT(stomping ? TXT_KILLMSG_STOMP : killer ==
1149                              fragged ? TXT_KILLMSG_SUICIDE : TXT_KILLMSG_WEAPON0 +
1150                              killer->readyWeapon);
1151 
1152     for(; *in; in++)
1153     {
1154         if(in[0] == '%')
1155         {
1156             if(in[1] == '1')
1157             {
1158                 strcat(buf, Net_GetPlayerName(killer - players));
1159                 in++;
1160                 continue;
1161             }
1162 
1163             if(in[1] == '2')
1164             {
1165                 strcat(buf, Net_GetPlayerName(fragged - players));
1166                 in++;
1167                 continue;
1168             }
1169 
1170             if(in[1] == '%')
1171             {
1172                 in++;
1173             }
1174         }
1175         tmp[0] = *in;
1176         strcat(buf, tmp);
1177     }
1178 
1179     // Send the message to everybody.
1180     NetSv_SendMessage(DDSP_ALL_PLAYERS, buf);
1181 #else
1182     DENG2_UNUSED3(killer, fragged, stomping);
1183 #endif
1184 }
1185 
NetSv_SendPlayerClass(int plrNum,char cls)1186 void NetSv_SendPlayerClass(int plrNum, char cls)
1187 {
1188     App_Log(DE2_DEV_NET_MSG, "NetSv_SendPlayerClass: Player %i has class %i", plrNum, cls);
1189 
1190     writer_s *writer = D_NetWrite();
1191     Writer_WriteByte(writer, cls);
1192     Net_SendPacket(plrNum, GPT_CLASS, Writer_Data(writer), Writer_Size(writer));
1193 }
1194 
NetSv_SendJumpPower(int target,float power)1195 void NetSv_SendJumpPower(int target, float power)
1196 {
1197     if(!IS_SERVER) return;
1198 
1199     writer_s *writer = D_NetWrite();
1200     Writer_WriteFloat(writer, power);
1201     Net_SendPacket(target, GPT_JUMP_POWER, Writer_Data(writer), Writer_Size(writer));
1202 }
1203 
NetSv_ExecuteCheat(int player,char const * command)1204 void NetSv_ExecuteCheat(int player, char const *command)
1205 {
1206     // Killing self is always allowed.
1207     /// @todo fixme: really? Even in deathmatch?? (should be a game rule)
1208     if(!qstrnicmp(command, "suicide", 7))
1209     {
1210         DD_Executef(false, "suicide %i", player);
1211     }
1212 
1213     // If cheating is not allowed, we ain't doing nuthin'.
1214     if(!netSvAllowCheats)
1215     {
1216         NetSv_SendMessage(player, "--- CHEATS DISABLED ON THIS SERVER ---");
1217         return;
1218     }
1219 
1220     /// @todo Can't we use the multipurpose cheat command here?
1221     if(!qstrnicmp(command, "god", 3)
1222        || !qstrnicmp(command, "noclip", 6)
1223        || !qstrnicmp(command, "give", 4)
1224        || !qstrnicmp(command, "kill", 4)
1225 #ifdef __JHERETIC__
1226        || !qstrnicmp(command, "chicken", 7)
1227 #elif __JHEXEN__
1228        || !qstrnicmp(command, "class", 5)
1229        || !qstrnicmp(command, "pig", 3)
1230        || !qstrnicmp(command, "runscript", 9)
1231 #endif
1232        )
1233     {
1234         DD_Executef(false, "%s %i", command, player);
1235     }
1236 }
1237 
NetSv_DoCheat(int player,reader_s * msg)1238 void NetSv_DoCheat(int player, reader_s *msg)
1239 {
1240     size_t len = Reader_ReadUInt16(msg);
1241     char *command = (char *)Z_Calloc(len + 1, PU_GAMESTATIC, 0);
1242 
1243     Reader_Read(msg, command, len);
1244     NetSv_ExecuteCheat(player, command);
1245     Z_Free(command);
1246 }
1247 
1248 /**
1249  * Calls @a callback on @a thing while it is temporarily placed at the
1250  * specified position and angle. Afterwards the thing's old position is restored.
1251  */
NetSv_TemporaryPlacedCallback(mobj_t * thing,void * param,coord_t tempOrigin[3],angle_t angle,void (* callback)(mobj_t *,void *))1252 void NetSv_TemporaryPlacedCallback(mobj_t *thing, void *param, coord_t tempOrigin[3],
1253     angle_t angle, void (*callback)(mobj_t *, void *))
1254 {
1255     coord_t const oldOrigin[3] = { thing->origin[VX], thing->origin[VY], thing->origin[VZ] };
1256     coord_t const oldFloorZ    = thing->floorZ;
1257     coord_t const oldCeilingZ  = thing->ceilingZ;
1258     angle_t const oldAngle     = thing->angle;
1259 
1260     // We will temporarily move the object to the temp coords.
1261     if(P_CheckPosition(thing, tempOrigin))
1262     {
1263         P_MobjUnlink(thing);
1264         thing->origin[VX] = tempOrigin[VX];
1265         thing->origin[VY] = tempOrigin[VY];
1266         thing->origin[VZ] = tempOrigin[VZ];
1267         P_MobjLink(thing);
1268         thing->floorZ = tmFloorZ;
1269         thing->ceilingZ = tmCeilingZ;
1270     }
1271     thing->angle = angle;
1272 
1273     callback(thing, param);
1274 
1275     // Restore the old position.
1276     P_MobjUnlink(thing);
1277     thing->origin[VX] = oldOrigin[VX];
1278     thing->origin[VY] = oldOrigin[VY];
1279     thing->origin[VZ] = oldOrigin[VZ];
1280     P_MobjLink(thing);
1281     thing->floorZ = oldFloorZ;
1282     thing->ceilingZ = oldCeilingZ;
1283     thing->angle = oldAngle;
1284 }
1285 
NetSv_UseActionCallback(mobj_t *,void * context)1286 static void NetSv_UseActionCallback(mobj_t * /*mo*/, void *context)
1287 {
1288     P_UseLines(static_cast<player_t *>(context));
1289 }
1290 
NetSv_FireWeaponCallback(mobj_t *,void * context)1291 static void NetSv_FireWeaponCallback(mobj_t * /*mo*/, void *context)
1292 {
1293     P_FireWeapon(static_cast<player_t *>(context));
1294 }
1295 
NetSv_HitFloorCallback(mobj_t * mo,void *)1296 static void NetSv_HitFloorCallback(mobj_t *mo, void * /*context*/)
1297 {
1298     App_Log(DE2_DEV_MAP_XVERBOSE, "NetSv_HitFloorCallback: mo %i", mo->thinker.id);
1299 
1300     P_HitFloor(mo);
1301 }
1302 
NetSv_DoFloorHit(int player,reader_s * msg)1303 void NetSv_DoFloorHit(int player, reader_s *msg)
1304 {
1305     player_t *plr = &players[player];
1306 
1307     if(player < 0 || player >= MAXPLAYERS)
1308         return;
1309 
1310     mobj_t *mo = plr->plr->mo;
1311     if(!mo) return;
1312 
1313     coord_t pos[3];
1314     pos[VX] = Reader_ReadFloat(msg);
1315     pos[VY] = Reader_ReadFloat(msg);
1316     pos[VZ] = Reader_ReadFloat(msg);
1317 
1318     // The momentum is included, although we don't really need it.
1319     /*mom[MX] =*/ Reader_ReadFloat(msg);
1320     /*mom[MY] =*/ Reader_ReadFloat(msg);
1321     /*mom[MZ] =*/ Reader_ReadFloat(msg);
1322 
1323     NetSv_TemporaryPlacedCallback(mo, 0, pos, mo->angle, NetSv_HitFloorCallback);
1324 }
1325 
NetSv_DoAction(int player,reader_s * msg)1326 void NetSv_DoAction(int player, reader_s *msg)
1327 {
1328     player_t *pl = &players[player];
1329 
1330     int type        = Reader_ReadInt32(msg);
1331     coord_t pos[3];
1332     pos[VX]         = Reader_ReadFloat(msg);
1333     pos[VY]         = Reader_ReadFloat(msg);
1334     pos[VZ]         = Reader_ReadFloat(msg);
1335     angle_t angle   = Reader_ReadUInt32(msg);
1336     float lookDir   = Reader_ReadFloat(msg);
1337     int actionParam = Reader_ReadInt32(msg);
1338 
1339     App_Log(DE2_DEV_MAP_VERBOSE,
1340             "NetSv_DoAction: player=%i, action=%i, xyz=(%.1f,%.1f,%.1f)\n  "
1341             "angle=%x lookDir=%g param=%i",
1342             player, type, pos[VX], pos[VY], pos[VZ],
1343             angle, lookDir, actionParam);
1344 
1345     if(G_GameState() != GS_MAP)
1346     {
1347         if(G_GameState() == GS_INTERMISSION)
1348         {
1349             if(type == GPA_USE || type == GPA_FIRE)
1350             {
1351                 App_Log(DE2_NET_MSG, "Intermission skip requested");
1352                 IN_SkipToNext();
1353             }
1354         }
1355         return;
1356     }
1357 
1358     if(pl->playerState == PST_DEAD)
1359     {
1360         // This player is dead. Rise, my friend!
1361         P_PlayerReborn(pl);
1362         return;
1363     }
1364 
1365     switch(type)
1366     {
1367     case GPA_USE:
1368     case GPA_FIRE:
1369         if(pl->plr->mo)
1370         {
1371             // Update lookdir to match client's direction at the time of the action.
1372             pl->plr->lookDir = lookDir;
1373 
1374             if(type == GPA_FIRE)
1375             {
1376                 pl->refire = actionParam;
1377             }
1378 
1379             NetSv_TemporaryPlacedCallback(pl->plr->mo, pl, pos, angle,
1380                                           type == GPA_USE? NetSv_UseActionCallback :
1381                                                            NetSv_FireWeaponCallback);
1382         }
1383         break;
1384 
1385     case GPA_CHANGE_WEAPON:
1386         pl->brain.changeWeapon = actionParam;
1387         break;
1388 
1389     case GPA_USE_FROM_INVENTORY:
1390 #if __JHERETIC__ || __JHEXEN__ || __JDOOM64__
1391         P_InventoryUse(player, inventoryitemtype_t(actionParam), true);
1392 #endif
1393         break;
1394     }
1395 }
1396 
NetSv_DoDamage(int player,reader_s * msg)1397 void NetSv_DoDamage(int player, reader_s *msg)
1398 {
1399     int damage       = Reader_ReadInt32(msg);
1400     thid_t target    = Reader_ReadUInt16(msg);
1401     thid_t inflictor = Reader_ReadUInt16(msg);
1402     thid_t source    = Reader_ReadUInt16(msg);
1403 
1404     App_Log(DE2_DEV_MAP_XVERBOSE,
1405             "NetSv_DoDamage: Client %i requests damage %i on %i via %i by %i",
1406             player, damage, target, inflictor, source);
1407 
1408     P_DamageMobj2(Mobj_ById(target), Mobj_ById(inflictor), Mobj_ById(source), damage,
1409                   false /*not stomping*/, true /*just do it*/);
1410 }
1411 
NetSv_SaveGame(uint sessionId)1412 void NetSv_SaveGame(uint sessionId)
1413 {
1414 #if !__JHEXEN__
1415 
1416     if(!IS_SERVER || !IS_NETGAME)
1417         return;
1418 
1419     // This will make the clients save their games.
1420     writer_s *writer = D_NetWrite();
1421     Writer_WriteUInt32(writer, sessionId);
1422     Net_SendPacket(DDSP_ALL_PLAYERS, GPT_SAVE, Writer_Data(writer), Writer_Size(writer));
1423 
1424 #else
1425     DENG2_UNUSED(sessionId);
1426 #endif
1427 }
1428 
NetSv_LoadGame(uint sessionId)1429 void NetSv_LoadGame(uint sessionId)
1430 {
1431 #if !__JHEXEN__
1432 
1433     if(!IS_SERVER || !IS_NETGAME)
1434         return;
1435 
1436     writer_s *writer = D_NetWrite();
1437     Writer_WriteUInt32(writer, sessionId);
1438     Net_SendPacket(DDSP_ALL_PLAYERS, GPT_LOAD, Writer_Data(writer), Writer_Size(writer));
1439 
1440 #else
1441     DENG2_UNUSED(sessionId);
1442 #endif
1443 }
1444 
NetSv_SendMessageEx(int plrNum,char const * msg,dd_bool yellow)1445 void NetSv_SendMessageEx(int plrNum, char const *msg, dd_bool yellow)
1446 {
1447     if(IS_CLIENT || !netSvAllowSendMsg)
1448         return;
1449 
1450     if(plrNum >= 0 && plrNum < MAXPLAYERS)
1451     {
1452         if(!players[plrNum].plr->inGame)
1453             return;
1454     }
1455 
1456     App_Log(DE2_DEV_NET_VERBOSE, "NetSv_SendMessageEx: '%s'", msg);
1457 
1458     if((unsigned)plrNum == DDSP_ALL_PLAYERS)
1459     {
1460         // Also show locally. No sound is played!
1461         D_NetMessageNoSound(CONSOLEPLAYER, msg);
1462     }
1463 
1464     writer_s *writer = D_NetWrite();
1465     Writer_WriteUInt16(writer, uint16_t(strlen(msg)));
1466     Writer_Write(writer, msg, strlen(msg));
1467     Net_SendPacket(plrNum,
1468                    yellow ? GPT_YELLOW_MESSAGE : GPT_MESSAGE,
1469                    Writer_Data(writer), Writer_Size(writer));
1470 }
1471 
NetSv_SendMessage(int plrNum,char const * msg)1472 void NetSv_SendMessage(int plrNum, char const *msg)
1473 {
1474     NetSv_SendMessageEx(plrNum, msg, false);
1475 }
1476 
NetSv_SendYellowMessage(int plrNum,char const * msg)1477 void NetSv_SendYellowMessage(int plrNum, char const *msg)
1478 {
1479     NetSv_SendMessageEx(plrNum, msg, true);
1480 }
1481 
NetSv_MaybeChangeWeapon(int plrNum,int weapon,int ammo,int force)1482 void NetSv_MaybeChangeWeapon(int plrNum, int weapon, int ammo, int force)
1483 {
1484     if(IS_CLIENT) return;
1485 
1486     if(plrNum < 0 || plrNum >= MAXPLAYERS)
1487         return;
1488 
1489     App_Log(DE2_DEV_NET_VERBOSE,
1490             "NetSv_MaybeChangeWeapon: Plr=%i Weapon=%i Ammo=%i Force=%i",
1491             plrNum, weapon, ammo, force);
1492 
1493     writer_s *writer = D_NetWrite();
1494     Writer_WriteInt16(writer, weapon);
1495     Writer_WriteInt16(writer, ammo);
1496     Writer_WriteByte(writer, force != 0);
1497     Net_SendPacket(plrNum, GPT_MAYBE_CHANGE_WEAPON, Writer_Data(writer), Writer_Size(writer));
1498 }
1499 
NetSv_SendLocalMobjState(mobj_t * mobj,char const * stateName)1500 void NetSv_SendLocalMobjState(mobj_t *mobj, char const *stateName)
1501 {
1502     DENG2_ASSERT(mobj != 0);
1503 
1504     ddstring_t name;
1505     Str_InitStatic(&name, stateName);
1506 
1507     // Inform the client about this.
1508     writer_s *msg = D_NetWrite();
1509     Writer_WriteUInt16(msg, mobj->thinker.id);
1510     Writer_WriteUInt16(msg, mobj->target? mobj->target->thinker.id : 0); // target id
1511     Str_Write(&name, msg); // state to switch to
1512 #if !defined(__JDOOM__) && !defined(__JDOOM64__)
1513     Writer_WriteInt32(msg, mobj->special1);
1514 #else
1515     Writer_WriteInt32(msg, 0);
1516 #endif
1517 
1518     Net_SendPacket(DDSP_ALL_PLAYERS, GPT_LOCAL_MOBJ_STATE, Writer_Data(msg), Writer_Size(msg));
1519 }
1520 
1521 /**
1522  * Handles the console commands "startcycle" and "endcycle".
1523  */
D_CMD(MapCycle)1524 D_CMD(MapCycle)
1525 {
1526     DENG2_UNUSED2(src, argc);
1527 
1528     if(!IS_SERVER)
1529     {
1530         App_Log(DE2_SCR_ERROR, "Only allowed for a server");
1531         return false;
1532     }
1533 
1534     if(!qstricmp(argv[0], "startcycle")) // (Re)start rotation?
1535     {
1536         // Find the first map in the sequence.
1537         de::Uri mapUri = NetSv_ScanCycle(cycleIndex = 0);
1538         if(mapUri.path().isEmpty())
1539         {
1540             App_Log(DE2_SCR_ERROR, "MapCycle \"%s\" is invalid.", mapCycle);
1541             return false;
1542         }
1543         for(int i = 0; i < MAXPLAYERS; ++i)
1544         {
1545             cycleRulesCounter[i] = 0;
1546         }
1547         // Warp there.
1548         NetSv_CycleToMapNum(mapUri);
1549         cyclingMaps = true;
1550     }
1551     else
1552     {
1553         // OK, then we need to end it.
1554         if(cyclingMaps)
1555         {
1556             cyclingMaps = false;
1557             NetSv_SendMessage(DDSP_ALL_PLAYERS, "MAP ROTATION ENDS");
1558         }
1559     }
1560 
1561     return true;
1562 }
1563