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