qzdoom/src/p_user.cpp
Randy Heit 9fdcb553aa - Added some hackery at the start of MouseRead_Win32() that prevents it from
yanking the mouse around if they keys haven't been read yet to combat the
  same situation that causes the keyboard to return DIERR_NOTACQUIRED in
  KeyRead(): The window is sort of in focus and sort of not. User.dll
  considers it to be focused and it's drawn as such, but another focused
  window is on top of it, and DirectInput doesn't see it as focused.
- Fixed: KeyRead() should handle DIERR_NOTACQUIRED errors the same way it
  handles DIERR_INPUTLOST errors. This can happen if our window had the
  focus stolen away from it before we tried to acquire the keyboard in
  DI_Init2(). Strangely, MouseRead_DI() already did this.
- When a stack overflow occurs, report.txt now only includes the first and
  last 16KB of the stack to make it more manageable.
- Limited StreamEditBinary() to the first 64KB of the file to keep it from
  taking too long on large dumps.
- And now I know why gathering crash information in the same process that
  crashed can be bad: Stack overflows. You get one spare page to play with
  when the stack overflows. MiniDumpWriteDump() needs more than that and
  causes an access violation when it runs out of leftover stack, silently
  terminating the application. Windows XP x64 offers SetThreadStackGuarantee()
  to increase this, but that isn't available on anything older, including
  32-bit XP. To get around this, a new thread is created to write the mini
  dump when the stack overflows.
- Changed A_Burnination() to be closer to Strife's.
- Fixed: When playing back demos, DoAddBot() can be called without an
  associated call to SpawnBot(). So if the bot can't spawn, botnum can
  go negative, which will cause problems later in DCajunMaster::Main()
  when it sees that wanted_botnum (0) is higher than botnum (-1).
- Fixed: Stopping demo recording in multiplayer games should not abruptly
  drop the recorder out of the game without notifying the other players.
  In fact, there's no reason why it should drop them out of multiplayer at
  all.
- Fixed: Earthquakes were unreliable in multiplayer games because
  P_PredictPlayer() did not preserve the player's xviewshift.
- Fixed: PlayerIsGone() needs to stop any scripts that belong to the player
  who left, in addition to executing disconnect scripts.
- Fixed: APlayerPawn::AddInventory() should also check for a NULL player->mo
  in case the player left but somebody still has a reference to their actor.
- Fixed: DDrawFB::PaintToWindow() should simulate proper unlocking behavior
  and set Buffer to NULL.
- Improved feedback for network game initialization with the console ticker.
- Moved i_net.cpp and i_net.h out of sdl/ and win32/ and into the main source
  directory. They are identical, so keeping two copies of them is bad.
- Fixed: (At least with Creative's driver's,) EAX settings are global and not
  per-application. So if you play a multiplayer ZDoom game on one computer
  (or even another EAX-using application), ZDoom needs to restore the
  environment when it regains focus.
