1 /** @file d_netcl.cpp  Common code related to netgames (client-side).
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2015 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "common.h"
22 #include "d_netcl.h"
23 
24 #include <cstdio>
25 #include <cstring>
26 #include "d_netsv.h"       ///< @todo remove me
27 #include "g_common.h"
28 #include "g_defs.h"
29 #include "gamesession.h"
30 #include "hu_inventory.h"
31 #include "p_actor.h"
32 #include "p_inventory.h"
33 #include "p_map.h"
34 #include "p_saveg.h"
35 #include "p_start.h"
36 #include "r_common.h"
37 #include "player.h"
38 #include "st_stuff.h"
39 
40 using namespace de;
41 using namespace common;
42 
NetCl_UpdateGameState(reader_s * msg)43 void NetCl_UpdateGameState(reader_s *msg)
44 {
45     BusyMode_FreezeGameForBusyMode();
46 
47     byte gsFlags = Reader_ReadByte(msg);
48 
49     // Game identity key.
50     AutoStr *gsGameId = AutoStr_NewStd();
51     Str_Read(gsGameId, msg);
52 
53     // Current map.
54     uri_s *gsMapUri = Uri_FromReader(msg);
55     Uri_SetScheme(gsMapUri, "Maps");
56 
57     // Current episode.
58     AutoStr *gsEpisodeId = AutoStr_NewStd();
59     Str_Read(gsEpisodeId, msg);
60 
61     /*uint gsMap     =*/ Reader_ReadByte(msg);
62 
63     /// @todo Not communicated to clients??
64     //uint gsMapEntrance = ??;
65 
66     byte configFlags = Reader_ReadByte(msg);
67 
68     GameRules gsRules(gfw_Session()->rules()); // Initialize with a copy of the current rules.
69     GameRules_Set(gsRules, deathmatch, configFlags & 0x3);
70     GameRules_Set(gsRules, noMonsters, !(configFlags & 0x4? true : false));
71 #if !__JHEXEN__
72     GameRules_Set(gsRules, respawnMonsters, (configFlags & 0x8? true : false));
73 #endif
74     /// @todo Not applied??
75     //byte gsJumping          = (configFlags & 0x10? true : false);
76 
77     GameRules_Set(gsRules, skill, skillmode_t(Reader_ReadByte(msg)));
78 
79     // Interpret skill modes outside the normal range as "spawn no things".
80     if(gsRules.values.skill < SM_BABY || gsRules.values.skill >= NUM_SKILL_MODES)
81     {
82         GameRules_Set(gsRules, skill, SM_NOTHINGS);
83     }
84 
85     coord_t gsGravity = Reader_ReadFloat(msg);
86 
87     LOGDEV_MAP_NOTE("NetCl_UpdateGameState: Flags=%x") << gsFlags;
88 
89     // Demo game state changes are only effective during demo playback.
90     if(gsFlags & GSF_DEMO && !Get(DD_PLAYBACK))
91     {
92         Uri_Delete(gsMapUri);
93         return;
94     }
95 
96     // Check for a game mode mismatch.
97     /// @todo  Automatically load the server's game if it is available.
98     /// However, note that this can only occur if the server changes its game
99     /// while a netgame is running (which currently will end the netgame).
100     if (gfw_GameId().compare(Str_Text(gsGameId)))
101     {
102         LOG_NET_ERROR("Game mismatch: server's identity key (%s) is different to yours (%s)")
103                 << gsGameId << gfw_GameId();
104         DD_Execute(false, "net disconnect");
105         Uri_Delete(gsMapUri);
106         return;
107     }
108 
109     // Some statistics.
110     LOG_NOTE("%s - %s\n  %s")
111             << gsRules.description()
112             << Str_Text(Uri_ToString(gsMapUri))
113             << gsRules.asText();
114 
115     // Do we need to change the map?
116     if(gsFlags & GSF_CHANGE_MAP)
117     {
118         gfw_Session()->end();
119         gfw_Session()->begin(gsRules, Str_Text(gsEpisodeId),
120                                   *reinterpret_cast<de::Uri *>(gsMapUri),
121                                   gfw_Session()->mapEntryPoint() /*gsMapEntrance*/);
122     }
123     else
124     {
125         /// @todo Breaks session management logic; rules cannot change once the session has
126         /// begun and setting the current map and/or entrance is illogical at this point.
127         DENG2_ASSERT(!Str_Compare(gsEpisodeId, gfw_Session()->episodeId().toLatin1().constData()));
128         DENG2_ASSERT(*reinterpret_cast<de::Uri *>(gsMapUri) == gfw_Session()->mapUri());
129 
130         gfw_Session()->applyNewRules(gsRules);
131     }
132 
133     // Set gravity.
134     /// @todo This is a map-property, not a global property.
135     DD_SetVariable(DD_MAP_GRAVITY, &gsGravity);
136 
137     // Camera init included?
138     if(gsFlags & GSF_CAMERA_INIT)
139     {
140         player_t *pl = &players[CONSOLEPLAYER];
141         if(mobj_t *mo = pl->plr->mo)
142         {
143             P_MobjUnlink(mo);
144             mo->origin[VX] = Reader_ReadFloat(msg);
145             mo->origin[VY] = Reader_ReadFloat(msg);
146             mo->origin[VZ] = Reader_ReadFloat(msg);
147             P_MobjLink(mo);
148             mo->angle      = Reader_ReadUInt32(msg);
149             // Update floorz and ceilingz.
150 #if __JDOOM__ || __JDOOM64__
151             P_CheckPosition(mo, mo->origin);
152 #else
153             P_CheckPositionXY(mo, mo->origin[VX], mo->origin[VY]);
154 #endif
155             mo->floorZ     = tmFloorZ;
156             mo->ceilingZ   = tmCeilingZ;
157         }
158         else
159         {
160             float mx       = Reader_ReadFloat(msg);
161             float my       = Reader_ReadFloat(msg);
162             float mz       = Reader_ReadFloat(msg);
163             angle_t angle  = Reader_ReadUInt32(msg);
164 
165             LOGDEV_NET_WARNING("NetCl_UpdateGameState: Got camera init, but player has no mobj; "
166                                "pos=%f,%f,%f Angle=%x") << mx << my << mz << angle;
167         }
168     }
169 
170     // Tell the server we're ready to begin receiving frames.
171     Net_SendPacket(0, DDPT_OK, 0, 0);
172 
173     Uri_Delete(gsMapUri);
174 }
175 
NetCl_MobjImpulse(reader_s * msg)176 void NetCl_MobjImpulse(reader_s *msg)
177 {
178     mobj_t *mo   = players[CONSOLEPLAYER].plr->mo;
179     mobj_t *clmo = ClPlayer_ClMobj(CONSOLEPLAYER);
180 
181     if(!mo || !clmo) return;
182 
183     thid_t id = Reader_ReadUInt16(msg);
184     if(id != clmo->thinker.id)
185     {
186         // Not applicable; wrong mobj.
187         return;
188     }
189 
190     App_Log(DE2_DEV_MAP_VERBOSE, "NetCl_MobjImpulse: Player %i, clmobj %i", CONSOLEPLAYER, id);
191 
192     // Apply to the local mobj.
193     mo->mom[MX] += Reader_ReadFloat(msg);
194     mo->mom[MY] += Reader_ReadFloat(msg);
195     mo->mom[MZ] += Reader_ReadFloat(msg);
196 }
197 
NetCl_PlayerSpawnPosition(reader_s * msg)198 void NetCl_PlayerSpawnPosition(reader_s *msg)
199 {
200     player_t *p = &players[CONSOLEPLAYER];
201 
202     coord_t x     = Reader_ReadFloat(msg);
203     coord_t y     = Reader_ReadFloat(msg);
204     coord_t z     = Reader_ReadFloat(msg);
205     angle_t angle = Reader_ReadUInt32(msg);
206 
207     App_Log(DE2_DEV_MAP_NOTE, "Got player spawn position (%g, %g, %g) facing %x",
208             x, y, z, angle);
209 
210     mobj_t *mo = p->plr->mo;
211     DENG2_ASSERT(mo != 0);
212 
213     P_TryMoveXYZ(mo, x, y, z);
214     mo->angle = angle;
215 }
216 
NetCl_UpdatePlayerState2(reader_s * msg,int plrNum)217 void NetCl_UpdatePlayerState2(reader_s *msg, int plrNum)
218 {
219     player_t *pl = &players[plrNum];
220 
221     if(!Get(DD_GAME_READY))
222     {
223         App_Log(DE2_DEV_NET_WARNING, "NetCl_UpdatePlayerState2: game isn't ready yet!");
224         return;
225     }
226 
227     if(plrNum < 0)
228     {
229         // Player number included in the message.
230         plrNum = Reader_ReadByte(msg);
231     }
232     uint flags = Reader_ReadUInt32(msg);
233 
234     if(flags & PSF2_OWNED_WEAPONS)
235     {
236         int k = Reader_ReadUInt16(msg);
237         for(int i = 0; i < NUM_WEAPON_TYPES; ++i)
238         {
239             bool owned = CPP_BOOL(k & (1 << i));
240 
241             // Maybe unhide the HUD?
242             if(owned && pl->weapons[i].owned == false)
243             {
244                 ST_HUDUnHide(pl - players, HUE_ON_PICKUP_WEAPON);
245             }
246 
247             pl->weapons[i].owned = owned;
248         }
249     }
250 
251     if(flags & PSF2_STATE)
252     {
253         int oldPlayerState = pl->playerState;
254 
255         byte b = Reader_ReadByte(msg);
256         pl->playerState = playerstate_t(b & 0xf);
257 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
258         pl->armorType = b >> 4;
259 #endif
260 
261         App_Log(DE2_DEV_MAP_MSG, "NetCl_UpdatePlayerState2: New player state = %s",
262                 pl->playerState == PST_LIVE? "PST_LIVE" :
263                 pl->playerState == PST_DEAD? "PST_DEAD" : "PST_REBORN");
264 
265         // Player state changed?
266         if(oldPlayerState != pl->playerState)
267         {
268             // Set or clear the DEAD flag for this player.
269             if(pl->playerState == PST_LIVE)
270             {
271                 // Becoming alive again...
272                 // After being reborn, the server will tell us the new weapon.
273                 pl->plr->flags |= DDPF_UNDEFINED_WEAPON;
274 
275                 App_Log(DE2_DEV_MAP_MSG, "NetCl_UpdatePlayerState2: Player %i: Marking weapon as undefined",
276                         (int)(pl - players));
277 
278                 pl->plr->flags &= ~DDPF_DEAD;
279             }
280             else
281             {
282                 pl->plr->flags |= DDPF_DEAD;
283             }
284         }
285 
286         pl->cheats = Reader_ReadByte(msg);
287 
288         // Set or clear the NOCLIP flag.
289         if(P_GetPlayerCheats(pl) & CF_NOCLIP)
290             pl->plr->flags |= DDPF_NOCLIP;
291         else
292             pl->plr->flags &= ~DDPF_NOCLIP;
293     }
294 }
295 
NetCl_UpdatePlayerState(reader_s * msg,int plrNum)296 void NetCl_UpdatePlayerState(reader_s *msg, int plrNum)
297 {
298     int i;
299     byte b;
300     int s;
301 
302     if(!Get(DD_GAME_READY))
303         return;
304 
305     if(plrNum < 0)
306     {
307         plrNum = Reader_ReadByte(msg);
308     }
309     player_t *pl = &players[plrNum];
310 
311     int flags = Reader_ReadUInt16(msg);
312 
313     if(flags & PSF_STATE)       // and armor type (the same bit)
314     {
315         byte b = Reader_ReadByte(msg);
316         pl->playerState = playerstate_t(b & 0xf);
317 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
318         pl->armorType = b >> 4;
319 #endif
320         // Set or clear the DEAD flag for this player.
321         if(pl->playerState == PST_LIVE)
322             pl->plr->flags &= ~DDPF_DEAD;
323         else
324             pl->plr->flags |= DDPF_DEAD;
325 
326         //if(oldstate != pl->playerState) // && oldstate == PST_DEAD)
327         {
328             P_SetupPsprites(pl);
329         }
330     }
331 
332     if(flags & PSF_HEALTH)
333     {
334         int health = Reader_ReadByte(msg);
335 
336         if(health < pl->health)
337             ST_HUDUnHide(plrNum, HUE_ON_DAMAGE);
338 
339         pl->health = health;
340         if(pl->plr->mo)
341         {
342             pl->plr->mo->health = pl->health;
343         }
344         else
345         {
346             App_Log(DE2_DEV_MAP_ERROR, "NetCl_UpdatePlayerState: Player mobj not yet allocated at this time");
347         }
348     }
349 
350     if(flags & PSF_ARMOR_POINTS)
351     {
352         byte    ap;
353 #if __JHEXEN__
354         for(i = 0; i < NUMARMOR; ++i)
355         {
356             ap = Reader_ReadByte(msg);
357 
358             // Maybe unhide the HUD?
359             if(ap >= pl->armorPoints[i] &&
360                 pl == &players[CONSOLEPLAYER])
361                 ST_HUDUnHide(plrNum, HUE_ON_PICKUP_ARMOR);
362 
363             pl->armorPoints[i] = ap;
364         }
365 #else
366         ap = Reader_ReadByte(msg);
367 
368         // Maybe unhide the HUD?
369         if(ap >= pl->armorPoints)
370             ST_HUDUnHide(plrNum, HUE_ON_PICKUP_ARMOR);
371 
372         pl->armorPoints = ap;
373 #endif
374 
375     }
376 
377 #if __JHERETIC__ || __JHEXEN__ || __JDOOM64__
378     if(flags & PSF_INVENTORY)
379     {
380         for(uint i = 0; i < NUM_INVENTORYITEM_TYPES; ++i)
381         {
382             inventoryitemtype_t type = inventoryitemtype_t(IIT_FIRST + i);
383             uint count = P_InventoryCount(plrNum, type);
384 
385             for(uint j = 0; j < count; ++j)
386             {
387                 P_InventoryTake(plrNum, type, true);
388             }
389         }
390 
391         uint count = Reader_ReadByte(msg);
392         for(uint i = 0; i < count; ++i)
393         {
394             s = Reader_ReadUInt16(msg);
395 
396             inventoryitemtype_t const type = inventoryitemtype_t(s & 0xff);
397             uint const num                 = s >> 8;
398 
399             for(uint j = 0; j < num; ++j)
400             {
401                 P_InventoryGive(plrNum, type, true);
402             }
403         }
404     }
405 #endif
406 
407     if(flags & PSF_POWERS)
408     {
409         b = Reader_ReadByte(msg);
410 
411         // Only the non-zero powers are included in the message.
412 #if __JHEXEN__ || __JSTRIFE__
413         for(i = 0; i < NUM_POWER_TYPES - 1; ++i)
414         {
415             byte val = ((b & (1 << i))? (Reader_ReadByte(msg) * 35) : 0);
416 
417             // Maybe unhide the HUD?
418             if(val > pl->powers[i])
419                 ST_HUDUnHide(pl - players, HUE_ON_PICKUP_POWER);
420 
421             pl->powers[i + 1] = val;
422         }
423 #else
424         for(i = 0; i < NUM_POWER_TYPES; ++i)
425         {
426 #  if __JDOOM__ || __JDOOM64__
427             if(i == PT_IRONFEET || i == PT_STRENGTH)
428                 continue;
429 #  endif
430             {
431                 int val = ((b & (1 << i))? (Reader_ReadByte(msg) * 35) : 0);
432 
433                 /**
434                  * @todo This function duplicates logic in P_GivePower(). The
435                  * redundancy should be removed for instance by adding a new
436                  * game packet GPT_GIVE_POWER that calls the appropriate
437                  * P_GivePower() on clientside after it has been called on the
438                  * server. -jk
439                  */
440 
441                 // Maybe unhide the HUD?
442                 if(val > pl->powers[i])
443                     ST_HUDUnHide(plrNum, HUE_ON_PICKUP_POWER);
444 
445                 pl->powers[i] = val;
446 
447                 if(val && i == PT_FLIGHT && pl->plr->mo)
448                 {
449                     pl->plr->mo->flags2 |= MF2_FLY;
450                     pl->plr->mo->flags |= MF_NOGRAVITY;
451                     pl->flyHeight = 10;
452                     pl->powers[i] = val;
453 
454                     App_Log(DE2_DEV_MAP_MSG, "NetCl_UpdatePlayerState: Local mobj flight enabled");
455                 }
456 
457                 // Should we reveal the map?
458                 if(val && i == PT_ALLMAP && plrNum == CONSOLEPLAYER)
459                 {
460                     App_Log(DE2_DEV_MAP_MSG, "NetCl_UpdatePlayerState: Revealing automap");
461 
462                     ST_RevealAutomap(plrNum, true);
463                 }
464             }
465         }
466 #endif
467     }
468 
469     if(flags & PSF_KEYS)
470     {
471         b = Reader_ReadByte(msg);
472 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
473         for(i = 0; i < NUM_KEY_TYPES; ++i)
474         {
475             dd_bool val = (b & (1 << i)) != 0;
476 
477             // Maybe unhide the HUD?
478             if(val && !pl->keys[i])
479                 ST_HUDUnHide(plrNum, HUE_ON_PICKUP_KEY);
480 
481             pl->keys[i] = val;
482         }
483 #endif
484 #if __JHEXEN__
485         if((pl->keys & b) != 0)
486         {
487             ST_HUDUnHide(plrNum, HUE_ON_PICKUP_KEY);
488         }
489         pl->keys = b;
490 #endif
491     }
492 
493     if(flags & PSF_FRAGS)
494     {
495         de::zap(pl->frags);
496         // First comes the number of frag counts included.
497         for(i = Reader_ReadByte(msg); i > 0; i--)
498         {
499             s = Reader_ReadUInt16(msg);
500             pl->frags[s >> 12] = s & 0xfff;
501         }
502     }
503 
504     if(flags & PSF_OWNED_WEAPONS)
505     {
506         b = Reader_ReadByte(msg);
507         for(i = 0; i < NUM_WEAPON_TYPES; ++i)
508         {
509             bool owned = CPP_BOOL(b & (1 << i));
510 
511             // Maybe unhide the HUD?
512             if(owned && pl->weapons[i].owned == false)
513             {
514                 ST_HUDUnHide(plrNum, HUE_ON_PICKUP_WEAPON);
515             }
516 
517             pl->weapons[i].owned = owned;
518         }
519     }
520 
521     if(flags & PSF_AMMO)
522     {
523         for(i = 0; i < NUM_AMMO_TYPES; ++i)
524         {
525             int val = Reader_ReadInt16(msg);
526 
527             // Maybe unhide the HUD?
528             if(val > pl->ammo[i].owned)
529             {
530                 ST_HUDUnHide(plrNum, HUE_ON_PICKUP_AMMO);
531             }
532 
533             pl->ammo[i].owned = val;
534         }
535     }
536 
537     if(flags & PSF_MAX_AMMO)
538     {
539 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__ // Hexen has no use for max ammo.
540         for(i = 0; i < NUM_AMMO_TYPES; i++)
541             pl->ammo[i].max = Reader_ReadInt16(msg);
542 #endif
543     }
544 
545     if(flags & PSF_COUNTERS)
546     {
547         pl->killCount   = Reader_ReadInt16(msg);
548         pl->itemCount   = Reader_ReadByte(msg);
549         pl->secretCount = Reader_ReadByte(msg);
550 
551         App_Log(DE2_DEV_MAP_MSG, "NetCl_UpdatePlayerState: kills=%i, items=%i, secrets=%i",
552                 pl->killCount, pl->itemCount, pl->secretCount);
553     }
554 
555     if(flags & PSF_PENDING_WEAPON || flags & PSF_READY_WEAPON)
556     {
557         dd_bool wasUndefined = (pl->plr->flags & DDPF_UNDEFINED_WEAPON) != 0;
558 
559         b = Reader_ReadByte(msg);
560         if(flags & PSF_PENDING_WEAPON)
561         {
562             if(!wasUndefined)
563             {
564                 int weapon = b & 0xf;
565                 if(weapon != WT_NOCHANGE)
566                 {
567                     App_Log(DE2_DEV_MAP_MSG, "NetCl_UpdatePlayerState: Weapon already known, "
568                             "using an impulse to switch to %i", weapon);
569 
570                     P_Impulse(pl - players, CTL_WEAPON1 + weapon);
571                 }
572             }
573             else
574             {
575                 pl->pendingWeapon = weapontype_t(b & 0xf);
576 
577                 App_Log(DE2_DEV_MAP_MSG, "NetCl_UpdatePlayerState: pendingweapon=%i", pl->pendingWeapon);
578             }
579 
580             pl->plr->flags &= ~DDPF_UNDEFINED_WEAPON;
581         }
582 
583         if(flags & PSF_READY_WEAPON)
584         {
585             if(wasUndefined)
586             {
587                 pl->readyWeapon = weapontype_t(b >> 4);
588                 App_Log(DE2_DEV_MAP_MSG, "NetCl_UpdatePlayerState: readyweapon=%i", pl->readyWeapon);
589             }
590             else
591             {
592                 App_Log(DE2_DEV_MAP_NOTE, "NetCl_UpdatePlayerState: Readyweapon already known (%i), "
593                         "not setting server's value %i", pl->readyWeapon, b >> 4);
594             }
595 
596             pl->plr->flags &= ~DDPF_UNDEFINED_WEAPON;
597         }
598 
599         if(!(pl->plr->flags & DDPF_UNDEFINED_WEAPON) && wasUndefined)
600         {
601             App_Log(DE2_DEV_MAP_NOTE, "NetCl_UpdatePlayerState: Weapon was undefined, bringing it up now");
602 
603             // Bring it up now.
604             P_BringUpWeapon(pl);
605         }
606     }
607 
608     if(flags & PSF_VIEW_HEIGHT)
609     {
610         pl->viewHeight = (float) Reader_ReadByte(msg);
611     }
612 
613 #if __JHERETIC__ || __JHEXEN__ || __JSTRIFE__
614     if(flags & PSF_MORPH_TIME)
615     {
616         pl->morphTics = Reader_ReadByte(msg) * 35;
617         App_Log(DE2_DEV_MAP_MSG, "NetCl_UpdatePlayerState: Player %i morphtics = %i", plrNum, pl->morphTics);
618     }
619 #endif
620 
621 #if defined(HAVE_EARTHQUAKE) || __JSTRIFE__
622     if(flags & PSF_LOCAL_QUAKE)
623     {
624         localQuakeHappening[plrNum] = Reader_ReadByte(msg);
625     }
626 #endif
627 }
628 
NetCl_UpdatePSpriteState(reader_s *)629 void NetCl_UpdatePSpriteState(reader_s * /*msg*/)
630 {
631     // Not used.
632     /*
633     unsigned short s;
634 
635     NetCl_SetReadBuffer(data);
636     s = NetCl_ReadShort();
637     P_SetPsprite(&players[CONSOLEPLAYER], ps_weapon, s);
638      */
639 }
640 
NetCl_Intermission(reader_s * msg)641 void NetCl_Intermission(reader_s *msg)
642 {
643     int flags = Reader_ReadByte(msg);
644 
645     if(flags & IMF_BEGIN)
646     {
647         // Close any HUDs left open at the end of the previous map.
648         for(uint i = 0; i < MAXPLAYERS; ++i)
649         {
650             ST_CloseAll(i, true/*fast*/);
651         }
652 
653         G_ResetViewEffects();
654 
655 #if __JHEXEN__
656         SN_StopAllSequences();
657 #endif
658 
659         /// @todo jHeretic does not transmit the intermission info!
660 #if !defined(__JHERETIC__)
661 #  if __JDOOM__ || __JDOOM64__
662         ::wmInfo.maxKills   = de::max<int>(1, Reader_ReadUInt16(msg));
663         ::wmInfo.maxItems   = de::max<int>(1, Reader_ReadUInt16(msg));
664         ::wmInfo.maxSecret  = de::max<int>(1, Reader_ReadUInt16(msg));
665 #  endif
666         Uri_Read(reinterpret_cast<uri_s *>(&::wmInfo.nextMap), msg);
667 #  if __JHEXEN__
668         ::wmInfo.nextMapEntryPoint = Reader_ReadByte(msg);
669 #  else
670         Uri_Read(reinterpret_cast<uri_s *>(&::wmInfo.currentMap), msg);
671 #  endif
672 #  if __JDOOM__ || __JDOOM64__
673         ::wmInfo.didSecret  = Reader_ReadByte(msg);
674 
675         G_PrepareWIData();
676 #  endif
677 #endif
678 
679         IN_Begin(::wmInfo);
680 
681 #if __JDOOM64__
682         S_StartMusic("dm2int", true);
683 #elif __JDOOM__
684         S_StartMusic((gameModeBits & GM_ANY_DOOM2)? "dm2int" : "inter", true);
685 #elif __JHERETIC__
686         S_StartMusic("intr", true);
687 #elif __JHEXEN__
688         S_StartMusic("hub", true);
689 #endif
690         G_ChangeGameState(GS_INTERMISSION);
691     }
692 
693     if(flags & IMF_END)
694     {
695         IN_End();
696     }
697 
698     if(flags & IMF_STATE)
699     {
700 #if __JDOOM__ || __JDOOM64__
701         IN_SetState(interludestate_t(Reader_ReadInt16(msg)));
702 #elif __JHERETIC__ || __JHEXEN__
703         IN_SetState(Reader_ReadInt16(msg));
704 #endif
705     }
706 
707 #if __JHERETIC__
708     if(flags & IMF_TIME)
709     {
710         IN_SetTime(Reader_ReadUInt16(msg));
711     }
712 #endif
713 }
714 
715 #if 0 // MOVED INTO THE ENGINE
716 /**
717  * This is where clients start their InFine interludes.
718  */
719 void NetCl_Finale(int packetType, reader_s *msg)
720 {
721     int         flags, len, numConds, i;
722     byte       *script = NULL;
723 
724     flags = Reader_ReadByte(msg);
725     if(flags & FINF_SCRIPT)
726     {
727         // First read the values of the conditions.
728         if(packetType == GPT_FINALE2)
729         {
730             numConds = Reader_ReadByte(msg);
731             for(i = 0; i < numConds; ++i)
732             {
733                 FI_SetCondition(i, Reader_ReadByte(msg));
734             }
735         }
736 
737         // Read the script into map-scope memory. It will be freed
738         // when the next map is loaded.
739         len = Reader_ReadUInt32(msg);
740         script = Z_Malloc(len + 1, PU_MAP, 0);
741         Reader_Read(msg, script, len);
742         script[len] = 0;
743     }
744 
745     if(flags & FINF_BEGIN && script)
746     {
747         // Start the script.
748         FI_Start((char*)script,
749                  (flags & FINF_AFTER) ? FIMODE_AFTER : (flags & FINF_OVERLAY) ?
750                  FIMODE_OVERLAY : FIMODE_BEFORE);
751     }
752 
753     if(flags & FINF_END)
754     {   // Stop InFine.
755         FI_End();
756     }
757 
758     if(flags & FINF_SKIP)
759     {
760         FI_SkipRequest();
761     }
762 }
763 #endif
764 
NetCl_UpdatePlayerInfo(reader_s * msg)765 void NetCl_UpdatePlayerInfo(reader_s *msg)
766 {
767     int num = Reader_ReadByte(msg);
768     cfg.playerColor[num] = Reader_ReadByte(msg);
769     players[num].colorMap = cfg.playerColor[num];
770 #if __JHEXEN__ || __JHERETIC__
771     cfg.playerClass[num] = playerclass_t(Reader_ReadByte(msg));
772     players[num].class_ = cfg.playerClass[num];
773 #endif
774 
775 #if __JDOOM__ || __JDOOM64__
776     App_Log(DE2_MAP_VERBOSE, "Player %i color set to %i", num, cfg.playerColor[num]);
777 #else
778     App_Log(DE2_MAP_VERBOSE, "Player %i color set to %i and class to %i", num, cfg.playerColor[num], cfg.playerClass[num]);
779 #endif
780 }
781 
782 /**
783  * Send CONSOLEPLAYER's settings to the server.
784  */
NetCl_SendPlayerInfo()785 void NetCl_SendPlayerInfo()
786 {
787     if(!IS_CLIENT) return;
788 
789     writer_s *msg = D_NetWrite();
790 
791     Writer_WriteByte(msg, cfg.common.netColor);
792 #ifdef __JHEXEN__
793     Writer_WriteByte(msg, cfg.netClass);
794 #else
795     Writer_WriteByte(msg, PCLASS_PLAYER);
796 #endif
797 
798     Net_SendPacket(0, GPT_PLAYER_INFO, Writer_Data(msg), Writer_Size(msg));
799 }
800 
NetCl_SaveGame(reader_s * msg)801 void NetCl_SaveGame(reader_s *msg)
802 {
803 #if __JHEXEN__
804     DENG2_UNUSED(msg);
805 #endif
806 
807     if(Get(DD_PLAYBACK)) return;
808 
809 #if !__JHEXEN__
810     SV_SaveGameClient(Reader_ReadUInt32(msg));
811 #endif
812 #if __JDOOM__ || __JDOOM64__
813     P_SetMessageWithFlags(&players[CONSOLEPLAYER], TXT_GAMESAVED, LMF_NO_HIDE);
814 #endif
815 }
816 
NetCl_LoadGame(reader_s * msg)817 void NetCl_LoadGame(reader_s *msg)
818 {
819 #if __JHEXEN__
820     DENG2_UNUSED(msg);
821 #endif
822 
823     if(!IS_CLIENT || Get(DD_PLAYBACK)) return;
824 
825 #if !__JHEXEN__
826     SV_LoadGameClient(Reader_ReadUInt32(msg));
827 #endif
828 #if __JDOOM__ || __JDOOM64__
829     P_SetMessage(&players[CONSOLEPLAYER], GET_TXT(TXT_CLNETLOAD));
830 #endif
831 }
832 
NetCl_CheatRequest(char const * command)833 void NetCl_CheatRequest(char const *command)
834 {
835     writer_s *msg = D_NetWrite();
836 
837     Writer_WriteUInt16(msg, uint16_t(strlen(command)));
838     Writer_Write(msg, command, strlen(command));
839 
840     if(IS_CLIENT)
841     {
842         Net_SendPacket(0, GPT_CHEAT_REQUEST, Writer_Data(msg), Writer_Size(msg));
843     }
844     else
845     {
846         NetSv_ExecuteCheat(CONSOLEPLAYER, command);
847     }
848 }
849 
NetCl_UpdateJumpPower(reader_s * msg)850 void NetCl_UpdateJumpPower(reader_s *msg)
851 {
852     netJumpPower = Reader_ReadFloat(msg);
853 
854     App_Log(DE2_LOG_VERBOSE, "Jump power: %g", netJumpPower);
855 }
856 
NetCl_DismissHUDs(reader_s * msg)857 void NetCl_DismissHUDs(reader_s *msg)
858 {
859     dd_bool fast = Reader_ReadByte(msg)? true : false;
860     ST_CloseAll(CONSOLEPLAYER, fast);
861 }
862 
NetCl_FloorHitRequest(player_t * player)863 void NetCl_FloorHitRequest(player_t *player)
864 {
865     writer_s *msg;
866     mobj_t *mo;
867 
868     if(!IS_CLIENT || !player->plr->mo)
869         return;
870 
871     mo = player->plr->mo;
872     msg = D_NetWrite();
873 
874     App_Log(DE2_DEV_MAP_VERBOSE, "NetCl_FloorHitRequest: Player %i", (int)(player - players));
875 
876     // Include the position and momentum of the hit.
877     Writer_WriteFloat(msg, mo->origin[VX]);
878     Writer_WriteFloat(msg, mo->origin[VY]);
879     Writer_WriteFloat(msg, mo->origin[VZ]);
880     Writer_WriteFloat(msg, mo->mom[MX]);
881     Writer_WriteFloat(msg, mo->mom[MY]);
882     Writer_WriteFloat(msg, mo->mom[MZ]);
883 
884     Net_SendPacket(0, GPT_FLOOR_HIT_REQUEST, Writer_Data(msg), Writer_Size(msg));
885 }
886 
NetCl_PlayerActionRequest(player_t * player,int actionType,int actionParam)887 void NetCl_PlayerActionRequest(player_t *player, int actionType, int actionParam)
888 {
889     writer_s *msg;
890 
891     if(!IS_CLIENT)
892         return;
893 
894     msg = D_NetWrite();
895 
896     App_Log(DE2_DEV_NET_VERBOSE, "NetCl_PlayerActionRequest: Player %i, action %i",
897             (int)(player - players), actionType);
898 
899     // Type of the request.
900     Writer_WriteInt32(msg, actionType);
901 
902     // Position of the action.
903     if(G_GameState() == GS_MAP)
904     {
905         Writer_WriteFloat(msg, player->plr->mo->origin[VX]);
906         Writer_WriteFloat(msg, player->plr->mo->origin[VY]);
907         Writer_WriteFloat(msg, player->plr->mo->origin[VZ]);
908 
909         // Which way is the player looking at?
910         Writer_WriteUInt32(msg, player->plr->mo->angle);
911         Writer_WriteFloat(msg, player->plr->lookDir);
912     }
913     else
914     {
915         // Not in a map, so can't provide position/direction.
916         Writer_WriteFloat(msg, 0);
917         Writer_WriteFloat(msg, 0);
918         Writer_WriteFloat(msg, 0);
919         Writer_WriteUInt32(msg, 0);
920         Writer_WriteFloat(msg, 0);
921     }
922 
923     Writer_WriteInt32(msg, actionParam);
924 
925     Net_SendPacket(0, GPT_ACTION_REQUEST, Writer_Data(msg), Writer_Size(msg));
926 }
927 
NetCl_LocalMobjState(reader_s * msg)928 void NetCl_LocalMobjState(reader_s *msg)
929 {
930     thid_t mobjId = Reader_ReadUInt16(msg);
931     thid_t targetId = Reader_ReadUInt16(msg);
932     int newState = 0;
933     int special1 = 0;
934     mobj_t* mo = 0;
935     ddstring_t* stateName = Str_New();
936 
937     Str_Read(stateName, msg);
938     newState = Defs().getStateNum(Str_Text(stateName));
939     Str_Delete(stateName);
940 
941     special1 = Reader_ReadInt32(msg);
942 
943     if(!(mo = ClMobj_Find(mobjId)))
944     {
945         App_Log(DE2_DEV_MAP_NOTE, "NetCl_LocalMobjState: ClMobj %i not found", mobjId);
946         return;
947     }
948 
949     // Let it run the sequence locally.
950     ClMobj_EnableLocalActions(mo, true);
951 
952     App_Log(DE2_DEV_MAP_VERBOSE, "ClMobj %i => state %i (target:%i, special1:%i)",
953             mobjId, newState, targetId, special1);
954 
955     if(!targetId)
956     {
957         mo->target = NULL;
958     }
959     else
960     {
961         mo->target = ClMobj_Find(targetId);
962     }
963 #if !defined(__JDOOM__) && !defined(__JDOOM64__)
964     mo->special1 = special1;
965 #endif
966     P_MobjChangeState(mo, statenum_t(newState));
967 }
968 
NetCl_DamageRequest(mobj_t * target,mobj_t * inflictor,mobj_t * source,int damage)969 void NetCl_DamageRequest(mobj_t *target, mobj_t *inflictor, mobj_t *source, int damage)
970 {
971     if(!IS_CLIENT) return;
972     if(!target) return;
973 
974     App_Log(DE2_DEV_NET_MSG,
975             "NetCl_DamageRequest: Damage %i on target=%i via inflictor=%i by source=%i",
976             damage, target->thinker.id, inflictor? inflictor->thinker.id : 0,
977             source? source->thinker.id : 0);
978 
979     writer_s *msg = D_NetWrite();
980 
981     // Amount of damage.
982     Writer_WriteInt32(msg, damage);
983 
984     // Mobjs.
985     Writer_WriteUInt16(msg, target->thinker.id);
986     Writer_WriteUInt16(msg, inflictor? inflictor->thinker.id : 0);
987     Writer_WriteUInt16(msg, source? source->thinker.id : 0);
988 
989     Net_SendPacket(0, GPT_DAMAGE_REQUEST, Writer_Data(msg), Writer_Size(msg));
990 }
991 
NetCl_UpdateTotalCounts(reader_s * msg)992 void NetCl_UpdateTotalCounts(reader_s *msg)
993 {
994 #ifndef __JHEXEN__
995     totalKills  = Reader_ReadInt32(msg);
996     totalItems  = Reader_ReadInt32(msg);
997     totalSecret = Reader_ReadInt32(msg);
998 
999     App_Log(DE2_DEV_NET_MSG, "NetCl_UpdateTotalCounts: kills=%i, items=%i, secrets=%i",
1000             totalKills, totalItems, totalSecret);
1001 #else
1002     DENG2_UNUSED(msg);
1003 #endif
1004 }
1005