1 /** @file player.cpp  Common playsim routines relating to players.
2  *
3  * @authors Copyright © 2006-2019 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-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 "player.h"
23 
24 #include <cstdlib>
25 #include <cstring>
26 #include <cstdio>
27 #include <de/memory.h>
28 #include <de/Slope>
29 #include <de/Matrix>
30 #include <doomsday/plugins.h>
31 #include "d_net.h"
32 #include "d_netcl.h"
33 #include "d_netsv.h"
34 #include "dmu_lib.h"
35 #include "g_common.h"
36 #include "g_defs.h"
37 #include "gamesession.h"
38 #if __JHERETIC__ || __JHEXEN__
39 #  include "hu_inventory.h"
40 #endif
41 #include "p_actor.h"
42 #include "p_inventory.h"
43 #include "p_map.h"
44 #include "p_saveg.h"
45 #include "p_start.h"
46 #include "r_special.h"
47 
48 using namespace de;
49 
50 #define MESSAGETICS             (4 * TICSPERSEC)
51 #define CAMERA_FRICTION_THRESHOLD (.4f)
52 
53 struct weaponslotinfo_t
54 {
55     uint num;
56     weapontype_t *types;
57 };
58 static weaponslotinfo_t weaponSlots[NUM_WEAPON_SLOTS];
59 
slotForWeaponType(weapontype_t type,uint * position=0)60 static byte slotForWeaponType(weapontype_t type, uint *position = 0)
61 {
62     byte i = 0, found = 0;
63 
64     do
65     {
66         weaponslotinfo_t *slot = &weaponSlots[i];
67         uint j = 0;
68 
69         while(!found && j < slot->num)
70         {
71             if(slot->types[j] == type)
72             {
73                 found = i + 1;
74                 if(position)
75                 {
76                     *position = j;
77                 }
78             }
79             else
80             {
81                 j++;
82             }
83         }
84 
85     } while(!found && ++i < NUM_WEAPON_SLOTS);
86 
87     return found;
88 }
89 
unlinkWeaponInSlot(byte slotidx,weapontype_t type)90 static void unlinkWeaponInSlot(byte slotidx, weapontype_t type)
91 {
92     weaponslotinfo_t *slot = &weaponSlots[slotidx - 1];
93 
94     uint i;
95     for(i = 0; i < slot->num; ++i)
96     {
97         if(slot->types[i] == type)
98             break;
99     }
100     if(i == slot->num)
101         return; // Not linked to this slot.
102 
103     memmove(&slot->types[i], &slot->types[i+1], sizeof(weapontype_t) * (slot->num - 1 - i));
104     slot->types = (weapontype_t *)M_Realloc(slot->types, sizeof(weapontype_t) * --slot->num);
105 }
106 
linkWeaponInSlot(byte slotidx,weapontype_t type)107 static void linkWeaponInSlot(byte slotidx, weapontype_t type)
108 {
109     weaponslotinfo_t *slot = &weaponSlots[slotidx-1];
110 
111     slot->types = (weapontype_t *)M_Realloc(slot->types, sizeof(weapontype_t) * ++slot->num);
112     if(slot->num > 1)
113     {
114         memmove(&slot->types[1], &slot->types[0],
115                 sizeof(weapontype_t) * (slot->num - 1));
116     }
117 
118     slot->types[0] = type;
119 }
120 
P_InitWeaponSlots()121 void P_InitWeaponSlots()
122 {
123     de::zap(weaponSlots);
124 }
125 
P_FreeWeaponSlots()126 void P_FreeWeaponSlots()
127 {
128     for(int i = 0; i < NUM_WEAPON_SLOTS; ++i)
129     {
130         weaponslotinfo_t *slot = &weaponSlots[i];
131 
132         M_Free(slot->types);
133         slot->types = 0;
134         slot->num   = 0;
135     }
136 }
137 
P_SetWeaponSlot(weapontype_t type,byte slot)138 dd_bool P_SetWeaponSlot(weapontype_t type, byte slot)
139 {
140     if(slot > NUM_WEAPON_SLOTS)
141         return false;
142 
143     // First, remove the weapon (if found).
144     byte currentSlot = slotForWeaponType(type);
145     if(currentSlot)
146     {
147         unlinkWeaponInSlot(currentSlot, type);
148     }
149 
150     if(slot != 0)
151     {
152         // Add this weapon to the specified slot (head).
153         linkWeaponInSlot(slot, type);
154     }
155 
156     return true;
157 }
158 
P_GetWeaponSlot(weapontype_t type)159 byte P_GetWeaponSlot(weapontype_t type)
160 {
161     if(type >= WT_FIRST && type < NUM_WEAPON_TYPES)
162     {
163         return slotForWeaponType(type);
164     }
165     return 0;
166 }
167 
P_WeaponSlotCycle(weapontype_t type,dd_bool prev)168 weapontype_t P_WeaponSlotCycle(weapontype_t type, dd_bool prev)
169 {
170     if(type >= WT_FIRST && type < NUM_WEAPON_TYPES)
171     {
172         uint position;
173         if(byte slotidx = slotForWeaponType(type, &position))
174         {
175             weaponslotinfo_t *slot = &weaponSlots[slotidx - 1];
176 
177             if(slot->num > 1)
178             {
179                 if(prev)
180                 {
181                     if(position == 0)
182                         position = slot->num - 1;
183                     else
184                         position--;
185                 }
186                 else
187                 {
188                     if(position == slot->num - 1)
189                         position = 0;
190                     else
191                         position++;
192                 }
193 
194                 return slot->types[position];
195             }
196         }
197     }
198 
199     return type;
200 }
201 
P_IterateWeaponsBySlot(byte slot,dd_bool reverse,int (* callback)(weapontype_t,void * context),void * context)202 int P_IterateWeaponsBySlot(byte slot, dd_bool reverse, int (*callback) (weapontype_t, void *context),
203     void *context)
204 {
205     int result = 1;
206 
207     if(slot <= NUM_WEAPON_SLOTS)
208     {
209         uint i = 0;
210         weaponslotinfo_t const *sl = &weaponSlots[slot];
211 
212         while(i < sl->num &&
213              (result = callback(sl->types[reverse ? sl->num - 1 - i : i],
214                                 context)) != 0)
215         {
216             i++;
217         }
218     }
219 
220     return result;
221 }
222 
223 #if __JHEXEN__
P_InitPlayerClassInfo()224 void P_InitPlayerClassInfo()
225 {
226     PCLASS_INFO(PCLASS_FIGHTER)->niceName = GET_TXT(TXT_PLAYERCLASS1);
227     PCLASS_INFO(PCLASS_CLERIC)->niceName = GET_TXT(TXT_PLAYERCLASS2);
228     PCLASS_INFO(PCLASS_MAGE)->niceName = GET_TXT(TXT_PLAYERCLASS3);
229     PCLASS_INFO(PCLASS_PIG)->niceName = GET_TXT(TXT_PLAYERCLASS4);
230 }
231 #endif
232 
P_GetPlayerNum(player_t const * player)233 int P_GetPlayerNum(player_t const *player)
234 {
235     for(int i = 0; i < MAXPLAYERS; ++i)
236     {
237         if(player == &players[i])
238         {
239             return i;
240         }
241     }
242     return 0;
243 }
244 
P_GetPlayerCheats(player_t const * player)245 int P_GetPlayerCheats(player_t const *player)
246 {
247     if(!player) return 0;
248 
249     if(player->plr->flags & DDPF_CAMERA)
250     {
251         return (player->cheats | CF_GODMODE | (cfg.common.cameraNoClip? CF_NOCLIP : 0));
252     }
253     return player->cheats;
254 }
255 
P_CountPlayersInGame(PlayerSelectionCriteria const & criteria)256 int P_CountPlayersInGame(PlayerSelectionCriteria const &criteria)
257 {
258     int count = 0;
259     for(int i = 0; i < MAXPLAYERS; ++i)
260     {
261         player_t *player = players + i;
262 
263         if(!player->plr->inGame) continue;
264         if((criteria & LocalOnly) && !(player->plr->flags & DDPF_LOCAL)) continue;
265 
266         count += 1;
267     }
268     return count;
269 }
270 
P_PlayerInWalkState(player_t * pl)271 dd_bool P_PlayerInWalkState(player_t *pl)
272 {
273     if(!pl->plr->mo) return false;
274 
275     /// @todo  Implementation restricts possibilities for modifying behavior solely with state definitions.
276 
277 #if __JDOOM__
278     return pl->plr->mo->state - STATES - PCLASS_INFO(pl->class_)->runState < 4;
279 #endif
280 
281 #if __JHERETIC__
282     return pl->plr->mo->state - STATES - PCLASS_INFO(pl->class_)->runState < 4;
283 #endif
284 
285 #if __JHEXEN__
286     return ((unsigned) ((pl->plr->mo->state - STATES) - PCLASS_INFO(pl->class_)->runState) < 4);
287 #endif
288 
289 #if __JDOOM64__
290     return pl->plr->mo->state - STATES - PCLASS_INFO(pl->class_)->runState < 4;
291 #endif
292 }
293 
P_TrajectoryNoise(angle_t * angle,float * slope,float degreesPhi,float degreesTheta)294 void P_TrajectoryNoise(angle_t *angle, float *slope, float degreesPhi, float degreesTheta)
295 {
296     Slope trajectory(float(*angle) / ANG180 * DD_PI, *slope);
297 
298     const Vector2f angles(degreesPhi * (randf() - randf()), degreesTheta * (randf() - randf()));
299 
300     const Vector3f front = trajectory.toUnitVec();
301     const Vector3f side  = front.cross(Vector3f(0, 0, 1)).normalize();
302     const Vector3f up    = front.cross(side);
303 
304     trajectory = Slope::fromVec(Matrix4f::rotate(angles.x, up) *
305                                 Matrix4f::rotate(angles.y, side) * front);
306 
307     *angle = int(trajectory.angle / DD_PI * ANG180);
308     *slope = trajectory.slope;
309 }
310 
P_ShotAmmo(player_t * player)311 void P_ShotAmmo(player_t *player)
312 {
313     DENG_ASSERT(player != 0);
314 
315     weaponinfo_t *wInfo = &weaponInfo[player->readyWeapon][player->class_];
316 
317     if(IS_CLIENT) return; // Server keeps track of this.
318 
319     int fireMode = 0;
320 #if __JHERETIC__
321     if(gfw_Rule(deathmatch))
322         fireMode = 0; // In deathmatch always use mode zero.
323     else
324         fireMode = (player->powers[PT_WEAPONLEVEL2]? 1 : 0);
325 #endif
326 
327     for(int i = 0; i < NUM_AMMO_TYPES; ++i)
328     {
329         if(!wInfo->mode[fireMode].ammoType[i])
330             continue; // Weapon does not take this ammo.
331 
332         // Don't let it fall below zero.
333         player->ammo[i].owned = MAX_OF(0,
334             player->ammo[i].owned - wInfo->mode[fireMode].perShot[i]);
335     }
336     player->update |= PSF_AMMO;
337 }
338 
P_MaybeChangeWeapon(player_t * player,weapontype_t weapon,ammotype_t ammo,dd_bool force)339 weapontype_t P_MaybeChangeWeapon(player_t *player, weapontype_t weapon, ammotype_t ammo, dd_bool force)
340 {
341     if(IS_NETWORK_SERVER)
342     {
343         // This is done on clientside.
344         NetSv_MaybeChangeWeapon(player - players, weapon, ammo, force);
345         return WT_NOCHANGE;
346     }
347 
348     App_Log(DE2_DEV_MAP_XVERBOSE,
349             "P_MaybeChangeWeapon: plr %i, weapon %i, ammo %i, force %i",
350             (int)(player - players), weapon, ammo, force);
351 
352     int pclass = player->class_;
353 
354     // Assume weapon power level zero.
355     int lvl = 0;
356 #if __JHERETIC__
357     if(player->powers[PT_WEAPONLEVEL2])
358         lvl = 1;
359 #endif
360 
361     bool found = false;
362     weapontype_t retVal = WT_NOCHANGE;
363 
364     if(weapon == WT_NOCHANGE && ammo == AT_NOAMMO) // Out of ammo.
365     {
366         bool good;
367 
368         // Note we have no auto-logical choice for a forced change.
369         // Preferences are set by the user.
370         for(int i = 0; i < NUM_WEAPON_TYPES && !found; ++i)
371         {
372             weapontype_t candidate = weapontype_t(cfg.common.weaponOrder[i]);
373             weaponinfo_t *winf     = &weaponInfo[candidate][pclass];
374 
375             // Is candidate available in this game mode?
376             if(!(winf->mode[lvl].gameModeBits & gameModeBits))
377                 continue;
378 
379             // Does the player actually own this candidate?
380             if(!player->weapons[candidate].owned)
381                 continue;
382 
383             // Is there sufficent ammo for the candidate weapon?
384             // Check amount for each used ammo type.
385             good = true;
386             for(int ammotype = 0; ammotype < NUM_AMMO_TYPES && good; ++ammotype)
387             {
388                 if(!winf->mode[lvl].ammoType[ammotype])
389                     continue; // Weapon does not take this type of ammo.
390 
391 #if __JHERETIC__
392                 // Heretic always uses lvl 0 ammo requirements in deathmatch
393                 if(gfw_Rule(deathmatch) &&
394                    player->ammo[ammotype].owned < winf->mode[0].perShot[ammotype])
395                 {
396                     // Not enough ammo of this type. Candidate is NOT good.
397                     good = false;
398                 }
399                 else
400 #endif
401                 if(player->ammo[ammotype].owned < winf->mode[lvl].perShot[ammotype])
402                 {
403                     // Not enough ammo of this type. Candidate is NOT good.
404                     good = false;
405                 }
406             }
407 
408             if(good)
409             {
410                 // Candidate weapon meets the criteria.
411                 retVal = candidate;
412                 found = true;
413             }
414         }
415     }
416     else if(weapon != WT_NOCHANGE) // Player was given a NEW weapon.
417     {
418         // A forced weapon change?
419         if(force)
420         {
421             retVal = weapon;
422         }
423         // Is the player currently firing?
424         else if(!((player->brain.attack) && cfg.common.noWeaponAutoSwitchIfFiring))
425         {
426             // Should we change weapon automatically?
427 
428             if(cfg.common.weaponAutoSwitch == 2) // Behavior: Always change
429             {
430                 retVal = weapon;
431             }
432             else if(cfg.common.weaponAutoSwitch == 1) // Behavior: Change if better
433             {
434                 // Iterate the weapon order array and see if a weapon change
435                 // should be made. Preferences are user selectable.
436                 for(int i = 0; i < NUM_WEAPON_TYPES; ++i)
437                 {
438                     weapontype_t candidate = weapontype_t(cfg.common.weaponOrder[i]);
439                     weaponinfo_t *winf     = &weaponInfo[candidate][pclass];
440 
441                     // Is candidate available in this game mode?
442                     if(!(winf->mode[lvl].gameModeBits & gameModeBits))
443                         continue;
444 
445                     if(weapon == candidate)
446                     {
447                         // weapon has a higher priority than the readyweapon.
448                         retVal = weapon;
449                     }
450                     else if(player->readyWeapon == candidate)
451                     {
452                         // readyweapon has a higher priority so don't change.
453                         break;
454                     }
455                 }
456             }
457         }
458     }
459     else if(ammo != AT_NOAMMO) // Player is about to be given some ammo.
460     {
461         if(force || (!(player->ammo[ammo].owned > 0) && cfg.common.ammoAutoSwitch))
462         {
463             // We were down to zero, so select a new weapon.
464 
465             // Iterate the weapon order array and see if the player owns a weapon that can be used
466             // now they have this ammo.
467             // Preferences are user selectable.
468             for(int i = 0; i < NUM_WEAPON_TYPES; ++i)
469             {
470                 weapontype_t candidate = weapontype_t(cfg.common.weaponOrder[i]);
471                 weaponinfo_t *winf     = &weaponInfo[candidate][pclass];
472 
473                 // Is candidate available in this game mode?
474                 if(!(winf->mode[lvl].gameModeBits & gameModeBits))
475                     continue;
476 
477                 // Does the player actually own this candidate?
478                 if(!player->weapons[candidate].owned)
479                     continue;
480 
481                 // Does the weapon use this type of ammo?
482                 if(!(winf->mode[lvl].ammoType[ammo]))
483                     continue;
484 
485                 /**
486                  * @todo Have we got enough of ALL used ammo types?
487                  *
488                  * Problem, since the ammo has not been given yet (could be an object that gives
489                  * several ammo types eg backpack) we can't test for this with what we know!
490                  *
491                  * This routine should be called AFTER the new ammo has been given. Somewhat complex
492                  * logic to decipher first...
493                  */
494 
495                 if(cfg.common.ammoAutoSwitch == 2) // Behavior: Always change
496                 {
497                     retVal = candidate;
498                     break;
499                 }
500                 else if(cfg.common.ammoAutoSwitch == 1 && player->readyWeapon == candidate)
501                 {
502                     // readyweapon has a higher priority so don't change.
503                     break;
504                 }
505             }
506         }
507     }
508 
509     // Don't change to the existing weapon.
510     if(retVal == player->readyWeapon)
511         retVal = WT_NOCHANGE;
512 
513     // Choosen a weapon to change to?
514     if(retVal != WT_NOCHANGE)
515     {
516         App_Log(DE2_DEV_MAP_XVERBOSE, "P_MaybeChangeWeapon: Player %i decided to change to weapon %i",
517                 (int)(player - players), retVal);
518 
519         player->pendingWeapon = retVal;
520 
521         if(IS_CLIENT)
522         {
523             // Tell the server.
524             NetCl_PlayerActionRequest(player, GPA_CHANGE_WEAPON, player->pendingWeapon);
525         }
526     }
527 
528     return retVal;
529 }
530 
P_CheckAmmo(player_t * plr)531 dd_bool P_CheckAmmo(player_t *plr)
532 {
533     DENG_ASSERT(plr != 0);
534 
535     weaponinfo_t *wInfo = &weaponInfo[plr->readyWeapon][plr->class_];
536 
537     int fireMode = 0;
538 #if __JHERETIC__
539     // If deathmatch always use firemode two ammo requirements.
540     if(plr->powers[PT_WEAPONLEVEL2] && !gfw_Rule(deathmatch))
541     {
542         fireMode = 1;
543     }
544 #endif
545 
546 #if __JHEXEN__
547     //// @todo Kludge: Work around the multiple firing modes problems.
548     //// We need to split the weapon firing routines and implement them as
549     //// new fire modes.
550     if(plr->class_ == PCLASS_FIGHTER && plr->readyWeapon != WT_FOURTH)
551     {
552         return true;
553     }
554     // < KLUDGE
555 #endif
556 
557     // Check we have enough of ALL ammo types used by this weapon.
558     bool good = true;
559     for(int i = 0; i < NUM_AMMO_TYPES && good; ++i)
560     {
561         if(!wInfo->mode[fireMode].ammoType[i])
562             continue; // Weapon does not take this type of ammo.
563 
564         // Minimal amount for one shot varies.
565         if(plr->ammo[i].owned < wInfo->mode[fireMode].perShot[i])
566         {
567             // Insufficent ammo to fire this weapon.
568             good = false;
569         }
570     }
571 
572     if(good) return true;
573 
574     // Out of ammo, pick a weapon to change to.
575     P_MaybeChangeWeapon(plr, WT_NOCHANGE, AT_NOAMMO, false);
576 
577     // Now set appropriate weapon overlay.
578     if(plr->pendingWeapon != WT_NOCHANGE)
579     {
580         P_SetPsprite(plr, ps_weapon, statenum_t(wInfo->mode[fireMode].states[WSN_DOWN]));
581     }
582 
583     return false;
584 }
585 
P_PlayerFindWeapon(player_t * player,dd_bool prev)586 weapontype_t P_PlayerFindWeapon(player_t *player, dd_bool prev)
587 {
588     DENG_ASSERT(player != 0);
589 
590 #if __JDOOM__
591     static weapontype_t wp_list[] = {
592         WT_FIRST, WT_SECOND, WT_THIRD, WT_NINETH, WT_FOURTH,
593         WT_FIFTH, WT_SIXTH, WT_SEVENTH, WT_EIGHTH
594     };
595 
596 #elif __JDOOM64__
597     static weapontype_t wp_list[] = {
598         WT_FIRST, WT_SECOND, WT_THIRD, WT_NINETH, WT_FOURTH,
599         WT_FIFTH, WT_SIXTH, WT_SEVENTH, WT_EIGHTH, WT_TENTH
600     };
601 #elif __JHERETIC__
602     static weapontype_t wp_list[] = {
603         WT_FIRST, WT_SECOND, WT_THIRD, WT_FOURTH, WT_FIFTH,
604         WT_SIXTH, WT_SEVENTH, WT_EIGHTH
605     };
606 
607 #elif __JHEXEN__ || __JSTRIFE__
608     static weapontype_t wp_list[] = {
609         WT_FIRST, WT_SECOND, WT_THIRD, WT_FOURTH
610     };
611 #endif
612 
613     int lvl = 0;
614 #if __JHERETIC__
615     lvl = (player->powers[PT_WEAPONLEVEL2]? 1 : 0);
616 #endif
617 
618     // Are we using weapon order preferences for next/previous?
619     weapontype_t *list;
620     if(cfg.common.weaponNextMode)
621     {
622         list = (weapontype_t *) cfg.common.weaponOrder;
623         prev = !prev; // Invert order.
624     }
625     else
626     {
627         list = wp_list;
628     }
629 
630 
631     // Find the current position in the weapon list.
632     int i = 0;
633     weapontype_t w = WT_FIRST;
634     for(; i < NUM_WEAPON_TYPES; ++i)
635     {
636         w = list[i];
637         if(!cfg.common.weaponCycleSequential || player->pendingWeapon == WT_NOCHANGE)
638         {
639             if(w == player->readyWeapon)
640                 break;
641         }
642         else if(w == player->pendingWeapon)
643         {
644             break;
645         }
646     }
647 
648     // Locate the next or previous weapon owned by the player.
649     weapontype_t initial = w;
650     for(;;)
651     {
652         // Move the iterator.
653         if(prev)
654             i--;
655         else
656             i++;
657 
658         if(i < 0)
659             i = NUM_WEAPON_TYPES - 1;
660         else if(i > NUM_WEAPON_TYPES - 1)
661             i = 0;
662 
663         w = list[i];
664 
665         // Have we circled around?
666         if(w == initial)
667             break;
668 
669         // Available in this game mode? And a valid weapon?
670         if((weaponInfo[w][player->class_].mode[lvl].
671             gameModeBits & gameModeBits) && player->weapons[w].owned)
672             break;
673     }
674 
675     return w;
676 }
677 
678 #if __JHEXEN__
P_PlayerChangeClass(player_t * player,playerclass_t newClass)679 void P_PlayerChangeClass(player_t *player, playerclass_t newClass)
680 {
681     DENG_ASSERT(player != 0);
682 
683     if(newClass < PCLASS_FIRST || newClass >= NUM_PLAYER_CLASSES)
684         return;
685 
686     // Don't change if morphed.
687     if(player->morphTics) return;
688     if(!PCLASS_INFO(newClass)->userSelectable) return;
689 
690     player->class_ = newClass;
691     cfg.playerClass[player - players] = newClass;
692     P_ClassForPlayerWhenRespawning(player - players, true /*clear change request*/);
693 
694     // Take away armor.
695     for(int i = 0; i < NUMARMOR; ++i)
696     {
697         player->armorPoints[i] = 0;
698     }
699     player->update |= PSF_ARMOR_POINTS;
700 
701     P_PostMorphWeapon(player, WT_FIRST);
702 
703     if(player->plr->mo)
704     {
705         // Respawn the player and destroy the old mobj.
706         mobj_t *oldMo = player->plr->mo;
707 
708         P_SpawnPlayer(player - players, newClass, oldMo->origin[VX],
709                       oldMo->origin[VY], oldMo->origin[VZ], oldMo->angle, 0,
710                       P_MobjIsCamera(oldMo), true);
711         P_MobjRemove(oldMo, true);
712     }
713 }
714 #endif
715 
P_SetMessageWithFlags(const player_t * pl,char const * msg,int flags)716 void P_SetMessageWithFlags(const player_t *pl, char const *msg, int flags)
717 {
718     DENG2_ASSERT(pl);
719 
720     if(!msg || !msg[0]) return;
721 
722     ST_LogPost(pl - players, flags, msg);
723 
724     if(pl == &players[CONSOLEPLAYER])
725     {
726         App_Log(DE2_LOG_MAP | (cfg.common.echoMsg? DE2_LOG_NOTE : DE2_LOG_VERBOSE), "%s", msg);
727     }
728 
729     // Servers are responsible for sending these messages to the clients.
730     NetSv_SendMessage(pl - players, msg);
731 }
732 
P_SetMessage(const player_t * plr,char const * msg)733 void P_SetMessage(const player_t *plr, char const *msg)
734 {
735     P_SetMessageWithFlags(plr, msg, 0);
736 }
737 
738 #if __JHEXEN__
P_SetYellowMessageWithFlags(player_t * pl,char const * msg,int flags)739 void P_SetYellowMessageWithFlags(player_t *pl, char const *msg, int flags)
740 {
741 #define YELLOW_FMT      "{r=1;g=0.7;b=0.3;}"
742 #define YELLOW_FMT_LEN  18
743 
744     if(!msg || !msg[0]) return;
745 
746     size_t len = strlen(msg);
747 
748     AutoStr *buf = AutoStr_NewStd();
749     Str_Reserve(buf, YELLOW_FMT_LEN + len+1);
750     Str_Set(buf, YELLOW_FMT);
751     Str_Appendf(buf, "%s", msg);
752 
753     ST_LogPost(pl - players, flags, Str_Text(buf));
754 
755     if(pl == &players[CONSOLEPLAYER])
756     {
757         App_Log(DE2_LOG_MAP | (cfg.common.echoMsg? DE2_LOG_NOTE : DE2_LOG_VERBOSE), "%s", msg);
758     }
759 
760     // Servers are responsible for sending these messages to the clients.
761     /// @todo We shouldn't need to send the format string along with every
762     /// important game message. Instead flag a bit in the packet and then
763     /// reconstruct on the other end.
764     NetSv_SendMessage(pl - players, Str_Text(buf));
765 
766 #undef YELLOW_FMT_LEN
767 #undef YELLOW_FMT
768 }
769 
P_SetYellowMessage(player_t * pl,char const * msg)770 void P_SetYellowMessage(player_t *pl, char const *msg)
771 {
772     P_SetYellowMessageWithFlags(pl, msg, 0);
773 }
774 #endif
775 
P_Thrust3D(player_t * player,angle_t angle,float lookdir,coord_t forwardMove,coord_t sideMove)776 void P_Thrust3D(player_t *player, angle_t angle, float lookdir, coord_t forwardMove, coord_t sideMove)
777 {
778     angle_t pitch = int(LOOKDIR2DEG(lookdir) / 360 * ANGLE_MAX);
779     angle_t sideangle = angle - ANG90;
780     mobj_t *mo = player->plr->mo;
781     coord_t zmul, mom[3];
782 
783     angle >>= ANGLETOFINESHIFT;
784     pitch >>= ANGLETOFINESHIFT;
785     mom[MX] = forwardMove * FIX2FLT(finecosine[angle]);
786     mom[MY] = forwardMove * FIX2FLT(finesine[angle]);
787     mom[MZ] = forwardMove * FIX2FLT(finesine[pitch]);
788 
789     zmul = FIX2FLT(finecosine[pitch]);
790     mom[MX] *= zmul;
791     mom[MY] *= zmul;
792 
793     sideangle >>= ANGLETOFINESHIFT;
794     mom[MX] += sideMove * FIX2FLT(finecosine[sideangle]);
795     mom[MY] += sideMove * FIX2FLT(finesine[sideangle]);
796 
797     mo->mom[MX] += mom[MX];
798     mo->mom[MY] += mom[MY];
799     mo->mom[MZ] += mom[MZ];
800 }
801 
P_CameraXYMovement(mobj_t * mo)802 int P_CameraXYMovement(mobj_t *mo)
803 {
804     if(!P_MobjIsCamera(mo))
805         return false;
806 
807 #if __JDOOM__ || __JDOOM64__
808     if(mo->flags & MF_NOCLIP ||
809        // This is a very rough check! Sometimes you get stuck in things.
810        P_CheckPositionXYZ(mo, mo->origin[VX] + mo->mom[MX], mo->origin[VY] + mo->mom[MY], mo->origin[VZ]))
811     {
812 #endif
813 
814         P_MobjUnlink(mo);
815         mo->origin[VX] += mo->mom[MX];
816         mo->origin[VY] += mo->mom[MY];
817         P_MobjLink(mo);
818         P_CheckPositionXY(mo, mo->origin[VX], mo->origin[VY]);
819         mo->floorZ = tmFloorZ;
820         mo->ceilingZ = tmCeilingZ;
821 
822 #if __JDOOM__ || __JDOOM64__
823     }
824 #endif
825 
826     // Friction.
827     if(!INRANGE_OF(mo->player->brain.forwardMove, 0, CAMERA_FRICTION_THRESHOLD) ||
828        !INRANGE_OF(mo->player->brain.sideMove, 0, CAMERA_FRICTION_THRESHOLD) ||
829        !INRANGE_OF(mo->player->brain.upMove, 0, CAMERA_FRICTION_THRESHOLD))
830     {
831         // While moving; normal friction applies.
832         mo->mom[MX] *= FRICTION_NORMAL;
833         mo->mom[MY] *= FRICTION_NORMAL;
834     }
835     else
836     {
837         // Else lose momentum, quickly!.
838         mo->mom[MX] *= FRICTION_HIGH;
839         mo->mom[MY] *= FRICTION_HIGH;
840     }
841 
842     return true;
843 }
844 
P_CameraZMovement(mobj_t * mo)845 int P_CameraZMovement(mobj_t *mo)
846 {
847     if(!P_MobjIsCamera(mo))
848         return false;
849 
850     mo->origin[VZ] += mo->mom[MZ];
851 
852     // Friction.
853     if(!INRANGE_OF(mo->player->brain.forwardMove, 0, CAMERA_FRICTION_THRESHOLD) ||
854        !INRANGE_OF(mo->player->brain.sideMove, 0, CAMERA_FRICTION_THRESHOLD) ||
855        !INRANGE_OF(mo->player->brain.upMove, 0, CAMERA_FRICTION_THRESHOLD))
856     {
857         // While moving; normal friction applies.
858         mo->mom[MZ] *= FRICTION_NORMAL;
859     }
860     else
861     {
862         // Lose momentum quickly!.
863         mo->mom[MZ] *= FRICTION_HIGH;
864     }
865 
866     return true;
867 }
868 
P_PlayerThinkCamera(player_t * player)869 void P_PlayerThinkCamera(player_t *player)
870 {
871     DENG_ASSERT(player != 0);
872 
873     mobj_t *mo = player->plr->mo;
874     if(!mo) return;
875 
876     // If this player is not a camera, get out of here.
877     if(!(player->plr->flags & DDPF_CAMERA))
878     {
879         if(player->playerState == PST_LIVE)
880         {
881             mo->flags |= (MF_SOLID | MF_SHOOTABLE | MF_PICKUP);
882         }
883         return;
884     }
885 
886     mo->flags &= ~(MF_SOLID | MF_SHOOTABLE | MF_PICKUP);
887 
888     // How about viewlock?
889     if(player->viewLock)
890     {
891         mobj_t *target = players->viewLock;
892 
893         if(!target->player || !target->player->plr->inGame)
894         {
895             player->viewLock = 0;
896             return;
897         }
898 
899         int full = player->lockFull;
900 
901         //player->plr->flags |= DDPF_FIXANGLES;
902         /* $unifiedangles */
903         mo->angle = M_PointToAngle2(mo->origin, target->origin);
904         //player->plr->clAngle = mo->angle;
905         player->plr->flags |= DDPF_INTERYAW;
906 
907         if(full)
908         {
909             coord_t dist = M_ApproxDistance(mo->origin[VX] - target->origin[VX],
910                                             mo->origin[VY] - target->origin[VY]);
911             angle_t angle = M_PointXYToAngle2(0, 0,
912                                               target->origin[VZ] + (target->height / 2) - mo->origin[VZ],
913                                               dist);
914 
915             player->plr->lookDir = -(angle / (float) ANGLE_MAX * 360.0f - 90);
916             if(player->plr->lookDir > 180)
917                 player->plr->lookDir -= 360;
918 
919             player->plr->lookDir *= 110.0f / 85.0f;
920 
921             if(player->plr->lookDir > 110)
922                 player->plr->lookDir = 110;
923             if(player->plr->lookDir < -110)
924                 player->plr->lookDir = -110;
925 
926             player->plr->flags |= DDPF_INTERPITCH;
927         }
928     }
929 }
930 
D_CMD(SetCamera)931 D_CMD(SetCamera)
932 {
933     DENG_UNUSED(src); DENG_UNUSED(argc);
934 
935     int p = atoi(argv[1]);
936     if(p < 0 || p >= MAXPLAYERS)
937     {
938         App_Log(DE2_LOG_SCR| DE2_LOG_ERROR, "Invalid console number %i", p);
939         return false;
940     }
941 
942     player_t *player = &players[p];
943 
944     player->plr->flags ^= DDPF_CAMERA;
945     if(player->plr->inGame)
946     {
947         if(player->plr->flags & DDPF_CAMERA)
948         {
949             // Is now a camera.
950             if(player->plr->mo)
951             {
952                 player->plr->mo->origin[VZ] += player->viewHeight;
953             }
954         }
955         else
956         {
957             // Is now a "real" player.
958             if(player->plr->mo)
959             {
960                 player->plr->mo->origin[VZ] -= player->viewHeight;
961             }
962         }
963     }
964 
965     return true;
966 }
967 
968 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
P_PlayerGiveArmorBonus(player_t * plr,int points)969 int P_PlayerGiveArmorBonus(player_t *plr, int points)
970 #else // __JHEXEN__
971 int P_PlayerGiveArmorBonus(player_t *plr, armortype_t type, int points)
972 #endif
973 {
974     if(!points) return 0;
975 
976 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
977     int *current = &plr->armorPoints;
978 #else // __JHEXEN__
979     int *current = &plr->armorPoints[type];
980 #endif
981 
982     int oldPoints = *current;
983     int delta;
984     if(points > 0)
985     {
986         delta = points; /// @todo No upper limit?
987     }
988     else
989     {
990         if(*current + points < 0)
991             delta = -(*current);
992         else
993             delta = points;
994     }
995 
996     *current += delta;
997     if(*current != oldPoints)
998     {
999         plr->update |= PSF_ARMOR_POINTS;
1000     }
1001 
1002     return delta;
1003 }
1004 
1005 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
P_PlayerSetArmorType(player_t * plr,int type)1006 void P_PlayerSetArmorType(player_t *plr, int type)
1007 {
1008     int oldType = plr->armorType;
1009 
1010     plr->armorType = type;
1011     if(plr->armorType != oldType)
1012     {
1013         plr->update |= PSF_ARMOR_TYPE;
1014     }
1015 }
1016 #endif
1017 
D_CMD(SetViewMode)1018 D_CMD(SetViewMode)
1019 {
1020     DENG_UNUSED(src);
1021 
1022     if(argc > 2) return false;
1023 
1024     int pl = CONSOLEPLAYER;
1025     if(argc == 2)
1026     {
1027         pl = atoi(argv[1]);
1028     }
1029     if(pl < 0 || pl >= MAXPLAYERS)
1030         return false;
1031 
1032     if(!(players[pl].plr->flags & DDPF_CHASECAM))
1033     {
1034         players[pl].plr->flags |= DDPF_CHASECAM;
1035     }
1036     else
1037     {
1038         players[pl].plr->flags &= ~DDPF_CHASECAM;
1039     }
1040 
1041     return true;
1042 }
1043 
D_CMD(SetViewLock)1044 D_CMD(SetViewLock)
1045 {
1046     DENG_UNUSED(src);
1047 
1048     int pl = CONSOLEPLAYER, lock;
1049 
1050     if(!qstricmp(argv[0], "lockmode"))
1051     {
1052         lock = atoi(argv[1]);
1053         if(lock)
1054             players[pl].lockFull = true;
1055         else
1056             players[pl].lockFull = false;
1057 
1058         return true;
1059     }
1060     if(argc < 2)
1061         return false;
1062 
1063     if(argc >= 3)
1064         pl = atoi(argv[2]); // Console number.
1065 
1066     lock = atoi(argv[1]);
1067 
1068     if(!(lock == pl || lock < 0 || lock >= MAXPLAYERS))
1069     {
1070         if(players[lock].plr->inGame && players[lock].plr->mo)
1071         {
1072             players[pl].viewLock = players[lock].plr->mo;
1073             return true;
1074         }
1075     }
1076 
1077     players[pl].viewLock = 0;
1078 
1079     return false;
1080 }
1081 
D_CMD(MakeLocal)1082 D_CMD(MakeLocal)
1083 {
1084     DENG_UNUSED(src); DENG_UNUSED(argc);
1085 
1086     if(G_GameState() != GS_MAP)
1087     {
1088         App_Log(DE2_LOG_ERROR | DE2_LOG_MAP, "You must be in a game to create a local player.");
1089         return false;
1090     }
1091 
1092     int p = atoi(argv[1]);
1093     if(p < 0 || p >= MAXPLAYERS)
1094     {
1095         App_Log(DE2_SCR_ERROR, "Invalid console number %i.", p);
1096         return false;
1097     }
1098 
1099     player_t *plr = &players[p];
1100     if(plr->plr->inGame)
1101     {
1102         App_Log(DE2_LOG_ERROR | DE2_LOG_MAP, "Player %i is already in the game.", p);
1103         return false;
1104     }
1105 
1106     plr->playerState = PST_REBORN;
1107     plr->plr->inGame = true;
1108 
1109     char buf[20];
1110     sprintf(buf, "conlocp %i", p);
1111     DD_Execute(false, buf);
1112 
1113     P_DealPlayerStarts(0);
1114 
1115     return true;
1116 }
1117 
D_CMD(PrintPlayerCoords)1118 D_CMD(PrintPlayerCoords)
1119 {
1120     DENG_UNUSED(src); DENG_UNUSED(argc); DENG_UNUSED(argv);
1121 
1122     mobj_t *mo;
1123 
1124     if(G_GameState() != GS_MAP)
1125         return false;
1126 
1127     if(!(mo = players[CONSOLEPLAYER].plr->mo))
1128         return false;
1129 
1130     App_Log(DE2_LOG_MAP, "Console %i: X=%g Y=%g Z=%g", CONSOLEPLAYER,
1131                          mo->origin[VX], mo->origin[VY], mo->origin[VZ]);
1132 
1133     return true;
1134 }
1135 
D_CMD(CycleSpy)1136 D_CMD(CycleSpy)
1137 {
1138     DENG_UNUSED(src); DENG_UNUSED(argc); DENG_UNUSED(argv);
1139 
1140     //// @todo The engine should do this.
1141     App_Log(DE2_LOG_MAP | DE2_LOG_ERROR, "Spying not allowed.");
1142 #if 0
1143     if(G_GameState() == GS_MAP && !deathmatch)
1144     {
1145         // Cycle the display player.
1146         do
1147         {
1148             Set(DD_DISPLAYPLAYER, DISPLAYPLAYER + 1);
1149             if(DISPLAYPLAYER == MAXPLAYERS)
1150             {
1151                 Set(DD_DISPLAYPLAYER, 0);
1152             }
1153         } while(!players[DISPLAYPLAYER].plr->inGame &&
1154                 DISPLAYPLAYER != CONSOLEPLAYER);
1155     }
1156 #endif
1157     return true;
1158 }
1159 
D_CMD(SpawnMobj)1160 D_CMD(SpawnMobj)
1161 {
1162     DENG_UNUSED(src);
1163 
1164     if(argc != 5 && argc != 6)
1165     {
1166         App_Log(DE2_SCR_NOTE, "Usage: %s (type) (x) (y) (z) (angle)", argv[0]);
1167         App_Log(DE2_LOG_SCR, "Type must be a defined Thing ID or Name.");
1168         App_Log(DE2_LOG_SCR, "Z is an offset from the floor, 'floor', 'ceil' or 'random'.");
1169         App_Log(DE2_LOG_SCR, "Angle (0..360) is optional.");
1170         return true;
1171     }
1172 
1173     if(IS_CLIENT)
1174     {
1175         App_Log(DE2_SCR_ERROR, "%s can't be used by clients", argv[0]);
1176         return false;
1177     }
1178 
1179     // First try to find the thing by ID.
1180     mobjtype_t type;
1181     if((type = mobjtype_t(Defs().getMobjNum(argv[1]))) < 0)
1182     {
1183         // Try to find it by name instead.
1184         if((type = mobjtype_t(Defs().getMobjNumForName(argv[1]))) < 0)
1185         {
1186             App_Log(DE2_LOG_RES | DE2_LOG_ERROR, "Undefined thing type %s", argv[1]);
1187             return false;
1188         }
1189     }
1190 
1191     // The coordinates.
1192     coord_t pos[3];
1193     pos[VX] = strtod(argv[2], 0);
1194     pos[VY] = strtod(argv[3], 0);
1195     pos[VZ] = 0;
1196 
1197     int spawnFlags = 0;
1198     if(!qstricmp(argv[4], "ceil"))
1199     {
1200         spawnFlags |= MSF_Z_CEIL;
1201     }
1202     else if(!qstricmp(argv[4], "random"))
1203     {
1204         spawnFlags |= MSF_Z_RANDOM;
1205     }
1206     else
1207     {
1208         spawnFlags |= MSF_Z_FLOOR;
1209         if(qstricmp(argv[4], "floor"))
1210         {
1211             pos[VZ] = strtod(argv[4], 0);
1212         }
1213     }
1214 
1215     angle_t angle = 0;
1216     if(argc == 6)
1217     {
1218         angle = ((int) (strtod(argv[5], 0) / 360 * FRACUNIT)) << 16;
1219     }
1220 
1221     if(mobj_t *mo = P_SpawnMobj(type, pos, angle, spawnFlags))
1222     {
1223 #if __JDOOM64__
1224         // jd64 > kaiser - another cheesy hack!!!
1225         if(mo->type == MT_DART)
1226         {
1227             S_StartSound(SFX_SKESWG, mo); // We got darts! spawn skeswg sound!
1228         }
1229         else
1230         {
1231             S_StartSound(SFX_ITMBK, mo); // If not dart, then spawn itmbk sound
1232             mo->translucency = 255;
1233             mo->spawnFadeTics = 0;
1234             mo->intFlags |= MIF_FADE;
1235         }
1236     // << d64tc
1237 #else
1238         DENG_UNUSED(mo);
1239 #endif
1240     }
1241 
1242     return true;
1243 }
1244 
Player_LeaveMap(player_t * player,dd_bool newHub)1245 void Player_LeaveMap(player_t *player, dd_bool newHub)
1246 {
1247     DENG2_ASSERT(player);
1248     int const plrNum = player - players;
1249 #if !__JHEXEN__
1250     DENG2_UNUSED(newHub);
1251 #endif
1252 
1253     if(!player->plr->inGame) return;
1254 
1255 #if __JHEXEN__
1256     // Remember if flying.
1257     int const flightPower = player->powers[PT_FLIGHT];
1258 #endif
1259 
1260 #if __JHERETIC__
1261     // Empty the inventory of excess items
1262     for(int i = 0; i < NUM_INVENTORYITEM_TYPES; ++i)
1263     {
1264         inventoryitemtype_t type = inventoryitemtype_t(IIT_FIRST + i);
1265         uint count = P_InventoryCount(plrNum, type);
1266 
1267         if(count)
1268         {
1269             if(type != IIT_FLY)
1270             {
1271                 count--;
1272             }
1273 
1274             for(uint k = 0; k < count; ++k)
1275             {
1276                 P_InventoryTake(plrNum, type, true);
1277             }
1278         }
1279     }
1280 #endif
1281 
1282 #if __JHEXEN__
1283     if(newHub)
1284     {
1285         uint count = P_InventoryCount(plrNum, IIT_FLY);
1286         for(uint i = 0; i < count; ++i)
1287         {
1288             P_InventoryTake(plrNum, IIT_FLY, true);
1289         }
1290     }
1291 #endif
1292 
1293     // Remove their powers.
1294     player->update |= PSF_POWERS;
1295     de::zap(player->powers);
1296 
1297     R_UpdateSpecialFilterWithTimeDelta(plrNum, 0 /* instantly */);
1298 
1299 #if __JHEXEN__
1300     if(!newHub && !gfw_Rule(deathmatch))
1301     {
1302         player->powers[PT_FLIGHT] = flightPower; // Restore flight.
1303     }
1304 #endif
1305 
1306     // Remove their keys.
1307 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
1308     player->update |= PSF_KEYS;
1309     de::zap(player->keys);
1310 #else
1311     if(!gfw_Rule(deathmatch) && newHub)
1312     {
1313         player->keys = 0;
1314     }
1315 #endif
1316 
1317     // Misc
1318 #if __JHERETIC__
1319     player->rain1 = nullptr;
1320     player->rain2 = nullptr;
1321 #endif
1322 
1323     // Un-morph?
1324 #if __JHERETIC__ || __JHEXEN__
1325     player->update |= PSF_MORPH_TIME;
1326     if(player->morphTics)
1327     {
1328         player->readyWeapon = weapontype_t(player->plr->mo->special1); // Restore weapon.
1329         player->morphTics = 0;
1330     }
1331 #endif
1332 
1333     player->plr->mo->flags &= ~MF_SHADOW; // Cancel invisibility.
1334 
1335     player->plr->lookDir       = 0;
1336     player->plr->extraLight    = 0; // Cancel gun flashes.
1337     player->plr->fixedColorMap = 0; // Cancel IR goggles.
1338 
1339     // Clear filter.
1340     player->plr->flags &= ~DDPF_VIEW_FILTER;
1341     player->damageCount = 0; // No palette changes.
1342     player->bonusCount  = 0;
1343 
1344 #if __JHEXEN__
1345     player->poisonCount = 0;
1346 #endif
1347 
1348     ST_LogEmpty(plrNum);
1349 
1350     // Update this client's stats.
1351     NetSv_SendPlayerState(plrNum, DDSP_ALL_PLAYERS, PSF_FRAGS | PSF_COUNTERS, true);
1352 }
1353 
Player_WaitingForReborn(player_t const * plr)1354 dd_bool Player_WaitingForReborn(player_t const *plr)
1355 {
1356     DENG2_ASSERT(plr != 0);
1357     return (plr->plr->inGame && plr->playerState == PST_REBORN && !P_MobjIsCamera(plr->plr->mo));
1358 }
1359 
Player_ViewYawAngle(int playerNum)1360 angle_t Player_ViewYawAngle(int playerNum)
1361 {
1362     if(playerNum < 0 || playerNum >= MAXPLAYERS)
1363     {
1364         return 0;
1365     }
1366 
1367     ddplayer_t *plr = players[playerNum].plr;
1368     angle_t ang = plr->mo->angle + (int) (ANGLE_MAX * -G_GetLookOffset(playerNum));
1369 
1370     if(Get(DD_USING_HEAD_TRACKING))
1371     {
1372         // The actual head yaw angle will be used for rendering.
1373         ang -= plr->appliedBodyYaw;
1374     }
1375 
1376     return ang;
1377 }
1378 
write(writer_s * writer,playerheader_t & plrHdr) const1379 void player_s::write(writer_s *writer, playerheader_t &plrHdr) const
1380 {
1381 #if __JDOOM64__ || __JHERETIC__ || __JHEXEN__
1382     int const plrnum = P_GetPlayerNum(this);
1383 #endif
1384 
1385     player_t temp, *p = &temp;
1386     ddplayer_t ddtemp, *dp = &ddtemp;
1387 
1388     // Make a copy of the player.
1389     std::memcpy(p, this, sizeof(temp));
1390     std::memcpy(dp, plr, sizeof(ddtemp));
1391     temp.plr = &ddtemp;
1392 
1393     // Convert the psprite states.
1394     for(int i = 0; i < plrHdr.numPSprites; ++i)
1395     {
1396         pspdef_t *pspDef = &temp.pSprites[i];
1397 
1398         if(pspDef->state)
1399         {
1400             pspDef->state = (state_t *) (pspDef->state - STATES);
1401         }
1402     }
1403 
1404     // Version number. Increase when you make changes to the player data
1405     // segment format.
1406     Writer_WriteByte(writer, 6);
1407 
1408 #if __JHEXEN__
1409     // Class.
1410     Writer_WriteByte(writer, cfg.playerClass[plrnum]);
1411 #endif
1412 
1413     Writer_WriteInt32(writer, p->playerState);
1414 #if __JHEXEN__
1415     Writer_WriteInt32(writer, p->class_);    // 2nd class...?
1416 #endif
1417     Writer_WriteInt32(writer, FLT2FIX(p->viewZ));
1418     Writer_WriteInt32(writer, FLT2FIX(p->viewHeight));
1419     Writer_WriteInt32(writer, FLT2FIX(p->viewHeightDelta));
1420 #if !__JHEXEN__
1421     Writer_WriteFloat(writer, dp->lookDir);
1422 #endif
1423     Writer_WriteInt32(writer, FLT2FIX(p->bob));
1424 #if __JHEXEN__
1425     Writer_WriteInt32(writer, p->flyHeight);
1426     Writer_WriteFloat(writer, dp->lookDir);
1427     Writer_WriteInt32(writer, p->centering);
1428 #endif
1429     Writer_WriteInt32(writer, p->health);
1430 
1431 #if __JHEXEN__
1432     for(int i = 0; i < plrHdr.numArmorTypes; ++i)
1433     {
1434         Writer_WriteInt32(writer, p->armorPoints[i]);
1435     }
1436 #else
1437     Writer_WriteInt32(writer, p->armorPoints);
1438     Writer_WriteInt32(writer, p->armorType);
1439 #endif
1440 
1441 #if __JDOOM64__ || __JHEXEN__
1442     for(int i = 0; i < plrHdr.numInvItemTypes; ++i)
1443     {
1444         inventoryitemtype_t type = inventoryitemtype_t(IIT_FIRST + i);
1445 
1446         Writer_WriteInt32(writer, type);
1447         Writer_WriteInt32(writer, P_InventoryCount(plrnum, type));
1448     }
1449     Writer_WriteInt32(writer, P_InventoryReadyItem(plrnum));
1450 #endif
1451 
1452     for(int i = 0; i < plrHdr.numPowers; ++i)
1453     {
1454         Writer_WriteInt32(writer, p->powers[i]);
1455     }
1456 
1457 #if __JHEXEN__
1458     Writer_WriteInt32(writer, p->keys);
1459 #else
1460     for(int i = 0; i < plrHdr.numKeys; ++i)
1461     {
1462         Writer_WriteInt32(writer, p->keys[i]);
1463     }
1464 #endif
1465 
1466 #if __JHEXEN__
1467     Writer_WriteInt32(writer, p->pieces);
1468 #else
1469     Writer_WriteInt32(writer, p->backpack);
1470 #endif
1471 
1472     for(int i = 0; i < plrHdr.numFrags; ++i)
1473     {
1474         Writer_WriteInt32(writer, p->frags[i]);
1475     }
1476 
1477     Writer_WriteInt32(writer, p->readyWeapon);
1478     Writer_WriteInt32(writer, p->pendingWeapon);
1479 
1480     for(int i = 0; i < plrHdr.numWeapons; ++i)
1481     {
1482         Writer_WriteInt32(writer, p->weapons[i].owned);
1483     }
1484 
1485     for(int i = 0; i < plrHdr.numAmmoTypes; ++i)
1486     {
1487         Writer_WriteInt32(writer, p->ammo[i].owned);
1488 #if !__JHEXEN__
1489         Writer_WriteInt32(writer, p->ammo[i].max);
1490 #endif
1491     }
1492 
1493     Writer_WriteInt32(writer, p->attackDown);
1494     Writer_WriteInt32(writer, p->useDown);
1495 
1496     Writer_WriteInt32(writer, p->cheats);
1497 
1498     Writer_WriteInt32(writer, p->refire);
1499 
1500     Writer_WriteInt32(writer, p->killCount);
1501     Writer_WriteInt32(writer, p->itemCount);
1502     Writer_WriteInt32(writer, p->secretCount);
1503 
1504     Writer_WriteInt32(writer, p->damageCount);
1505     Writer_WriteInt32(writer, p->bonusCount);
1506 #if __JHEXEN__
1507     Writer_WriteInt32(writer, p->poisonCount);
1508 #endif
1509 
1510     Writer_WriteInt32(writer, dp->extraLight);
1511     Writer_WriteInt32(writer, dp->fixedColorMap);
1512     Writer_WriteInt32(writer, p->colorMap);
1513 
1514     for(int i = 0; i < plrHdr.numPSprites; ++i)
1515     {
1516         pspdef_t *psp = &p->pSprites[i];
1517 
1518         Writer_WriteInt32(writer, PTR2INT(psp->state));
1519         Writer_WriteInt32(writer, psp->tics);
1520         Writer_WriteInt32(writer, FLT2FIX(psp->pos[VX]));
1521         Writer_WriteInt32(writer, FLT2FIX(psp->pos[VY]));
1522     }
1523 
1524 #if !__JHEXEN__
1525     Writer_WriteInt32(writer, p->didSecret);
1526 
1527     // Added in ver 2 with __JDOOM__
1528     Writer_WriteInt32(writer, p->flyHeight);
1529 #endif
1530 
1531 #if __JHERETIC__
1532     for(int i = 0; i < plrHdr.numInvItemTypes; ++i)
1533     {
1534         inventoryitemtype_t type = inventoryitemtype_t(IIT_FIRST + i);
1535 
1536         Writer_WriteInt32(writer, type);
1537         Writer_WriteInt32(writer, P_InventoryCount(plrnum, type));
1538     }
1539     Writer_WriteInt32(writer, P_InventoryReadyItem(plrnum));
1540     Writer_WriteInt32(writer, p->chickenPeck);
1541 #endif
1542 
1543 #if __JHERETIC__ || __JHEXEN__
1544     Writer_WriteInt32(writer, p->morphTics);
1545 #endif
1546 
1547     Writer_WriteInt32(writer, p->airCounter);
1548 
1549 #if __JHEXEN__
1550     Writer_WriteInt32(writer, p->jumpTics);
1551     Writer_WriteInt32(writer, p->worldTimer);
1552 #elif __JHERETIC__
1553     Writer_WriteInt32(writer, p->flameCount);
1554 
1555     // Added in ver 2
1556     Writer_WriteByte(writer, p->class_);
1557 #endif
1558 }
1559 
read(reader_s * reader,playerheader_t & plrHdr)1560 void player_s::read(reader_s *reader, playerheader_t &plrHdr)
1561 {
1562     int const plrnum = P_GetPlayerNum(this);
1563 
1564     byte ver = Reader_ReadByte(reader);
1565 
1566 #if __JHEXEN__
1567     cfg.playerClass[plrnum] = playerclass_t(Reader_ReadByte(reader));
1568 #endif
1569 
1570     ddplayer_t *dp = plr;
1571 
1572 #if __JHEXEN__
1573     de::zapPtr(this); // Force everything NULL,
1574     plr = dp;   // but restore the ddplayer pointer.
1575 #endif
1576 
1577     playerState     = playerstate_t(Reader_ReadInt32(reader));
1578 #if __JHEXEN__
1579     class_          = playerclass_t(Reader_ReadInt32(reader)); // 2nd class?? (ask Raven...)
1580 #endif
1581 
1582     viewZ           = FIX2FLT(Reader_ReadInt32(reader));
1583     viewHeight      = FIX2FLT(Reader_ReadInt32(reader));
1584     viewHeightDelta = FIX2FLT(Reader_ReadInt32(reader));
1585 #if !__JHEXEN__
1586     dp->lookDir       = Reader_ReadFloat(reader);
1587 #endif
1588     bob             = FIX2FLT(Reader_ReadInt32(reader));
1589 #if __JHEXEN__
1590     flyHeight       = Reader_ReadInt32(reader);
1591 
1592     dp->lookDir        = Reader_ReadFloat(reader);
1593 
1594     centering       = Reader_ReadInt32(reader);
1595 #endif
1596     health          = Reader_ReadInt32(reader);
1597 
1598 #if __JHEXEN__
1599     for(int i = 0; i < plrHdr.numArmorTypes; ++i)
1600     {
1601         armorPoints[i] = Reader_ReadInt32(reader);
1602     }
1603 #else
1604     armorPoints     = Reader_ReadInt32(reader);
1605     armorType       = Reader_ReadInt32(reader);
1606 #endif
1607 
1608 #if __JDOOM64__ || __JHEXEN__
1609     P_InventoryEmpty(plrnum);
1610     for(int i = 0; i < plrHdr.numInvItemTypes; ++i)
1611     {
1612         inventoryitemtype_t type = inventoryitemtype_t(Reader_ReadInt32(reader));
1613         int count = Reader_ReadInt32(reader);
1614 
1615         for(int k = 0; k < count; ++k)
1616         {
1617             P_InventoryGive(plrnum, type, true);
1618         }
1619     }
1620 
1621     P_InventorySetReadyItem(plrnum, inventoryitemtype_t(Reader_ReadInt32(reader)));
1622 # if __JHEXEN__
1623     Hu_InventorySelect(plrnum, P_InventoryReadyItem(plrnum));
1624     if(ver < 5)
1625     {
1626         /*artifactCount   =*/ Reader_ReadInt32(reader);
1627     }
1628     if(ver < 6)
1629     {
1630         /*inventorySlotNum =*/ Reader_ReadInt32(reader);
1631     }
1632 # endif
1633 #endif
1634 
1635     for(int i = 0; i < plrHdr.numPowers; ++i)
1636     {
1637         powers[i] = Reader_ReadInt32(reader);
1638     }
1639     if(powers[PT_ALLMAP])
1640     {
1641         ST_RevealAutomap(plrnum, true);
1642     }
1643 
1644 #if __JHEXEN__
1645     keys = Reader_ReadInt32(reader);
1646 #else
1647     for(int i = 0; i < plrHdr.numKeys; ++i)
1648     {
1649         keys[i] = Reader_ReadInt32(reader);
1650     }
1651 #endif
1652 
1653 #if __JHEXEN__
1654     pieces   = Reader_ReadInt32(reader);
1655 #else
1656     backpack = Reader_ReadInt32(reader);
1657 #endif
1658 
1659     for(int i = 0; i < plrHdr.numFrags; ++i)
1660     {
1661         frags[i] = Reader_ReadInt32(reader);
1662     }
1663 
1664     readyWeapon = weapontype_t(Reader_ReadInt32(reader));
1665 #if __JHEXEN__
1666     if(ver < 5)
1667         pendingWeapon = WT_NOCHANGE;
1668     else
1669 #endif
1670         pendingWeapon = weapontype_t(Reader_ReadInt32(reader));
1671 
1672     for(int i = 0; i < plrHdr.numWeapons; ++i)
1673     {
1674         weapons[i].owned = (Reader_ReadInt32(reader)? true : false);
1675     }
1676 
1677     for(int i = 0; i < plrHdr.numAmmoTypes; ++i)
1678     {
1679         ammo[i].owned = Reader_ReadInt32(reader);
1680 
1681 #if !__JHEXEN__
1682         ammo[i].max = Reader_ReadInt32(reader);
1683 #endif
1684     }
1685 
1686     attackDown  = Reader_ReadInt32(reader);
1687     useDown     = Reader_ReadInt32(reader);
1688     cheats      = Reader_ReadInt32(reader);
1689     refire      = Reader_ReadInt32(reader);
1690     killCount   = Reader_ReadInt32(reader);
1691     itemCount   = Reader_ReadInt32(reader);
1692     secretCount = Reader_ReadInt32(reader);
1693 
1694 #if __JHEXEN__
1695     if(ver <= 1)
1696     {
1697         /*messageTics     =*/ Reader_ReadInt32(reader);
1698         /*ultimateMessage =*/ Reader_ReadInt32(reader);
1699         /*yellowMessage   =*/ Reader_ReadInt32(reader);
1700     }
1701 #endif
1702 
1703     damageCount = Reader_ReadInt32(reader);
1704     bonusCount  = Reader_ReadInt32(reader);
1705 #if __JHEXEN__
1706     poisonCount = Reader_ReadInt32(reader);
1707 #endif
1708 
1709     dp->extraLight    = Reader_ReadInt32(reader);
1710     dp->fixedColorMap = Reader_ReadInt32(reader);
1711 
1712     colorMap    = Reader_ReadInt32(reader);
1713 
1714     for(int i = 0; i < plrHdr.numPSprites; ++i)
1715     {
1716         pspdef_t *psp = &pSprites[i];
1717 
1718         psp->state   = INT2PTR(state_t, Reader_ReadInt32(reader));
1719         psp->tics    = Reader_ReadInt32(reader);
1720         psp->pos[VX] = FIX2FLT(Reader_ReadInt32(reader));
1721         psp->pos[VY] = FIX2FLT(Reader_ReadInt32(reader));
1722     }
1723 
1724 #if !__JHEXEN__
1725     didSecret = Reader_ReadInt32(reader);
1726 
1727 # if __JDOOM__ || __JDOOM64__
1728     if(ver == 2)
1729     {
1730         /*messageTics =*/ Reader_ReadInt32(reader);
1731     }
1732 
1733     if(ver >= 2)
1734     {
1735         flyHeight = Reader_ReadInt32(reader);
1736     }
1737 
1738 # elif __JHERETIC__
1739     if(ver < 3)
1740     {
1741         /*messageTics =*/ Reader_ReadInt32(reader);
1742     }
1743 
1744     flyHeight = Reader_ReadInt32(reader);
1745 
1746     P_InventoryEmpty(plrnum);
1747     for(int i = 0; i < plrHdr.numInvItemTypes; ++i)
1748     {
1749         inventoryitemtype_t type = inventoryitemtype_t(Reader_ReadInt32(reader));
1750         int count = Reader_ReadInt32(reader);
1751 
1752         for(int k = 0; k < count; ++k)
1753         {
1754             P_InventoryGive(plrnum, type, true);
1755         }
1756     }
1757 
1758     P_InventorySetReadyItem(plrnum, (inventoryitemtype_t) Reader_ReadInt32(reader));
1759     Hu_InventorySelect(plrnum, P_InventoryReadyItem(plrnum));
1760     if(ver < 5)
1761     {
1762         Reader_ReadInt32(reader); // Current inventory item count?
1763     }
1764     if(ver < 6)
1765     {
1766         /*inventorySlotNum =*/ Reader_ReadInt32(reader);
1767     }
1768 
1769     chickenPeck = Reader_ReadInt32(reader);
1770 # endif
1771 #endif
1772 
1773 #if __JHERETIC__ || __JHEXEN__
1774     morphTics = Reader_ReadInt32(reader);
1775 #endif
1776 
1777     if(ver >= 2)
1778     {
1779         airCounter = Reader_ReadInt32(reader);
1780     }
1781 
1782 #if __JHEXEN__
1783     jumpTics   = Reader_ReadInt32(reader);
1784     worldTimer = Reader_ReadInt32(reader);
1785 #elif __JHERETIC__
1786     flameCount = Reader_ReadInt32(reader);
1787 
1788     if(ver >= 2)
1789     {
1790         class_ = playerclass_t(Reader_ReadByte(reader));
1791     }
1792 #endif
1793 
1794 #if !__JHEXEN__
1795     // Will be set when unarc thinker.
1796     plr->mo = 0;
1797     attacker = 0;
1798 #endif
1799 
1800     // Demangle it.
1801     for(int i = 0; i < plrHdr.numPSprites; ++i)
1802     {
1803         if(pSprites[i].state)
1804         {
1805             pSprites[i].state = &STATES[PTR2INT(pSprites[i].state)];
1806         }
1807     }
1808 
1809     // Mark the player for fixpos and fixangles.
1810     dp->flags |= DDPF_FIXORIGIN | DDPF_FIXANGLES | DDPF_FIXMOM;
1811     update |= PSF_REBORN;
1812 }
1813 
Player_WeaponId(player_t const * plr)1814 String Player_WeaponId(player_t const *plr)
1815 {
1816     String value = "Weapon Info|";
1817 #ifdef __JHEXEN__
1818     static char const *className[] = { "Fighter", "Cleric", "Mage", "Pig" };
1819     value.append(className[plr->class_]);
1820     value.append("|");
1821 #endif
1822 #ifdef __JHERETIC__
1823     if(plr->class_ == PCLASS_CHICKEN)
1824     {
1825         value.append("Beak");
1826     }
1827     else
1828     {
1829         value.append(QString::number(plr->readyWeapon));
1830     }
1831 #endif
1832 #if defined(__JDOOM__) || defined(__JDOOM64__) || defined(__JHEXEN__)
1833     value.append(QString::number(plr->readyWeapon));
1834 #endif
1835     value.append("|Id");
1836 
1837     if(auto *def = Defs().getValueById(value))
1838     {
1839         return def->text;
1840     }
1841     return "";
1842 }
1843 
Player_PostTick(player_t * player)1844 void Player_PostTick(player_t *player)
1845 {
1846     if(!player->plr->inGame) return;
1847 
1848     int const console = player - players;
1849 
1850     // Update the game status cvars for player data.
1851     if(console == CONSOLEPLAYER)
1852     {
1853         Player_UpdateStatusCVars(player);
1854     }
1855 
1856     // Notify engine whenever the current weapon changes.
1857     if(player->update & PSF_READY_WEAPON)
1858     {
1859         Block const id = Player_WeaponId(player).toUtf8();
1860 
1861         ddnotify_player_weapon_changed_t args;
1862         args.player = console;
1863         args.weapon = player->readyWeapon;
1864         args.weaponId = id.constData();
1865         Plug_Notify(DD_NOTIFY_PLAYER_WEAPON_CHANGED, &args);
1866     }
1867 }
1868 
Player_NotifyPSpriteChange(player_t * player,int position)1869 void Player_NotifyPSpriteChange(player_t *player, int position)
1870 {
1871     if(position == ps_weapon)
1872     {
1873         ddnotify_psprite_state_changed_t args;
1874         args.player = player - players;
1875         args.state  = player->pSprites[position].state;
1876         Plug_Notify(DD_NOTIFY_PSPRITE_STATE_CHANGED, &args);
1877     }
1878 }
1879 
1880 /**
1881  * Updates game status cvars for the specified player.
1882  */
Player_UpdateStatusCVars(player_t const * player)1883 void Player_UpdateStatusCVars(player_t const *player)
1884 {
1885     DENG2_ASSERT(player);
1886 
1887     QChar const CVAR_DELIM('-');
1888 
1889     static Path const var_player_health        ("player-health",         CVAR_DELIM);
1890     static Path const var_player_armor         ("player-armor",          CVAR_DELIM);
1891     static Path const var_player_weapon_current("player-weapon-current", CVAR_DELIM);
1892 
1893     Con_SetVariable(var_player_health, player->health, SVF_WRITE_OVERRIDE);
1894 
1895 #if !__JHEXEN__
1896     static Path const var_game_stats_kills  ("game-stats-kills",   CVAR_DELIM);
1897     static Path const var_game_stats_items  ("game-stats-items",   CVAR_DELIM);
1898     static Path const var_game_stats_secrets("game-stats-secrets", CVAR_DELIM);
1899 
1900     // Map stats.
1901     Con_SetVariable(var_game_stats_kills,   player->killCount,   SVF_WRITE_OVERRIDE);
1902     Con_SetVariable(var_game_stats_items,   player->itemCount,   SVF_WRITE_OVERRIDE);
1903     Con_SetVariable(var_game_stats_secrets, player->secretCount, SVF_WRITE_OVERRIDE);
1904 #endif
1905 
1906     // Armor.
1907 #if __JHEXEN__
1908     int const armorPoints = FixedDiv(PCLASS_INFO(player->class_)->autoArmorSave
1909                                      + player->armorPoints[ARMOR_ARMOR]
1910                                      + player->armorPoints[ARMOR_SHIELD]
1911                                      + player->armorPoints[ARMOR_HELMET]
1912                                      + player->armorPoints[ARMOR_AMULET], 5 * FRACUNIT) >> FRACBITS;
1913     Con_SetVariable(var_player_armor, armorPoints, SVF_WRITE_OVERRIDE);
1914 #else
1915     Con_SetVariable(var_player_armor, player->armorPoints, SVF_WRITE_OVERRIDE);
1916 #endif
1917 
1918     // Owned keys.
1919     static Path const keyIds[NUM_KEY_TYPES] = {
1920 #if __JDOOM__ || __JDOOM64__
1921         /* KT_BLUECARD */    { "player-key-blue", CVAR_DELIM },
1922         /* KT_YELLOWCARD */  { "player-key-yellow", CVAR_DELIM },
1923         /* KT_REDCARD */     { "player-key-red", CVAR_DELIM },
1924         /* KT_BLUESKULL */   { "player-key-blueskull", CVAR_DELIM },
1925         /* KT_YELLOWSKULL */ { "player-key-yellowskull", CVAR_DELIM },
1926         /* KT_REDSKULL */    { "player-key-redskull", CVAR_DELIM },
1927 #elif __JHERETIC__
1928         /* KT_YELLOW */      { "player-key-yellow", CVAR_DELIM },
1929         /* KT_GREEN */       { "player-key-green", CVAR_DELIM },
1930         /* KT_BLUE */        { "player-key-blue", CVAR_DELIM },
1931 #elif __JHEXEN__
1932         /* KT_KEY1 */        { "player-key-steel", CVAR_DELIM },
1933         /* KT_KEY2 */        { "player-key-cave", CVAR_DELIM },
1934         /* KT_KEY3 */        { "player-key-axe", CVAR_DELIM },
1935         /* KT_KEY4 */        { "player-key-fire", CVAR_DELIM },
1936         /* KT_KEY5 */        { "player-key-emerald", CVAR_DELIM },
1937         /* KT_KEY6 */        { "player-key-dungeon", CVAR_DELIM },
1938         /* KT_KEY7 */        { "player-key-silver", CVAR_DELIM },
1939         /* KT_KEY8 */        { "player-key-rusted", CVAR_DELIM },
1940         /* KT_KEY9 */        { "player-key-horn", CVAR_DELIM },
1941         /* KT_KEYA */        { "player-key-swamp", CVAR_DELIM },
1942         /* KT_KEYB */        { "player-key-castle", CVAR_DELIM },
1943 #endif
1944     };
1945     for(int i = 0; i < NUM_KEY_TYPES; ++i)
1946     {
1947 #if __JHEXEN__
1948         int ownedKeys = (player->keys & (1 << i))? 1 : 0;
1949 #else
1950         int ownedKeys = player->keys[i];
1951 #endif
1952         Con_SetVariable(keyIds[i], ownedKeys, SVF_WRITE_OVERRIDE);
1953     }
1954 
1955     // Current weapon
1956     Con_SetVariable(var_player_weapon_current, player->readyWeapon, SVF_WRITE_OVERRIDE);
1957 
1958     // Owned weapons
1959     static Path const weaponIds[NUM_WEAPON_TYPES] = {
1960 #if __JDOOM__ || __JDOOM64__
1961         /* WT_FIRST */   { "player-weapon-fist", CVAR_DELIM },
1962         /* WT_SECOND */  { "player-weapon-pistol", CVAR_DELIM },
1963         /* WT_THIRD */   { "player-weapon-shotgun", CVAR_DELIM },
1964         /* WT_FOURTH */  { "player-weapon-chaingun", CVAR_DELIM },
1965         /* WT_FIFTH */   { "player-weapon-mlauncher", CVAR_DELIM },
1966         /* WT_SIXTH */   { "player-weapon-plasmarifle", CVAR_DELIM },
1967         /* WT_SEVENTH */ { "player-weapon-bfg", CVAR_DELIM },
1968         /* WT_EIGHTH */  { "player-weapon-chainsaw", CVAR_DELIM },
1969         /* WT_NINETH */  { "player-weapon-sshotgun", CVAR_DELIM },
1970     #if __JDOOM64__
1971         /* WT_TENTH */   { "player-weapon-unmaker", CVAR_DELIM },
1972     #endif
1973 #elif __JHERETIC__
1974         /* WT_FIRST */   { "player-weapon-staff", CVAR_DELIM },
1975         /* WT_SECOND */  { "player-weapon-goldwand", CVAR_DELIM },
1976         /* WT_THIRD */   { "player-weapon-crossbow", CVAR_DELIM },
1977         /* WT_FOURTH */  { "player-weapon-dragonclaw", CVAR_DELIM },
1978         /* WT_FIFTH */   { "player-weapon-hellstaff", CVAR_DELIM },
1979         /* WT_SIXTH */   { "player-weapon-phoenixrod", CVAR_DELIM },
1980         /* WT_SEVENTH */ { "player-weapon-mace", CVAR_DELIM },
1981         /* WT_EIGHTH */  { "player-weapon-gauntlets", CVAR_DELIM },
1982 #elif __JHEXEN__
1983         /* WT_FIRST */   { "player-weapon-first", CVAR_DELIM },
1984         /* WT_SECOND */  { "player-weapon-second", CVAR_DELIM },
1985         /* WT_THIRD */   { "player-weapon-third", CVAR_DELIM },
1986         /* WT_FOURTH*/   { "player-weapon-fourth", CVAR_DELIM },
1987 #endif
1988     };
1989     for(int i = 0; i < NUM_WEAPON_TYPES; ++i)
1990     {
1991         Con_SetVariable(weaponIds[i], player->weapons[i].owned, SVF_WRITE_OVERRIDE);
1992     }
1993 
1994 #if __JHEXEN__
1995     for(int i = 0; i < WEAPON_FOURTH_PIECE_COUNT; ++i)
1996     {
1997         Path const varPath { String("player-weapon-piece%1").arg(i + 1), CVAR_DELIM };
1998         Con_SetVariable(varPath, (player->pieces & (1 << i))? 1 : 0, SVF_WRITE_OVERRIDE);
1999     }
2000     static Path const var_player_weapon_allpieces("player-weapon-allpieces", CVAR_DELIM);
2001     Con_SetVariable(var_player_weapon_allpieces, (player->pieces == WEAPON_FOURTH_COMPLETE)? 1 : 0, SVF_WRITE_OVERRIDE);
2002 #endif
2003 
2004     // Current ammo amounts.
2005     static Path const ammoIds[NUM_AMMO_TYPES] = {
2006 #if __JDOOM__ || __JDOOM64__
2007         /* AT_CLIP */      { "player-ammo-bullets", CVAR_DELIM },
2008         /* AT_SHELL */     { "player-ammo-shells", CVAR_DELIM },
2009         /* AT_CELL */      { "player-ammo-cells", CVAR_DELIM },
2010         /* AT_MISSILE */   { "player-ammo-missiles", CVAR_DELIM },
2011 #elif __JHERETIC__
2012         /* AT_CRYSTAL */   { "player-ammo-goldwand", CVAR_DELIM },
2013         /* AT_ARROW */     { "player-ammo-crossbow", CVAR_DELIM },
2014         /* AT_ORB */       { "player-ammo-dragonclaw", CVAR_DELIM },
2015         /* AT_RUNE */      { "player-ammo-hellstaff", CVAR_DELIM },
2016         /* AT_FIREORB */   { "player-ammo-phoenixrod", CVAR_DELIM },
2017         /* AT_MSPHERE */   { "player-ammo-mace", CVAR_DELIM },
2018 #elif __JHEXEN__
2019         /* AT_BLUEMANA */  { "player-ammo-bluemana", CVAR_DELIM },
2020         /* AT_GREENMANA */ { "player-ammo-greenmana", CVAR_DELIM },
2021 #endif
2022     };
2023     for(int i = 0; i < NUM_AMMO_TYPES; ++i)
2024     {
2025         //Block cvarName = String("player-ammo-%1").arg(ammoIds[i]).toUtf8();
2026         Con_SetVariable(ammoIds[i], player->ammo[i].owned, SVF_WRITE_OVERRIDE);
2027     }
2028 
2029 #if __JHERETIC__ || __JHEXEN__ || __JDOOM64__
2030     // Inventory items.
2031     static Path const invItemIds[NUM_INVENTORYITEM_TYPES] = {
2032 #  if __JDOOM64__
2033         /* IIT_DEMONKEY1 */         { "player-artifact-bluedemonkey", CVAR_DELIM },
2034         /* IIT_DEMONKEY2 */         { "player-artifact-yellowdemonkey", CVAR_DELIM },
2035         /* IIT_DEMONKEY3 */         { "player-artifact-reddemonkey", CVAR_DELIM },
2036 #  elif __JHERETIC__
2037         /* IIT_INVULNERABILITY */   { "player-artifact-ring", CVAR_DELIM },
2038         /* IIT_INVISIBILITY */      { "player-artifact-shadowsphere", CVAR_DELIM },
2039         /* IIT_HEALTH */            { "player-artifact-crystalvial", CVAR_DELIM },
2040         /* IIT_SUPERHEALTH */       { "player-artifact-mysticurn", CVAR_DELIM },
2041         /* IIT_TOMBOFPOWER */       { "player-artifact-tomeofpower", CVAR_DELIM },
2042         /* IIT_TORCH */             { "player-artifact-torch", CVAR_DELIM },
2043         /* IIT_FIREBOMB */          { "player-artifact-firebomb", CVAR_DELIM },
2044         /* IIT_EGG */               { "player-artifact-egg", CVAR_DELIM },
2045         /* IIT_FLY */               { "player-artifact-wings", CVAR_DELIM },
2046         /* IIT_TELEPORT */          { "player-artifact-chaosdevice", CVAR_DELIM },
2047 #  elif __JHEXEN__
2048         /* IIT_INVULNERABILITY */   { "player-artifact-defender", CVAR_DELIM },
2049         /* IIT_HEALTH */            { "player-artifact-quartzflask", CVAR_DELIM },
2050         /* IIT_SUPERHEALTH */       { "player-artifact-mysticurn", CVAR_DELIM },
2051         /* IIT_HEALINGRADIUS */     { "player-artifact-mysticambit", CVAR_DELIM },
2052         /* IIT_SUMMON */            { "player-artifact-darkservant", CVAR_DELIM },
2053         /* IIT_TORCH */             { "player-artifact-torch", CVAR_DELIM },
2054         /* IIT_EGG */               { "player-artifact-porkalator", CVAR_DELIM },
2055         /* IIT_FLY */               { "player-artifact-wings", CVAR_DELIM },
2056         /* IIT_BLASTRADIUS */       { "player-artifact-repulsion", CVAR_DELIM },
2057         /* IIT_POISONBAG */         { "player-artifact-flechette", CVAR_DELIM },
2058         /* IIT_TELEPORTOTHER */     { "player-artifact-banishment", CVAR_DELIM },
2059         /* IIT_SPEED */             { "player-artifact-speed", CVAR_DELIM },
2060         /* IIT_BOOSTMANA */         { "player-artifact-might", CVAR_DELIM },
2061         /* IIT_BOOSTARMOR */        { "player-artifact-bracers", CVAR_DELIM },
2062         /* IIT_TELEPORT */          { "player-artifact-chaosdevice", CVAR_DELIM },
2063         /* IIT_PUZZSKULL */         { "player-artifact-skull", CVAR_DELIM },
2064         /* IIT_PUZZGEMBIG */        { "player-artifact-heart", CVAR_DELIM },
2065         /* IIT_PUZZGEMRED */        { "player-artifact-ruby", CVAR_DELIM },
2066         /* IIT_PUZZGEMGREEN1 */     { "player-artifact-emerald1", CVAR_DELIM },
2067         /* IIT_PUZZGEMGREEN2 */     { "player-artifact-emerald2", CVAR_DELIM },
2068         /* IIT_PUZZGEMBLUE1 */      { "player-artifact-sapphire1", CVAR_DELIM },
2069         /* IIT_PUZZGEMBLUE2 */      { "player-artifact-sapphire2", CVAR_DELIM },
2070         /* IIT_PUZZBOOK1 */         { "player-artifact-daemoncodex", CVAR_DELIM },
2071         /* IIT_PUZZBOOK2 */         { "player-artifact-liberoscura", CVAR_DELIM },
2072         /* IIT_PUZZSKULL2 */        { "player-artifact-flamemask", CVAR_DELIM },
2073         /* IIT_PUZZFWEAPON */       { "player-artifact-glaiveseal", CVAR_DELIM },
2074         /* IIT_PUZZCWEAPON */       { "player-artifact-holyrelic", CVAR_DELIM },
2075         /* IIT_PUZZMWEAPON */       { "player-artifact-sigilmagus", CVAR_DELIM },
2076         /* IIT_PUZZGEAR1 */         { "player-artifact-gear1", CVAR_DELIM },
2077         /* IIT_PUZZGEAR2 */         { "player-artifact-gear2", CVAR_DELIM },
2078         /* IIT_PUZZGEAR3 */         { "player-artifact-gear3", CVAR_DELIM },
2079         /* IIT_PUZZGEAR4 */         { "player-artifact-gear4", CVAR_DELIM },
2080 #  endif
2081     };
2082     int const plrNum = player - players;
2083     for(int i = IIT_FIRST; i < NUM_INVENTORYITEM_TYPES; ++i)
2084     {
2085         //String cvarName = String("player-artifact-%1").arg(invItemIds[i - 1]);
2086         int numItems = 0;
2087         if(player->plr->inGame && G_GameState() == GS_MAP)
2088         {
2089             numItems = P_InventoryCount(plrNum, inventoryitemtype_t(i));
2090         }
2091         Con_SetVariable(invItemIds[i - 1], numItems, SVF_WRITE_OVERRIDE);
2092     }
2093 #endif
2094 }
2095