1 /** @file d_net.cpp Common code related to net games.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2006-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, see:
18 * http://www.gnu.org/licenses</small>
19 */
20
21 #include "common.h"
22 #include "d_net.h"
23
24 #include <de/RecordValue>
25 #include "d_netcl.h"
26 #include "d_netsv.h"
27 #include "fi_lib.h"
28 #include "g_common.h"
29 #include "g_defs.h"
30 #include "gamesession.h"
31 #include "hu_menu.h"
32 #include "p_mapsetup.h"
33 #include "p_start.h"
34 #include "player.h"
35
36 using namespace de;
37 using namespace common;
38
39 D_CMD(SetColor);
40 #if __JHEXEN__
41 D_CMD(SetClass);
42 #endif
43 D_CMD(LocalMessage);
44
45 static void D_NetMessageEx(int player, char const *msg, dd_bool playSound);
46
47 float netJumpPower = 9;
48
49 static writer_s *netWriter;
50 static reader_s *netReader;
51
notifyAllowCheatsChange()52 static void notifyAllowCheatsChange()
53 {
54 if(IS_NETGAME && IS_NETWORK_SERVER && G_GameState() != GS_STARTUP)
55 {
56 String const msg = String("--- CHEATS NOW %1 ON THIS SERVER ---")
57 .arg(netSvAllowCheats? "ENABLED" : "DISABLED");
58 NetSv_SendMessage(DDSP_ALL_PLAYERS, msg.toUtf8().constData());
59 }
60 }
61
D_NetDefaultEpisode()62 String D_NetDefaultEpisode()
63 {
64 return FirstPlayableEpisodeId();
65 }
66
D_NetDefaultMap()67 de::Uri D_NetDefaultMap()
68 {
69 String const episodeId = D_NetDefaultEpisode();
70
71 de::Uri map("Maps:", RC_NULL);
72 if(!episodeId.isEmpty())
73 {
74 map = de::makeUri(Defs().episodes.find("id", episodeId).gets("startMap"));
75 DENG2_ASSERT(!map.isEmpty());
76 }
77 return map;
78 }
79
D_NetConsoleRegister()80 void D_NetConsoleRegister()
81 {
82 C_VAR_CHARPTR("mapcycle", &mapCycle, CVF_HIDE | CVF_NO_ARCHIVE, 0, 0);
83
84 C_CMD ("setcolor", "i", SetColor);
85 #if __JHEXEN__
86 C_CMD_FLAGS ("setclass", "i", SetClass, CMDF_NO_DEDICATED);
87 #endif
88 C_CMD ("startcycle", "", MapCycle);
89 C_CMD ("endcycle", "", MapCycle);
90 C_CMD ("message", "s", LocalMessage);
91
92 if(IS_DEDICATED)
93 {
94 C_VAR_CHARPTR("server-game-episode", &cfg.common.netEpisode, 0, 0, 0);
95 C_VAR_URIPTR ("server-game-map", &cfg.common.netMap, 0, 0, 0);
96
97 // Use the first playable map as the default.
98 String episodeId = D_NetDefaultEpisode();
99 de::Uri map = D_NetDefaultMap();
100
101 Con_SetString("server-game-episode", episodeId.toUtf8().constData());
102 Con_SetUri ("server-game-map", reinterpret_cast<uri_s *>(&map));
103 }
104
105 /// @todo "server-*" cvars should only be registered by dedicated servers.
106 //if(IS_DEDICATED) return;
107
108 #if !__JHEXEN__
109 C_VAR_BYTE ("server-game-announce-secret", &cfg.secretMsg, 0, 0, 1);
110 #endif
111 #if __JDOOM__ || __JDOOM64__
112 C_VAR_BYTE ("server-game-bfg-freeaim", &cfg.netBFGFreeLook, 0, 0, 1);
113 #endif
114 C_VAR_INT2 ("server-game-cheat", &netSvAllowCheats, 0, 0, 1, notifyAllowCheatsChange);
115 #if __JDOOM__ || __JDOOM64__
116 C_VAR_BYTE ("server-game-deathmatch", &cfg.common.netDeathmatch, 0, 0, 2);
117 #else
118 C_VAR_BYTE ("server-game-deathmatch", &cfg.common.netDeathmatch, 0, 0, 1);
119 #endif
120 C_VAR_BYTE ("server-game-jump", &cfg.common.netJumping, 0, 0, 1);
121 C_VAR_CHARPTR("server-game-mapcycle", &mapCycle, 0, 0, 0);
122 C_VAR_BYTE ("server-game-mapcycle-noexit", &mapCycleNoExit, 0, 0, 1);
123 #if __JHERETIC__
124 C_VAR_BYTE ("server-game-maulotaur-fixfloorfire", &cfg.fixFloorFire, 0, 0, 1);
125 #endif
126 C_VAR_BYTE ("server-game-monster-meleeattack-nomaxz", &cfg.common.netNoMaxZMonsterMeleeAttack,0, 0, 1);
127 #if __JDOOM__ || __JDOOM64__
128 C_VAR_BYTE ("server-game-nobfg", &cfg.noNetBFG, 0, 0, 1);
129 #endif
130 C_VAR_BYTE ("server-game-nomonsters", &cfg.common.netNoMonsters, 0, 0, 1);
131 #if !__JHEXEN__
132 C_VAR_BYTE ("server-game-noteamdamage", &cfg.noTeamDamage, 0, 0, 1);
133 #endif
134 #if __JHERETIC__
135 C_VAR_BYTE ("server-game-plane-fixmaterialscroll", &cfg.fixPlaneScrollMaterialsEastOnly, 0, 0, 1);
136 #endif
137 C_VAR_BYTE ("server-game-radiusattack-nomaxz", &cfg.common.netNoMaxZRadiusAttack, 0, 0, 1);
138 #if __JHEXEN__
139 C_VAR_BYTE ("server-game-randclass", &cfg.netRandomClass, 0, 0, 1);
140 #endif
141 #if !__JHEXEN__
142 C_VAR_BYTE ("server-game-respawn", &cfg.netRespawn, 0, 0, 1);
143 #endif
144 #if __JDOOM__ || __JHERETIC__
145 C_VAR_BYTE ("server-game-respawn-monsters-nightmare", &cfg.respawnMonstersNightmare, 0, 0, 1);
146 #endif
147 C_VAR_BYTE ("server-game-skill", &cfg.common.netSkill, 0, 0, 4);
148
149 // Modifiers:
150 C_VAR_BYTE ("server-game-mod-damage", &cfg.common.netMobDamageModifier, 0, 1, 100);
151 C_VAR_INT ("server-game-mod-gravity", &cfg.common.netGravity, 0, -1, 100);
152 C_VAR_BYTE ("server-game-mod-health", &cfg.common.netMobHealthModifier, 0, 1, 20);
153
154 // Coop:
155 #if !__JHEXEN__
156 C_VAR_BYTE ("server-game-coop-nodamage", &cfg.noCoopDamage, 0, 0, 1);
157 #endif
158 #if __JDOOM__ || __JDOOM64__
159 //C_VAR_BYTE ("server-game-coop-nothing", &cfg.noCoopAnything, 0, 0, 1); // not implemented atm, see P_SpawnMobjXYZ
160 C_VAR_BYTE ("server-game-coop-noweapons", &cfg.noCoopWeapons, 0, 0, 1);
161 C_VAR_BYTE ("server-game-coop-respawn-items", &cfg.coopRespawnItems, 0, 0, 1);
162 #endif
163
164 // Deathmatch:
165 #if __JDOOM__ || __JDOOM64__
166 C_VAR_BYTE ("server-game-deathmatch-killmsg", &cfg.killMessages, 0, 0, 1);
167 #endif
168 }
169
D_NetWrite()170 writer_s *D_NetWrite()
171 {
172 if(netWriter)
173 {
174 Writer_Delete(netWriter);
175 }
176 netWriter = Writer_NewWithDynamicBuffer(0 /*unlimited*/);
177 return netWriter;
178 }
179
D_NetRead(byte const * buffer,size_t len)180 reader_s *D_NetRead(byte const *buffer, size_t len)
181 {
182 // Get rid of the old reader.
183 if(netReader)
184 {
185 Reader_Delete(netReader);
186 }
187 netReader = Reader_NewWithBuffer(buffer, len);
188 return netReader;
189 }
190
D_NetClearBuffer()191 void D_NetClearBuffer()
192 {
193 if(netReader) Reader_Delete(netReader);
194 if(netWriter) Writer_Delete(netWriter);
195
196 netReader = 0;
197 netWriter = 0;
198 }
199
D_NetServerStarted(int before)200 int D_NetServerStarted(int before)
201 {
202 if(before) return true;
203
204 // We're the server, so...
205 ::cfg.playerColor[0] = PLR_COLOR(0, ::cfg.common.netColor);
206
207 #if __JHEXEN__
208 ::cfg.playerClass[0] = playerclass_t(::cfg.netClass);
209 #elif __JHERETIC__
210 ::cfg.playerClass[0] = PCLASS_PLAYER;
211 #endif
212 P_ResetPlayerRespawnClasses();
213
214 String episodeId = Con_GetString("server-game-episode");
215 de::Uri mapUri = *reinterpret_cast<de::Uri const *>(Con_GetUri("server-game-map"));
216 if(mapUri.scheme().isEmpty()) mapUri.setScheme("Maps");
217
218 GameRules rules(gfw_Session()->rules()); // Make a copy of the current rules.
219 GameRules_Set(rules, skill, skillmode_t(cfg.common.netSkill));
220
221 gfw_Session()->end();
222
223 try
224 {
225 // First try the configured map.
226 gfw_Session()->begin(rules, episodeId, mapUri);
227 }
228 catch(Error const &er)
229 {
230 LOGDEV_ERROR("Failed to start server: %s") << er.asText();
231 episodeId = D_NetDefaultEpisode();
232 mapUri = D_NetDefaultMap();
233 LOG_INFO("Using the default map (%s) to start the server due to failure to load the configured map")
234 << mapUri;
235
236 gfw_Session()->begin(rules, episodeId, mapUri);
237 }
238
239 G_SetGameAction(GA_NONE); /// @todo Necessary?
240
241 return true;
242 }
243
D_NetServerClose(int before)244 int D_NetServerClose(int before)
245 {
246 if (!before)
247 {
248 P_ResetPlayerRespawnClasses();
249
250 // Restore normal game state.
251 /// @todo fixme: "normal" is defined by the game rules config which may
252 /// be changed from the command line (e.g., -fast, -nomonsters).
253 /// In order to "restore normal" this logic is insufficient.
254 GameRules newRules(gfw_Session()->rules());
255 GameRules_Set(newRules, deathmatch, 0);
256 GameRules_Set(newRules, noMonsters, false);
257 #if __JHEXEN__
258 GameRules_Set(newRules, randomClasses, false);
259 #endif
260 gfw_Session()->applyNewRules(newRules);
261
262 D_NetMessage(CONSOLEPLAYER, "NETGAME ENDS");
263 D_NetClearBuffer();
264 }
265 return true;
266 }
267
D_NetConnect(int before)268 int D_NetConnect(int before)
269 {
270 if(before)
271 {
272 BusyMode_FreezeGameForBusyMode();
273 return true;
274 }
275
276 // After connecting we tell the server a bit about ourselves.
277 NetCl_SendPlayerInfo();
278
279 // Close the menu, the game begins!!
280 // advancedemo = false;
281 Hu_MenuCommand(MCMD_CLOSE);
282 return true;
283 }
284
D_NetDisconnect(int before)285 int D_NetDisconnect(int before)
286 {
287 if(before)
288 {
289 // Free PU_MAP, Zone-allocated storage for the local world state.
290 P_ResetWorldState();
291 return true;
292 }
293
294 D_NetClearBuffer();
295
296 // Start demo.
297 gfw_Session()->endAndBeginTitle();
298
299 /*GameRules newRules(gfw_Session()->rules());
300 newRules.deathmatch = false;
301 newRules.noMonsters = false;
302 #if __JHEXEN__
303 newRules.randomClasses = false;
304 #endif
305 gfw_Session()->applyNewRules(newRules);*/
306
307 return true;
308 }
309
D_NetPlayerEvent(int plrNumber,int peType,void * data)310 long int D_NetPlayerEvent(int plrNumber, int peType, void *data)
311 {
312 // If this isn't a netgame, we won't react.
313 if(!IS_NETGAME)
314 return true;
315
316 if(peType == DDPE_ARRIVAL)
317 {
318 dd_bool showmsg = true;
319
320 if(IS_SERVER)
321 {
322 NetSv_NewPlayerEnters(plrNumber);
323 }
324 else if(plrNumber == CONSOLEPLAYER)
325 {
326 // We have arrived, the game should be begun.
327 App_Log(DE2_NET_NOTE, "Arrived in netgame, waiting for data...");
328 G_ChangeGameState(GS_WAITING);
329 showmsg = false;
330 }
331 else
332 {
333 // Client responds to new player?
334 App_Log(DE2_LOG_NOTE, "Player %i has arrived in the game", plrNumber);
335 P_RebornPlayerInMultiplayer(plrNumber);
336 //players[plrNumber].playerstate = PST_REBORN;
337 }
338 if(showmsg)
339 {
340 // Print a notification.
341 AutoStr *str = AutoStr_New();
342 Str_Appendf(str, "%s joined the game", Net_GetPlayerName(plrNumber));
343 D_NetMessage(CONSOLEPLAYER, Str_Text(str));
344 }
345 }
346 else if(peType == DDPE_EXIT)
347 {
348 AutoStr *str = AutoStr_New();
349
350 App_Log(DE2_LOG_NOTE, "Player %i has left the game", plrNumber);
351
352 players[plrNumber].playerState = playerstate_t(PST_GONE);
353
354 // Print a notification.
355 Str_Appendf(str, "%s left the game", Net_GetPlayerName(plrNumber));
356 D_NetMessage(CONSOLEPLAYER, Str_Text(str));
357
358 if(IS_SERVER)
359 {
360 P_DealPlayerStarts(0);
361 }
362 }
363 // DDPE_CHAT_MESSAGE occurs when a PKT_CHAT is received.
364 // Here we will only display the message.
365 else if(peType == DDPE_CHAT_MESSAGE)
366 {
367 int oldecho = cfg.common.echoMsg;
368 AutoStr *msg = AutoStr_New();
369
370 if(plrNumber > 0)
371 {
372 Str_Appendf(msg, "%s: %s", Net_GetPlayerName(plrNumber), (char const *) data);
373 }
374 else
375 {
376 Str_Appendf(msg, "[sysop] %s", (char const *) data);
377 }
378 Str_Truncate(msg, NETBUFFER_MAXMESSAGE); // not overly long, please
379
380 // The chat message is already echoed by the console.
381 cfg.common.echoMsg = false;
382 D_NetMessageEx(CONSOLEPLAYER, Str_Text(msg), (cfg.common.chatBeep? true : false));
383 cfg.common.echoMsg = oldecho;
384 }
385
386 return true;
387 }
388
D_NetWorldEvent(int type,int parm,void * data)389 int D_NetWorldEvent(int type, int parm, void *data)
390 {
391 switch(type)
392 {
393 //
394 // Server events:
395 //
396 case DDWE_HANDSHAKE: {
397 dd_bool newPlayer = *((dd_bool*) data);
398
399 // A new player is entering the game. We as a server should send him
400 // the handshake packet(s) to update his world.
401 // If 'data' is zero, this is a re-handshake that's used to
402 // begin demos.
403 App_Log(DE2_DEV_NET_MSG, "Sending a game state %shandshake to player %i",
404 newPlayer? "" : "(re)", parm);
405
406 // Mark new player for update.
407 players[parm].update |= PSF_REBORN;
408
409 // First, the game state.
410 NetSv_SendGameState(GSF_CHANGE_MAP | GSF_CAMERA_INIT | (newPlayer ? 0 : GSF_DEMO), parm);
411
412 // Send info about all players to the new one.
413 for(int i = 0; i < MAXPLAYERS; ++i)
414 {
415 if(players[i].plr->inGame && i != parm)
416 NetSv_SendPlayerInfo(i, parm);
417 }
418
419 // Send info about our jump power.
420 NetSv_SendJumpPower(parm, cfg.common.jumpEnabled? cfg.common.jumpPower : 0);
421 NetSv_Paused(paused);
422 break; }
423
424 //
425 // Client events:
426 //
427 #if 0
428 case DDWE_SECTOR_SOUND:
429 // High word: sector number, low word: sound id.
430 if(parm & 0xffff)
431 S_SectorSound(P_ToPtr(DMU_SECTOR, parm >> 16), parm & 0xffff);
432 else
433 S_SectorStopSounds(P_ToPtr(DMU_SECTOR, parm >> 16));
434
435 break;
436
437 case DDWE_DEMO_END:
438 // Demo playback has ended. Advance demo sequence.
439 if(parm) // Playback was aborted.
440 G_DemoAborted();
441 else // Playback ended normally.
442 G_DemoEnds();
443
444 // Restore normal game state.
445 gfw_Rule(deathmatch) = false;
446 gfw_Session()->rules().noMonsters = false;
447 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
448 gfw_Rule(espawnMonsters) = false;
449 #endif
450
451 #if __JHEXEN__
452 gfw_Session()->rules().randomClasses = false;
453 #endif
454 break;
455 #endif
456
457 default:
458 return false;
459 }
460
461 return true;
462 }
463
D_HandlePacket(int fromplayer,int type,void * data,size_t length)464 void D_HandlePacket(int fromplayer, int type, void *data, size_t length)
465 {
466 reader_s *reader = D_NetRead((byte *)data, length);
467
468 //
469 // Server events.
470 //
471 if(IS_SERVER)
472 {
473 switch (type)
474 {
475 case GPT_PLAYER_INFO:
476 // A player has changed color or other settings.
477 NetSv_ChangePlayerInfo(fromplayer, reader);
478 break;
479
480 case GPT_CHEAT_REQUEST:
481 NetSv_DoCheat(fromplayer, reader);
482 break;
483
484 case GPT_FLOOR_HIT_REQUEST:
485 NetSv_DoFloorHit(fromplayer, reader);
486 break;
487
488 case GPT_ACTION_REQUEST:
489 NetSv_DoAction(fromplayer, reader);
490 break;
491
492 case GPT_DAMAGE_REQUEST:
493 NetSv_DoDamage(fromplayer, reader);
494 break;
495 }
496 return;
497 }
498 //
499 // Client events.
500 //
501 switch(type)
502 {
503 case GPT_GAME_STATE:
504 App_Log(DE2_DEV_NET_MSG, "Received GTP_GAME_STATE");
505 NetCl_UpdateGameState(reader);
506
507 // Tell the engine we're ready to proceed. It'll start handling
508 // the world updates after this variable is set.
509 Set(DD_GAME_READY, true);
510 break;
511
512 case GPT_PLAYER_SPAWN_POSITION:
513 NetCl_PlayerSpawnPosition(reader);
514 break;
515
516 case GPT_TOTAL_COUNTS:
517 NetCl_UpdateTotalCounts(reader);
518 break;
519
520 case GPT_MOBJ_IMPULSE:
521 NetCl_MobjImpulse(reader);
522 break;
523
524 case GPT_LOCAL_MOBJ_STATE:
525 NetCl_LocalMobjState(reader);
526 break;
527
528 case GPT_MESSAGE:
529 #if __JHEXEN__
530 case GPT_YELLOW_MESSAGE:
531 #endif
532 {
533 size_t len = Reader_ReadUInt16(reader);
534 char *msg = (char *)Z_Malloc(len + 1, PU_GAMESTATIC, 0);
535 Reader_Read(reader, msg, len); msg[len] = 0;
536
537 #if __JHEXEN__
538 if(type == GPT_YELLOW_MESSAGE)
539 {
540 P_SetYellowMessage(&players[CONSOLEPLAYER], msg);
541 }
542 else
543 #endif
544 {
545 P_SetMessage(&players[CONSOLEPLAYER], msg);
546 }
547 Z_Free(msg);
548 break; }
549
550 case GPT_MAYBE_CHANGE_WEAPON: {
551 weapontype_t wt = (weapontype_t) Reader_ReadInt16(reader);
552 ammotype_t at = (ammotype_t) Reader_ReadInt16(reader);
553 dd_bool force = (Reader_ReadByte(reader) != 0);
554 P_MaybeChangeWeapon(&players[CONSOLEPLAYER], wt, at, force);
555 break; }
556
557 case GPT_CONSOLEPLAYER_STATE:
558 NetCl_UpdatePlayerState(reader, CONSOLEPLAYER);
559 break;
560
561 case GPT_CONSOLEPLAYER_STATE2:
562 NetCl_UpdatePlayerState2(reader, CONSOLEPLAYER);
563 break;
564
565 case GPT_PLAYER_STATE:
566 NetCl_UpdatePlayerState(reader, -1);
567 break;
568
569 case GPT_PLAYER_STATE2:
570 NetCl_UpdatePlayerState2(reader, -1);
571 break;
572
573 case GPT_PSPRITE_STATE:
574 NetCl_UpdatePSpriteState(reader);
575 break;
576
577 case GPT_INTERMISSION:
578 NetCl_Intermission(reader);
579 break;
580
581 case GPT_FINALE_STATE:
582 NetCl_UpdateFinaleState(reader);
583 break;
584
585 case GPT_PLAYER_INFO:
586 NetCl_UpdatePlayerInfo(reader);
587 break;
588
589 #if __JHERETIC__ || __JHEXEN__ || __JSTRIFE__
590 case GPT_CLASS: {
591 player_t *plr = &players[CONSOLEPLAYER];
592 playerclass_t newClass = playerclass_t(Reader_ReadByte(reader));
593 # if __JHERETIC__
594 playerclass_t oldClass = plr->class_;
595 # endif
596 plr->class_ = newClass;
597 App_Log(DE2_DEV_MAP_MSG, "Player %i class changed to %i", CONSOLEPLAYER, plr->class_);
598
599 # if __JHERETIC__
600 if(oldClass != newClass)
601 {
602 if(newClass == PCLASS_CHICKEN)
603 {
604 App_Log(DE2_DEV_MAP_VERBOSE, "Player %i activating morph", CONSOLEPLAYER);
605
606 P_ActivateMorphWeapon(plr);
607 }
608 else if(oldClass == PCLASS_CHICKEN)
609 {
610 App_Log(DE2_DEV_MAP_VERBOSE, "Player %i post-morph weapon %i", CONSOLEPLAYER, plr->readyWeapon);
611
612 // The morph has ended.
613 P_PostMorphWeapon(plr, plr->readyWeapon);
614 }
615 }
616 # endif
617 break; }
618 #endif
619
620 case GPT_SAVE:
621 NetCl_SaveGame(reader);
622 break;
623
624 case GPT_LOAD:
625 NetCl_LoadGame(reader);
626 break;
627
628 case GPT_PAUSE:
629 NetCl_Paused(reader);
630 break;
631
632 case GPT_JUMP_POWER:
633 NetCl_UpdateJumpPower(reader);
634 break;
635
636 case GPT_DISMISS_HUDS:
637 NetCl_DismissHUDs(reader);
638 break;
639
640 default:
641 App_Log(DE2_NET_WARNING, "Game received unknown packet (type:%i)", type);
642 }
643 }
644
645 /**
646 * Plays a (local) chat sound.
647 */
D_ChatSound()648 void D_ChatSound()
649 {
650 #if __JHEXEN__
651 S_LocalSound(SFX_CHAT, NULL);
652 #elif __JSTRIFE__
653 S_LocalSound(SFX_CHAT, NULL);
654 #elif __JHERETIC__
655 S_LocalSound(SFX_CHAT, NULL);
656 #else
657 # if __JDOOM__
658 if(gameModeBits & GM_ANY_DOOM2)
659 S_LocalSound(SFX_RADIO, NULL);
660 else
661 # endif
662 S_LocalSound(SFX_TINK, NULL);
663 #endif
664 }
665
666 /**
667 * Show a message on screen, optionally accompanied by Chat sound effect.
668 *
669 * @param player Player number to send the message to.
670 * @param playSound @c true = play the chat sound.
671 */
D_NetMessageEx(int player,char const * msg,dd_bool playSound)672 static void D_NetMessageEx(int player, char const *msg, dd_bool playSound)
673 {
674 if(player < 0 || player > MAXPLAYERS) return;
675 player_t *plr = &players[player];
676
677 if(!plr->plr->inGame)
678 return;
679
680 // This is intended to be a local message.
681 // Let's make sure P_SetMessageWithFlags doesn't forward it anywhere.
682 netSvAllowSendMsg = false;
683 P_SetMessage(plr, msg);
684
685 if(playSound)
686 {
687 D_ChatSound();
688 }
689
690 netSvAllowSendMsg = true;
691 }
692
D_NetMessage(int player,char const * msg)693 void D_NetMessage(int player, char const *msg)
694 {
695 D_NetMessageEx(player, msg, true);
696 }
697
D_NetMessageNoSound(int player,char const * msg)698 void D_NetMessageNoSound(int player, char const *msg)
699 {
700 D_NetMessageEx(player, msg, false);
701 }
702
D_NetDamageMobj(mobj_t * target,mobj_t * inflictor,mobj_t * source,int damage)703 dd_bool D_NetDamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, int damage)
704 {
705 int sourcePlrNum = -1;
706 if(source && source->player)
707 {
708 sourcePlrNum = source->player - players;
709 }
710
711 if(source && !source->player)
712 {
713 // Not applicable: only damage from players.
714 return false;
715 }
716
717 if(IS_SERVER && sourcePlrNum > 0)
718 {
719 /*
720 * A client is trying to do damage. However, it is not guaranteed that the server is 100%
721 * accurately aware of the gameplay situation in which the damage is being inflicted (due
722 * to network latency), so instead of applying the damage now we will wait for the client
723 * to request it separately.
724 */
725 return false;
726 }
727 else if(IS_CLIENT)
728 {
729 if((sourcePlrNum < 0 || sourcePlrNum == CONSOLEPLAYER) &&
730 target && target->player && target->player - players == CONSOLEPLAYER)
731 {
732 // Clients are allowed to damage themselves.
733 NetCl_DamageRequest(ClPlayer_ClMobj(CONSOLEPLAYER), inflictor, source, damage);
734
735 // No further processing of this damage is needed.
736 return true;
737 }
738 }
739 return false;
740 }
741
742 /**
743 * Console command to change the players' colors.
744 */
D_CMD(SetColor)745 D_CMD(SetColor)
746 {
747 DENG2_UNUSED2(src, argc);
748
749 cfg.common.netColor = atoi(argv[1]);
750 if(IS_SERVER) // A local player?
751 {
752 if(IS_DEDICATED) return false;
753
754 int player = CONSOLEPLAYER;
755
756 // Server players, must be treated as a special case because this is
757 // a local mobj we're dealing with. We'll change the color translation
758 // bits directly.
759
760 cfg.playerColor[player] = PLR_COLOR(player, cfg.common.netColor);
761 players[player].colorMap = cfg.playerColor[player];
762
763 if(players[player].plr->mo)
764 {
765 // Change the color of the mobj (translation flags).
766 players[player].plr->mo->flags &= ~MF_TRANSLATION;
767 players[player].plr->mo->flags |= (cfg.playerColor[player] << MF_TRANSSHIFT);
768 }
769
770 // Tell the clients about the change.
771 NetSv_SendPlayerInfo(player, DDSP_ALL_PLAYERS);
772 }
773 else
774 {
775 // Tell the server about the change.
776 NetCl_SendPlayerInfo();
777 }
778
779 return true;
780 }
781
782 /**
783 * Console command to change the players' class.
784 */
785 #if __JHEXEN__
D_CMD(SetClass)786 D_CMD(SetClass)
787 {
788 DENG2_UNUSED2(src, argc);
789
790 playerclass_t newClass = playerclass_t(atoi(argv[1]));
791
792 if(!(newClass < NUM_PLAYER_CLASSES))
793 {
794 return false;
795 }
796
797 if(!PCLASS_INFO(newClass)->userSelectable)
798 {
799 return false;
800 }
801
802 cfg.netClass = newClass; // Stored as a cvar.
803
804 if(IS_CLIENT)
805 {
806 // Tell the server that we want to change our class.
807 NetCl_SendPlayerInfo();
808 }
809 else
810 {
811 // On the server (or singleplayer) we can do an immediate change.
812 P_PlayerChangeClass(&players[CONSOLEPLAYER], playerclass_t(cfg.netClass));
813 }
814
815 return true;
816 }
817 #endif
818
819 /**
820 * Post a local game message.
821 */
D_CMD(LocalMessage)822 D_CMD(LocalMessage)
823 {
824 DENG2_UNUSED2(src, argc);
825 D_NetMessageNoSound(CONSOLEPLAYER, argv[1]);
826 return true;
827 }
828