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