- Maybe fixed: (See http://forum.zdoom.org/potato.php?t=10689) Apparently,
  PacketGet can receive ECONNRESET from nodes that aren't in the game. It
  should be safe to just ignore these packets.
- Fixed: PlayerIsGone() should set the gone player's camera to NULL in case
  the player who left was player 0. This is because if a remaining player
  receives a "recoverable" error, they will become player 0. Once that happens,
  they game will try to update sounds through their camera and crash in
  FMODSoundRenderer::UpdateListener() because the zones array is now NULL.
  G_NewInit() should also clear all the player structures.


SVN r233 (trunk)
2006-06-30 02:13:26 +00:00

1892 lines
46 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// $Log:$
//
// DESCRIPTION:
// Player related stuff.
// Bobbing POV/weapon, movement.
// Pending weapon.
//
//-----------------------------------------------------------------------------
#include "templates.h"
#include "doomdef.h"
#include "d_event.h"
#include "p_local.h"
#include "doomstat.h"
#include "s_sound.h"
#include "i_system.h"
#include "r_draw.h"
#include "gi.h"
#include "m_random.h"
#include "p_pspr.h"
#include "p_enemy.h"
#include "p_effect.h"
#include "s_sound.h"
#include "a_sharedglobal.h"
#include "a_keys.h"
#include "statnums.h"
#include "v_palette.h"
#include "v_video.h"
#include "w_wad.h"
#include "cmdlib.h"
#include "sbar.h"
#include "f_finale.h"
#include "c_console.h"
#include "doomdef.h"
static FRandom pr_healradius ("HealRadius");
// [RH] # of ticks to complete a turn180
#define TURN180_TICKS ((TICRATE / 4) + 1)
// Variables for prediction
CVAR (Bool, cl_noprediction, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
static player_t PredictionPlayerBackup;
static BYTE PredictionActorBackup[sizeof(AActor)];
static TArray<sector_t *> PredictionTouchingSectorsBackup;
//
// Movement.
//
// 16 pixels of bob
#define MAXBOB 0x100000
BOOL onground;
// The player_s constructor. Since LogText is not a POD, we cannot just
// memset it all to 0.
player_s::player_s()
: mo(0),
playerstate(0),
cls(0),
DesiredFOV(0),
FOV(0),
viewz(0),
viewheight(0),
defaultviewheight(0),
deltaviewheight(0),
bob(0),
momx(0),
momy(0),
centering(0),
turnticks(0),
oldbuttons(0),
attackdown(0),
health(0),
InvFirst(0),
InvSel(0),
inventorytics(0),
CurrentPlayerClass(0),
pieces(0),
backpack(0),
fragcount(0),
lastkilltime(0),
multicount(0),
spreecount(0),
ReadyWeapon(0),
PendingWeapon(0),
cheats(0),
Powers(0),
refire(0),
inconsistant(0),
killcount(0),
itemcount(0),
secretcount(0),
damagecount(0),
bonuscount(0),
hazardcount(0),
poisoncount(0),
poisoner(0),
attacker(0),
extralight(0),
xviewshift(0),
morphTics(0),
PremorphWeapon(0),
chickenPeck(0),
jumpTics(0),
respawn_time(0),
camera(0),
air_finished(0),
accuracy(0),
stamina(0),
savedyaw(0),
savedpitch(0),
angle(0),
dest(0),
prev(0),
enemy(0),
missile(0),
mate(0),
last_mate(0),
t_active(0),
t_respawn(0),
t_strafe(0),
t_react(0),
t_fight(0),
t_roam(0),
t_rocket(0),
isbot(0),
first_shot(0),
sleft(0),
allround(0),
oldx(0),
oldy(0),
skin(0),
BlendR(0),
BlendG(0),
BlendB(0),
BlendA(0),
LogText(),
crouching(0),
crouchdir(0),
crouchfactor(0),
crouchoffset(0),
crouchviewdelta(0)
{
memset (&cmd, 0, sizeof(cmd));
memset (&userinfo, 0, sizeof(userinfo));
memset (frags, 0, sizeof(frags));
memset (psprites, 0, sizeof(psprites));
memset (&skill, 0, sizeof(skill));
}
// This function supplements the pointer cleanup in dobject.cpp, because
// player_s is not derived from DObject. (I tried it, and DestroyScan was
// unable to properly determine the player object's type--possibly
// because it gets staticly allocated in an array.)
//
// This function checks all the DObject pointers in a player_s and NULLs any
// that match the pointer passed in. If you add any pointers that point to
// DObject (or a subclass), add them here too.
void player_s::FixPointers (const DObject *old, DObject *rep)
{
APlayerPawn *replacement = static_cast<APlayerPawn *>(rep);
if (mo == old) mo = replacement;
if (poisoner == old) poisoner = replacement;
if (attacker == old) attacker = replacement;
if (camera == old) camera = replacement;
if (dest == old) dest = replacement;
if (prev == old) prev = replacement;
if (enemy == old) enemy = replacement;
if (missile == old) missile = replacement;
if (mate == old) mate = replacement;
if (last_mate == old) last_mate = replacement;
if (ReadyWeapon == old) ReadyWeapon = static_cast<AWeapon *>(rep);
if (PendingWeapon == old) PendingWeapon = static_cast<AWeapon *>(rep);
}
void player_s::SetLogNumber (int num)
{
char lumpname[16];
int lumpnum;
sprintf (lumpname, "LOG%d", num);
lumpnum = Wads.CheckNumForName (lumpname);
if (lumpnum == -1)
{
// Leave the log message alone if this one doesn't exist.
//SetLogText (lumpname);
}
else
{
int length=Wads.LumpLength(lumpnum);
char *data= new char[length+1];
Wads.ReadLump (lumpnum, data);
data[length]=0;
SetLogText (data);
delete[] data;
// Print log text to console
AddToConsole(-1, TEXTCOLOR_GOLD);
AddToConsole(-1, LogText);
AddToConsole(-1, "\n");
}
}
void player_s::SetLogText (const char *text)
{
LogText = text;
}
//===========================================================================
//
// APlayerPawn
//
//===========================================================================
IMPLEMENT_ABSTRACT_ACTOR (APlayerPawn)
IMPLEMENT_ABSTRACT_ACTOR (APlayerChunk)
void APlayerPawn::Serialize (FArchive &arc)
{
Super::Serialize (arc);
arc << JumpZ;
}
void APlayerPawn::BeginPlay ()
{
Super::BeginPlay ();
ChangeStatNum (STAT_PLAYER);
}
//===========================================================================
//
// APlayerPawn :: Tick
//
//===========================================================================
void APlayerPawn::Tick()
{
if (player != NULL && player->mo == this && player->morphTics == 0 && player->playerstate != PST_DEAD)
{
height = FixedMul(GetDefault()->height, player->crouchfactor);
}
else
{
if (health > 0) height = GetDefault()->height;
}
Super::Tick();
}
//===========================================================================
//
// APlayerPawn :: AddInventory
//
//===========================================================================
void APlayerPawn::AddInventory (AInventory *item)
{
// Adding inventory to a voodoo doll should add it to the real player instead.
if (player != NULL && player->mo != this && player->mo != NULL)
{
player->mo->AddInventory (item);
return;
}
Super::AddInventory (item);
// If nothing is selected, select this item.
if (player != NULL && player->InvSel == NULL && (item->ItemFlags & IF_INVBAR))
{
player->InvSel = item;
}
}
//===========================================================================
//
// APlayerPawn :: RemoveInventory
//
//===========================================================================
void APlayerPawn::RemoveInventory (AInventory *item)
{
bool pickWeap = false;
// Since voodoo dolls aren't supposed to have an inventory, there should be
// no need to redirect them to the real player here as there is with AddInventory.
// If the item removed is the selected one, select something else, either the next
// item, if there is one, or the previous item.
if (player != NULL)
{
if (player->InvSel == item)
{
player->InvSel = item->NextInv ();
if (player->InvSel == NULL)
{
player->InvSel = item->PrevInv ();
}
}
if (player->InvFirst == item)
{
player->InvFirst = item->NextInv ();
if (player->InvFirst == NULL)
{
player->InvFirst = item->PrevInv ();
}
}
if (item == player->PendingWeapon)
{
player->PendingWeapon = WP_NOCHANGE;
}
if (item == player->ReadyWeapon)
{
// If the current weapon is removed, pick a new one.
pickWeap = true;
player->ReadyWeapon = NULL;
}
}
Super::RemoveInventory (item);
if (pickWeap && player->mo == this && player->PendingWeapon == WP_NOCHANGE)
{
PickNewWeapon (NULL);
}
}
//===========================================================================
//
// APlayerPawn :: UseInventory
//
//===========================================================================
bool APlayerPawn::UseInventory (AInventory *item)
{
const PClass *itemtype = item->GetClass();
if (player->cheats & CF_TOTALLYFROZEN)
{ // You can't use items if you're totally frozen
return false;
}
if (!Super::UseInventory (item))
{
// Heretic and Hexen advance the inventory cursor if the use failed.
// Should this behavior be retained?
return false;
}
if (player == &players[consoleplayer])
{
S_SoundID (this, CHAN_ITEM, item->UseSound, 1, ATTN_NORM);
StatusBar->FlashItem (itemtype);
}
return true;
}
//===========================================================================
//
// APlayerPawn :: BestWeapon
//
// Returns the best weapon a player has, possibly restricted to a single
// type of ammo.
//
//===========================================================================
AWeapon *APlayerPawn::BestWeapon (const PClass *ammotype)
{
AWeapon *bestMatch = NULL;
int bestOrder = INT_MAX;
AInventory *item;
AWeapon *weap;
bool tomed = NULL != FindInventory (RUNTIME_CLASS(APowerWeaponLevel2));
// Find the best weapon the player has.
for (item = Inventory; item != NULL; item = item->Inventory)
{
if (!item->IsKindOf (RUNTIME_CLASS(AWeapon)))
continue;
weap = static_cast<AWeapon *> (item);
// Don't select it if it's worse than what was already found.
if (weap->SelectionOrder > bestOrder)
continue;
// Don't select it if its primary fire doesn't use the desired ammo.
if (ammotype != NULL &&
(weap->Ammo1 == NULL ||
weap->Ammo1->GetClass() != ammotype))
continue;
// Don't select it if the Tome is active and this isn't the powered-up version.
if (tomed && weap->SisterWeapon != NULL && weap->SisterWeapon->WeaponFlags & WIF_POWERED_UP)
continue;
// Don't select it if it's powered-up and the Tome is not active.
if (!tomed && weap->WeaponFlags & WIF_POWERED_UP)
continue;
// Don't select it if there isn't enough ammo to use its primary fire.
if (!(weap->WeaponFlags & WIF_AMMO_OPTIONAL) &&
!weap->CheckAmmo (AWeapon::PrimaryFire, false))
continue;
// This weapon is usable!
bestOrder = weap->SelectionOrder;
bestMatch = weap;
}
return bestMatch;
}
//===========================================================================
//
// APlayerPawn :: PickNewWeapon
//
// Picks a new weapon for this player. Used mostly for running out of ammo,
// but it also works when an ACS script explicitly takes the ready weapon
// away or the player picks up some ammo they had previously run out of.
//
//===========================================================================
AWeapon *APlayerPawn::PickNewWeapon (const PClass *ammotype)
{
AWeapon *best = BestWeapon (ammotype);
if (best != NULL)
{
player->PendingWeapon = best;
if (player->ReadyWeapon != NULL)
{
P_SetPsprite (player, ps_weapon, player->ReadyWeapon->DownState);
}
else if (player->PendingWeapon != WP_NOCHANGE)
{
P_BringUpWeapon (player);
}
}
return best;
}
//===========================================================================
//
// APlayerPawn :: GiveDeathmatchInventory
//
// Gives players items they should have in addition to their default
// inventory when playing deathmatch. (i.e. all keys)
//
//===========================================================================
void APlayerPawn::GiveDeathmatchInventory()
{
for (unsigned int i = 0; i < PClass::m_Types.Size(); ++i)
{
if (PClass::m_Types[i]->IsDescendantOf (RUNTIME_CLASS(AKey)))
{
AKey *key = (AKey *)GetDefaultByType (PClass::m_Types[i]);
if (key->KeyNumber != 0)
{
key = static_cast<AKey *>(Spawn (PClass::m_Types[i], 0,0,0));
if (!key->TryPickup (this))
{
key->Destroy ();
}
}
}
}
}
//===========================================================================
//
// APlayerPawn :: FilterCoopRespawnInventory
//
// When respawning in coop, this function is called to walk through the dead
// player's inventory and modify it according to the current game flags so
// that it can be transferred to the new live player. This player currently
// has the default inventory, and the oldplayer has the inventory at the time
// of death.
//
//===========================================================================
void APlayerPawn::FilterCoopRespawnInventory (APlayerPawn *oldplayer)
{
AInventory *item, *next, *defitem;
// If we're losing everything, this is really simple.
if (dmflags & DF_COOP_LOSE_INVENTORY)
{
oldplayer->DestroyAllInventory();
return;
}
// If we don't want to lose anything, then we don't need to bother checking
// the old inventory.
if (dmflags & (DF_COOP_LOSE_KEYS |
DF_COOP_LOSE_WEAPONS |
DF_COOP_LOSE_AMMO |
DF_COOP_HALVE_AMMO |
DF_COOP_LOSE_ARMOR |
DF_COOP_LOSE_POWERUPS))
{
// Walk through the old player's inventory and destroy or modify
// according to dmflags.
for (item = oldplayer->Inventory; item != NULL; item = next)
{
next = item->Inventory;
// If this item is part of the default inventory, we never want
// to destroy it, although we might want to copy the default
// inventory amount.
defitem = FindInventory (item->GetClass());
if ((dmflags & DF_COOP_LOSE_KEYS) &&
defitem == NULL &&
item->IsKindOf(RUNTIME_CLASS(AKey)))
{
item->Destroy();
}
else if ((dmflags & DF_COOP_LOSE_WEAPONS) &&
defitem == NULL &&
item->IsKindOf(RUNTIME_CLASS(AWeapon)))
{
item->Destroy();
}
else if ((dmflags & DF_COOP_LOSE_ARMOR) &&
defitem == NULL &&
item->IsKindOf(RUNTIME_CLASS(AArmor)))
{
item->Destroy();
}
else if ((dmflags & DF_COOP_LOSE_POWERUPS) &&
defitem == NULL &&
item->IsKindOf(RUNTIME_CLASS(APowerupGiver)))
{
item->Destroy();
}
else if ((dmflags & (DF_COOP_LOSE_AMMO | DF_COOP_HALVE_AMMO)) &&
item->IsKindOf(RUNTIME_CLASS(AAmmo)))
{
if (defitem == NULL)
{
if (dmflags & DF_COOP_LOSE_AMMO)
{
// Do NOT destroy the ammo, because a weapon might reference it.
item->Amount = 0;
}
else if (item->Amount > 1)
{
item->Amount /= 2;
}
}
else
{
// When set to lose ammo, you get to keep all your starting ammo.
// When set to halve ammo, you won't be left with less than your starting amount.
if (dmflags & DF_COOP_LOSE_AMMO)
{
item->Amount = defitem->Amount;
}
else if (item->Amount > 1)
{
item->Amount = MAX(item->Amount / 2, defitem->Amount);
}
}
}
}
}
// Now destroy the default inventory this player is holding and move
// over the old player's remaining inventory.
DestroyAllInventory();
ObtainInventory (oldplayer);
player->ReadyWeapon = NULL;
PickNewWeapon (NULL);
}
const char *APlayerPawn::GetSoundClass ()
{
if (player != NULL &&
player->userinfo.skin != 0 &&
(unsigned)player->userinfo.skin < numskins)
{
return skins[player->userinfo.skin].name;
}
return "player";
}
void APlayerPawn::PlayIdle ()
{
if (state >= SeeState && state < MissileState)
SetState (SpawnState);
}
void APlayerPawn::PlayRunning ()
{
if (state == SpawnState)
SetState (SeeState);
}
void APlayerPawn::PlayAttacking ()
{
SetState (MissileState);
}
void APlayerPawn::PlayAttacking2 ()
{
SetState (MissileState+1);
}
void APlayerPawn::ThrowPoisonBag ()
{
}
void APlayerPawn::GiveDefaultInventory ()
{
}
void APlayerPawn::MorphPlayerThink ()
{
}
void APlayerPawn::ActivateMorphWeapon ()
{
}
fixed_t APlayerPawn::GetJumpZ ()
{
return 8*FRACUNIT;
}
void APlayerPawn::Die (AActor *source, AActor *inflictor)
{
Super::Die (source, inflictor);
if (player != NULL && player->mo != this)
{ // Make the real player die, too
player->mo->Die (source, inflictor);
}
else
{
if (player != NULL && (dmflags2 & DF2_YES_WEAPONDROP))
{ // Voodoo dolls don't drop weapons
AWeapon *weap = player->ReadyWeapon;
if (weap != NULL)
{
AInventory *item;
if (weap->SpawnState != NULL &&
weap->SpawnState != &AActor::States[AActor::S_NULL])
{
item = P_DropItem (this, weap->GetClass(), -1, 256);
if (item != NULL)
{
if (weap->AmmoGive1 && weap->Ammo1)
{
static_cast<AWeapon *>(item)->AmmoGive1 = weap->Ammo1->Amount;
}
if (weap->AmmoGive2 && weap->Ammo2)
{
static_cast<AWeapon *>(item)->AmmoGive2 = weap->Ammo2->Amount;
}
}
}
else
{
item = P_DropItem (this, weap->AmmoType1, -1, 256);
if (item != NULL)
{
item->Amount = weap->Ammo1->Amount;
}
item = P_DropItem (this, weap->AmmoType2, -1, 256);
if (item != NULL)
{
item->Amount = weap->Ammo2->Amount;
}
}
}
}
if (!multiplayer && (level.flags & LEVEL_DEATHSLIDESHOW))
{
F_StartSlideshow ();
}
}
}
void APlayerPawn::TweakSpeeds (int &forward, int &side)
{
if ((player->Powers & PW_SPEED) && !player->morphTics)
{ // Adjust for a player with a speed artifact
forward = (3*forward)>>1;
side = (3*side)>>1;
}
}
// The standard healing radius behavior is the cleric's
bool APlayerPawn::DoHealingRadius (APlayerPawn *other)
{
if (P_GiveBody (other, 50 + (pr_healradius()%50)))
{
S_Sound (other, CHAN_AUTO, "MysticIncant", 1, ATTN_NORM);
return true;
}
return false;
}
void APlayerPawn::SpecialInvulnerabilityHandling (EInvulState setting, fixed_t * pAlpha)
{
if (setting == INVUL_GetAlpha && pAlpha!=NULL) *pAlpha=FIXED_MAX; // indicates no change
}
//===========================================================================
//
// P_CheckPlayerSprites
//
// Here's the place where crouching sprites are handled
// This must be called each frame before rendering
//
//===========================================================================
void P_CheckPlayerSprites()
{
for(int i=0; i<MAXPLAYERS; i++)
{
player_t * player = &players[i];
APlayerPawn * mo = player->mo;
if (playeringame[i] && mo != NULL)
{
int crouchspriteno;
int defyscale = mo->GetDefault()->yscale;
if (player->userinfo.skin != 0)
{
defyscale = skins[player->userinfo.skin].scale;
}
// FIXME: Handle skins
if (player->crouchfactor < FRACUNIT*3/4)
{
if (mo->sprite == mo->SpawnState->sprite.index || mo->sprite == mo->crouchsprite)
{
crouchspriteno = mo->crouchsprite;
}
else if (mo->sprite == skins[player->userinfo.skin].sprite ||
mo->sprite == skins[player->userinfo.skin].crouchsprite)
{
crouchspriteno = skins[player->userinfo.skin].crouchsprite;
}
else
{
// no sprite -> squash the existing one
crouchspriteno = -1;
}
if (crouchspriteno > 0)
{
mo->sprite = crouchspriteno;
mo->yscale = defyscale;
}
else if (player->playerstate != PST_DEAD)
{
mo->yscale = player->crouchfactor < FRACUNIT*3/4 ? defyscale/2 : defyscale;
}
}
else
{
if (mo->sprite == mo->crouchsprite)
{
mo->sprite = mo->SpawnState->sprite.index;
}
else if (mo->sprite == skins[player->userinfo.skin].crouchsprite)
{
mo->sprite = skins[player->userinfo.skin].sprite;
}
mo->yscale = defyscale;
}
}
}
}
/*
==================
=
= P_Thrust
=
= moves the given origin along a given angle
=
==================
*/
void P_SideThrust (player_t *player, angle_t angle, fixed_t move)
{
angle = (angle - ANGLE_90) >> ANGLETOFINESHIFT;
player->mo->momx += FixedMul (move, finecosine[angle]);
player->mo->momy += FixedMul (move, finesine[angle]);
}
void P_ForwardThrust (player_t *player, angle_t angle, fixed_t move)
{
angle >>= ANGLETOFINESHIFT;
if ((player->mo->waterlevel || (player->mo->flags & MF_NOGRAVITY))
&& player->mo->pitch != 0)
{
angle_t pitch = (angle_t)player->mo->pitch >> ANGLETOFINESHIFT;
fixed_t zpush = FixedMul (move, finesine[pitch]);
if (player->mo->waterlevel && player->mo->waterlevel < 2 && zpush < 0)
zpush = 0;
player->mo->momz -= zpush;
move = FixedMul (move, finecosine[pitch]);
}
player->mo->momx += FixedMul (move, finecosine[angle]);
player->mo->momy += FixedMul (move, finesine[angle]);
}
//
// P_Bob
// Same as P_Thrust, but only affects bobbing.
//
// killough 10/98: We apply thrust separately between the real physical player
// and the part which affects bobbing. This way, bobbing only comes from player
// motion, nothing external, avoiding many problems, e.g. bobbing should not
// occur on conveyors, unless the player walks on one, and bobbing should be
// reduced at a regular rate, even on ice (where the player coasts).
//
void P_Bob (player_t *player, angle_t angle, fixed_t move)
{
angle >>= ANGLETOFINESHIFT;
player->momx += FixedMul(move,finecosine[angle]);
player->momy += FixedMul(move,finesine[angle]);
}
/*
==================
=
= P_CalcHeight
=
=
Calculate the walking / running height adjustment
=
==================
*/
void P_CalcHeight (player_t *player)
{
int angle;
fixed_t bob;
bool still = false;
// Regular movement bobbing
// (needs to be calculated for gun swing even if not on ground)
// OPTIMIZE: tablify angle
// killough 10/98: Make bobbing depend only on player-applied motion.
//
// Note: don't reduce bobbing here if on ice: if you reduce bobbing here,
// it causes bobbing jerkiness when the player moves from ice to non-ice,
// and vice-versa.
if ((player->mo->flags & MF_NOGRAVITY) && !onground)
{
player->bob = FRACUNIT / 2;
}
else
{
player->bob = DMulScale16 (player->momx, player->momx, player->momy, player->momy);
if (player->bob == 0)
{
still = true;
}
else
{
player->bob = FixedMul (player->bob, player->userinfo.MoveBob);
if (player->bob > MAXBOB)
player->bob = MAXBOB;
}
}
fixed_t defaultviewheight = player->defaultviewheight + player->crouchviewdelta;
if (player->cheats & CF_NOMOMENTUM)
{
player->viewz = player->mo->z + defaultviewheight;
if (player->viewz > player->mo->ceilingz-4*FRACUNIT)
player->viewz = player->mo->ceilingz-4*FRACUNIT;
return;
}
if (still)
{
if (player->health > 0)
{
angle = DivScale13 (level.time, 120*TICRATE/35) & FINEMASK;
bob = FixedMul (player->userinfo.StillBob, finesine[angle]);
}
else
{
bob = 0;
}
}
else
{
// DivScale 13 because FINEANGLES == (1<<13)
angle = DivScale13 (level.time, 20*TICRATE/35) & FINEMASK;
bob = FixedMul (player->bob>>(player->mo->waterlevel > 1 ? 2 : 1), finesine[angle]);
}
// move viewheight
if (player->playerstate == PST_LIVE)
{
player->viewheight += player->deltaviewheight;
if (player->viewheight > defaultviewheight)
{
player->viewheight = defaultviewheight;
player->deltaviewheight = 0;
}
else if (player->viewheight < (defaultviewheight>>1))
{
player->viewheight = defaultviewheight>>1;
if (player->deltaviewheight <= 0)
player->deltaviewheight = 1;
}
if (player->deltaviewheight)
{
player->deltaviewheight += FRACUNIT/4;
if (!player->deltaviewheight)
player->deltaviewheight = 1;
}
}
if (player->morphTics)
{
player->viewz = player->mo->z + player->viewheight - (20 * FRACUNIT);
}
else
{
player->viewz = player->mo->z + player->viewheight + bob;
}
if (player->mo->floorclip && player->playerstate != PST_DEAD
&& player->mo->z <= player->mo->floorz)
{
player->viewz -= player->mo->floorclip;
}
if (player->viewz > player->mo->ceilingz - 4*FRACUNIT)
{
player->viewz = player->mo->ceilingz - 4*FRACUNIT;
}
if (player->viewz < player->mo->floorz + 4*FRACUNIT)
{
player->viewz = player->mo->floorz + 4*FRACUNIT;
}
}
/*
=================
=
= P_MovePlayer
=
=================
*/
CUSTOM_CVAR (Float, sv_aircontrol, 0.00390625f, CVAR_SERVERINFO|CVAR_NOSAVE)
{
level.aircontrol = (fixed_t)(self * 65536.f);
G_AirControlChanged ();
}
void P_MovePlayer (player_t *player)
{
ticcmd_t *cmd = &player->cmd;
APlayerPawn *mo = player->mo;
// [RH] 180-degree turn overrides all other yaws
if (player->turnticks)
{
player->turnticks--;
mo->angle += (ANGLE_180 / TURN180_TICKS);
}
else
{
mo->angle += cmd->ucmd.yaw << 16;
}
onground = (mo->z <= mo->floorz) || (mo->flags2 & MF2_ONMOBJ);
// killough 10/98:
//
// We must apply thrust to the player and bobbing separately, to avoid
// anomalies. The thrust applied to bobbing is always the same strength on
// ice, because the player still "works just as hard" to move, while the
// thrust applied to the movement varies with 'movefactor'.
if (cmd->ucmd.forwardmove | cmd->ucmd.sidemove)
{
fixed_t forwardmove, sidemove;
int bobfactor;
int friction, movefactor;
int fm, sm;
movefactor = P_GetMoveFactor (mo, &friction);
bobfactor = friction < ORIG_FRICTION ? movefactor : ORIG_FRICTION_FACTOR;
if (!onground && !(player->mo->flags & MF_NOGRAVITY) && !player->mo->waterlevel)
{
// [RH] allow very limited movement if not on ground.
movefactor = FixedMul (movefactor, level.aircontrol);
bobfactor = FixedMul (bobfactor, level.aircontrol);
}
fm = cmd->ucmd.forwardmove;
sm = cmd->ucmd.sidemove;
mo->TweakSpeeds (fm, sm);
fm = FixedMul (fm, player->mo->Speed);
sm = FixedMul (sm, player->mo->Speed);
// When crouching speed and bobbing have to be reduced
if (player->morphTics==0 && player->crouchfactor != FRACUNIT)
{
fm = FixedMul(fm, player->crouchfactor);
sm = FixedMul(sm, player->crouchfactor);
bobfactor = FixedMul(bobfactor, player->crouchfactor);
}
forwardmove = Scale (fm, movefactor * 35, TICRATE << 8);
sidemove = Scale (sm, movefactor * 35, TICRATE << 8);
if (forwardmove)
{
P_Bob (player, mo->angle, (cmd->ucmd.forwardmove * bobfactor) >> 8);
P_ForwardThrust (player, mo->angle, forwardmove);
}
if (sidemove)
{
P_Bob (player, mo->angle-ANG90, (cmd->ucmd.sidemove * bobfactor) >> 8);
P_SideThrust (player, mo->angle, sidemove);
}
if (debugfile)
{
fprintf (debugfile, "move player for pl %d%c: (%ld,%ld,%ld) (%ld,%ld) %d %d w%d [", player-players,
player->cheats&CF_PREDICTING?'p':' ',
player->mo->x, player->mo->y, player->mo->z,forwardmove, sidemove, movefactor, friction, player->mo->waterlevel);
msecnode_t *n = player->mo->touching_sectorlist;
while (n != NULL)
{
fprintf (debugfile, "%d ", n->m_sector-sectors);
n = n->m_tnext;
}
fprintf (debugfile, "]\n");
}
if (!(player->cheats & CF_PREDICTING))
{
player->mo->PlayRunning ();
}
if (player->cheats & CF_REVERTPLEASE)
{
player->cheats &= ~CF_REVERTPLEASE;
player->camera = player->mo;
}
}
}
//==========================================================================
//
// P_FallingDamage
//
//==========================================================================
void P_FallingDamage (AActor *actor)
{
int damagestyle;
int damage;
fixed_t mom;
damagestyle = ((level.flags >> 15) | (dmflags)) &
(DF_FORCE_FALLINGZD | DF_FORCE_FALLINGHX);
if (damagestyle == 0)
return;
mom = abs (actor->momz);
// Since Hexen falling damage is stronger than ZDoom's, it takes
// precedence. ZDoom falling damage may not be as strong, but it
// gets felt sooner.
switch (damagestyle)
{
case DF_FORCE_FALLINGHX: // Hexen falling damage
if (mom <= 23*FRACUNIT)
{ // Not fast enough to hurt
return;
}
if (mom >= 63*FRACUNIT)
{ // automatic death
damage = 1000000;
}
else
{
mom = FixedMul (mom, 16*FRACUNIT/23);
damage = ((FixedMul (mom, mom) / 10) >> FRACBITS) - 24;
if (actor->momz > -39*FRACUNIT && damage > actor->health
&& actor->health != 1)
{ // No-death threshold
damage = actor->health-1;
}
}
break;
case DF_FORCE_FALLINGZD: // ZDoom falling damage
if (mom <= 19*FRACUNIT)
{ // Not fast enough to hurt
return;
}
if (mom >= 84*FRACUNIT)
{ // automatic death
damage = 1000000;
}
else
{
damage = ((MulScale23 (mom, mom*11) >> FRACBITS) - 30) / 2;
if (damage < 1)
{
damage = 1;
}
}
break;
case DF_FORCE_FALLINGST: // Strife falling damage
if (mom <= 20*FRACUNIT)
{ // Not fast enough to hurt
return;
}
// The minimum amount of damage you take from falling in Strife
// is 52. Ouch!
damage = mom / 25000;
break;
default:
return;
}
if (actor->player)
{
S_Sound (actor, CHAN_AUTO, "*land", 1, ATTN_NORM);
P_NoiseAlert (actor, actor, true);
if (damage == 1000000 && (actor->player->cheats & CF_GODMODE))
{
damage = 999;
}
}
P_DamageMobj (actor, NULL, NULL, damage, MOD_FALLING);
}
//==========================================================================
//
// P_DeathThink
//
//==========================================================================
void P_DeathThink (player_t *player)
{
int dir;
angle_t delta;
int lookDelta;
P_MovePsprites (player);
onground = (player->mo->z <= player->mo->floorz);
if (player->mo->IsKindOf (RUNTIME_CLASS(APlayerChunk)))
{ // Flying bloody skull or flying ice chunk
player->viewheight = 6 * FRACUNIT;
player->deltaviewheight = 0;
if (onground)
{
if (player->mo->pitch > -(int)ANGLE_1*19)
{
lookDelta = (-(int)ANGLE_1*19 - player->mo->pitch) / 8;
player->mo->pitch += lookDelta;
}
}
}
else if (player->mo->DamageType != MOD_ICE)
{ // Fall to ground (if not frozen)
player->deltaviewheight = 0;
if (player->viewheight > 6*FRACUNIT)
{
player->viewheight -= FRACUNIT;
}
if (player->viewheight < 6*FRACUNIT)
{
player->viewheight = 6*FRACUNIT;
}
if (player->mo->pitch < 0)
{
player->mo->pitch += ANGLE_1*3;
}
else if (player->mo->pitch > 0)
{
player->mo->pitch -= ANGLE_1*3;
}
if (abs(player->mo->pitch) < ANGLE_1*3)
{
player->mo->pitch = 0;
}
}
P_CalcHeight (player);
if (player->attacker && player->attacker != player->mo)
{ // Watch killer
dir = P_FaceMobj (player->mo, player->attacker, &delta);
if (delta < ANGLE_1*10)
{ // Looking at killer, so fade damage and poison counters
if (player->damagecount)
{
player->damagecount--;
}
if (player->poisoncount)
{
player->poisoncount--;
}
}
delta /= 8;
if (delta > ANGLE_1*5)
{
delta = ANGLE_1*5;
}
if (dir)
{ // Turn clockwise
player->mo->angle += delta;
}
else
{ // Turn counter clockwise
player->mo->angle -= delta;
}
}
else
{
if (player->damagecount)
{
player->damagecount--;
}
if (player->poisoncount)
{
player->poisoncount--;
}
}
if (player->cmd.ucmd.buttons & BT_USE ||
((deathmatch || alwaysapplydmflags) && (dmflags & DF_FORCE_RESPAWN)))
{
if (level.time >= player->respawn_time || ((player->cmd.ucmd.buttons & BT_USE) && !player->isbot))
{
player->cls = NULL; // Force a new class if the player is using a random class
player->playerstate = multiplayer ? PST_REBORN : PST_ENTER;
if (player->mo->special1 > 2)
{
player->mo->special1 = 0;
}
}
}
}
//----------------------------------------------------------------------------
//
// PROC P_CrouchMove
//
//----------------------------------------------------------------------------
void P_CrouchMove(player_t * player, int direction)
{
fixed_t defaultheight = player->mo->GetDefault()->height;
fixed_t savedheight = player->mo->height;
fixed_t crouchspeed = direction * CROUCHSPEED;
player->crouchdir = (signed char) direction;
player->crouchfactor += crouchspeed;
// check whether the move is ok
player->mo->height = FixedMul(defaultheight, player->crouchfactor);
if (!P_TryMove(player->mo, player->mo->x, player->mo->y, false, false))
{
player->mo->height = savedheight;
if (direction > 0)
{
// doesn't fit
player->crouchfactor -= crouchspeed;
return;
}
}
player->mo->height = savedheight;
player->crouchfactor = clamp<fixed_t>(player->crouchfactor, FRACUNIT/2, FRACUNIT);
player->viewheight = FixedMul(player->defaultviewheight, player->crouchfactor);
player->crouchviewdelta = player->viewheight - player->defaultviewheight;
}
//----------------------------------------------------------------------------
//
// PROC P_PlayerThink
//
//----------------------------------------------------------------------------
void P_PlayerThink (player_t *player)
{
ticcmd_t *cmd;
if (player->mo == NULL)
{
I_Error ("No player %d start\n", player - players + 1);
}
if (debugfile && !(player->cheats & CF_PREDICTING))
{
fprintf (debugfile, "tic %d for pl %d: (%ld, %ld, %ld, %lu) b:%02x p:%d y:%d f:%d s:%d u:%d\n",
gametic, player-players, player->mo->x, player->mo->y, player->mo->z,
player->mo->angle>>ANGLETOFINESHIFT, player->cmd.ucmd.buttons,
player->cmd.ucmd.pitch, player->cmd.ucmd.yaw, player->cmd.ucmd.forwardmove,
player->cmd.ucmd.sidemove, player->cmd.ucmd.upmove);
}
player->xviewshift = 0; // [RH] Make sure view is in right place
// [RH] Zoom the player's FOV
if (player->FOV != player->DesiredFOV)
{
if (fabsf (player->FOV - player->DesiredFOV) < 7.f)
{
player->FOV = player->DesiredFOV;
}
else
{
float zoom = MAX(7.f, fabsf (player->FOV - player->DesiredFOV) * 0.025f);
if (player->FOV > player->DesiredFOV)
{
player->FOV = player->FOV - zoom;
}
else
{
player->FOV = player->FOV + zoom;
}
}
}
if (player->inventorytics)
{
player->inventorytics--;
}
// No-clip cheat
if (player->cheats & CF_NOCLIP)
{
player->mo->flags |= MF_NOCLIP;
}
else
{
player->mo->flags &= ~MF_NOCLIP;
}
cmd = &player->cmd;
if (player->mo->flags & MF_JUSTATTACKED)
{ // Chainsaw/Gauntlets attack auto forward motion
cmd->ucmd.yaw = 0;
cmd->ucmd.forwardmove = 0xc800/2;
cmd->ucmd.sidemove = 0;
player->mo->flags &= ~MF_JUSTATTACKED;
}
// [RH] Being totally frozen zeros out most input parameters.
if (player->cheats & CF_TOTALLYFROZEN || gamestate == GS_TITLELEVEL)
{
if (gamestate == GS_TITLELEVEL)
{
cmd->ucmd.buttons = 0;
}
else
{
cmd->ucmd.buttons &= BT_USE;
}
cmd->ucmd.pitch = 0;
cmd->ucmd.yaw = 0;
cmd->ucmd.roll = 0;
cmd->ucmd.forwardmove = 0;
cmd->ucmd.sidemove = 0;
cmd->ucmd.upmove = 0;
player->turnticks = 0;
}
else if (player->cheats & CF_FROZEN)
{
cmd->ucmd.forwardmove = 0;
cmd->ucmd.sidemove = 0;
cmd->ucmd.upmove = 0;
}
// Handle crouching
if (player->cmd.ucmd.buttons & BT_JUMP) player->cmd.ucmd.buttons &= ~BT_DUCK;
if (player->morphTics == 0 && player->health > 0 && !(dmflags & DF_NO_CROUCH))
{
if (!(player->cheats & CF_TOTALLYFROZEN))
{
int crouchdir = player->crouching;
if (crouchdir==0)
{
crouchdir = (player->cmd.ucmd.buttons & BT_DUCK)? -1 : 1;
}
else if (player->cmd.ucmd.buttons & BT_DUCK)
{
player->crouching=0;
}
if (crouchdir == 1 && player->crouchfactor < FRACUNIT &&
player->mo->z + player->mo->height < player->mo->ceilingz)
{
P_CrouchMove(player, 1);
}
else if (crouchdir == -1 && player->crouchfactor > FRACUNIT/2)
{
P_CrouchMove(player, -1);
}
}
}
else
{
player->Uncrouch();
}
player->crouchoffset = -FixedMul(player->defaultviewheight, (FRACUNIT - player->crouchfactor));
if (player->playerstate == PST_DEAD)
{
player->Uncrouch();
P_DeathThink (player);
return;
}
if (player->jumpTics)
{
player->jumpTics--;
}
if (player->morphTics && !(player->cheats & CF_PREDICTING))
{
player->mo->MorphPlayerThink ();
}
// [RH] Look up/down stuff
if (dmflags & DF_NO_FREELOOK)
{
player->mo->pitch = 0;
}
else
{
int look = cmd->ucmd.pitch << 16;
// The player's view pitch is clamped between -32 and +56 degrees,
// which translates to about half a screen height up and (more than)
// one full screen height down from straight ahead when view panning
// is used.
if (look)
{
if (look == -32768 << 16)
{ // center view
player->mo->pitch = 0;
}
else
{
player->mo->pitch -= look;
if (look > 0)
{ // look up
if (player->mo->pitch < -ANGLE_1*MAX_UP_ANGLE)
player->mo->pitch = -ANGLE_1*MAX_UP_ANGLE;
}
else
{ // look down
if (player->mo->pitch > ANGLE_1*MAX_DN_ANGLE)
player->mo->pitch = ANGLE_1*MAX_DN_ANGLE;
}
}
}
}
// [RH] Check for fast turn around
if (cmd->ucmd.buttons & BT_TURN180 && !(player->oldbuttons & BT_TURN180))
{
player->turnticks = TURN180_TICKS;
}
// Handle movement
if (player->mo->reactiontime)
{ // Player is frozen
player->mo->reactiontime--;
}
else
{
P_MovePlayer (player);
// [RH] check for jump
if (cmd->ucmd.buttons & BT_JUMP)
{
if (player->crouchoffset!=0)
{
// Jumping while crouching will force an un-crouch but not jump
player->crouching = 1;
}
else
if (player->mo->waterlevel >= 2)
{
player->mo->momz = 4*FRACUNIT;
}
else if (player->mo->flags & MF_NOGRAVITY)
{
player->mo->momz = 3*FRACUNIT;
}
else if (!(dmflags & DF_NO_JUMP) && onground && !player->jumpTics)
{
fixed_t JumpZ = (player->mo->JumpZ > 0 ? player->mo->JumpZ : player->mo->GetJumpZ ()); // [GRB]
player->mo->momz += JumpZ*35/TICRATE;
S_Sound (player->mo, CHAN_BODY, "*jump", 1, ATTN_NORM);
player->mo->flags2 &= ~MF2_ONMOBJ;
player->jumpTics = 18*TICRATE/35;
}
}
if (cmd->ucmd.upmove == -32768)
{ // Only land if in the air
if ((player->mo->flags & MF_NOGRAVITY) && player->mo->waterlevel < 2)
{
//player->mo->flags2 &= ~MF2_FLY;
player->mo->flags &= ~MF_NOGRAVITY;
}
}
else if (cmd->ucmd.upmove != 0)
{
// Clamp the speed to some reasonable maximum.
int magnitude = abs (cmd->ucmd.upmove);
if (magnitude > 0x300)
{
cmd->ucmd.upmove = ksgn (cmd->ucmd.upmove) * 0x300;
}
if (player->mo->waterlevel >= 2 || (player->mo->flags2 & MF2_FLY))
{
player->mo->momz = cmd->ucmd.upmove << 9;
if (player->mo->waterlevel < 2 && !(player->mo->flags & MF_NOGRAVITY))
{
player->mo->flags2 |= MF2_FLY;
player->mo->flags |= MF_NOGRAVITY;
if (player->mo->momz <= -39*FRACUNIT)
{ // Stop falling scream
S_StopSound (player->mo, CHAN_VOICE);
}
}
}
else if (cmd->ucmd.upmove > 0 && !(player->cheats & CF_PREDICTING))
{
AInventory *fly = player->mo->FindInventory (PClass::FindClass (NAME_ArtiFly));
if (fly != NULL)
{
player->mo->UseInventory (fly);
}
}
}
}
P_CalcHeight (player);
if (!(player->cheats & CF_PREDICTING))
{
if (player->mo->Sector->special || player->mo->Sector->damage)
{
P_PlayerInSpecialSector (player);
}
P_PlayerOnSpecialFlat (player, P_GetThingFloorType (player->mo));
if (player->mo->momz <= -35*FRACUNIT &&
player->mo->momz >= -40*FRACUNIT && !player->morphTics &&
player->mo->waterlevel == 0)
{
int id = S_FindSkinnedSound (player->mo, "*falling");
if (id != 0 && !S_GetSoundPlayingInfo (player->mo, id))
{
S_SoundID (player->mo, CHAN_VOICE, id, 1, ATTN_NORM);
}
}
// check for use
if ((cmd->ucmd.buttons & BT_USE) && !(player->oldbuttons & BT_USE))
{
P_UseLines (player);
}
// Morph counter
if (player->morphTics)
{
if (player->chickenPeck)
{ // Chicken attack counter
player->chickenPeck -= 3;
}
if (!--player->morphTics)
{ // Attempt to undo the chicken/pig
P_UndoPlayerMorph (player);
}
}
// Cycle psprites
P_MovePsprites (player);
// Other Counters
if (player->damagecount)
player->damagecount--;
if (player->bonuscount)
player->bonuscount--;
if (player->hazardcount)
{
player->hazardcount--;
if (!(level.time & 31) && player->hazardcount > 16*TICRATE)
P_DamageMobj (player->mo, NULL, NULL, 5, MOD_SLIME);
}
if (player->poisoncount && !(level.time & 15))
{
player->poisoncount -= 5;
if (player->poisoncount < 0)
{
player->poisoncount = 0;
}
P_PoisonDamage (player, player->poisoner, 1, true);
}
// Handle air supply
if (level.airsupply>0)
{
if (player->mo->waterlevel < 3 ||
(player->mo->flags2 & MF2_INVULNERABLE) ||
(player->cheats & CF_GODMODE))
{
player->air_finished = level.time + level.airsupply;
}
else if (player->air_finished <= level.time && !(level.time & 31))
{
P_DamageMobj (player->mo, NULL, NULL, 2 + 2*((level.time-player->air_finished)/TICRATE), MOD_WATER);
}
}
}
// Save buttons
player->oldbuttons = cmd->ucmd.buttons;
}
void P_PredictPlayer (player_t *player)
{
int maxtic;
if (cl_noprediction ||
singletics ||
demoplayback ||
player->mo == NULL ||
player != &players[consoleplayer] ||
player->playerstate != PST_LIVE ||
!netgame ||
/*player->morphTics ||*/
(player->cheats & CF_PREDICTING))
{
return;
}
maxtic = maketic;
if (gametic == maxtic)
{
return;
}
// Save original values for restoration later
PredictionPlayerBackup = *player;
AActor *act = player->mo;
memcpy (PredictionActorBackup, &act->x, sizeof(AActor)-((BYTE *)&act->x-(BYTE *)act));
act->flags &= ~MF_PICKUP;
act->flags2 &= ~MF2_PUSHWALL;
player->cheats |= CF_PREDICTING;
// The ordering of the touching_sectorlist needs to remain unchanged
msecnode_t *mnode = act->touching_sectorlist;
PredictionTouchingSectorsBackup.Clear ();
while (mnode != NULL)
{
PredictionTouchingSectorsBackup.Push (mnode->m_sector);
mnode = mnode->m_tnext;
}
// Blockmap ordering also needs to stay the same, so unlink the block nodes
// without releasing them. (They will be used again in P_UnpredictPlayer).
FBlockNode *block = act->BlockNode;
while (block != NULL)
{
if (block->NextActor != NULL)
{
block->NextActor->PrevActor = block->PrevActor;
}
*(block->PrevActor) = block->NextActor;
block = block->NextBlock;
}
act->BlockNode = NULL;
int xviewshift = player->xviewshift;
for (int i = gametic; i < maxtic; ++i)
{
player->cmd = localcmds[i % LOCALCMDTICS];
P_PlayerThink (player);
player->mo->Tick ();
}
player->xviewshift = xviewshift;
}
extern msecnode_t *P_AddSecnode (sector_t *s, AActor *thing, msecnode_t *nextnode);
void P_UnPredictPlayer ()
{
player_t *player = &players[consoleplayer];
if (player->cheats & CF_PREDICTING)
{
AActor *act = player->mo;
*player = PredictionPlayerBackup;
act->UnlinkFromWorld ();
memcpy (&act->x, PredictionActorBackup, sizeof(AActor)-((BYTE *)&act->x-(BYTE *)act));
// Make the sector_list match the player's touching_sectorlist before it got predicted.
P_DelSeclist (sector_list);
sector_list = NULL;
for (unsigned int i = PredictionTouchingSectorsBackup.Size (); i-- > 0; )
{
sector_list = P_AddSecnode (PredictionTouchingSectorsBackup[i], act, sector_list);
}
// The blockmap ordering needs to remain unchanged, too. Right now, act has the right
// pointers, so temporarily set its MF_NOBLOCKMAP flag so that LinkToWorld() does not
// mess with them.
act->flags |= MF_NOBLOCKMAP;
act->LinkToWorld ();
act->flags &= ~MF_NOBLOCKMAP;
// Now fix the pointers in the blocknode chain
FBlockNode *block = act->BlockNode;
while (block != NULL)
{
*(block->PrevActor) = block;
if (block->NextActor != NULL)
{
block->NextActor->PrevActor = &block->NextActor;
}
block = block->NextBlock;
}
}
}
void player_s::Serialize (FArchive &arc)
{
int i;
arc << cls
<< mo
<< camera
<< playerstate
<< cmd
<< userinfo
<< DesiredFOV << FOV
<< viewz
<< viewheight
<< defaultviewheight
<< deltaviewheight
<< bob
<< momx
<< momy
<< centering
<< health
<< inventorytics
<< InvFirst << InvSel
<< pieces
<< backpack
<< fragcount
<< spreecount
<< multicount
<< lastkilltime
<< ReadyWeapon << PendingWeapon
<< cheats
<< refire
<< inconsistant
<< killcount
<< itemcount
<< secretcount
<< damagecount
<< bonuscount
<< hazardcount
<< poisoncount
<< poisoner
<< attacker
<< extralight
<< fixedcolormap
<< xviewshift
<< morphTics
<< PremorphWeapon
<< chickenPeck
<< jumpTics
<< respawn_time
<< air_finished
<< turnticks
<< oldbuttons
<< isbot
<< BlendR
<< BlendG
<< BlendB
<< BlendA
<< accuracy << stamina
<< LogText;
for (i = 0; i < MAXPLAYERS; i++)
arc << frags[i];
for (i = 0; i < NUMPSPRITES; i++)
arc << psprites[i];
arc << CurrentPlayerClass;
arc << crouchfactor
<< crouching
<< crouchdir
<< crouchviewdelta;
if (isbot)
{
arc << angle
<< dest
<< prev
<< enemy
<< missile
<< mate
<< last_mate
<< skill
<< t_active
<< t_respawn
<< t_strafe
<< t_react
<< t_fight
<< t_roam
<< t_rocket
<< first_shot
<< sleft
<< allround
<< oldx
<< oldy;
}
else
{
dest = prev = enemy = missile = mate = last_mate = NULL;
}
if (arc.IsLoading ())
{
// If the player reloaded because they pressed +use after dying, we
// don't want +use to still be down after the game is loaded.
oldbuttons = ~0;
}
}