1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id:$
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 //
8 // This source is available for distribution and/or modification
9 // only under the terms of the DOOM Source Code License as
10 // published by id Software. All rights reserved.
11 //
12 // The source is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
15 // for more details.
16 //
17 // $Log:$
18 //
19 // DESCRIPTION:
20 // Player related stuff.
21 // Bobbing POV/weapon, movement.
22 // Pending weapon.
23 //
24 //-----------------------------------------------------------------------------
25
26 #include "templates.h"
27 #include "doomdef.h"
28 #include "d_event.h"
29 #include "p_local.h"
30 #include "doomstat.h"
31 #include "s_sound.h"
32 #include "i_system.h"
33 #include "gi.h"
34 #include "m_random.h"
35 #include "p_pspr.h"
36 #include "p_enemy.h"
37 #include "s_sound.h"
38 #include "a_sharedglobal.h"
39 #include "a_keys.h"
40 #include "statnums.h"
41 #include "v_palette.h"
42 #include "v_video.h"
43 #include "w_wad.h"
44 #include "cmdlib.h"
45 #include "sbar.h"
46 #include "intermission/intermission.h"
47 #include "c_console.h"
48 #include "doomdef.h"
49 #include "c_dispatch.h"
50 #include "tarray.h"
51 #include "thingdef/thingdef.h"
52 #include "g_level.h"
53 #include "d_net.h"
54 #include "gstrings.h"
55 #include "farchive.h"
56 #include "r_renderer.h"
57
58 static FRandom pr_skullpop ("SkullPop");
59
60 // [RH] # of ticks to complete a turn180
61 #define TURN180_TICKS ((TICRATE / 4) + 1)
62
63 // Variables for prediction
64 CVAR (Bool, cl_noprediction, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
65 CVAR(Bool, cl_predict_specials, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
66
67 CUSTOM_CVAR(Float, cl_predict_lerpscale, 0.05f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
68 {
69 P_PredictionLerpReset();
70 }
71 CUSTOM_CVAR(Float, cl_predict_lerpthreshold, 2.00f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
72 {
73 if (self < 0.1f)
74 self = 0.1f;
75 P_PredictionLerpReset();
76 }
77
78 struct PredictPos
79 {
80 int gametic;
81 fixed_t x;
82 fixed_t y;
83 fixed_t z;
84 fixed_t pitch;
85 fixed_t yaw;
86 } static PredictionLerpFrom, PredictionLerpResult, PredictionLast;
87 static int PredictionLerptics;
88
89 static player_t PredictionPlayerBackup;
90 static BYTE PredictionActorBackup[sizeof(APlayerPawn)];
91 static TArray<sector_t *> PredictionTouchingSectorsBackup;
92 static TArray<AActor *> PredictionSectorListBackup;
93 static TArray<msecnode_t *> PredictionSector_sprev_Backup;
94
95 // [GRB] Custom player classes
96 TArray<FPlayerClass> PlayerClasses;
97
FPlayerClass()98 FPlayerClass::FPlayerClass ()
99 {
100 Type = NULL;
101 Flags = 0;
102 }
103
FPlayerClass(const FPlayerClass & other)104 FPlayerClass::FPlayerClass (const FPlayerClass &other)
105 {
106 Type = other.Type;
107 Flags = other.Flags;
108 Skins = other.Skins;
109 }
110
~FPlayerClass()111 FPlayerClass::~FPlayerClass ()
112 {
113 }
114
CheckSkin(int skin)115 bool FPlayerClass::CheckSkin (int skin)
116 {
117 for (unsigned int i = 0; i < Skins.Size (); i++)
118 {
119 if (Skins[i] == skin)
120 return true;
121 }
122
123 return false;
124 }
125
126 //===========================================================================
127 //
128 // GetDisplayName
129 //
130 //===========================================================================
131
GetPrintableDisplayName(const PClass * cls)132 const char *GetPrintableDisplayName(const PClass *cls)
133 {
134 // Fixme; This needs a decent way to access the string table without creating a mess.
135 const char *name = cls->Meta.GetMetaString(APMETA_DisplayName);
136 return name;
137 }
138
ValidatePlayerClass(const PClass * ti,const char * name)139 bool ValidatePlayerClass(const PClass *ti, const char *name)
140 {
141 if (!ti)
142 {
143 Printf ("Unknown player class '%s'\n", name);
144 return false;
145 }
146 else if (!ti->IsDescendantOf (RUNTIME_CLASS (APlayerPawn)))
147 {
148 Printf ("Invalid player class '%s'\n", name);
149 return false;
150 }
151 else if (ti->Meta.GetMetaString (APMETA_DisplayName) == NULL)
152 {
153 Printf ("Missing displayname for player class '%s'\n", name);
154 return false;
155 }
156 return true;
157 }
158
SetupPlayerClasses()159 void SetupPlayerClasses ()
160 {
161 FPlayerClass newclass;
162
163 PlayerClasses.Clear();
164 for (unsigned i=0; i<gameinfo.PlayerClasses.Size(); i++)
165 {
166 newclass.Flags = 0;
167 newclass.Type = PClass::FindClass(gameinfo.PlayerClasses[i]);
168
169 if (ValidatePlayerClass(newclass.Type, gameinfo.PlayerClasses[i]))
170 {
171 if ((GetDefaultByType(newclass.Type)->flags6 & MF6_NOMENU))
172 {
173 newclass.Flags |= PCF_NOMENU;
174 }
175 PlayerClasses.Push (newclass);
176 }
177 }
178 }
179
CCMD(clearplayerclasses)180 CCMD (clearplayerclasses)
181 {
182 if (ParsingKeyConf)
183 {
184 PlayerClasses.Clear ();
185 }
186 }
187
CCMD(addplayerclass)188 CCMD (addplayerclass)
189 {
190 if (ParsingKeyConf && argv.argc () > 1)
191 {
192 const PClass *ti = PClass::FindClass (argv[1]);
193
194 if (ValidatePlayerClass(ti, argv[1]))
195 {
196 FPlayerClass newclass;
197
198 newclass.Type = ti;
199 newclass.Flags = 0;
200
201 int arg = 2;
202 while (arg < argv.argc ())
203 {
204 if (!stricmp (argv[arg], "nomenu"))
205 {
206 newclass.Flags |= PCF_NOMENU;
207 }
208 else
209 {
210 Printf ("Unknown flag '%s' for player class '%s'\n", argv[arg], argv[1]);
211 }
212
213 arg++;
214 }
215
216 PlayerClasses.Push (newclass);
217 }
218 }
219 }
220
CCMD(playerclasses)221 CCMD (playerclasses)
222 {
223 for (unsigned int i = 0; i < PlayerClasses.Size (); i++)
224 {
225 Printf ("%3d: Class = %s, Name = %s\n", i,
226 PlayerClasses[i].Type->TypeName.GetChars(),
227 PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName));
228 }
229 }
230
231
232 //
233 // Movement.
234 //
235
236 // 16 pixels of bob
237 #define MAXBOB 0x100000
238
operator <<(FArchive & arc,player_t * & p)239 FArchive &operator<< (FArchive &arc, player_t *&p)
240 {
241 return arc.SerializePointer (players, (BYTE **)&p, sizeof(*players));
242 }
243
244 // The player_t constructor. Since LogText is not a POD, we cannot just
245 // memset it all to 0.
player_t()246 player_t::player_t()
247 : mo(0),
248 playerstate(0),
249 cls(0),
250 DesiredFOV(0),
251 FOV(0),
252 viewz(0),
253 viewheight(0),
254 deltaviewheight(0),
255 bob(0),
256 velx(0),
257 vely(0),
258 centering(0),
259 turnticks(0),
260 attackdown(0),
261 usedown(0),
262 oldbuttons(0),
263 health(0),
264 inventorytics(0),
265 CurrentPlayerClass(0),
266 fragcount(0),
267 lastkilltime(0),
268 multicount(0),
269 spreecount(0),
270 WeaponState(0),
271 ReadyWeapon(0),
272 PendingWeapon(0),
273 cheats(0),
274 timefreezer(0),
275 refire(0),
276 inconsistant(0),
277 killcount(0),
278 itemcount(0),
279 secretcount(0),
280 damagecount(0),
281 bonuscount(0),
282 hazardcount(0),
283 poisoncount(0),
284 poisoner(0),
285 attacker(0),
286 extralight(0),
287 morphTics(0),
288 MorphedPlayerClass(0),
289 MorphStyle(0),
290 MorphExitFlash(0),
291 PremorphWeapon(0),
292 chickenPeck(0),
293 jumpTics(0),
294 onground(0),
295 respawn_time(0),
296 camera(0),
297 air_finished(0),
298 MUSINFOactor(0),
299 MUSINFOtics(-1),
300 crouching(0),
301 crouchdir(0),
302 Bot(0),
303 BlendR(0),
304 BlendG(0),
305 BlendB(0),
306 BlendA(0),
307 LogText(),
308 crouchfactor(0),
309 crouchoffset(0),
310 crouchviewdelta(0),
311 ConversationNPC(0),
312 ConversationPC(0),
313 ConversationNPCAngle(0),
314 ConversationFaceTalker(0)
315 {
316 memset (&cmd, 0, sizeof(cmd));
317 memset (frags, 0, sizeof(frags));
318 memset (psprites, 0, sizeof(psprites));
319 }
320
operator =(const player_t & p)321 player_t &player_t::operator=(const player_t &p)
322 {
323 mo = p.mo;
324 playerstate = p.playerstate;
325 cmd = p.cmd;
326 original_cmd = p.original_cmd;
327 original_oldbuttons = p.original_oldbuttons;
328 // Intentionally not copying userinfo!
329 cls = p.cls;
330 DesiredFOV = p.DesiredFOV;
331 FOV = p.FOV;
332 viewz = p.viewz;
333 viewheight = p.viewheight;
334 deltaviewheight = p.deltaviewheight;
335 bob = p.bob;
336 velx = p.velx;
337 vely = p.vely;
338 centering = p.centering;
339 turnticks = p.turnticks;
340 attackdown = p.attackdown;
341 usedown = p.usedown;
342 oldbuttons = p.oldbuttons;
343 health = p.health;
344 inventorytics = p.inventorytics;
345 CurrentPlayerClass = p.CurrentPlayerClass;
346 memcpy(frags, &p.frags, sizeof(frags));
347 fragcount = p.fragcount;
348 lastkilltime = p.lastkilltime;
349 multicount = p.multicount;
350 spreecount = p.spreecount;
351 WeaponState = p.WeaponState;
352 ReadyWeapon = p.ReadyWeapon;
353 PendingWeapon = p.PendingWeapon;
354 cheats = p.cheats;
355 timefreezer = p.timefreezer;
356 refire = p.refire;
357 inconsistant = p.inconsistant;
358 waiting = p.waiting;
359 killcount = p.killcount;
360 itemcount = p.itemcount;
361 secretcount = p.secretcount;
362 damagecount = p.damagecount;
363 bonuscount = p.bonuscount;
364 hazardcount = p.hazardcount;
365 hazardtype = p.hazardtype;
366 hazardinterval = p.hazardinterval;
367 poisoncount = p.poisoncount;
368 poisontype = p.poisontype;
369 poisonpaintype = p.poisonpaintype;
370 poisoner = p.poisoner;
371 attacker = p.attacker;
372 extralight = p.extralight;
373 fixedcolormap = p.fixedcolormap;
374 fixedlightlevel = p.fixedlightlevel;
375 memcpy(psprites, &p.psprites, sizeof(psprites));
376 morphTics = p.morphTics;
377 MorphedPlayerClass = p.MorphedPlayerClass;
378 MorphStyle = p.MorphStyle;
379 MorphExitFlash = p.MorphExitFlash;
380 PremorphWeapon = p.PremorphWeapon;
381 chickenPeck = p.chickenPeck;
382 jumpTics = p.jumpTics;
383 onground = p.onground;
384 respawn_time = p.respawn_time;
385 camera = p.camera;
386 air_finished = p.air_finished;
387 LastDamageType = p.LastDamageType;
388 Bot = p.Bot;
389 settings_controller = p.settings_controller;
390 BlendR = p.BlendR;
391 BlendG = p.BlendG;
392 BlendB = p.BlendB;
393 BlendA = p.BlendA;
394 LogText = p.LogText;
395 MinPitch = p.MinPitch;
396 MaxPitch = p.MaxPitch;
397 crouching = p.crouching;
398 crouchdir = p.crouchdir;
399 crouchfactor = p.crouchfactor;
400 crouchoffset = p.crouchoffset;
401 crouchviewdelta = p.crouchviewdelta;
402 weapons = p.weapons;
403 ConversationNPC = p.ConversationNPC;
404 ConversationPC = p.ConversationPC;
405 ConversationNPCAngle = p.ConversationNPCAngle;
406 ConversationFaceTalker = p.ConversationFaceTalker;
407 MUSINFOactor = p.MUSINFOactor;
408 MUSINFOtics = p.MUSINFOtics;
409 return *this;
410 }
411
412 // This function supplements the pointer cleanup in dobject.cpp, because
413 // player_t is not derived from DObject. (I tried it, and DestroyScan was
414 // unable to properly determine the player object's type--possibly
415 // because it gets staticly allocated in an array.)
416 //
417 // This function checks all the DObject pointers in a player_t and NULLs any
418 // that match the pointer passed in. If you add any pointers that point to
419 // DObject (or a subclass), add them here too.
420
FixPointers(const DObject * old,DObject * rep)421 size_t player_t::FixPointers (const DObject *old, DObject *rep)
422 {
423 APlayerPawn *replacement = static_cast<APlayerPawn *>(rep);
424 size_t changed = 0;
425
426 // The construct *& is used in several of these to avoid the read barriers
427 // that would turn the pointer we want to check to NULL if the old object
428 // is pending deletion.
429 if (mo == old) mo = replacement, changed++;
430 if (*&poisoner == old) poisoner = replacement, changed++;
431 if (*&attacker == old) attacker = replacement, changed++;
432 if (*&camera == old) camera = replacement, changed++;
433 if (*&Bot == old) Bot = static_cast<DBot *>(rep), changed++;
434 if (ReadyWeapon == old) ReadyWeapon = static_cast<AWeapon *>(rep), changed++;
435 if (PendingWeapon == old) PendingWeapon = static_cast<AWeapon *>(rep), changed++;
436 if (*&PremorphWeapon == old) PremorphWeapon = static_cast<AWeapon *>(rep), changed++;
437 if (*&ConversationNPC == old) ConversationNPC = replacement, changed++;
438 if (*&ConversationPC == old) ConversationPC = replacement, changed++;
439 if (*&MUSINFOactor == old) MUSINFOactor = replacement, changed++;
440 return changed;
441 }
442
PropagateMark()443 size_t player_t::PropagateMark()
444 {
445 GC::Mark(mo);
446 GC::Mark(poisoner);
447 GC::Mark(attacker);
448 GC::Mark(camera);
449 GC::Mark(Bot);
450 GC::Mark(ReadyWeapon);
451 GC::Mark(ConversationNPC);
452 GC::Mark(ConversationPC);
453 GC::Mark(MUSINFOactor);
454 GC::Mark(PremorphWeapon);
455 if (PendingWeapon != WP_NOCHANGE)
456 {
457 GC::Mark(PendingWeapon);
458 }
459 return sizeof(*this);
460 }
461
SetLogNumber(int num)462 void player_t::SetLogNumber (int num)
463 {
464 char lumpname[16];
465 int lumpnum;
466
467 mysnprintf (lumpname, countof(lumpname), "LOG%d", num);
468 lumpnum = Wads.CheckNumForName (lumpname);
469 if (lumpnum == -1)
470 {
471 // Leave the log message alone if this one doesn't exist.
472 //SetLogText (lumpname);
473 }
474 else
475 {
476 int length=Wads.LumpLength(lumpnum);
477 char *data= new char[length+1];
478 Wads.ReadLump (lumpnum, data);
479 data[length]=0;
480 SetLogText (data);
481 delete[] data;
482 }
483 }
484
SetLogText(const char * text)485 void player_t::SetLogText (const char *text)
486 {
487 LogText = text;
488
489 // Print log text to console
490 AddToConsole(-1, TEXTCOLOR_GOLD);
491 AddToConsole(-1, LogText);
492 AddToConsole(-1, "\n");
493 }
494
GetSpawnClass()495 int player_t::GetSpawnClass()
496 {
497 const PClass * type = PlayerClasses[CurrentPlayerClass].Type;
498 return static_cast<APlayerPawn*>(GetDefaultByType(type))->SpawnMask;
499 }
500
501 //===========================================================================
502 //
503 // player_t :: SendPitchLimits
504 //
505 // Ask the local player's renderer what pitch restrictions should be imposed
506 // and let everybody know. Only sends data for the consoleplayer, since the
507 // local player is the only one our data is valid for.
508 //
509 //===========================================================================
510
SendPitchLimits() const511 void player_t::SendPitchLimits() const
512 {
513 if (this - players == consoleplayer)
514 {
515 Net_WriteByte(DEM_SETPITCHLIMIT);
516 Net_WriteByte(Renderer->GetMaxViewPitch(false)); // up
517 Net_WriteByte(Renderer->GetMaxViewPitch(true)); // down
518 }
519 }
520
521 //===========================================================================
522 //
523 // APlayerPawn
524 //
525 //===========================================================================
526
527 IMPLEMENT_POINTY_CLASS (APlayerPawn)
DECLARE_POINTER(InvFirst)528 DECLARE_POINTER(InvFirst)
529 DECLARE_POINTER(InvSel)
530 END_POINTERS
531
532 IMPLEMENT_CLASS (APlayerChunk)
533
534 void APlayerPawn::Serialize (FArchive &arc)
535 {
536 Super::Serialize (arc);
537
538 arc << JumpZ
539 << MaxHealth
540 << RunHealth
541 << SpawnMask
542 << ForwardMove1
543 << ForwardMove2
544 << SideMove1
545 << SideMove2
546 << ScoreIcon
547 << InvFirst
548 << InvSel
549 << MorphWeapon
550 << DamageFade
551 << PlayerFlags
552 << FlechetteType;
553 if (SaveVersion < 3829)
554 {
555 GruntSpeed = 12*FRACUNIT;
556 FallingScreamMinSpeed = 35*FRACUNIT;
557 FallingScreamMaxSpeed = 40*FRACUNIT;
558 }
559 else
560 {
561 arc << GruntSpeed << FallingScreamMinSpeed << FallingScreamMaxSpeed;
562 }
563 if (SaveVersion >= 4502)
564 {
565 arc << UseRange;
566 }
567 if (SaveVersion >= 4503)
568 {
569 arc << AirCapacity;
570 }
571 if (SaveVersion >= 4526)
572 {
573 arc << ViewHeight;
574 }
575 }
576
577 //===========================================================================
578 //
579 // APlayerPawn :: MarkPrecacheSounds
580 //
581 //===========================================================================
582
MarkPrecacheSounds() const583 void APlayerPawn::MarkPrecacheSounds() const
584 {
585 Super::MarkPrecacheSounds();
586 S_MarkPlayerSounds(GetSoundClass());
587 }
588
589 //===========================================================================
590 //
591 // APlayerPawn :: BeginPlay
592 //
593 //===========================================================================
594
BeginPlay()595 void APlayerPawn::BeginPlay ()
596 {
597 Super::BeginPlay ();
598 ChangeStatNum (STAT_PLAYER);
599
600 // Check whether a PWADs normal sprite is to be combined with the base WADs
601 // crouch sprite. In such a case the sprites normally don't match and it is
602 // best to disable the crouch sprite.
603 if (crouchsprite > 0)
604 {
605 // This assumes that player sprites always exist in rotated form and
606 // that the front view is always a separate sprite. So far this is
607 // true for anything that exists.
608 FString normspritename = sprites[SpawnState->sprite].name;
609 FString crouchspritename = sprites[crouchsprite].name;
610
611 int spritenorm = Wads.CheckNumForName(normspritename + "A1", ns_sprites);
612 if (spritenorm==-1)
613 {
614 spritenorm = Wads.CheckNumForName(normspritename + "A0", ns_sprites);
615 }
616
617 int spritecrouch = Wads.CheckNumForName(crouchspritename + "A1", ns_sprites);
618 if (spritecrouch==-1)
619 {
620 spritecrouch = Wads.CheckNumForName(crouchspritename + "A0", ns_sprites);
621 }
622
623 if (spritenorm==-1 || spritecrouch ==-1)
624 {
625 // Sprites do not exist so it is best to disable the crouch sprite.
626 crouchsprite = 0;
627 return;
628 }
629
630 int wadnorm = Wads.GetLumpFile(spritenorm);
631 int wadcrouch = Wads.GetLumpFile(spritenorm);
632
633 if (wadnorm > FWadCollection::IWAD_FILENUM && wadcrouch <= FWadCollection::IWAD_FILENUM)
634 {
635 // Question: Add an option / disable crouching or do what?
636 crouchsprite = 0;
637 }
638 }
639 }
640
641 //===========================================================================
642 //
643 // APlayerPawn :: Tick
644 //
645 //===========================================================================
646
Tick()647 void APlayerPawn::Tick()
648 {
649 if (player != NULL && player->mo == this && player->CanCrouch() && player->playerstate != PST_DEAD)
650 {
651 height = FixedMul(GetDefault()->height, player->crouchfactor);
652 }
653 else
654 {
655 if (health > 0) height = GetDefault()->height;
656 }
657 Super::Tick();
658 }
659
660 //===========================================================================
661 //
662 // APlayerPawn :: PostBeginPlay
663 //
664 //===========================================================================
665
PostBeginPlay()666 void APlayerPawn::PostBeginPlay()
667 {
668 Super::PostBeginPlay();
669 SetupWeaponSlots();
670
671 // Voodoo dolls: restore original floorz/ceilingz logic
672 if (player == NULL || player->mo != this)
673 {
674 dropoffz = floorz = Sector->floorplane.ZatPoint(this);
675 ceilingz = Sector->ceilingplane.ZatPoint(this);
676 P_FindFloorCeiling(this, FFCF_ONLYSPAWNPOS);
677 SetZ(floorz);
678 }
679 else
680 {
681 player->SendPitchLimits();
682 }
683 }
684
685 //===========================================================================
686 //
687 // APlayerPawn :: SetupWeaponSlots
688 //
689 // Sets up the default weapon slots for this player. If this is also the
690 // local player, determines local modifications and sends those across the
691 // network. Ignores voodoo dolls.
692 //
693 //===========================================================================
694
SetupWeaponSlots()695 void APlayerPawn::SetupWeaponSlots()
696 {
697 if (player != NULL && player->mo == this)
698 {
699 player->weapons.StandardSetup(GetClass());
700 // If we're the local player, then there's a bit more work to do.
701 // This also applies if we're a bot and this is the net arbitrator.
702 if (player - players == consoleplayer ||
703 (player->Bot != NULL && consoleplayer == Net_Arbitrator))
704 {
705 FWeaponSlots local_slots(player->weapons);
706 if (player->Bot != NULL)
707 { // Bots only need weapons from KEYCONF, not INI modifications.
708 P_PlaybackKeyConfWeapons(&local_slots);
709 }
710 else
711 {
712 local_slots.LocalSetup(GetClass());
713 }
714 local_slots.SendDifferences(int(player - players), player->weapons);
715 }
716 }
717 }
718
719 //===========================================================================
720 //
721 // APlayerPawn :: AddInventory
722 //
723 //===========================================================================
724
AddInventory(AInventory * item)725 void APlayerPawn::AddInventory (AInventory *item)
726 {
727 // Adding inventory to a voodoo doll should add it to the real player instead.
728 if (player != NULL && player->mo != this && player->mo != NULL)
729 {
730 player->mo->AddInventory (item);
731 return;
732 }
733 Super::AddInventory (item);
734
735 // If nothing is selected, select this item.
736 if (InvSel == NULL && (item->ItemFlags & IF_INVBAR))
737 {
738 InvSel = item;
739 }
740 }
741
742 //===========================================================================
743 //
744 // APlayerPawn :: RemoveInventory
745 //
746 //===========================================================================
747
RemoveInventory(AInventory * item)748 void APlayerPawn::RemoveInventory (AInventory *item)
749 {
750 bool pickWeap = false;
751
752 // Since voodoo dolls aren't supposed to have an inventory, there should be
753 // no need to redirect them to the real player here as there is with AddInventory.
754
755 // If the item removed is the selected one, select something else, either the next
756 // item, if there is one, or the previous item.
757 if (player != NULL)
758 {
759 if (InvSel == item)
760 {
761 InvSel = item->NextInv ();
762 if (InvSel == NULL)
763 {
764 InvSel = item->PrevInv ();
765 }
766 }
767 if (InvFirst == item)
768 {
769 InvFirst = item->NextInv ();
770 if (InvFirst == NULL)
771 {
772 InvFirst = item->PrevInv ();
773 }
774 }
775 if (item == player->PendingWeapon)
776 {
777 player->PendingWeapon = WP_NOCHANGE;
778 }
779 if (item == player->ReadyWeapon)
780 {
781 // If the current weapon is removed, clear the refire counter and pick a new one.
782 pickWeap = true;
783 player->ReadyWeapon = NULL;
784 player->refire = 0;
785 }
786 }
787 Super::RemoveInventory (item);
788 if (pickWeap && player->mo == this && player->PendingWeapon == WP_NOCHANGE)
789 {
790 PickNewWeapon (NULL);
791 }
792 }
793
794 //===========================================================================
795 //
796 // APlayerPawn :: UseInventory
797 //
798 //===========================================================================
799
UseInventory(AInventory * item)800 bool APlayerPawn::UseInventory (AInventory *item)
801 {
802 const PClass *itemtype = item->GetClass();
803
804 if (player->cheats & CF_TOTALLYFROZEN)
805 { // You can't use items if you're totally frozen
806 return false;
807 }
808 if ((level.flags2 & LEVEL2_FROZEN) && (player == NULL || player->timefreezer == 0))
809 {
810 // Time frozen
811 return false;
812 }
813
814 if (!Super::UseInventory (item))
815 {
816 // Heretic and Hexen advance the inventory cursor if the use failed.
817 // Should this behavior be retained?
818 return false;
819 }
820 if (player == &players[consoleplayer])
821 {
822 S_Sound (this, CHAN_ITEM, item->UseSound, 1, ATTN_NORM);
823 StatusBar->FlashItem (itemtype);
824 }
825 return true;
826 }
827
828 //===========================================================================
829 //
830 // APlayerPawn :: BestWeapon
831 //
832 // Returns the best weapon a player has, possibly restricted to a single
833 // type of ammo.
834 //
835 //===========================================================================
836
BestWeapon(const PClass * ammotype)837 AWeapon *APlayerPawn::BestWeapon (const PClass *ammotype)
838 {
839 AWeapon *bestMatch = NULL;
840 int bestOrder = INT_MAX;
841 AInventory *item;
842 AWeapon *weap;
843 bool tomed = NULL != FindInventory (RUNTIME_CLASS(APowerWeaponLevel2), true);
844
845 // Find the best weapon the player has.
846 for (item = Inventory; item != NULL; item = item->Inventory)
847 {
848 if (!item->IsKindOf (RUNTIME_CLASS(AWeapon)))
849 continue;
850
851 weap = static_cast<AWeapon *> (item);
852
853 // Don't select it if it's worse than what was already found.
854 if (weap->SelectionOrder > bestOrder)
855 continue;
856
857 // Don't select it if its primary fire doesn't use the desired ammo.
858 if (ammotype != NULL &&
859 (weap->Ammo1 == NULL ||
860 weap->Ammo1->GetClass() != ammotype))
861 continue;
862
863 // Don't select it if the Tome is active and this isn't the powered-up version.
864 if (tomed && weap->SisterWeapon != NULL && weap->SisterWeapon->WeaponFlags & WIF_POWERED_UP)
865 continue;
866
867 // Don't select it if it's powered-up and the Tome is not active.
868 if (!tomed && weap->WeaponFlags & WIF_POWERED_UP)
869 continue;
870
871 // Don't select it if there isn't enough ammo to use its primary fire.
872 if (!(weap->WeaponFlags & WIF_AMMO_OPTIONAL) &&
873 !weap->CheckAmmo (AWeapon::PrimaryFire, false))
874 continue;
875
876 // Don't select if if there isn't enough ammo as determined by the weapon's author.
877 if (weap->MinSelAmmo1 > 0 && (weap->Ammo1 == NULL || weap->Ammo1->Amount < weap->MinSelAmmo1))
878 continue;
879 if (weap->MinSelAmmo2 > 0 && (weap->Ammo2 == NULL || weap->Ammo2->Amount < weap->MinSelAmmo2))
880 continue;
881
882 // This weapon is usable!
883 bestOrder = weap->SelectionOrder;
884 bestMatch = weap;
885 }
886 return bestMatch;
887 }
888
889 //===========================================================================
890 //
891 // APlayerPawn :: PickNewWeapon
892 //
893 // Picks a new weapon for this player. Used mostly for running out of ammo,
894 // but it also works when an ACS script explicitly takes the ready weapon
895 // away or the player picks up some ammo they had previously run out of.
896 //
897 //===========================================================================
898
PickNewWeapon(const PClass * ammotype)899 AWeapon *APlayerPawn::PickNewWeapon (const PClass *ammotype)
900 {
901 AWeapon *best = BestWeapon (ammotype);
902
903 if (best != NULL)
904 {
905 player->PendingWeapon = best;
906 if (player->ReadyWeapon != NULL)
907 {
908 P_DropWeapon(player);
909 }
910 else if (player->PendingWeapon != WP_NOCHANGE)
911 {
912 P_BringUpWeapon (player);
913 }
914 }
915 return best;
916 }
917
918
919 //===========================================================================
920 //
921 // APlayerPawn :: CheckWeaponSwitch
922 //
923 // Checks if weapons should be changed after picking up ammo
924 //
925 //===========================================================================
926
CheckWeaponSwitch(const PClass * ammotype)927 void APlayerPawn::CheckWeaponSwitch(const PClass *ammotype)
928 {
929 if (!player->userinfo.GetNeverSwitch() &&
930 player->PendingWeapon == WP_NOCHANGE &&
931 (player->ReadyWeapon == NULL ||
932 (player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)))
933 {
934 AWeapon *best = BestWeapon (ammotype);
935 if (best != NULL && (player->ReadyWeapon == NULL ||
936 best->SelectionOrder < player->ReadyWeapon->SelectionOrder))
937 {
938 player->PendingWeapon = best;
939 }
940 }
941 }
942
943 //===========================================================================
944 //
945 // APlayerPawn :: GiveDeathmatchInventory
946 //
947 // Gives players items they should have in addition to their default
948 // inventory when playing deathmatch. (i.e. all keys)
949 //
950 //===========================================================================
951
GiveDeathmatchInventory()952 void APlayerPawn::GiveDeathmatchInventory()
953 {
954 for (unsigned int i = 0; i < PClass::m_Types.Size(); ++i)
955 {
956 if (PClass::m_Types[i]->IsDescendantOf (RUNTIME_CLASS(AKey)))
957 {
958 AKey *key = (AKey *)GetDefaultByType (PClass::m_Types[i]);
959 if (key->KeyNumber != 0)
960 {
961 key = static_cast<AKey *>(Spawn (PClass::m_Types[i], 0,0,0, NO_REPLACE));
962 if (!key->CallTryPickup (this))
963 {
964 key->Destroy ();
965 }
966 }
967 }
968 }
969 }
970
971 //===========================================================================
972 //
973 // APlayerPawn :: FilterCoopRespawnInventory
974 //
975 // When respawning in coop, this function is called to walk through the dead
976 // player's inventory and modify it according to the current game flags so
977 // that it can be transferred to the new live player. This player currently
978 // has the default inventory, and the oldplayer has the inventory at the time
979 // of death.
980 //
981 //===========================================================================
982
FilterCoopRespawnInventory(APlayerPawn * oldplayer)983 void APlayerPawn::FilterCoopRespawnInventory (APlayerPawn *oldplayer)
984 {
985 AInventory *item, *next, *defitem;
986
987 // If we're losing everything, this is really simple.
988 if (dmflags & DF_COOP_LOSE_INVENTORY)
989 {
990 oldplayer->DestroyAllInventory();
991 return;
992 }
993
994 if (dmflags & (DF_COOP_LOSE_KEYS |
995 DF_COOP_LOSE_WEAPONS |
996 DF_COOP_LOSE_AMMO |
997 DF_COOP_HALVE_AMMO |
998 DF_COOP_LOSE_ARMOR |
999 DF_COOP_LOSE_POWERUPS))
1000 {
1001 // Walk through the old player's inventory and destroy or modify
1002 // according to dmflags.
1003 for (item = oldplayer->Inventory; item != NULL; item = next)
1004 {
1005 next = item->Inventory;
1006
1007 // If this item is part of the default inventory, we never want
1008 // to destroy it, although we might want to copy the default
1009 // inventory amount.
1010 defitem = FindInventory (item->GetClass());
1011
1012 if ((dmflags & DF_COOP_LOSE_KEYS) &&
1013 defitem == NULL &&
1014 item->IsKindOf(RUNTIME_CLASS(AKey)))
1015 {
1016 item->Destroy();
1017 }
1018 else if ((dmflags & DF_COOP_LOSE_WEAPONS) &&
1019 defitem == NULL &&
1020 item->IsKindOf(RUNTIME_CLASS(AWeapon)))
1021 {
1022 item->Destroy();
1023 }
1024 else if ((dmflags & DF_COOP_LOSE_ARMOR) &&
1025 item->IsKindOf(RUNTIME_CLASS(AArmor)))
1026 {
1027 if (defitem == NULL)
1028 {
1029 item->Destroy();
1030 }
1031 else if (item->IsKindOf(RUNTIME_CLASS(ABasicArmor)))
1032 {
1033 static_cast<ABasicArmor*>(item)->SavePercent = static_cast<ABasicArmor*>(defitem)->SavePercent;
1034 item->Amount = defitem->Amount;
1035 }
1036 else if (item->IsKindOf(RUNTIME_CLASS(AHexenArmor)))
1037 {
1038 static_cast<AHexenArmor*>(item)->Slots[0] = static_cast<AHexenArmor*>(defitem)->Slots[0];
1039 static_cast<AHexenArmor*>(item)->Slots[1] = static_cast<AHexenArmor*>(defitem)->Slots[1];
1040 static_cast<AHexenArmor*>(item)->Slots[2] = static_cast<AHexenArmor*>(defitem)->Slots[2];
1041 static_cast<AHexenArmor*>(item)->Slots[3] = static_cast<AHexenArmor*>(defitem)->Slots[3];
1042 }
1043 }
1044 else if ((dmflags & DF_COOP_LOSE_POWERUPS) &&
1045 defitem == NULL &&
1046 item->IsKindOf(RUNTIME_CLASS(APowerupGiver)))
1047 {
1048 item->Destroy();
1049 }
1050 else if ((dmflags & (DF_COOP_LOSE_AMMO | DF_COOP_HALVE_AMMO)) &&
1051 item->IsKindOf(RUNTIME_CLASS(AAmmo)))
1052 {
1053 if (defitem == NULL)
1054 {
1055 if (dmflags & DF_COOP_LOSE_AMMO)
1056 {
1057 // Do NOT destroy the ammo, because a weapon might reference it.
1058 item->Amount = 0;
1059 }
1060 else if (item->Amount > 1)
1061 {
1062 item->Amount /= 2;
1063 }
1064 }
1065 else
1066 {
1067 // When set to lose ammo, you get to keep all your starting ammo.
1068 // When set to halve ammo, you won't be left with less than your starting amount.
1069 if (dmflags & DF_COOP_LOSE_AMMO)
1070 {
1071 item->Amount = defitem->Amount;
1072 }
1073 else if (item->Amount > 1)
1074 {
1075 item->Amount = MAX(item->Amount / 2, defitem->Amount);
1076 }
1077 }
1078 }
1079 }
1080 }
1081
1082 // Now destroy the default inventory this player is holding and move
1083 // over the old player's remaining inventory.
1084 DestroyAllInventory();
1085 ObtainInventory (oldplayer);
1086
1087 player->ReadyWeapon = NULL;
1088 PickNewWeapon (NULL);
1089 }
1090
1091 //===========================================================================
1092 //
1093 // APlayerPawn :: GetSoundClass
1094 //
1095 //===========================================================================
1096
GetSoundClass() const1097 const char *APlayerPawn::GetSoundClass() const
1098 {
1099 if (player != NULL &&
1100 (player->mo == NULL || !(player->mo->flags4 &MF4_NOSKIN)) &&
1101 (unsigned int)player->userinfo.GetSkin() >= PlayerClasses.Size () &&
1102 (size_t)player->userinfo.GetSkin() < numskins)
1103 {
1104 return skins[player->userinfo.GetSkin()].name;
1105 }
1106
1107 // [GRB]
1108 const char *sclass = GetClass ()->Meta.GetMetaString (APMETA_SoundClass);
1109 return sclass != NULL ? sclass : "player";
1110 }
1111
1112 //===========================================================================
1113 //
1114 // APlayerPawn :: GetMaxHealth
1115 //
1116 // only needed because Boom screwed up Dehacked.
1117 //
1118 //===========================================================================
1119
GetMaxHealth() const1120 int APlayerPawn::GetMaxHealth() const
1121 {
1122 return MaxHealth > 0? MaxHealth : ((i_compatflags&COMPATF_DEHHEALTH)? 100 : deh.MaxHealth);
1123 }
1124
1125 //===========================================================================
1126 //
1127 // APlayerPawn :: UpdateWaterLevel
1128 //
1129 // Plays surfacing and diving sounds, as appropriate.
1130 //
1131 //===========================================================================
1132
UpdateWaterLevel(fixed_t oldz,bool splash)1133 bool APlayerPawn::UpdateWaterLevel (fixed_t oldz, bool splash)
1134 {
1135 int oldlevel = waterlevel;
1136 bool retval = Super::UpdateWaterLevel (oldz, splash);
1137 if (player != NULL)
1138 {
1139 if (oldlevel < 3 && waterlevel == 3)
1140 { // Our head just went under.
1141 S_Sound (this, CHAN_VOICE, "*dive", 1, ATTN_NORM);
1142 }
1143 else if (oldlevel == 3 && waterlevel < 3)
1144 { // Our head just came up.
1145 if (player->air_finished > level.time)
1146 { // We hadn't run out of air yet.
1147 S_Sound (this, CHAN_VOICE, "*surface", 1, ATTN_NORM);
1148 }
1149 // If we were running out of air, then ResetAirSupply() will play *gasp.
1150 }
1151 }
1152 return retval;
1153 }
1154
1155 //===========================================================================
1156 //
1157 // APlayerPawn :: ResetAirSupply
1158 //
1159 // Gives the player a full "tank" of air. If they had previously completely
1160 // run out of air, also plays the *gasp sound. Returns true if the player
1161 // was drowning.
1162 //
1163 //===========================================================================
1164
ResetAirSupply(bool playgasp)1165 bool APlayerPawn::ResetAirSupply (bool playgasp)
1166 {
1167 bool wasdrowning = (player->air_finished < level.time);
1168
1169 if (playgasp && wasdrowning)
1170 {
1171 S_Sound (this, CHAN_VOICE, "*gasp", 1, ATTN_NORM);
1172 }
1173 if (level.airsupply> 0 && player->mo->AirCapacity > 0) player->air_finished = level.time + FixedMul(level.airsupply, player->mo->AirCapacity);
1174 else player->air_finished = INT_MAX;
1175 return wasdrowning;
1176 }
1177
1178 //===========================================================================
1179 //
1180 // Animations
1181 //
1182 //===========================================================================
1183
PlayIdle()1184 void APlayerPawn::PlayIdle ()
1185 {
1186 if (InStateSequence(state, SeeState))
1187 SetState (SpawnState);
1188 }
1189
PlayRunning()1190 void APlayerPawn::PlayRunning ()
1191 {
1192 if (InStateSequence(state, SpawnState) && SeeState != NULL)
1193 SetState (SeeState);
1194 }
1195
PlayAttacking()1196 void APlayerPawn::PlayAttacking ()
1197 {
1198 if (MissileState != NULL) SetState (MissileState);
1199 }
1200
PlayAttacking2()1201 void APlayerPawn::PlayAttacking2 ()
1202 {
1203 if (MeleeState != NULL) SetState (MeleeState);
1204 }
1205
ThrowPoisonBag()1206 void APlayerPawn::ThrowPoisonBag ()
1207 {
1208 }
1209
1210 //===========================================================================
1211 //
1212 // APlayerPawn :: GiveDefaultInventory
1213 //
1214 //===========================================================================
1215
GiveDefaultInventory()1216 void APlayerPawn::GiveDefaultInventory ()
1217 {
1218 if (player == NULL) return;
1219
1220 // HexenArmor must always be the first item in the inventory because
1221 // it provides player class based protection that should not affect
1222 // any other protection item.
1223 fixed_t hx[5];
1224 for(int i=0;i<5;i++)
1225 {
1226 hx[i] = GetClass()->Meta.GetMetaFixed(APMETA_Hexenarmor0+i);
1227 }
1228 GiveInventoryType (RUNTIME_CLASS(AHexenArmor));
1229 AHexenArmor *harmor = FindInventory<AHexenArmor>();
1230 harmor->Slots[4] = hx[0];
1231 harmor->SlotsIncrement[0] = hx[1];
1232 harmor->SlotsIncrement[1] = hx[2];
1233 harmor->SlotsIncrement[2] = hx[3];
1234 harmor->SlotsIncrement[3] = hx[4];
1235
1236 // BasicArmor must come right after that. It should not affect any
1237 // other protection item as well but needs to process the damage
1238 // before the HexenArmor does.
1239 ABasicArmor *barmor = Spawn<ABasicArmor> (0,0,0, NO_REPLACE);
1240 barmor->BecomeItem ();
1241 barmor->SavePercent = 0;
1242 barmor->Amount = 0;
1243 AddInventory (barmor);
1244
1245 // Now add the items from the DECORATE definition
1246 FDropItem *di = GetDropItems();
1247
1248 while (di)
1249 {
1250 const PClass *ti = PClass::FindClass (di->Name);
1251 if (ti)
1252 {
1253 AInventory *item = FindInventory (ti);
1254 if (item != NULL)
1255 {
1256 item->Amount = clamp<int>(
1257 item->Amount + (di->amount ? di->amount : ((AInventory *)item->GetDefault ())->Amount),
1258 0, item->MaxAmount);
1259 }
1260 else
1261 {
1262 item = static_cast<AInventory *>(Spawn (ti, 0,0,0, NO_REPLACE));
1263 item->ItemFlags|=IF_IGNORESKILL; // no skill multiplicators here
1264 item->Amount = di->amount;
1265 if (item->IsKindOf (RUNTIME_CLASS (AWeapon)))
1266 {
1267 // To allow better control any weapon is emptied of
1268 // ammo before being given to the player.
1269 static_cast<AWeapon*>(item)->AmmoGive1 =
1270 static_cast<AWeapon*>(item)->AmmoGive2 = 0;
1271 }
1272 AActor *check;
1273 if (!item->CallTryPickup(this, &check))
1274 {
1275 if (check != this)
1276 {
1277 // Player was morphed. This is illegal at game start.
1278 // This problem is only detectable when it's too late to do something about it...
1279 I_Error("Cannot give morph items when starting a game");
1280 }
1281 item->Destroy ();
1282 item = NULL;
1283 }
1284 }
1285 if (item != NULL && item->IsKindOf (RUNTIME_CLASS (AWeapon)) &&
1286 static_cast<AWeapon*>(item)->CheckAmmo(AWeapon::EitherFire, false))
1287 {
1288 player->ReadyWeapon = player->PendingWeapon = static_cast<AWeapon *> (item);
1289 }
1290 }
1291 di = di->Next;
1292 }
1293 }
1294
MorphPlayerThink()1295 void APlayerPawn::MorphPlayerThink ()
1296 {
1297 }
1298
ActivateMorphWeapon()1299 void APlayerPawn::ActivateMorphWeapon ()
1300 {
1301 const PClass *morphweapon = PClass::FindClass (MorphWeapon);
1302 player->PendingWeapon = WP_NOCHANGE;
1303 player->psprites[ps_weapon].sy = WEAPONTOP;
1304
1305 if (morphweapon == NULL || !morphweapon->IsDescendantOf (RUNTIME_CLASS(AWeapon)))
1306 { // No weapon at all while morphed!
1307 player->ReadyWeapon = NULL;
1308 P_SetPsprite (player, ps_weapon, NULL);
1309 }
1310 else
1311 {
1312 player->ReadyWeapon = static_cast<AWeapon *>(player->mo->FindInventory (morphweapon));
1313 if (player->ReadyWeapon == NULL)
1314 {
1315 player->ReadyWeapon = static_cast<AWeapon *>(player->mo->GiveInventoryType (morphweapon));
1316 if (player->ReadyWeapon != NULL)
1317 {
1318 player->ReadyWeapon->GivenAsMorphWeapon = true; // flag is used only by new beastweap semantics in P_UndoPlayerMorph
1319 }
1320 }
1321 if (player->ReadyWeapon != NULL)
1322 {
1323 P_SetPsprite (player, ps_weapon, player->ReadyWeapon->GetReadyState());
1324 }
1325 else
1326 {
1327 P_SetPsprite (player, ps_weapon, NULL);
1328 }
1329 }
1330 P_SetPsprite (player, ps_flash, NULL);
1331
1332 player->PendingWeapon = WP_NOCHANGE;
1333 }
1334
1335 //===========================================================================
1336 //
1337 // APlayerPawn :: Die
1338 //
1339 //===========================================================================
1340
Die(AActor * source,AActor * inflictor,int dmgflags)1341 void APlayerPawn::Die (AActor *source, AActor *inflictor, int dmgflags)
1342 {
1343 Super::Die (source, inflictor, dmgflags);
1344
1345 if (player != NULL && player->mo == this) player->bonuscount = 0;
1346
1347 if (player != NULL && player->mo != this)
1348 { // Make the real player die, too
1349 player->mo->Die (source, inflictor, dmgflags);
1350 }
1351 else
1352 {
1353 if (player != NULL && (dmflags2 & DF2_YES_WEAPONDROP))
1354 { // Voodoo dolls don't drop weapons
1355 AWeapon *weap = player->ReadyWeapon;
1356 if (weap != NULL)
1357 {
1358 AInventory *item;
1359
1360 // kgDROP - start - modified copy from a_action.cpp
1361 FDropItem *di = weap->GetDropItems();
1362
1363 if (di != NULL)
1364 {
1365 while (di != NULL)
1366 {
1367 if (di->Name != NAME_None)
1368 {
1369 const PClass *ti = PClass::FindClass(di->Name);
1370 if (ti) P_DropItem (player->mo, ti, di->amount, di->probability);
1371 }
1372 di = di->Next;
1373 }
1374 } else
1375 // kgDROP - end
1376 if (weap->SpawnState != NULL &&
1377 weap->SpawnState != ::GetDefault<AActor>()->SpawnState)
1378 {
1379 item = P_DropItem (this, weap->GetClass(), -1, 256);
1380 if (item != NULL && item->IsKindOf(RUNTIME_CLASS(AWeapon)))
1381 {
1382 if (weap->AmmoGive1 && weap->Ammo1)
1383 {
1384 static_cast<AWeapon *>(item)->AmmoGive1 = weap->Ammo1->Amount;
1385 }
1386 if (weap->AmmoGive2 && weap->Ammo2)
1387 {
1388 static_cast<AWeapon *>(item)->AmmoGive2 = weap->Ammo2->Amount;
1389 }
1390 item->ItemFlags |= IF_IGNORESKILL;
1391 }
1392 }
1393 else
1394 {
1395 item = P_DropItem (this, weap->AmmoType1, -1, 256);
1396 if (item != NULL)
1397 {
1398 item->Amount = weap->Ammo1->Amount;
1399 item->ItemFlags |= IF_IGNORESKILL;
1400 }
1401 item = P_DropItem (this, weap->AmmoType2, -1, 256);
1402 if (item != NULL)
1403 {
1404 item->Amount = weap->Ammo2->Amount;
1405 item->ItemFlags |= IF_IGNORESKILL;
1406 }
1407 }
1408 }
1409 }
1410 if (!multiplayer && level.info->deathsequence != NAME_None)
1411 {
1412 F_StartIntermission(level.info->deathsequence, FSTATE_EndingGame);
1413 }
1414 }
1415 }
1416
1417 //===========================================================================
1418 //
1419 // APlayerPawn :: TweakSpeeds
1420 //
1421 //===========================================================================
1422
TweakSpeeds(int & forward,int & side)1423 void APlayerPawn::TweakSpeeds (int &forward, int &side)
1424 {
1425 // Strife's player can't run when its healh is below 10
1426 if (health <= RunHealth)
1427 {
1428 forward = clamp(forward, -0x1900, 0x1900);
1429 side = clamp(side, -0x1800, 0x1800);
1430 }
1431
1432 // [GRB]
1433 if ((unsigned int)(forward + 0x31ff) < 0x63ff)
1434 {
1435 forward = FixedMul (forward, ForwardMove1);
1436 }
1437 else
1438 {
1439 forward = FixedMul (forward, ForwardMove2);
1440 }
1441 if ((unsigned int)(side + 0x27ff) < 0x4fff)
1442 {
1443 side = FixedMul (side, SideMove1);
1444 }
1445 else
1446 {
1447 side = FixedMul (side, SideMove2);
1448 }
1449
1450 if (!player->morphTics && Inventory != NULL)
1451 {
1452 fixed_t factor = Inventory->GetSpeedFactor ();
1453 forward = FixedMul(forward, factor);
1454 side = FixedMul(side, factor);
1455 }
1456 }
1457
1458 //===========================================================================
1459 //
1460 // A_PlayerScream
1461 //
1462 // try to find the appropriate death sound and use suitable
1463 // replacements if necessary
1464 //
1465 //===========================================================================
1466
DEFINE_ACTION_FUNCTION(AActor,A_PlayerScream)1467 DEFINE_ACTION_FUNCTION(AActor, A_PlayerScream)
1468 {
1469 int sound = 0;
1470 int chan = CHAN_VOICE;
1471
1472 if (self->player == NULL || self->DeathSound != 0)
1473 {
1474 if (self->DeathSound != 0)
1475 {
1476 S_Sound (self, CHAN_VOICE, self->DeathSound, 1, ATTN_NORM);
1477 }
1478 else
1479 {
1480 S_Sound (self, CHAN_VOICE, "*death", 1, ATTN_NORM);
1481 }
1482 return;
1483 }
1484
1485 // Handle the different player death screams
1486 if ((((level.flags >> 15) | (dmflags)) &
1487 (DF_FORCE_FALLINGZD | DF_FORCE_FALLINGHX)) &&
1488 self->velz <= -39*FRACUNIT)
1489 {
1490 sound = S_FindSkinnedSound (self, "*splat");
1491 chan = CHAN_BODY;
1492 }
1493
1494 if (!sound && self->special1<10)
1495 { // Wimpy death sound
1496 sound = S_FindSkinnedSoundEx (self, "*wimpydeath", self->player->LastDamageType);
1497 }
1498 if (!sound && self->health <= -50)
1499 {
1500 if (self->health > -100)
1501 { // Crazy death sound
1502 sound = S_FindSkinnedSoundEx (self, "*crazydeath", self->player->LastDamageType);
1503 }
1504 if (!sound)
1505 { // Extreme death sound
1506 sound = S_FindSkinnedSoundEx (self, "*xdeath", self->player->LastDamageType);
1507 if (!sound)
1508 {
1509 sound = S_FindSkinnedSoundEx (self, "*gibbed", self->player->LastDamageType);
1510 chan = CHAN_BODY;
1511 }
1512 }
1513 }
1514 if (!sound)
1515 { // Normal death sound
1516 sound = S_FindSkinnedSoundEx (self, "*death", self->player->LastDamageType);
1517 }
1518
1519 if (chan != CHAN_VOICE)
1520 {
1521 for (int i = 0; i < 8; ++i)
1522 { // Stop most playing sounds from this player.
1523 // This is mainly to stop *land from messing up *splat.
1524 if (i != CHAN_WEAPON && i != CHAN_VOICE)
1525 {
1526 S_StopSound (self, i);
1527 }
1528 }
1529 }
1530 S_Sound (self, chan, sound, 1, ATTN_NORM);
1531 }
1532
1533
1534 //----------------------------------------------------------------------------
1535 //
1536 // PROC A_SkullPop
1537 //
1538 //----------------------------------------------------------------------------
1539
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_SkullPop)1540 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SkullPop)
1541 {
1542 ACTION_PARAM_START(1);
1543 ACTION_PARAM_CLASS(spawntype, 0);
1544
1545 APlayerPawn *mo;
1546 player_t *player;
1547
1548 // [GRB] Parameterized version
1549 if (!spawntype || !spawntype->IsDescendantOf (RUNTIME_CLASS (APlayerChunk)))
1550 {
1551 spawntype = PClass::FindClass("BloodySkull");
1552 if (spawntype == NULL) return;
1553 }
1554
1555 self->flags &= ~MF_SOLID;
1556 mo = (APlayerPawn *)Spawn (spawntype, self->PosPlusZ(48*FRACUNIT), NO_REPLACE);
1557 //mo->target = self;
1558 mo->velx = pr_skullpop.Random2() << 9;
1559 mo->vely = pr_skullpop.Random2() << 9;
1560 mo->velz = 2*FRACUNIT + (pr_skullpop() << 6);
1561 // Attach player mobj to bloody skull
1562 player = self->player;
1563 self->player = NULL;
1564 mo->ObtainInventory (self);
1565 mo->player = player;
1566 mo->health = self->health;
1567 mo->angle = self->angle;
1568 if (player != NULL)
1569 {
1570 player->mo = mo;
1571 player->damagecount = 32;
1572 }
1573 for (int i = 0; i < MAXPLAYERS; ++i)
1574 {
1575 if (playeringame[i] && players[i].camera == self)
1576 {
1577 players[i].camera = mo;
1578 }
1579 }
1580 }
1581
1582 //----------------------------------------------------------------------------
1583 //
1584 // PROC A_CheckSkullDone
1585 //
1586 //----------------------------------------------------------------------------
1587
DEFINE_ACTION_FUNCTION(AActor,A_CheckPlayerDone)1588 DEFINE_ACTION_FUNCTION(AActor, A_CheckPlayerDone)
1589 {
1590 if (self->player == NULL)
1591 {
1592 self->Destroy ();
1593 }
1594 }
1595
1596 //===========================================================================
1597 //
1598 // P_CheckPlayerSprites
1599 //
1600 // Here's the place where crouching sprites are handled.
1601 // R_ProjectSprite() calls this for any players.
1602 //
1603 //===========================================================================
1604
P_CheckPlayerSprite(AActor * actor,int & spritenum,fixed_t & scalex,fixed_t & scaley)1605 void P_CheckPlayerSprite(AActor *actor, int &spritenum, fixed_t &scalex, fixed_t &scaley)
1606 {
1607 player_t *player = actor->player;
1608 int crouchspriteno;
1609
1610 if (player->userinfo.GetSkin() != 0 && !(actor->flags4 & MF4_NOSKIN))
1611 {
1612 // Convert from default scale to skin scale.
1613 fixed_t defscaleY = actor->GetDefault()->scaleY;
1614 fixed_t defscaleX = actor->GetDefault()->scaleX;
1615 scaley = Scale(scaley, skins[player->userinfo.GetSkin()].ScaleY, defscaleY);
1616 scalex = Scale(scalex, skins[player->userinfo.GetSkin()].ScaleX, defscaleX);
1617 }
1618
1619 // Set the crouch sprite?
1620 if (player->crouchfactor < FRACUNIT*3/4)
1621 {
1622 if (spritenum == actor->SpawnState->sprite || spritenum == player->mo->crouchsprite)
1623 {
1624 crouchspriteno = player->mo->crouchsprite;
1625 }
1626 else if (!(actor->flags4 & MF4_NOSKIN) &&
1627 (spritenum == skins[player->userinfo.GetSkin()].sprite ||
1628 spritenum == skins[player->userinfo.GetSkin()].crouchsprite))
1629 {
1630 crouchspriteno = skins[player->userinfo.GetSkin()].crouchsprite;
1631 }
1632 else
1633 { // no sprite -> squash the existing one
1634 crouchspriteno = -1;
1635 }
1636
1637 if (crouchspriteno > 0)
1638 {
1639 spritenum = crouchspriteno;
1640 }
1641 else if (player->playerstate != PST_DEAD && player->crouchfactor < FRACUNIT*3/4)
1642 {
1643 scaley /= 2;
1644 }
1645 }
1646 }
1647
1648 /*
1649 ==================
1650 =
1651 = P_Thrust
1652 =
1653 = moves the given origin along a given angle
1654 =
1655 ==================
1656 */
1657
P_SideThrust(player_t * player,angle_t angle,fixed_t move)1658 void P_SideThrust (player_t *player, angle_t angle, fixed_t move)
1659 {
1660 angle = (angle - ANGLE_90) >> ANGLETOFINESHIFT;
1661
1662 player->mo->velx += FixedMul (move, finecosine[angle]);
1663 player->mo->vely += FixedMul (move, finesine[angle]);
1664 }
1665
P_ForwardThrust(player_t * player,angle_t angle,fixed_t move)1666 void P_ForwardThrust (player_t *player, angle_t angle, fixed_t move)
1667 {
1668 angle >>= ANGLETOFINESHIFT;
1669
1670 if ((player->mo->waterlevel || (player->mo->flags & MF_NOGRAVITY))
1671 && player->mo->pitch != 0)
1672 {
1673 angle_t pitch = (angle_t)player->mo->pitch >> ANGLETOFINESHIFT;
1674 fixed_t zpush = FixedMul (move, finesine[pitch]);
1675 if (player->mo->waterlevel && player->mo->waterlevel < 2 && zpush < 0)
1676 zpush = 0;
1677 player->mo->velz -= zpush;
1678 move = FixedMul (move, finecosine[pitch]);
1679 }
1680 player->mo->velx += FixedMul (move, finecosine[angle]);
1681 player->mo->vely += FixedMul (move, finesine[angle]);
1682 }
1683
1684 //
1685 // P_Bob
1686 // Same as P_Thrust, but only affects bobbing.
1687 //
1688 // killough 10/98: We apply thrust separately between the real physical player
1689 // and the part which affects bobbing. This way, bobbing only comes from player
1690 // motion, nothing external, avoiding many problems, e.g. bobbing should not
1691 // occur on conveyors, unless the player walks on one, and bobbing should be
1692 // reduced at a regular rate, even on ice (where the player coasts).
1693 //
1694
P_Bob(player_t * player,angle_t angle,fixed_t move,bool forward)1695 void P_Bob (player_t *player, angle_t angle, fixed_t move, bool forward)
1696 {
1697 if (forward
1698 && (player->mo->waterlevel || (player->mo->flags & MF_NOGRAVITY))
1699 && player->mo->pitch != 0)
1700 {
1701 angle_t pitch = (angle_t)player->mo->pitch >> ANGLETOFINESHIFT;
1702 move = FixedMul (move, finecosine[pitch]);
1703 }
1704
1705 angle >>= ANGLETOFINESHIFT;
1706
1707 player->velx += FixedMul(move, finecosine[angle]);
1708 player->vely += FixedMul(move, finesine[angle]);
1709 }
1710
1711 /*
1712 ==================
1713 =
1714 = P_CalcHeight
1715 =
1716 =
1717 Calculate the walking / running height adjustment
1718 =
1719 ==================
1720 */
1721
P_CalcHeight(player_t * player)1722 void P_CalcHeight (player_t *player)
1723 {
1724 int angle;
1725 fixed_t bob;
1726 bool still = false;
1727
1728 // Regular movement bobbing
1729 // (needs to be calculated for gun swing even if not on ground)
1730
1731 // killough 10/98: Make bobbing depend only on player-applied motion.
1732 //
1733 // Note: don't reduce bobbing here if on ice: if you reduce bobbing here,
1734 // it causes bobbing jerkiness when the player moves from ice to non-ice,
1735 // and vice-versa.
1736
1737 if (player->cheats & CF_NOCLIP2)
1738 {
1739 player->bob = 0;
1740 }
1741 else if ((player->mo->flags & MF_NOGRAVITY) && !player->onground)
1742 {
1743 player->bob = FRACUNIT / 2;
1744 }
1745 else
1746 {
1747 player->bob = DMulScale16 (player->velx, player->velx, player->vely, player->vely);
1748 if (player->bob == 0)
1749 {
1750 still = true;
1751 }
1752 else
1753 {
1754 player->bob = FixedMul (player->bob, player->userinfo.GetMoveBob());
1755
1756 if (player->bob > MAXBOB)
1757 player->bob = MAXBOB;
1758 }
1759 }
1760
1761 fixed_t defaultviewheight = player->mo->ViewHeight + player->crouchviewdelta;
1762
1763 if (player->cheats & CF_NOVELOCITY)
1764 {
1765 player->viewz = player->mo->Z() + defaultviewheight;
1766
1767 if (player->viewz > player->mo->ceilingz-4*FRACUNIT)
1768 player->viewz = player->mo->ceilingz-4*FRACUNIT;
1769
1770 return;
1771 }
1772
1773 if (still)
1774 {
1775 if (player->health > 0)
1776 {
1777 angle = DivScale13 (level.time, 120*TICRATE/35) & FINEMASK;
1778 bob = FixedMul (player->userinfo.GetStillBob(), finesine[angle]);
1779 }
1780 else
1781 {
1782 bob = 0;
1783 }
1784 }
1785 else
1786 {
1787 // DivScale 13 because FINEANGLES == (1<<13)
1788 angle = DivScale13 (level.time, 20*TICRATE/35) & FINEMASK;
1789 bob = FixedMul (player->bob>>(player->mo->waterlevel > 1 ? 2 : 1), finesine[angle]);
1790 }
1791
1792 // move viewheight
1793 if (player->playerstate == PST_LIVE)
1794 {
1795 player->viewheight += player->deltaviewheight;
1796
1797 if (player->viewheight > defaultviewheight)
1798 {
1799 player->viewheight = defaultviewheight;
1800 player->deltaviewheight = 0;
1801 }
1802 else if (player->viewheight < (defaultviewheight>>1))
1803 {
1804 player->viewheight = defaultviewheight>>1;
1805 if (player->deltaviewheight <= 0)
1806 player->deltaviewheight = 1;
1807 }
1808
1809 if (player->deltaviewheight)
1810 {
1811 player->deltaviewheight += FRACUNIT/4;
1812 if (!player->deltaviewheight)
1813 player->deltaviewheight = 1;
1814 }
1815 }
1816
1817 if (player->morphTics)
1818 {
1819 bob = 0;
1820 }
1821 player->viewz = player->mo->Z() + player->viewheight + bob;
1822 if (player->mo->floorclip && player->playerstate != PST_DEAD
1823 && player->mo->Z() <= player->mo->floorz)
1824 {
1825 player->viewz -= player->mo->floorclip;
1826 }
1827 if (player->viewz > player->mo->ceilingz - 4*FRACUNIT)
1828 {
1829 player->viewz = player->mo->ceilingz - 4*FRACUNIT;
1830 }
1831 if (player->viewz < player->mo->floorz + 4*FRACUNIT)
1832 {
1833 player->viewz = player->mo->floorz + 4*FRACUNIT;
1834 }
1835 }
1836
1837 /*
1838 =================
1839 =
1840 = P_MovePlayer
1841 =
1842 =================
1843 */
1844 CUSTOM_CVAR (Float, sv_aircontrol, 0.00390625f, CVAR_SERVERINFO|CVAR_NOSAVE)
1845 {
1846 level.aircontrol = (fixed_t)(self * 65536.f);
1847 G_AirControlChanged ();
1848 }
1849
P_MovePlayer(player_t * player)1850 void P_MovePlayer (player_t *player)
1851 {
1852 ticcmd_t *cmd = &player->cmd;
1853 APlayerPawn *mo = player->mo;
1854
1855 // [RH] 180-degree turn overrides all other yaws
1856 if (player->turnticks)
1857 {
1858 player->turnticks--;
1859 mo->angle += (ANGLE_180 / TURN180_TICKS);
1860 }
1861 else
1862 {
1863 mo->angle += cmd->ucmd.yaw << 16;
1864 }
1865
1866 player->onground = (mo->Z() <= mo->floorz) || (mo->flags2 & MF2_ONMOBJ) || (mo->BounceFlags & BOUNCE_MBF) || (player->cheats & CF_NOCLIP2);
1867
1868 // killough 10/98:
1869 //
1870 // We must apply thrust to the player and bobbing separately, to avoid
1871 // anomalies. The thrust applied to bobbing is always the same strength on
1872 // ice, because the player still "works just as hard" to move, while the
1873 // thrust applied to the movement varies with 'movefactor'.
1874
1875 if (cmd->ucmd.forwardmove | cmd->ucmd.sidemove)
1876 {
1877 fixed_t forwardmove, sidemove;
1878 int bobfactor;
1879 int friction, movefactor;
1880 int fm, sm;
1881
1882 movefactor = P_GetMoveFactor (mo, &friction);
1883 bobfactor = friction < ORIG_FRICTION ? movefactor : ORIG_FRICTION_FACTOR;
1884 if (!player->onground && !(player->mo->flags & MF_NOGRAVITY) && !player->mo->waterlevel)
1885 {
1886 // [RH] allow very limited movement if not on ground.
1887 movefactor = FixedMul (movefactor, level.aircontrol);
1888 bobfactor = FixedMul (bobfactor, level.aircontrol);
1889 }
1890
1891 fm = cmd->ucmd.forwardmove;
1892 sm = cmd->ucmd.sidemove;
1893 mo->TweakSpeeds (fm, sm);
1894 fm = FixedMul (fm, player->mo->Speed);
1895 sm = FixedMul (sm, player->mo->Speed);
1896
1897 // When crouching, speed and bobbing have to be reduced
1898 if (player->CanCrouch() && player->crouchfactor != FRACUNIT)
1899 {
1900 fm = FixedMul(fm, player->crouchfactor);
1901 sm = FixedMul(sm, player->crouchfactor);
1902 bobfactor = FixedMul(bobfactor, player->crouchfactor);
1903 }
1904
1905 forwardmove = Scale (fm, movefactor * 35, TICRATE << 8);
1906 sidemove = Scale (sm, movefactor * 35, TICRATE << 8);
1907
1908 if (forwardmove)
1909 {
1910 P_Bob (player, mo->angle, (cmd->ucmd.forwardmove * bobfactor) >> 8, true);
1911 P_ForwardThrust (player, mo->angle, forwardmove);
1912 }
1913 if (sidemove)
1914 {
1915 P_Bob (player, mo->angle-ANG90, (cmd->ucmd.sidemove * bobfactor) >> 8, false);
1916 P_SideThrust (player, mo->angle, sidemove);
1917 }
1918
1919 if (debugfile)
1920 {
1921 fprintf (debugfile, "move player for pl %d%c: (%d,%d,%d) (%d,%d) %d %d w%d [", int(player-players),
1922 player->cheats&CF_PREDICTING?'p':' ',
1923 player->mo->X(), player->mo->Y(), player->mo->Z(),forwardmove, sidemove, movefactor, friction, player->mo->waterlevel);
1924 msecnode_t *n = player->mo->touching_sectorlist;
1925 while (n != NULL)
1926 {
1927 fprintf (debugfile, "%td ", n->m_sector-sectors);
1928 n = n->m_tnext;
1929 }
1930 fprintf (debugfile, "]\n");
1931 }
1932
1933 if (!(player->cheats & CF_PREDICTING) && (forwardmove|sidemove))
1934 {
1935 player->mo->PlayRunning ();
1936 }
1937
1938 if (player->cheats & CF_REVERTPLEASE)
1939 {
1940 player->cheats &= ~CF_REVERTPLEASE;
1941 player->camera = player->mo;
1942 }
1943 }
1944 }
1945
1946 //==========================================================================
1947 //
1948 // P_FallingDamage
1949 //
1950 //==========================================================================
1951
P_FallingDamage(AActor * actor)1952 void P_FallingDamage (AActor *actor)
1953 {
1954 int damagestyle;
1955 int damage;
1956 fixed_t vel;
1957
1958 damagestyle = ((level.flags >> 15) | (dmflags)) &
1959 (DF_FORCE_FALLINGZD | DF_FORCE_FALLINGHX);
1960
1961 if (damagestyle == 0)
1962 return;
1963
1964 if (actor->floorsector->Flags & SECF_NOFALLINGDAMAGE)
1965 return;
1966
1967 vel = abs(actor->velz);
1968
1969 // Since Hexen falling damage is stronger than ZDoom's, it takes
1970 // precedence. ZDoom falling damage may not be as strong, but it
1971 // gets felt sooner.
1972
1973 switch (damagestyle)
1974 {
1975 case DF_FORCE_FALLINGHX: // Hexen falling damage
1976 if (vel <= 23*FRACUNIT)
1977 { // Not fast enough to hurt
1978 return;
1979 }
1980 if (vel >= 63*FRACUNIT)
1981 { // automatic death
1982 damage = 1000000;
1983 }
1984 else
1985 {
1986 vel = FixedMul (vel, 16*FRACUNIT/23);
1987 damage = ((FixedMul (vel, vel) / 10) >> FRACBITS) - 24;
1988 if (actor->velz > -39*FRACUNIT && damage > actor->health
1989 && actor->health != 1)
1990 { // No-death threshold
1991 damage = actor->health-1;
1992 }
1993 }
1994 break;
1995
1996 case DF_FORCE_FALLINGZD: // ZDoom falling damage
1997 if (vel <= 19*FRACUNIT)
1998 { // Not fast enough to hurt
1999 return;
2000 }
2001 if (vel >= 84*FRACUNIT)
2002 { // automatic death
2003 damage = 1000000;
2004 }
2005 else
2006 {
2007 damage = ((MulScale23 (vel, vel*11) >> FRACBITS) - 30) / 2;
2008 if (damage < 1)
2009 {
2010 damage = 1;
2011 }
2012 }
2013 break;
2014
2015 case DF_FORCE_FALLINGST: // Strife falling damage
2016 if (vel <= 20*FRACUNIT)
2017 { // Not fast enough to hurt
2018 return;
2019 }
2020 // The minimum amount of damage you take from falling in Strife
2021 // is 52. Ouch!
2022 damage = vel / 25000;
2023 break;
2024
2025 default:
2026 return;
2027 }
2028
2029 if (actor->player)
2030 {
2031 S_Sound (actor, CHAN_AUTO, "*land", 1, ATTN_NORM);
2032 P_NoiseAlert (actor, actor, true);
2033 if (damage == 1000000 && (actor->player->cheats & (CF_GODMODE | CF_BUDDHA)))
2034 {
2035 damage = 999;
2036 }
2037 }
2038 P_DamageMobj (actor, NULL, NULL, damage, NAME_Falling);
2039 }
2040
2041 //==========================================================================
2042 //
2043 // P_DeathThink
2044 //
2045 //==========================================================================
2046
P_DeathThink(player_t * player)2047 void P_DeathThink (player_t *player)
2048 {
2049 int dir;
2050 angle_t delta;
2051 int lookDelta;
2052
2053 P_MovePsprites (player);
2054
2055 player->onground = (player->mo->Z() <= player->mo->floorz);
2056 if (player->mo->IsKindOf (RUNTIME_CLASS(APlayerChunk)))
2057 { // Flying bloody skull or flying ice chunk
2058 player->viewheight = 6 * FRACUNIT;
2059 player->deltaviewheight = 0;
2060 if (player->onground)
2061 {
2062 if (player->mo->pitch > -(int)ANGLE_1*19)
2063 {
2064 lookDelta = (-(int)ANGLE_1*19 - player->mo->pitch) / 8;
2065 player->mo->pitch += lookDelta;
2066 }
2067 }
2068 }
2069 else if (!(player->mo->flags & MF_ICECORPSE))
2070 { // Fall to ground (if not frozen)
2071 player->deltaviewheight = 0;
2072 if (player->viewheight > 6*FRACUNIT)
2073 {
2074 player->viewheight -= FRACUNIT;
2075 }
2076 if (player->viewheight < 6*FRACUNIT)
2077 {
2078 player->viewheight = 6*FRACUNIT;
2079 }
2080 if (player->mo->pitch < 0)
2081 {
2082 player->mo->pitch += ANGLE_1*3;
2083 }
2084 else if (player->mo->pitch > 0)
2085 {
2086 player->mo->pitch -= ANGLE_1*3;
2087 }
2088 if (abs(player->mo->pitch) < ANGLE_1*3)
2089 {
2090 player->mo->pitch = 0;
2091 }
2092 }
2093 P_CalcHeight (player);
2094
2095 if (player->attacker && player->attacker != player->mo)
2096 { // Watch killer
2097 dir = P_FaceMobj (player->mo, player->attacker, &delta);
2098 if (delta < ANGLE_1*10)
2099 { // Looking at killer, so fade damage and poison counters
2100 if (player->damagecount)
2101 {
2102 player->damagecount--;
2103 }
2104 if (player->poisoncount)
2105 {
2106 player->poisoncount--;
2107 }
2108 }
2109 delta /= 8;
2110 if (delta > ANGLE_1*5)
2111 {
2112 delta = ANGLE_1*5;
2113 }
2114 if (dir)
2115 { // Turn clockwise
2116 player->mo->angle += delta;
2117 }
2118 else
2119 { // Turn counter clockwise
2120 player->mo->angle -= delta;
2121 }
2122 }
2123 else
2124 {
2125 if (player->damagecount)
2126 {
2127 player->damagecount--;
2128 }
2129 if (player->poisoncount)
2130 {
2131 player->poisoncount--;
2132 }
2133 }
2134
2135 if ((player->cmd.ucmd.buttons & BT_USE ||
2136 ((multiplayer || alwaysapplydmflags) && (dmflags & DF_FORCE_RESPAWN))) && !(dmflags2 & DF2_NO_RESPAWN))
2137 {
2138 if (level.time >= player->respawn_time || ((player->cmd.ucmd.buttons & BT_USE) && player->Bot == NULL))
2139 {
2140 player->cls = NULL; // Force a new class if the player is using a random class
2141 player->playerstate = (multiplayer || (level.flags2 & LEVEL2_ALLOWRESPAWN)) ? PST_REBORN : PST_ENTER;
2142 if (player->mo->special1 > 2)
2143 {
2144 player->mo->special1 = 0;
2145 }
2146 }
2147 }
2148 }
2149
2150 //----------------------------------------------------------------------------
2151 //
2152 // PROC P_CrouchMove
2153 //
2154 //----------------------------------------------------------------------------
2155
P_CrouchMove(player_t * player,int direction)2156 void P_CrouchMove(player_t * player, int direction)
2157 {
2158 fixed_t defaultheight = player->mo->GetDefault()->height;
2159 fixed_t savedheight = player->mo->height;
2160 fixed_t crouchspeed = direction * CROUCHSPEED;
2161 fixed_t oldheight = player->viewheight;
2162
2163 player->crouchdir = (signed char) direction;
2164 player->crouchfactor += crouchspeed;
2165
2166 // check whether the move is ok
2167 player->mo->height = FixedMul(defaultheight, player->crouchfactor);
2168 if (!P_TryMove(player->mo, player->mo->X(), player->mo->Y(), false, NULL))
2169 {
2170 player->mo->height = savedheight;
2171 if (direction > 0)
2172 {
2173 // doesn't fit
2174 player->crouchfactor -= crouchspeed;
2175 return;
2176 }
2177 }
2178 player->mo->height = savedheight;
2179
2180 player->crouchfactor = clamp<fixed_t>(player->crouchfactor, FRACUNIT/2, FRACUNIT);
2181 player->viewheight = FixedMul(player->mo->ViewHeight, player->crouchfactor);
2182 player->crouchviewdelta = player->viewheight - player->mo->ViewHeight;
2183
2184 // Check for eyes going above/below fake floor due to crouching motion.
2185 P_CheckFakeFloorTriggers(player->mo, player->mo->Z() + oldheight, true);
2186 }
2187
2188 //----------------------------------------------------------------------------
2189 //
2190 // PROC P_PlayerThink
2191 //
2192 //----------------------------------------------------------------------------
2193
P_PlayerThink(player_t * player)2194 void P_PlayerThink (player_t *player)
2195 {
2196 ticcmd_t *cmd;
2197
2198 if (player->mo == NULL)
2199 {
2200 I_Error ("No player %td start\n", player - players + 1);
2201 }
2202
2203 if (debugfile && !(player->cheats & CF_PREDICTING))
2204 {
2205 fprintf (debugfile, "tic %d for pl %td: (%d, %d, %d, %u) b:%02x p:%d y:%d f:%d s:%d u:%d\n",
2206 gametic, player-players, player->mo->X(), player->mo->Y(), player->mo->Z(),
2207 player->mo->angle>>ANGLETOFINESHIFT, player->cmd.ucmd.buttons,
2208 player->cmd.ucmd.pitch, player->cmd.ucmd.yaw, player->cmd.ucmd.forwardmove,
2209 player->cmd.ucmd.sidemove, player->cmd.ucmd.upmove);
2210 }
2211
2212 // [RH] Zoom the player's FOV
2213 float desired = player->DesiredFOV;
2214 // Adjust FOV using on the currently held weapon.
2215 if (player->playerstate != PST_DEAD && // No adjustment while dead.
2216 player->ReadyWeapon != NULL && // No adjustment if no weapon.
2217 player->ReadyWeapon->FOVScale != 0) // No adjustment if the adjustment is zero.
2218 {
2219 // A negative scale is used to prevent G_AddViewAngle/G_AddViewPitch
2220 // from scaling with the FOV scale.
2221 desired *= fabs(player->ReadyWeapon->FOVScale);
2222 }
2223 if (player->FOV != desired)
2224 {
2225 if (fabsf (player->FOV - desired) < 7.f)
2226 {
2227 player->FOV = desired;
2228 }
2229 else
2230 {
2231 float zoom = MAX(7.f, fabsf(player->FOV - desired) * 0.025f);
2232 if (player->FOV > desired)
2233 {
2234 player->FOV = player->FOV - zoom;
2235 }
2236 else
2237 {
2238 player->FOV = player->FOV + zoom;
2239 }
2240 }
2241 }
2242 if (player->inventorytics)
2243 {
2244 player->inventorytics--;
2245 }
2246 // Don't interpolate the view for more than one tic
2247 player->cheats &= ~CF_INTERPVIEW;
2248
2249 // No-clip cheat
2250 if ((player->cheats & (CF_NOCLIP | CF_NOCLIP2)) == CF_NOCLIP2)
2251 { // No noclip2 without noclip
2252 player->cheats &= ~CF_NOCLIP2;
2253 }
2254 if (player->cheats & (CF_NOCLIP | CF_NOCLIP2) || (player->mo->GetDefault()->flags & MF_NOCLIP))
2255 {
2256 player->mo->flags |= MF_NOCLIP;
2257 }
2258 else
2259 {
2260 player->mo->flags &= ~MF_NOCLIP;
2261 }
2262 if (player->cheats & CF_NOCLIP2)
2263 {
2264 player->mo->flags |= MF_NOGRAVITY;
2265 }
2266 else if (!(player->mo->flags2 & MF2_FLY) && !(player->mo->GetDefault()->flags & MF_NOGRAVITY))
2267 {
2268 player->mo->flags &= ~MF_NOGRAVITY;
2269 }
2270 cmd = &player->cmd;
2271
2272 // Make unmodified copies for ACS's GetPlayerInput.
2273 player->original_oldbuttons = player->original_cmd.buttons;
2274 player->original_cmd = cmd->ucmd;
2275
2276 if (player->mo->flags & MF_JUSTATTACKED)
2277 { // Chainsaw/Gauntlets attack auto forward motion
2278 cmd->ucmd.yaw = 0;
2279 cmd->ucmd.forwardmove = 0xc800/2;
2280 cmd->ucmd.sidemove = 0;
2281 player->mo->flags &= ~MF_JUSTATTACKED;
2282 }
2283
2284 bool totallyfrozen = P_IsPlayerTotallyFrozen(player);
2285
2286 // [RH] Being totally frozen zeros out most input parameters.
2287 if (totallyfrozen)
2288 {
2289 if (gamestate == GS_TITLELEVEL)
2290 {
2291 cmd->ucmd.buttons = 0;
2292 }
2293 else
2294 {
2295 cmd->ucmd.buttons &= BT_USE;
2296 }
2297 cmd->ucmd.pitch = 0;
2298 cmd->ucmd.yaw = 0;
2299 cmd->ucmd.roll = 0;
2300 cmd->ucmd.forwardmove = 0;
2301 cmd->ucmd.sidemove = 0;
2302 cmd->ucmd.upmove = 0;
2303 player->turnticks = 0;
2304 }
2305 else if (player->cheats & CF_FROZEN)
2306 {
2307 cmd->ucmd.forwardmove = 0;
2308 cmd->ucmd.sidemove = 0;
2309 cmd->ucmd.upmove = 0;
2310 }
2311
2312 // Handle crouching
2313 if (player->cmd.ucmd.buttons & BT_JUMP)
2314 {
2315 player->cmd.ucmd.buttons &= ~BT_CROUCH;
2316 }
2317 if (player->CanCrouch() && player->health > 0 && level.IsCrouchingAllowed())
2318 {
2319 if (!totallyfrozen)
2320 {
2321 int crouchdir = player->crouching;
2322
2323 if (crouchdir == 0)
2324 {
2325 crouchdir = (player->cmd.ucmd.buttons & BT_CROUCH) ? -1 : 1;
2326 }
2327 else if (player->cmd.ucmd.buttons & BT_CROUCH)
2328 {
2329 player->crouching = 0;
2330 }
2331 if (crouchdir == 1 && player->crouchfactor < FRACUNIT &&
2332 player->mo->Top() < player->mo->ceilingz)
2333 {
2334 P_CrouchMove(player, 1);
2335 }
2336 else if (crouchdir == -1 && player->crouchfactor > FRACUNIT/2)
2337 {
2338 P_CrouchMove(player, -1);
2339 }
2340 }
2341 }
2342 else
2343 {
2344 player->Uncrouch();
2345 }
2346
2347 player->crouchoffset = -FixedMul(player->mo->ViewHeight, (FRACUNIT - player->crouchfactor));
2348
2349 // MUSINFO stuff
2350 if (player->MUSINFOtics >= 0 && player->MUSINFOactor != NULL)
2351 {
2352 if (--player->MUSINFOtics < 0)
2353 {
2354 if (player - players == consoleplayer)
2355 {
2356 if (player->MUSINFOactor->args[0] != 0)
2357 {
2358 FName *music = level.info->MusicMap.CheckKey(player->MUSINFOactor->args[0]);
2359
2360 if (music != NULL)
2361 {
2362 S_ChangeMusic(music->GetChars(), player->MUSINFOactor->args[1]);
2363 }
2364 }
2365 else
2366 {
2367 S_ChangeMusic("*");
2368 }
2369 }
2370 DPrintf("MUSINFO change for player %d to %d\n", (int)(player - players), player->MUSINFOactor->args[0]);
2371 }
2372 }
2373
2374 if (player->playerstate == PST_DEAD)
2375 {
2376 player->Uncrouch();
2377 P_DeathThink (player);
2378 return;
2379 }
2380 if (player->jumpTics != 0)
2381 {
2382 player->jumpTics--;
2383 if (player->onground && player->jumpTics < -18)
2384 {
2385 player->jumpTics = 0;
2386 }
2387 }
2388 if (player->morphTics && !(player->cheats & CF_PREDICTING))
2389 {
2390 player->mo->MorphPlayerThink ();
2391 }
2392
2393 // [RH] Look up/down stuff
2394 if (!level.IsFreelookAllowed())
2395 {
2396 player->mo->pitch = 0;
2397 }
2398 else
2399 {
2400 int look = cmd->ucmd.pitch << 16;
2401
2402 // The player's view pitch is clamped between -32 and +56 degrees,
2403 // which translates to about half a screen height up and (more than)
2404 // one full screen height down from straight ahead when view panning
2405 // is used.
2406 if (look)
2407 {
2408 if (look == -32768 << 16)
2409 { // center view
2410 player->centering = true;
2411 }
2412 else if (!player->centering)
2413 {
2414 fixed_t oldpitch = player->mo->pitch;
2415 player->mo->pitch -= look;
2416 if (look > 0)
2417 { // look up
2418 player->mo->pitch = MAX(player->mo->pitch, player->MinPitch);
2419 if (player->mo->pitch > oldpitch)
2420 {
2421 player->mo->pitch = player->MinPitch;
2422 }
2423 }
2424 else
2425 { // look down
2426 player->mo->pitch = MIN(player->mo->pitch, player->MaxPitch);
2427 if (player->mo->pitch < oldpitch)
2428 {
2429 player->mo->pitch = player->MaxPitch;
2430 }
2431 }
2432 }
2433 }
2434 }
2435 if (player->centering)
2436 {
2437 if (abs(player->mo->pitch) > 2*ANGLE_1)
2438 {
2439 player->mo->pitch = FixedMul(player->mo->pitch, FRACUNIT*2/3);
2440 }
2441 else
2442 {
2443 player->mo->pitch = 0;
2444 player->centering = false;
2445 if (player - players == consoleplayer)
2446 {
2447 LocalViewPitch = 0;
2448 }
2449 }
2450 }
2451
2452 // [RH] Check for fast turn around
2453 if (cmd->ucmd.buttons & BT_TURN180 && !(player->oldbuttons & BT_TURN180))
2454 {
2455 player->turnticks = TURN180_TICKS;
2456 }
2457
2458 // Handle movement
2459 if (player->mo->reactiontime)
2460 { // Player is frozen
2461 player->mo->reactiontime--;
2462 }
2463 else
2464 {
2465 P_MovePlayer (player);
2466
2467 // [RH] check for jump
2468 if (cmd->ucmd.buttons & BT_JUMP)
2469 {
2470 if (player->crouchoffset != 0)
2471 {
2472 // Jumping while crouching will force an un-crouch but not jump
2473 player->crouching = 1;
2474 }
2475 else if (player->mo->waterlevel >= 2)
2476 {
2477 player->mo->velz = FixedMul(4*FRACUNIT, player->mo->Speed);
2478 }
2479 else if (player->mo->flags & MF_NOGRAVITY)
2480 {
2481 player->mo->velz = 3*FRACUNIT;
2482 }
2483 else if (level.IsJumpingAllowed() && player->onground && player->jumpTics == 0)
2484 {
2485 fixed_t jumpvelz = player->mo->JumpZ * 35 / TICRATE;
2486
2487 // [BC] If the player has the high jump power, double his jump velocity.
2488 if ( player->cheats & CF_HIGHJUMP ) jumpvelz *= 2;
2489
2490 player->mo->velz += jumpvelz;
2491 player->mo->flags2 &= ~MF2_ONMOBJ;
2492 player->jumpTics = -1;
2493 if (!(player->cheats & CF_PREDICTING))
2494 S_Sound(player->mo, CHAN_BODY, "*jump", 1, ATTN_NORM);
2495 }
2496 }
2497
2498 if (cmd->ucmd.upmove == -32768)
2499 { // Only land if in the air
2500 if ((player->mo->flags & MF_NOGRAVITY) && player->mo->waterlevel < 2)
2501 {
2502 //player->mo->flags2 &= ~MF2_FLY;
2503 player->mo->flags &= ~MF_NOGRAVITY;
2504 }
2505 }
2506 else if (cmd->ucmd.upmove != 0)
2507 {
2508 // Clamp the speed to some reasonable maximum.
2509 int magnitude = abs (cmd->ucmd.upmove);
2510 if (magnitude > 0x300)
2511 {
2512 cmd->ucmd.upmove = ksgn (cmd->ucmd.upmove) * 0x300;
2513 }
2514 if (player->mo->waterlevel >= 2 || (player->mo->flags2 & MF2_FLY) || (player->cheats & CF_NOCLIP2))
2515 {
2516 player->mo->velz = FixedMul(player->mo->Speed, cmd->ucmd.upmove << 9);
2517 if (player->mo->waterlevel < 2 && !(player->mo->flags & MF_NOGRAVITY))
2518 {
2519 player->mo->flags2 |= MF2_FLY;
2520 player->mo->flags |= MF_NOGRAVITY;
2521 if ((player->mo->velz <= -39 * FRACUNIT) && !(player->cheats & CF_PREDICTING))
2522 { // Stop falling scream
2523 S_StopSound (player->mo, CHAN_VOICE);
2524 }
2525 }
2526 }
2527 else if (cmd->ucmd.upmove > 0 && !(player->cheats & CF_PREDICTING))
2528 {
2529 AInventory *fly = player->mo->FindInventory (NAME_ArtiFly);
2530 if (fly != NULL)
2531 {
2532 player->mo->UseInventory (fly);
2533 }
2534 }
2535 }
2536 }
2537
2538 P_CalcHeight (player);
2539
2540 if (!(player->cheats & CF_PREDICTING))
2541 {
2542 P_PlayerOnSpecial3DFloor (player);
2543 P_PlayerInSpecialSector (player);
2544
2545 if (player->mo->Z() <= player->mo->Sector->floorplane.ZatPoint(player->mo) ||
2546 player->mo->waterlevel)
2547 {
2548 // Player must be touching the floor
2549 P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo));
2550 }
2551 if (player->mo->velz <= -player->mo->FallingScreamMinSpeed &&
2552 player->mo->velz >= -player->mo->FallingScreamMaxSpeed && !player->morphTics &&
2553 player->mo->waterlevel == 0)
2554 {
2555 int id = S_FindSkinnedSound (player->mo, "*falling");
2556 if (id != 0 && !S_IsActorPlayingSomething (player->mo, CHAN_VOICE, id))
2557 {
2558 S_Sound (player->mo, CHAN_VOICE, id, 1, ATTN_NORM);
2559 }
2560 }
2561 // check for use
2562 if (cmd->ucmd.buttons & BT_USE)
2563 {
2564 if (!player->usedown)
2565 {
2566 player->usedown = true;
2567 if (!P_TalkFacing(player->mo))
2568 {
2569 P_UseLines(player);
2570 }
2571 }
2572 }
2573 else
2574 {
2575 player->usedown = false;
2576 }
2577 // Morph counter
2578 if (player->morphTics)
2579 {
2580 if (player->chickenPeck)
2581 { // Chicken attack counter
2582 player->chickenPeck -= 3;
2583 }
2584 if (!--player->morphTics)
2585 { // Attempt to undo the chicken/pig
2586 P_UndoPlayerMorph (player, player, MORPH_UNDOBYTIMEOUT);
2587 }
2588 }
2589 // Cycle psprites
2590 P_MovePsprites (player);
2591
2592 // Other Counters
2593 if (player->damagecount)
2594 player->damagecount--;
2595
2596 if (player->bonuscount)
2597 player->bonuscount--;
2598
2599 if (player->hazardcount)
2600 {
2601 player->hazardcount--;
2602 if (!(level.time % player->hazardinterval) && player->hazardcount > 16*TICRATE)
2603 P_DamageMobj (player->mo, NULL, NULL, 5, player->hazardtype);
2604 }
2605
2606 if (player->poisoncount && !(level.time & 15))
2607 {
2608 player->poisoncount -= 5;
2609 if (player->poisoncount < 0)
2610 {
2611 player->poisoncount = 0;
2612 }
2613 P_PoisonDamage (player, player->poisoner, 1, true);
2614 }
2615
2616 // Apply degeneration.
2617 if (dmflags2 & DF2_YES_DEGENERATION)
2618 {
2619 if ((level.time % TICRATE) == 0 && player->health > deh.MaxHealth)
2620 {
2621 if (player->health - 5 < deh.MaxHealth)
2622 player->health = deh.MaxHealth;
2623 else
2624 player->health--;
2625
2626 player->mo->health = player->health;
2627 }
2628 }
2629
2630 // Handle air supply
2631 //if (level.airsupply > 0)
2632 {
2633 if (player->mo->waterlevel < 3 ||
2634 (player->mo->flags2 & MF2_INVULNERABLE) ||
2635 (player->cheats & (CF_GODMODE | CF_NOCLIP2)) ||
2636 (player->cheats & CF_GODMODE2))
2637 {
2638 player->mo->ResetAirSupply ();
2639 }
2640 else if (player->air_finished <= level.time && !(level.time & 31))
2641 {
2642 P_DamageMobj (player->mo, NULL, NULL, 2 + ((level.time-player->air_finished)/TICRATE), NAME_Drowning);
2643 }
2644 }
2645 }
2646 }
2647
P_PredictionLerpReset()2648 void P_PredictionLerpReset()
2649 {
2650 PredictionLerptics = PredictionLast.gametic = PredictionLerpFrom.gametic = PredictionLerpResult.gametic = 0;
2651 }
2652
P_LerpCalculate(PredictPos from,PredictPos to,PredictPos & result,float scale)2653 bool P_LerpCalculate(PredictPos from, PredictPos to, PredictPos &result, float scale)
2654 {
2655 FVector3 vecFrom(FIXED2DBL(from.x), FIXED2DBL(from.y), FIXED2DBL(from.z));
2656 FVector3 vecTo(FIXED2DBL(to.x), FIXED2DBL(to.y), FIXED2DBL(to.z));
2657 FVector3 vecResult;
2658 vecResult = vecTo - vecFrom;
2659 vecResult *= scale;
2660 vecResult = vecResult + vecFrom;
2661 FVector3 delta = vecResult - vecTo;
2662
2663 result.x = FLOAT2FIXED(vecResult.X);
2664 result.y = FLOAT2FIXED(vecResult.Y);
2665 result.z = FLOAT2FIXED(vecResult.Z);
2666
2667 // As a fail safe, assume extrapolation is the threshold.
2668 return (delta.LengthSquared() > cl_predict_lerpthreshold && scale <= 1.00f);
2669 }
2670
P_PredictPlayer(player_t * player)2671 void P_PredictPlayer (player_t *player)
2672 {
2673 int maxtic;
2674
2675 if (cl_noprediction ||
2676 singletics ||
2677 demoplayback ||
2678 player->mo == NULL ||
2679 player != &players[consoleplayer] ||
2680 player->playerstate != PST_LIVE ||
2681 !netgame ||
2682 /*player->morphTics ||*/
2683 (player->cheats & CF_PREDICTING))
2684 {
2685 return;
2686 }
2687
2688 maxtic = maketic;
2689
2690 if (gametic == maxtic)
2691 {
2692 return;
2693 }
2694
2695 // Save original values for restoration later
2696 PredictionPlayerBackup = *player;
2697
2698 APlayerPawn *act = player->mo;
2699 memcpy(PredictionActorBackup, &act->snext, sizeof(APlayerPawn) - ((BYTE *)&act->snext - (BYTE *)act));
2700
2701 act->flags &= ~MF_PICKUP;
2702 act->flags2 &= ~MF2_PUSHWALL;
2703 player->cheats |= CF_PREDICTING;
2704
2705 // The ordering of the touching_sectorlist needs to remain unchanged
2706 // Also store a copy of all previous sector_thinglist nodes
2707 msecnode_t *mnode = act->touching_sectorlist;
2708 msecnode_t *snode;
2709 PredictionSector_sprev_Backup.Clear();
2710 PredictionTouchingSectorsBackup.Clear ();
2711
2712 while (mnode != NULL)
2713 {
2714 PredictionTouchingSectorsBackup.Push (mnode->m_sector);
2715
2716 for (snode = mnode->m_sector->touching_thinglist; snode; snode = snode->m_snext)
2717 {
2718 if (snode->m_thing == act)
2719 {
2720 PredictionSector_sprev_Backup.Push(snode->m_sprev);
2721 break;
2722 }
2723 }
2724
2725 mnode = mnode->m_tnext;
2726 }
2727
2728 // Keep an ordered list off all actors in the linked sector.
2729 PredictionSectorListBackup.Clear();
2730 if (!(act->flags & MF_NOSECTOR))
2731 {
2732 AActor *link = act->Sector->thinglist;
2733
2734 while (link != NULL)
2735 {
2736 PredictionSectorListBackup.Push(link);
2737 link = link->snext;
2738 }
2739 }
2740
2741 // Blockmap ordering also needs to stay the same, so unlink the block nodes
2742 // without releasing them. (They will be used again in P_UnpredictPlayer).
2743 FBlockNode *block = act->BlockNode;
2744
2745 while (block != NULL)
2746 {
2747 if (block->NextActor != NULL)
2748 {
2749 block->NextActor->PrevActor = block->PrevActor;
2750 }
2751 *(block->PrevActor) = block->NextActor;
2752 block = block->NextBlock;
2753 }
2754 act->BlockNode = NULL;
2755
2756 // Values too small to be usable for lerping can be considered "off".
2757 bool CanLerp = (!(cl_predict_lerpscale < 0.01f) && (ticdup == 1)), DoLerp = false, NoInterpolateOld = R_GetViewInterpolationStatus();
2758 for (int i = gametic; i < maxtic; ++i)
2759 {
2760 if (!NoInterpolateOld)
2761 R_RebuildViewInterpolation(player);
2762
2763 player->cmd = localcmds[i % LOCALCMDTICS];
2764 P_PlayerThink (player);
2765 player->mo->Tick ();
2766
2767 if (CanLerp && PredictionLast.gametic > 0 && i == PredictionLast.gametic && !NoInterpolateOld)
2768 {
2769 // Z is not compared as lifts will alter this with no apparent change
2770 // Make lerping less picky by only testing whole units
2771 DoLerp = ((PredictionLast.x >> 16) != (player->mo->X() >> 16) ||
2772 (PredictionLast.y >> 16) != (player->mo->Y() >> 16));
2773
2774 // Aditional Debug information
2775 if (developer && DoLerp)
2776 {
2777 DPrintf("Lerp! Ltic (%d) && Ptic (%d) | Lx (%d) && Px (%d) | Ly (%d) && Py (%d)\n",
2778 PredictionLast.gametic, i,
2779 (PredictionLast.x >> 16), (player->mo->X() >> 16),
2780 (PredictionLast.y >> 16), (player->mo->Y() >> 16));
2781 }
2782 }
2783 }
2784
2785 if (CanLerp)
2786 {
2787 if (NoInterpolateOld)
2788 P_PredictionLerpReset();
2789
2790 else if (DoLerp)
2791 {
2792 // If lerping is already in effect, use the previous camera postion so the view doesn't suddenly snap
2793 PredictionLerpFrom = (PredictionLerptics == 0) ? PredictionLast : PredictionLerpResult;
2794 PredictionLerptics = 1;
2795 }
2796
2797 PredictionLast.gametic = maxtic - 1;
2798 PredictionLast.x = player->mo->X();
2799 PredictionLast.y = player->mo->Y();
2800 PredictionLast.z = player->mo->Z();
2801
2802 if (PredictionLerptics > 0)
2803 {
2804 if (PredictionLerpFrom.gametic > 0 &&
2805 P_LerpCalculate(PredictionLerpFrom, PredictionLast, PredictionLerpResult, (float)PredictionLerptics * cl_predict_lerpscale))
2806 {
2807 PredictionLerptics++;
2808 player->mo->SetXYZ(PredictionLerpResult.x, PredictionLerpResult.y, PredictionLerpResult.z);
2809 }
2810 else
2811 {
2812 PredictionLerptics = 0;
2813 }
2814 }
2815 }
2816 }
2817
2818 extern msecnode_t *P_AddSecnode (sector_t *s, AActor *thing, msecnode_t *nextnode);
2819
P_UnPredictPlayer()2820 void P_UnPredictPlayer ()
2821 {
2822 player_t *player = &players[consoleplayer];
2823
2824 if (player->cheats & CF_PREDICTING)
2825 {
2826 unsigned int i;
2827 APlayerPawn *act = player->mo;
2828 AActor *savedcamera = player->camera;
2829
2830 TObjPtr<AInventory> InvSel = act->InvSel;
2831 int inventorytics = player->inventorytics;
2832
2833 *player = PredictionPlayerBackup;
2834
2835 // Restore the camera instead of using the backup's copy, because spynext/prev
2836 // could cause it to change during prediction.
2837 player->camera = savedcamera;
2838
2839 act->UnlinkFromWorld();
2840 memcpy(&act->snext, PredictionActorBackup, sizeof(APlayerPawn) - ((BYTE *)&act->snext - (BYTE *)act));
2841
2842 // The blockmap ordering needs to remain unchanged, too.
2843 // Restore sector links and refrences.
2844 // [ED850] This is somewhat of a duplicate of LinkToWorld(), but we need to keep every thing the same,
2845 // otherwise we end up fixing bugs in blockmap logic (i.e undefined behaviour with polyobject collisions),
2846 // which we really don't want to do here.
2847 if (!(act->flags & MF_NOSECTOR))
2848 {
2849 sector_t *sec = act->Sector;
2850 AActor *me, *next;
2851 AActor **link;// , **prev;
2852
2853 // The thinglist is just a pointer chain. We are restoring the exact same things, so we can NULL the head safely
2854 sec->thinglist = NULL;
2855
2856 for (i = PredictionSectorListBackup.Size(); i-- > 0;)
2857 {
2858 me = PredictionSectorListBackup[i];
2859 link = &sec->thinglist;
2860 next = *link;
2861 if ((me->snext = next))
2862 next->sprev = &me->snext;
2863 me->sprev = link;
2864 *link = me;
2865 }
2866
2867 // Destroy old refrences
2868 msecnode_t *node = sector_list;
2869 while (node)
2870 {
2871 node->m_thing = NULL;
2872 node = node->m_tnext;
2873 }
2874
2875 // Make the sector_list match the player's touching_sectorlist before it got predicted.
2876 P_DelSeclist(sector_list);
2877 sector_list = NULL;
2878 for (i = PredictionTouchingSectorsBackup.Size(); i-- > 0;)
2879 {
2880 sector_list = P_AddSecnode(PredictionTouchingSectorsBackup[i], act, sector_list);
2881 }
2882 act->touching_sectorlist = sector_list; // Attach to thing
2883 sector_list = NULL; // clear for next time
2884
2885 node = sector_list;
2886 while (node)
2887 {
2888 if (node->m_thing == NULL)
2889 {
2890 if (node == sector_list)
2891 sector_list = node->m_tnext;
2892 node = P_DelSecnode(node);
2893 }
2894 else
2895 {
2896 node = node->m_tnext;
2897 }
2898 }
2899
2900 msecnode_t *snode;
2901
2902 // Restore sector thinglist order
2903 for (i = PredictionTouchingSectorsBackup.Size(); i-- > 0;)
2904 {
2905 // If we were already the head node, then nothing needs to change
2906 if (PredictionSector_sprev_Backup[i] == NULL)
2907 continue;
2908
2909 for (snode = PredictionTouchingSectorsBackup[i]->touching_thinglist; snode; snode = snode->m_snext)
2910 {
2911 if (snode->m_thing == act)
2912 {
2913 if (snode->m_sprev)
2914 snode->m_sprev->m_snext = snode->m_snext;
2915 else
2916 snode->m_sector->touching_thinglist = snode->m_snext;
2917 if (snode->m_snext)
2918 snode->m_snext->m_sprev = snode->m_sprev;
2919
2920 snode->m_sprev = PredictionSector_sprev_Backup[i];
2921
2922 // At the moment, we don't exist in the list anymore, but we do know what our previous node is, so we set its current m_snext->m_sprev to us.
2923 if (snode->m_sprev->m_snext)
2924 snode->m_sprev->m_snext->m_sprev = snode;
2925 snode->m_snext = snode->m_sprev->m_snext;
2926 snode->m_sprev->m_snext = snode;
2927 break;
2928 }
2929 }
2930 }
2931 }
2932
2933 // Now fix the pointers in the blocknode chain
2934 FBlockNode *block = act->BlockNode;
2935
2936 while (block != NULL)
2937 {
2938 *(block->PrevActor) = block;
2939 if (block->NextActor != NULL)
2940 {
2941 block->NextActor->PrevActor = &block->NextActor;
2942 }
2943 block = block->NextBlock;
2944 }
2945
2946 act->InvSel = InvSel;
2947 player->inventorytics = inventorytics;
2948 }
2949 }
2950
Serialize(FArchive & arc)2951 void player_t::Serialize (FArchive &arc)
2952 {
2953 int i;
2954 FString skinname;
2955
2956 arc << cls
2957 << mo
2958 << camera
2959 << playerstate
2960 << cmd;
2961 if (arc.IsLoading())
2962 {
2963 ReadUserInfo(arc, userinfo, skinname);
2964 }
2965 else
2966 {
2967 WriteUserInfo(arc, userinfo);
2968 }
2969 arc << DesiredFOV << FOV
2970 << viewz
2971 << viewheight
2972 << deltaviewheight
2973 << bob
2974 << velx
2975 << vely
2976 << centering
2977 << health
2978 << inventorytics;
2979 if (SaveVersion < 4513)
2980 {
2981 bool backpack;
2982 arc << backpack;
2983 }
2984 arc << fragcount
2985 << spreecount
2986 << multicount
2987 << lastkilltime
2988 << ReadyWeapon << PendingWeapon
2989 << cheats
2990 << refire
2991 << inconsistant
2992 << killcount
2993 << itemcount
2994 << secretcount
2995 << damagecount
2996 << bonuscount
2997 << hazardcount
2998 << poisoncount
2999 << poisoner
3000 << attacker
3001 << extralight
3002 << fixedcolormap << fixedlightlevel
3003 << morphTics
3004 << MorphedPlayerClass
3005 << MorphStyle
3006 << MorphExitFlash
3007 << PremorphWeapon
3008 << chickenPeck
3009 << jumpTics
3010 << respawn_time
3011 << air_finished
3012 << turnticks
3013 << oldbuttons;
3014 if (SaveVersion >= 4929)
3015 {
3016 arc << hazardtype
3017 << hazardinterval;
3018 }
3019 bool IsBot = false;
3020 if (SaveVersion >= 4514)
3021 {
3022 arc << Bot;
3023 }
3024 else
3025 {
3026 arc << IsBot;
3027 }
3028 arc << BlendR
3029 << BlendG
3030 << BlendB
3031 << BlendA;
3032 if (SaveVersion < 3427)
3033 {
3034 WORD oldaccuracy, oldstamina;
3035 arc << oldaccuracy << oldstamina;
3036 if (mo != NULL)
3037 {
3038 mo->accuracy = oldaccuracy;
3039 mo->stamina = oldstamina;
3040 }
3041 }
3042 if (SaveVersion < 4041)
3043 {
3044 // Move weapon state flags from cheats and into WeaponState.
3045 WeaponState = ((cheats >> 14) & 1) | ((cheats & (0x37 << 24)) >> (24 - 1));
3046 cheats &= ~((1 << 14) | (0x37 << 24));
3047 }
3048 if (SaveVersion < 4527)
3049 {
3050 BYTE oldWeaponState;
3051 arc << oldWeaponState;
3052 WeaponState = oldWeaponState;
3053 }
3054 else
3055 {
3056 arc << WeaponState;
3057 }
3058 arc << LogText
3059 << ConversationNPC
3060 << ConversationPC
3061 << ConversationNPCAngle
3062 << ConversationFaceTalker;
3063
3064 for (i = 0; i < MAXPLAYERS; i++)
3065 arc << frags[i];
3066 for (i = 0; i < NUMPSPRITES; i++)
3067 arc << psprites[i];
3068
3069 arc << CurrentPlayerClass;
3070
3071 arc << crouchfactor
3072 << crouching
3073 << crouchdir
3074 << crouchviewdelta
3075 << original_cmd
3076 << original_oldbuttons;
3077
3078 if (SaveVersion >= 3475)
3079 {
3080 arc << poisontype << poisonpaintype;
3081 }
3082 else if (poisoner != NULL)
3083 {
3084 poisontype = poisoner->DamageType;
3085 poisonpaintype = poisoner->PainType != NAME_None ? poisoner->PainType : poisoner->DamageType;
3086 }
3087
3088 if (SaveVersion >= 3599)
3089 {
3090 arc << timefreezer;
3091 }
3092 else
3093 {
3094 cheats &= ~(1 << 15); // make sure old CF_TIMEFREEZE bit is cleared
3095 }
3096 if (SaveVersion < 3640)
3097 {
3098 cheats &= ~(1 << 17); // make sure old CF_REGENERATION bit is cleared
3099 }
3100 if (SaveVersion >= 3780)
3101 {
3102 arc << settings_controller;
3103 }
3104 else
3105 {
3106 settings_controller = (this - players == Net_Arbitrator);
3107 }
3108 if (SaveVersion >= 4505)
3109 {
3110 arc << onground;
3111 }
3112 else
3113 {
3114 onground = (mo->Z() <= mo->floorz) || (mo->flags2 & MF2_ONMOBJ) || (mo->BounceFlags & BOUNCE_MBF) || (cheats & CF_NOCLIP2);
3115 }
3116
3117 if (SaveVersion < 4514 && IsBot)
3118 {
3119 Bot = new DBot;
3120
3121 arc << Bot->angle
3122 << Bot->dest
3123 << Bot->prev
3124 << Bot->enemy
3125 << Bot->missile
3126 << Bot->mate
3127 << Bot->last_mate
3128 << Bot->skill
3129 << Bot->t_active
3130 << Bot->t_respawn
3131 << Bot->t_strafe
3132 << Bot->t_react
3133 << Bot->t_fight
3134 << Bot->t_roam
3135 << Bot->t_rocket
3136 << Bot->first_shot
3137 << Bot->sleft
3138 << Bot->allround
3139 << Bot->oldx
3140 << Bot->oldy;
3141 }
3142
3143 if (SaveVersion < 4516 && Bot != NULL)
3144 {
3145 Bot->player = this;
3146 }
3147
3148 if (arc.IsLoading ())
3149 {
3150 // If the player reloaded because they pressed +use after dying, we
3151 // don't want +use to still be down after the game is loaded.
3152 oldbuttons = ~0;
3153 original_oldbuttons = ~0;
3154 }
3155 if (skinname.IsNotEmpty())
3156 {
3157 userinfo.SkinChanged(skinname, CurrentPlayerClass);
3158 }
3159 if (SaveVersion >= 4522)
3160 {
3161 arc << MUSINFOactor << MUSINFOtics;
3162 }
3163 }
3164
3165
GetPlayerColors(FName classname)3166 static FPlayerColorSetMap *GetPlayerColors(FName classname)
3167 {
3168 const PClass *cls = PClass::FindClass(classname);
3169
3170 if (cls != NULL)
3171 {
3172 FActorInfo *inf = cls->ActorInfo;
3173
3174 if (inf != NULL)
3175 {
3176 return inf->ColorSets;
3177 }
3178 }
3179 return NULL;
3180 }
3181
P_GetPlayerColorSet(FName classname,int setnum)3182 FPlayerColorSet *P_GetPlayerColorSet(FName classname, int setnum)
3183 {
3184 FPlayerColorSetMap *map = GetPlayerColors(classname);
3185 if (map == NULL)
3186 {
3187 return NULL;
3188 }
3189 return map->CheckKey(setnum);
3190 }
3191
intcmp(const void * a,const void * b)3192 static int STACK_ARGS intcmp(const void *a, const void *b)
3193 {
3194 return *(const int *)a - *(const int *)b;
3195 }
3196
P_EnumPlayerColorSets(FName classname,TArray<int> * out)3197 void P_EnumPlayerColorSets(FName classname, TArray<int> *out)
3198 {
3199 out->Clear();
3200 FPlayerColorSetMap *map = GetPlayerColors(classname);
3201 if (map != NULL)
3202 {
3203 FPlayerColorSetMap::Iterator it(*map);
3204 FPlayerColorSetMap::Pair *pair;
3205
3206 while (it.NextPair(pair))
3207 {
3208 out->Push(pair->Key);
3209 }
3210 qsort(&(*out)[0], out->Size(), sizeof(int), intcmp);
3211 }
3212 }
3213
P_IsPlayerTotallyFrozen(const player_t * player)3214 bool P_IsPlayerTotallyFrozen(const player_t *player)
3215 {
3216 return
3217 gamestate == GS_TITLELEVEL ||
3218 player->cheats & CF_TOTALLYFROZEN ||
3219 ((level.flags2 & LEVEL2_FROZEN) && player->timefreezer == 0);
3220 }
3221