mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-28 15:02:01 +00:00
1793 lines
48 KiB
C++
1793 lines
48 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// Copyright 1993-1996 id Software
|
|
// Copyright 1994-1996 Raven Software
|
|
// Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
|
|
// Copyright 1999-2016 Randy Heit
|
|
// Copyright 2002-2016 Christoph Oelckers
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// DESCRIPTION:
|
|
// Player related stuff.
|
|
// Bobbing POV/weapon, movement.
|
|
// Pending weapon.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/* For code that originates from ZDoom the following applies:
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#include "templates.h"
|
|
#include "doomdef.h"
|
|
#include "d_event.h"
|
|
#include "p_local.h"
|
|
#include "doomstat.h"
|
|
#include "s_sound.h"
|
|
#include "gi.h"
|
|
#include "m_random.h"
|
|
#include "p_pspr.h"
|
|
#include "p_enemy.h"
|
|
#include "a_sharedglobal.h"
|
|
#include "a_keys.h"
|
|
#include "w_wad.h"
|
|
#include "cmdlib.h"
|
|
#include "sbar.h"
|
|
#include "intermission/intermission.h"
|
|
#include "c_console.h"
|
|
#include "c_dispatch.h"
|
|
#include "d_net.h"
|
|
#include "serializer.h"
|
|
#include "r_renderer.h"
|
|
#include "d_player.h"
|
|
#include "r_utility.h"
|
|
#include "p_blockmap.h"
|
|
#include "a_morph.h"
|
|
#include "p_spec.h"
|
|
#include "vm.h"
|
|
#include "g_levellocals.h"
|
|
#include "actorinlines.h"
|
|
#include "p_acs.h"
|
|
#include "events.h"
|
|
|
|
static FRandom pr_skullpop ("SkullPop");
|
|
|
|
// [SP] Allows respawn in single player
|
|
CVAR(Bool, sv_singleplayerrespawn, false, CVAR_SERVERINFO | CVAR_CHEAT)
|
|
|
|
// Variables for prediction
|
|
CVAR (Bool, cl_noprediction, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
CVAR(Bool, cl_predict_specials, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|
|
|
CUSTOM_CVAR(Float, cl_predict_lerpscale, 0.05f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|
{
|
|
P_PredictionLerpReset();
|
|
}
|
|
CUSTOM_CVAR(Float, cl_predict_lerpthreshold, 2.00f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|
{
|
|
if (self < 0.1f)
|
|
self = 0.1f;
|
|
P_PredictionLerpReset();
|
|
}
|
|
|
|
ColorSetList ColorSets;
|
|
PainFlashList PainFlashes;
|
|
|
|
// [Nash] FOV cvar setting
|
|
CUSTOM_CVAR(Float, fov, 90.f, CVAR_ARCHIVE | CVAR_USERINFO | CVAR_NOINITCALL)
|
|
{
|
|
player_t *p = &players[consoleplayer];
|
|
p->SetFOV(fov);
|
|
}
|
|
|
|
struct PredictPos
|
|
{
|
|
int gametic;
|
|
DVector3 pos;
|
|
DRotator angles;
|
|
} static PredictionLerpFrom, PredictionLerpResult, PredictionLast;
|
|
static int PredictionLerptics;
|
|
|
|
static player_t PredictionPlayerBackup;
|
|
static AActor *PredictionActor;
|
|
static TArray<uint8_t> PredictionActorBackupArray;
|
|
static TArray<AActor *> PredictionSectorListBackup;
|
|
|
|
static TArray<sector_t *> PredictionTouchingSectorsBackup;
|
|
static TArray<msecnode_t *> PredictionTouchingSectors_sprev_Backup;
|
|
|
|
static TArray<sector_t *> PredictionRenderSectorsBackup;
|
|
static TArray<msecnode_t *> PredictionRenderSectors_sprev_Backup;
|
|
|
|
static TArray<sector_t *> PredictionPortalSectorsBackup;
|
|
static TArray<msecnode_t *> PredictionPortalSectors_sprev_Backup;
|
|
|
|
static TArray<FLinePortal *> PredictionPortalLinesBackup;
|
|
static TArray<portnode_t *> PredictionPortalLines_sprev_Backup;
|
|
|
|
// [GRB] Custom player classes
|
|
TArray<FPlayerClass> PlayerClasses;
|
|
|
|
FPlayerClass::FPlayerClass ()
|
|
{
|
|
Type = NULL;
|
|
Flags = 0;
|
|
}
|
|
|
|
FPlayerClass::FPlayerClass (const FPlayerClass &other)
|
|
{
|
|
Type = other.Type;
|
|
Flags = other.Flags;
|
|
Skins = other.Skins;
|
|
}
|
|
|
|
FPlayerClass::~FPlayerClass ()
|
|
{
|
|
}
|
|
|
|
bool FPlayerClass::CheckSkin (int skin)
|
|
{
|
|
for (unsigned int i = 0; i < Skins.Size (); i++)
|
|
{
|
|
if (Skins[i] == skin)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(FPlayerClass, CheckSkin)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(FPlayerClass);
|
|
PARAM_INT(skin);
|
|
ACTION_RETURN_BOOL(self->CheckSkin(skin));
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// GetDisplayName
|
|
//
|
|
//===========================================================================
|
|
|
|
FString GetPrintableDisplayName(PClassActor *cls)
|
|
{
|
|
// Fixme; This needs a decent way to access the string table without creating a mess.
|
|
// [RH] ????
|
|
return cls->GetDisplayName();
|
|
}
|
|
|
|
bool ValidatePlayerClass(PClassActor *ti, const char *name)
|
|
{
|
|
if (ti == NULL)
|
|
{
|
|
Printf("Unknown player class '%s'\n", name);
|
|
return false;
|
|
}
|
|
else if (!ti->IsDescendantOf(NAME_PlayerPawn))
|
|
{
|
|
Printf("Invalid player class '%s'\n", name);
|
|
return false;
|
|
}
|
|
else if (ti->GetDisplayName().IsEmpty())
|
|
{
|
|
Printf ("Missing displayname for player class '%s'\n", name);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void SetupPlayerClasses ()
|
|
{
|
|
FPlayerClass newclass;
|
|
|
|
PlayerClasses.Clear();
|
|
for (unsigned i = 0; i < gameinfo.PlayerClasses.Size(); i++)
|
|
{
|
|
PClassActor *cls = PClass::FindActor(gameinfo.PlayerClasses[i]);
|
|
if (ValidatePlayerClass(cls, gameinfo.PlayerClasses[i]))
|
|
{
|
|
newclass.Flags = 0;
|
|
newclass.Type = cls;
|
|
if ((GetDefaultByType(cls)->flags6 & MF6_NOMENU))
|
|
{
|
|
newclass.Flags |= PCF_NOMENU;
|
|
}
|
|
PlayerClasses.Push(newclass);
|
|
}
|
|
}
|
|
}
|
|
|
|
CCMD (clearplayerclasses)
|
|
{
|
|
if (ParsingKeyConf)
|
|
{
|
|
PlayerClasses.Clear ();
|
|
}
|
|
}
|
|
|
|
CCMD (addplayerclass)
|
|
{
|
|
if (ParsingKeyConf && argv.argc () > 1)
|
|
{
|
|
PClassActor *ti = PClass::FindActor(argv[1]);
|
|
|
|
if (ValidatePlayerClass(ti, argv[1]))
|
|
{
|
|
FPlayerClass newclass;
|
|
|
|
newclass.Type = ti;
|
|
newclass.Flags = 0;
|
|
|
|
int arg = 2;
|
|
while (arg < argv.argc())
|
|
{
|
|
if (!stricmp (argv[arg], "nomenu"))
|
|
{
|
|
newclass.Flags |= PCF_NOMENU;
|
|
}
|
|
else
|
|
{
|
|
Printf ("Unknown flag '%s' for player class '%s'\n", argv[arg], argv[1]);
|
|
}
|
|
|
|
arg++;
|
|
}
|
|
PlayerClasses.Push (newclass);
|
|
}
|
|
}
|
|
}
|
|
|
|
CCMD (playerclasses)
|
|
{
|
|
for (unsigned int i = 0; i < PlayerClasses.Size (); i++)
|
|
{
|
|
Printf ("%3d: Class = %s, Name = %s\n", i,
|
|
PlayerClasses[i].Type->TypeName.GetChars(),
|
|
PlayerClasses[i].Type->GetDisplayName().GetChars());
|
|
}
|
|
}
|
|
|
|
//
|
|
// Movement.
|
|
//
|
|
|
|
player_t::~player_t()
|
|
{
|
|
DestroyPSprites();
|
|
}
|
|
|
|
player_t &player_t::operator=(const player_t &p)
|
|
{
|
|
mo = p.mo;
|
|
playerstate = p.playerstate;
|
|
cmd = p.cmd;
|
|
original_cmd = p.original_cmd;
|
|
original_oldbuttons = p.original_oldbuttons;
|
|
// Intentionally not copying userinfo!
|
|
cls = p.cls;
|
|
DesiredFOV = p.DesiredFOV;
|
|
FOV = p.FOV;
|
|
viewz = p.viewz;
|
|
viewheight = p.viewheight;
|
|
deltaviewheight = p.deltaviewheight;
|
|
bob = p.bob;
|
|
Vel = p.Vel;
|
|
centering = p.centering;
|
|
turnticks = p.turnticks;
|
|
attackdown = p.attackdown;
|
|
usedown = p.usedown;
|
|
oldbuttons = p.oldbuttons;
|
|
health = p.health;
|
|
inventorytics = p.inventorytics;
|
|
CurrentPlayerClass = p.CurrentPlayerClass;
|
|
memcpy(frags, &p.frags, sizeof(frags));
|
|
fragcount = p.fragcount;
|
|
lastkilltime = p.lastkilltime;
|
|
multicount = p.multicount;
|
|
spreecount = p.spreecount;
|
|
WeaponState = p.WeaponState;
|
|
ReadyWeapon = p.ReadyWeapon;
|
|
PendingWeapon = p.PendingWeapon;
|
|
cheats = p.cheats;
|
|
timefreezer = p.timefreezer;
|
|
refire = p.refire;
|
|
inconsistant = p.inconsistant;
|
|
waiting = p.waiting;
|
|
killcount = p.killcount;
|
|
itemcount = p.itemcount;
|
|
secretcount = p.secretcount;
|
|
damagecount = p.damagecount;
|
|
bonuscount = p.bonuscount;
|
|
hazardcount = p.hazardcount;
|
|
hazardtype = p.hazardtype;
|
|
hazardinterval = p.hazardinterval;
|
|
poisoncount = p.poisoncount;
|
|
poisontype = p.poisontype;
|
|
poisonpaintype = p.poisonpaintype;
|
|
poisoner = p.poisoner;
|
|
attacker = p.attacker;
|
|
extralight = p.extralight;
|
|
fixedcolormap = p.fixedcolormap;
|
|
fixedlightlevel = p.fixedlightlevel;
|
|
psprites = p.psprites;
|
|
morphTics = p.morphTics;
|
|
MorphedPlayerClass = p.MorphedPlayerClass;
|
|
MorphStyle = p.MorphStyle;
|
|
MorphExitFlash = p.MorphExitFlash;
|
|
PremorphWeapon = p.PremorphWeapon;
|
|
chickenPeck = p.chickenPeck;
|
|
jumpTics = p.jumpTics;
|
|
onground = p.onground;
|
|
respawn_time = p.respawn_time;
|
|
camera = p.camera;
|
|
air_finished = p.air_finished;
|
|
LastDamageType = p.LastDamageType;
|
|
Bot = p.Bot;
|
|
settings_controller = p.settings_controller;
|
|
BlendR = p.BlendR;
|
|
BlendG = p.BlendG;
|
|
BlendB = p.BlendB;
|
|
BlendA = p.BlendA;
|
|
LogText = p.LogText;
|
|
MinPitch = p.MinPitch;
|
|
MaxPitch = p.MaxPitch;
|
|
crouching = p.crouching;
|
|
crouchdir = p.crouchdir;
|
|
crouchfactor = p.crouchfactor;
|
|
crouchoffset = p.crouchoffset;
|
|
crouchviewdelta = p.crouchviewdelta;
|
|
weapons = p.weapons;
|
|
ConversationNPC = p.ConversationNPC;
|
|
ConversationPC = p.ConversationPC;
|
|
ConversationNPCAngle = p.ConversationNPCAngle;
|
|
ConversationFaceTalker = p.ConversationFaceTalker;
|
|
MUSINFOactor = p.MUSINFOactor;
|
|
MUSINFOtics = p.MUSINFOtics;
|
|
return *this;
|
|
}
|
|
|
|
size_t player_t::PropagateMark()
|
|
{
|
|
GC::Mark(mo);
|
|
GC::Mark(poisoner);
|
|
GC::Mark(attacker);
|
|
GC::Mark(camera);
|
|
GC::Mark(Bot);
|
|
GC::Mark(ReadyWeapon);
|
|
GC::Mark(ConversationNPC);
|
|
GC::Mark(ConversationPC);
|
|
GC::Mark(MUSINFOactor);
|
|
GC::Mark(PremorphWeapon);
|
|
GC::Mark(psprites);
|
|
if (PendingWeapon != WP_NOCHANGE)
|
|
{
|
|
GC::Mark(PendingWeapon);
|
|
}
|
|
return sizeof(*this);
|
|
}
|
|
|
|
void player_t::SetLogNumber (int num)
|
|
{
|
|
char lumpname[16];
|
|
int lumpnum;
|
|
|
|
mysnprintf (lumpname, countof(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
|
|
{
|
|
auto lump = Wads.ReadLump(lumpnum);
|
|
SetLogText (lump.GetString());
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, SetLogNumber)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
PARAM_INT(log);
|
|
self->SetLogNumber(log);
|
|
return 0;
|
|
}
|
|
|
|
void player_t::SetLogText (const char *text)
|
|
{
|
|
LogText = text;
|
|
|
|
if (mo->CheckLocalView(consoleplayer))
|
|
{
|
|
// Print log text to console
|
|
AddToConsole(-1, TEXTCOLOR_GOLD);
|
|
AddToConsole(-1, LogText);
|
|
AddToConsole(-1, "\n");
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, SetLogText)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
PARAM_STRING(log);
|
|
self->SetLogText(log);
|
|
return 0;
|
|
}
|
|
|
|
int player_t::GetSpawnClass()
|
|
{
|
|
const PClass * type = PlayerClasses[CurrentPlayerClass].Type;
|
|
return GetDefaultByType(type)->IntVar(NAME_SpawnMask);
|
|
}
|
|
|
|
// [Nash] Set FOV
|
|
void player_t::SetFOV(float fov)
|
|
{
|
|
player_t *p = &players[consoleplayer];
|
|
if (p != nullptr && p->mo != nullptr)
|
|
{
|
|
if (dmflags & DF_NO_FOV)
|
|
{
|
|
if (consoleplayer == Net_Arbitrator)
|
|
{
|
|
Net_WriteByte(DEM_MYFOV);
|
|
}
|
|
else
|
|
{
|
|
Printf("A setting controller has disabled FOV changes.\n");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Net_WriteByte(DEM_MYFOV);
|
|
}
|
|
Net_WriteFloat(clamp<float>(fov, 5.f, 179.f));
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, SetFOV)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
PARAM_FLOAT(fov);
|
|
self->SetFOV((float)fov);
|
|
return 0;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// EnumColorsets
|
|
//
|
|
// Only used by the menu so it doesn't really matter that it's a bit
|
|
// inefficient.
|
|
//
|
|
//===========================================================================
|
|
|
|
static int intcmp(const void *a, const void *b)
|
|
{
|
|
return *(const int *)a - *(const int *)b;
|
|
}
|
|
|
|
void EnumColorSets(PClassActor *cls, TArray<int> *out)
|
|
{
|
|
TArray<int> deleteds;
|
|
|
|
out->Clear();
|
|
for (int i = ColorSets.Size() - 1; i >= 0; i--)
|
|
{
|
|
if (std::get<0>(ColorSets[i])->IsAncestorOf(cls))
|
|
{
|
|
int v = std::get<1>(ColorSets[i]);
|
|
if (out->Find(v) == out->Size() && deleteds.Find(v) == deleteds.Size())
|
|
{
|
|
if (std::get<2>(ColorSets[i]).Name == NAME_None) deleteds.Push(v);
|
|
else out->Push(v);
|
|
}
|
|
}
|
|
}
|
|
qsort(&(*out)[0], out->Size(), sizeof(int), intcmp);
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(FPlayerClass, EnumColorSets)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(FPlayerClass);
|
|
PARAM_POINTER(out, TArray<int>);
|
|
EnumColorSets(self->Type, out);
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
FPlayerColorSet *GetColorSet(PClassActor *cls, int setnum)
|
|
{
|
|
for (int i = ColorSets.Size() - 1; i >= 0; i--)
|
|
{
|
|
if (std::get<1>(ColorSets[i]) == setnum &&
|
|
std::get<0>(ColorSets[i])->IsAncestorOf(cls))
|
|
{
|
|
auto c = &std::get<2>(ColorSets[i]);
|
|
return c->Name != NAME_None ? c : nullptr;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(FPlayerClass, GetColorSetName)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(FPlayerClass);
|
|
PARAM_INT(setnum);
|
|
auto p = GetColorSet(self->Type, setnum);
|
|
ACTION_RETURN_INT(p ? p->Name.GetIndex() : 0);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
static int GetPainFlash(AActor *info, int type)
|
|
{
|
|
// go backwards through the list and return the first item with a
|
|
// matching damage type for an ancestor of our class.
|
|
// This will always return the best fit because any parent class
|
|
// must be processed before its children.
|
|
for (int i = PainFlashes.Size() - 1; i >= 0; i--)
|
|
{
|
|
if (std::get<1>(PainFlashes[i]) == ENamedName(type) &&
|
|
std::get<0>(PainFlashes[i])->IsAncestorOf(info->GetClass()))
|
|
{
|
|
return std::get<2>(PainFlashes[i]);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION_NATIVE(APlayerPawn, GetPainFlashForType, GetPainFlash)
|
|
{
|
|
PARAM_SELF_PROLOGUE(AActor);
|
|
PARAM_INT(type);
|
|
ACTION_RETURN_INT(GetPainFlash(self, type));
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// player_t :: SendPitchLimits
|
|
//
|
|
// Ask the local player's renderer what pitch restrictions should be imposed
|
|
// and let everybody know. Only sends data for the consoleplayer, since the
|
|
// local player is the only one our data is valid for.
|
|
//
|
|
//===========================================================================
|
|
|
|
EXTERN_CVAR(Float, maxviewpitch)
|
|
EXTERN_CVAR(Bool, cl_oldfreelooklimit);
|
|
|
|
|
|
static int GetSoftPitch(bool down)
|
|
{
|
|
int MAX_DN_ANGLE = MIN(56, (int)maxviewpitch); // Max looking down angle
|
|
int MAX_UP_ANGLE = MIN(32, (int)maxviewpitch); // Max looking up angle
|
|
return (down ? MAX_DN_ANGLE : ((cl_oldfreelooklimit) ? MAX_UP_ANGLE : MAX_DN_ANGLE));
|
|
}
|
|
|
|
void player_t::SendPitchLimits() const
|
|
{
|
|
if (this - players == consoleplayer)
|
|
{
|
|
int uppitch, downpitch;
|
|
|
|
if (V_IsSoftwareRenderer())
|
|
{
|
|
uppitch = GetSoftPitch(false);
|
|
downpitch = GetSoftPitch(true);
|
|
}
|
|
else
|
|
{
|
|
uppitch = downpitch = (int)maxviewpitch;
|
|
}
|
|
|
|
Net_WriteByte(DEM_SETPITCHLIMIT);
|
|
Net_WriteByte(uppitch);
|
|
Net_WriteByte(downpitch);
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, SendPitchLimits)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
self->SendPitchLimits();
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool player_t::HasWeaponsInSlot(int slot) const
|
|
{
|
|
for (int i = 0; i < weapons.SlotSize(slot); i++)
|
|
{
|
|
PClassActor *weap = weapons.GetWeapon(slot, i);
|
|
if (weap != NULL && mo->FindInventory(weap)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, HasWeaponsInSlot)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
PARAM_INT(slot);
|
|
ACTION_RETURN_BOOL(self->HasWeaponsInSlot(slot));
|
|
}
|
|
|
|
|
|
bool player_t::Resurrect()
|
|
{
|
|
if (mo == nullptr || mo->IsKindOf(NAME_PlayerChunk)) return false;
|
|
mo->Revive();
|
|
playerstate = PST_LIVE;
|
|
health = mo->health = mo->GetDefault()->health;
|
|
viewheight = DefaultViewHeight();
|
|
mo->renderflags &= ~RF_INVISIBLE;
|
|
mo->Height = mo->GetDefault()->Height;
|
|
mo->radius = mo->GetDefault()->radius;
|
|
mo->special1 = 0; // required for the Hexen fighter's fist attack.
|
|
// This gets set by AActor::Die as flag for the wimpy death and must be reset here.
|
|
mo->SetState(mo->SpawnState);
|
|
if (!(mo->flags2 & MF2_DONTTRANSLATE))
|
|
{
|
|
mo->Translation = TRANSLATION(TRANSLATION_Players, uint8_t(this - players));
|
|
}
|
|
if (ReadyWeapon != nullptr)
|
|
{
|
|
PendingWeapon = ReadyWeapon;
|
|
P_BringUpWeapon(this);
|
|
}
|
|
|
|
if (morphTics)
|
|
{
|
|
P_UnmorphActor(mo, mo);
|
|
}
|
|
|
|
// player is now alive.
|
|
// fire E_PlayerRespawned and start the ACS SCRIPT_Respawn.
|
|
E_PlayerRespawned(int(this - players));
|
|
//
|
|
level.Behaviors.StartTypedScripts(SCRIPT_Respawn, mo, true);
|
|
return true;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, Resurrect)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_BOOL(self->Resurrect());
|
|
}
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetUserName)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_STRING(self->userinfo.GetName());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetNeverSwitch)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_BOOL(self->userinfo.GetNeverSwitch());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetClassicFlight)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_BOOL(self->userinfo.GetClassicFlight());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetColor)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_INT(self->userinfo.GetColor());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetColorSet)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_INT(self->userinfo.GetColorSet());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetPlayerClassNum)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_INT(self->userinfo.GetPlayerClassNum());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetSkin)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_INT(self->userinfo.GetSkin());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetGender)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_INT(self->userinfo.GetGender());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetAutoaim)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_FLOAT(self->userinfo.GetAutoaim());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetTeam)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_INT(self->userinfo.GetTeam());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetNoAutostartMap)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_INT(self->userinfo.GetNoAutostartMap());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetWBobSpeed)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_FLOAT(self->userinfo.GetWBobSpeed());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetMoveBob)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_FLOAT(self->userinfo.GetMoveBob());
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetStillBob)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(player_t);
|
|
ACTION_RETURN_FLOAT(self->userinfo.GetStillBob());
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
static int SetupCrouchSprite(AActor *self, int crouchsprite)
|
|
{
|
|
// Check whether a PWADs normal sprite is to be combined with the base WADs
|
|
// crouch sprite. In such a case the sprites normally don't match and it is
|
|
// best to disable the crouch sprite.
|
|
if (crouchsprite > 0)
|
|
{
|
|
// This assumes that player sprites always exist in rotated form and
|
|
// that the front view is always a separate sprite. So far this is
|
|
// true for anything that exists.
|
|
FString normspritename = sprites[self->SpawnState->sprite].name;
|
|
FString crouchspritename = sprites[crouchsprite].name;
|
|
|
|
int spritenorm = Wads.CheckNumForName(normspritename + "A1", ns_sprites);
|
|
if (spritenorm == -1)
|
|
{
|
|
spritenorm = Wads.CheckNumForName(normspritename + "A0", ns_sprites);
|
|
}
|
|
|
|
int spritecrouch = Wads.CheckNumForName(crouchspritename + "A1", ns_sprites);
|
|
if (spritecrouch == -1)
|
|
{
|
|
spritecrouch = Wads.CheckNumForName(crouchspritename + "A0", ns_sprites);
|
|
}
|
|
|
|
if (spritenorm == -1 || spritecrouch == -1)
|
|
{
|
|
// Sprites do not exist so it is best to disable the crouch sprite.
|
|
return false;
|
|
}
|
|
|
|
int wadnorm = Wads.GetLumpFile(spritenorm);
|
|
int wadcrouch = Wads.GetLumpFile(spritenorm);
|
|
|
|
if (wadnorm > Wads.GetIwadNum() && wadcrouch <= Wads.GetIwadNum())
|
|
{
|
|
// Question: Add an option / disable crouching or do what?
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION_NATIVE(APlayerPawn, SetupCrouchSprite, SetupCrouchSprite)
|
|
{
|
|
PARAM_SELF_PROLOGUE(AActor);
|
|
PARAM_INT(crouchsprite);
|
|
ACTION_RETURN_INT(SetupCrouchSprite(self, crouchsprite));
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Animations
|
|
//
|
|
//===========================================================================
|
|
|
|
void PlayIdle (AActor *player)
|
|
{
|
|
IFVIRTUALPTRNAME(player, NAME_PlayerPawn, PlayIdle)
|
|
{
|
|
VMValue params[1] = { (DObject*)player };
|
|
VMCall(func, params, 1, nullptr, 0);
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_PlayerScream
|
|
//
|
|
// try to find the appropriate death sound and use suitable
|
|
// replacements if necessary
|
|
//
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_PlayerScream)
|
|
{
|
|
PARAM_SELF_PROLOGUE(AActor);
|
|
|
|
int sound = 0;
|
|
int chan = CHAN_VOICE;
|
|
|
|
if (self->player == NULL || self->DeathSound != 0)
|
|
{
|
|
if (self->DeathSound != 0)
|
|
{
|
|
S_Sound (self, CHAN_VOICE, self->DeathSound, 1, ATTN_NORM);
|
|
}
|
|
else
|
|
{
|
|
S_Sound (self, CHAN_VOICE, "*death", 1, ATTN_NORM);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Handle the different player death screams
|
|
if ((((level.flags >> 15) | (dmflags)) &
|
|
(DF_FORCE_FALLINGZD | DF_FORCE_FALLINGHX)) &&
|
|
self->Vel.Z <= -39)
|
|
{
|
|
sound = S_FindSkinnedSound (self, "*splat");
|
|
chan = CHAN_BODY;
|
|
}
|
|
|
|
if (!sound && self->special1<10)
|
|
{ // Wimpy death sound
|
|
sound = S_FindSkinnedSoundEx (self, "*wimpydeath", self->player->LastDamageType);
|
|
}
|
|
if (!sound && self->health <= -50)
|
|
{
|
|
if (self->health > -100)
|
|
{ // Crazy death sound
|
|
sound = S_FindSkinnedSoundEx (self, "*crazydeath", self->player->LastDamageType);
|
|
}
|
|
if (!sound)
|
|
{ // Extreme death sound
|
|
sound = S_FindSkinnedSoundEx (self, "*xdeath", self->player->LastDamageType);
|
|
if (!sound)
|
|
{
|
|
sound = S_FindSkinnedSoundEx (self, "*gibbed", self->player->LastDamageType);
|
|
chan = CHAN_BODY;
|
|
}
|
|
}
|
|
}
|
|
if (!sound)
|
|
{ // Normal death sound
|
|
sound = S_FindSkinnedSoundEx (self, "*death", self->player->LastDamageType);
|
|
}
|
|
|
|
if (chan != CHAN_VOICE)
|
|
{
|
|
for (int i = 0; i < 8; ++i)
|
|
{ // Stop most playing sounds from this player.
|
|
// This is mainly to stop *land from messing up *splat.
|
|
if (i != CHAN_WEAPON && i != CHAN_VOICE)
|
|
{
|
|
S_StopSound (self, i);
|
|
}
|
|
}
|
|
}
|
|
S_Sound (self, chan, sound, 1, ATTN_NORM);
|
|
return 0;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_CheckPlayerSprites
|
|
//
|
|
// Here's the place where crouching sprites are handled.
|
|
// R_ProjectSprite() calls this for any players.
|
|
//
|
|
//===========================================================================
|
|
|
|
void P_CheckPlayerSprite(AActor *actor, int &spritenum, DVector2 &scale)
|
|
{
|
|
player_t *player = actor->player;
|
|
int crouchspriteno;
|
|
|
|
if (player->userinfo.GetSkin() != 0 && !(actor->flags4 & MF4_NOSKIN))
|
|
{
|
|
// Convert from default scale to skin scale.
|
|
DVector2 defscale = actor->GetDefault()->Scale;
|
|
scale.X *= Skins[player->userinfo.GetSkin()].Scale.X / defscale.X;
|
|
scale.Y *= Skins[player->userinfo.GetSkin()].Scale.Y / defscale.Y;
|
|
}
|
|
|
|
// Set the crouch sprite?
|
|
if (player->crouchfactor < 0.75)
|
|
{
|
|
int crouchsprite = player->mo->IntVar(NAME_crouchsprite);
|
|
if (spritenum == actor->SpawnState->sprite || spritenum == crouchsprite)
|
|
{
|
|
crouchspriteno = crouchsprite;
|
|
}
|
|
else if (!(actor->flags4 & MF4_NOSKIN) &&
|
|
(spritenum == Skins[player->userinfo.GetSkin()].sprite ||
|
|
spritenum == Skins[player->userinfo.GetSkin()].crouchsprite))
|
|
{
|
|
crouchspriteno = Skins[player->userinfo.GetSkin()].crouchsprite;
|
|
}
|
|
else
|
|
{ // no sprite -> squash the existing one
|
|
crouchspriteno = -1;
|
|
}
|
|
|
|
if (crouchspriteno > 0)
|
|
{
|
|
spritenum = crouchspriteno;
|
|
}
|
|
else if (player->playerstate != PST_DEAD && player->crouchfactor < 0.75)
|
|
{
|
|
scale.Y *= 0.5;
|
|
}
|
|
}
|
|
}
|
|
|
|
CUSTOM_CVAR (Float, sv_aircontrol, 0.00390625f, CVAR_SERVERINFO|CVAR_NOSAVE)
|
|
{
|
|
level.aircontrol = self;
|
|
G_AirControlChanged ();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_FallingDamage
|
|
//
|
|
//==========================================================================
|
|
|
|
void P_FallingDamage (AActor *actor)
|
|
{
|
|
int damagestyle;
|
|
int damage;
|
|
double vel;
|
|
|
|
damagestyle = ((level.flags >> 15) | (dmflags)) &
|
|
(DF_FORCE_FALLINGZD | DF_FORCE_FALLINGHX);
|
|
|
|
if (damagestyle == 0)
|
|
return;
|
|
|
|
if (actor->floorsector->Flags & SECF_NOFALLINGDAMAGE)
|
|
return;
|
|
|
|
vel = fabs(actor->Vel.Z);
|
|
|
|
// 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 (vel <= 23)
|
|
{ // Not fast enough to hurt
|
|
return;
|
|
}
|
|
if (vel >= 63)
|
|
{ // automatic death
|
|
damage = TELEFRAG_DAMAGE;
|
|
}
|
|
else
|
|
{
|
|
vel *= (16. / 23);
|
|
damage = int((vel * vel) / 10 - 24);
|
|
if (actor->Vel.Z > -39 && damage > actor->health
|
|
&& actor->health != 1)
|
|
{ // No-death threshold
|
|
damage = actor->health-1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DF_FORCE_FALLINGZD: // ZDoom falling damage
|
|
if (vel <= 19)
|
|
{ // Not fast enough to hurt
|
|
return;
|
|
}
|
|
if (vel >= 84)
|
|
{ // automatic death
|
|
damage = TELEFRAG_DAMAGE;
|
|
}
|
|
else
|
|
{
|
|
damage = int((vel*vel*(11 / 128.) - 30) / 2);
|
|
if (damage < 1)
|
|
{
|
|
damage = 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DF_FORCE_FALLINGST: // Strife falling damage
|
|
if (vel <= 20)
|
|
{ // Not fast enough to hurt
|
|
return;
|
|
}
|
|
// The minimum amount of damage you take from falling in Strife
|
|
// is 52. Ouch!
|
|
damage = int(vel / (25000./65536.));
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (actor->player)
|
|
{
|
|
S_Sound (actor, CHAN_AUTO, "*land", 1, ATTN_NORM);
|
|
P_NoiseAlert (actor, actor, true);
|
|
if (damage >= TELEFRAG_DAMAGE && ((actor->player->cheats & (CF_GODMODE | CF_BUDDHA) ||
|
|
(actor->FindInventory(PClass::FindActor(NAME_PowerBuddha), true) != nullptr))))
|
|
{
|
|
damage = TELEFRAG_DAMAGE - 1;
|
|
}
|
|
}
|
|
P_DamageMobj (actor, NULL, NULL, damage, NAME_Falling);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC P_CheckMusicChange
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void P_CheckMusicChange(player_t *player)
|
|
{
|
|
// MUSINFO stuff
|
|
if (player->MUSINFOtics >= 0 && player->MUSINFOactor != NULL)
|
|
{
|
|
if (--player->MUSINFOtics < 0)
|
|
{
|
|
if (player - players == consoleplayer)
|
|
{
|
|
if (player->MUSINFOactor->args[0] != 0)
|
|
{
|
|
FName *music = level.info->MusicMap.CheckKey(player->MUSINFOactor->args[0]);
|
|
|
|
if (music != NULL)
|
|
{
|
|
S_ChangeMusic(music->GetChars(), player->MUSINFOactor->args[1]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
S_ChangeMusic("*");
|
|
}
|
|
}
|
|
DPrintf(DMSG_NOTIFY, "MUSINFO change for player %d to %d\n", (int)(player - players), player->MUSINFOactor->args[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(APlayerPawn, CheckMusicChange)
|
|
{
|
|
PARAM_SELF_PROLOGUE(AActor);
|
|
P_CheckMusicChange(self->player);
|
|
return 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC P_CheckEnviroment
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void P_CheckEnvironment(player_t *player)
|
|
{
|
|
P_PlayerOnSpecial3DFloor(player);
|
|
P_PlayerInSpecialSector(player);
|
|
|
|
if (!player->mo->isAbove(player->mo->Sector->floorplane.ZatPoint(player->mo)) ||
|
|
player->mo->waterlevel)
|
|
{
|
|
// Player must be touching the floor
|
|
P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo));
|
|
}
|
|
if (player->mo->Vel.Z <= -player->mo->FloatVar(NAME_FallingScreamMinSpeed) &&
|
|
player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && !player->morphTics &&
|
|
player->mo->waterlevel == 0)
|
|
{
|
|
int id = S_FindSkinnedSound(player->mo, "*falling");
|
|
if (id != 0 && !S_IsActorPlayingSomething(player->mo, CHAN_VOICE, id))
|
|
{
|
|
S_Sound(player->mo, CHAN_VOICE, id, 1, ATTN_NORM);
|
|
}
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(APlayerPawn, CheckEnvironment)
|
|
{
|
|
PARAM_SELF_PROLOGUE(AActor);
|
|
P_CheckEnvironment(self->player);
|
|
return 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC P_CheckUse
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void P_CheckUse(player_t *player)
|
|
{
|
|
// check for use
|
|
if (player->cmd.ucmd.buttons & BT_USE)
|
|
{
|
|
if (!player->usedown)
|
|
{
|
|
player->usedown = true;
|
|
if (!P_TalkFacing(player->mo))
|
|
{
|
|
P_UseLines(player);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player->usedown = false;
|
|
}
|
|
}
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(APlayerPawn, CheckUse)
|
|
{
|
|
PARAM_SELF_PROLOGUE(AActor);
|
|
P_CheckUse(self->player);
|
|
return 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC P_PlayerThink
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void P_PlayerThink (player_t *player)
|
|
{
|
|
ticcmd_t *cmd = &player->cmd;
|
|
|
|
if (player->mo == NULL)
|
|
{
|
|
I_Error ("No player %td start\n", player - players + 1);
|
|
}
|
|
|
|
if (debugfile && !(player->cheats & CF_PREDICTING))
|
|
{
|
|
fprintf (debugfile, "tic %d for pl %d: (%f, %f, %f, %f) b:%02x p:%d y:%d f:%d s:%d u:%d\n",
|
|
gametic, (int)(player-players), player->mo->X(), player->mo->Y(), player->mo->Z(),
|
|
player->mo->Angles.Yaw.Degrees, player->cmd.ucmd.buttons,
|
|
player->cmd.ucmd.pitch, player->cmd.ucmd.yaw, player->cmd.ucmd.forwardmove,
|
|
player->cmd.ucmd.sidemove, player->cmd.ucmd.upmove);
|
|
}
|
|
|
|
// Make unmodified copies for ACS's GetPlayerInput.
|
|
player->original_oldbuttons = player->original_cmd.buttons;
|
|
player->original_cmd = cmd->ucmd;
|
|
// Don't interpolate the view for more than one tic
|
|
player->cheats &= ~CF_INTERPVIEW;
|
|
|
|
IFVIRTUALPTRNAME(player->mo, NAME_PlayerPawn, PlayerThink)
|
|
{
|
|
VMValue param = player->mo;
|
|
VMCall(func, ¶m, 1, nullptr, 0);
|
|
}
|
|
}
|
|
|
|
void P_PredictionLerpReset()
|
|
{
|
|
PredictionLerptics = PredictionLast.gametic = PredictionLerpFrom.gametic = PredictionLerpResult.gametic = 0;
|
|
}
|
|
|
|
bool P_LerpCalculate(AActor *pmo, PredictPos from, PredictPos to, PredictPos &result, float scale)
|
|
{
|
|
//DVector2 pfrom = level.Displacements.getOffset(from.portalgroup, to.portalgroup);
|
|
DVector3 vecFrom = from.pos;
|
|
DVector3 vecTo = to.pos;
|
|
DVector3 vecResult;
|
|
vecResult = vecTo - vecFrom;
|
|
vecResult *= scale;
|
|
vecResult = vecResult + vecFrom;
|
|
DVector3 delta = vecResult - vecTo;
|
|
|
|
result.pos = pmo->Vec3Offset(vecResult - to.pos);
|
|
//result.portalgroup = P_PointInSector(result.pos.x, result.pos.y)->PortalGroup;
|
|
|
|
// As a fail safe, assume extrapolation is the threshold.
|
|
return (delta.LengthSquared() > cl_predict_lerpthreshold && scale <= 1.00f);
|
|
}
|
|
|
|
template<class nodetype, class linktype>
|
|
void BackupNodeList(AActor *act, nodetype *head, nodetype *linktype::*otherlist, TArray<nodetype*, nodetype*> &prevbackup, TArray<linktype *, linktype *> &otherbackup)
|
|
{
|
|
// The ordering of the touching_sectorlist needs to remain unchanged
|
|
// Also store a copy of all previous sector_thinglist nodes
|
|
prevbackup.Clear();
|
|
otherbackup.Clear();
|
|
|
|
for (auto mnode = head; mnode != nullptr; mnode = mnode->m_tnext)
|
|
{
|
|
otherbackup.Push(mnode->m_sector);
|
|
|
|
for (auto snode = mnode->m_sector->*otherlist; snode; snode = snode->m_snext)
|
|
{
|
|
if (snode->m_thing == act)
|
|
{
|
|
prevbackup.Push(snode->m_sprev);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class nodetype, class linktype>
|
|
nodetype *RestoreNodeList(AActor *act, nodetype *head, nodetype *linktype::*otherlist, TArray<nodetype*, nodetype*> &prevbackup, TArray<linktype *, linktype *> &otherbackup)
|
|
{
|
|
// Destroy old refrences
|
|
nodetype *node = head;
|
|
while (node)
|
|
{
|
|
node->m_thing = NULL;
|
|
node = node->m_tnext;
|
|
}
|
|
|
|
// Make the sector_list match the player's touching_sectorlist before it got predicted.
|
|
P_DelSeclist(head, otherlist);
|
|
head = NULL;
|
|
for (auto i = otherbackup.Size(); i-- > 0;)
|
|
{
|
|
head = P_AddSecnode(otherbackup[i], act, head, otherbackup[i]->*otherlist);
|
|
}
|
|
//act->touching_sectorlist = ctx.sector_list; // Attach to thing
|
|
//ctx.sector_list = NULL; // clear for next time
|
|
|
|
// In the old code this block never executed because of the commented-out NULL assignment above. Needs to be checked
|
|
node = head;
|
|
while (node)
|
|
{
|
|
if (node->m_thing == NULL)
|
|
{
|
|
if (node == head)
|
|
head = node->m_tnext;
|
|
node = P_DelSecnode(node, otherlist);
|
|
}
|
|
else
|
|
{
|
|
node = node->m_tnext;
|
|
}
|
|
}
|
|
|
|
nodetype *snode;
|
|
|
|
// Restore sector thinglist order
|
|
for (auto i = otherbackup.Size(); i-- > 0;)
|
|
{
|
|
// If we were already the head node, then nothing needs to change
|
|
if (prevbackup[i] == NULL)
|
|
continue;
|
|
|
|
for (snode = otherbackup[i]->*otherlist; snode; snode = snode->m_snext)
|
|
{
|
|
if (snode->m_thing == act)
|
|
{
|
|
if (snode->m_sprev)
|
|
snode->m_sprev->m_snext = snode->m_snext;
|
|
else
|
|
snode->m_sector->*otherlist = snode->m_snext;
|
|
if (snode->m_snext)
|
|
snode->m_snext->m_sprev = snode->m_sprev;
|
|
|
|
snode->m_sprev = prevbackup[i];
|
|
|
|
// 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.
|
|
if (snode->m_sprev->m_snext)
|
|
snode->m_sprev->m_snext->m_sprev = snode;
|
|
snode->m_snext = snode->m_sprev->m_snext;
|
|
snode->m_sprev->m_snext = snode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return head;
|
|
}
|
|
|
|
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;
|
|
|
|
auto act = player->mo;
|
|
PredictionActor = player->mo;
|
|
PredictionActorBackupArray.Resize(act->GetClass()->Size);
|
|
memcpy(PredictionActorBackupArray.Data(), &act->snext, act->GetClass()->Size - ((uint8_t *)&act->snext - (uint8_t *)act));
|
|
|
|
act->flags &= ~MF_PICKUP;
|
|
act->flags2 &= ~MF2_PUSHWALL;
|
|
player->cheats |= CF_PREDICTING;
|
|
|
|
BackupNodeList(act, act->touching_sectorlist, §or_t::touching_thinglist, PredictionTouchingSectors_sprev_Backup, PredictionTouchingSectorsBackup);
|
|
BackupNodeList(act, act->touching_rendersectors, §or_t::touching_renderthings, PredictionRenderSectors_sprev_Backup, PredictionRenderSectorsBackup);
|
|
BackupNodeList(act, act->touching_sectorportallist, §or_t::sectorportal_thinglist, PredictionPortalSectors_sprev_Backup, PredictionPortalSectorsBackup);
|
|
BackupNodeList(act, act->touching_lineportallist, &FLinePortal::lineportal_thinglist, PredictionPortalLines_sprev_Backup, PredictionPortalLinesBackup);
|
|
|
|
// Keep an ordered list off all actors in the linked sector.
|
|
PredictionSectorListBackup.Clear();
|
|
if (!(act->flags & MF_NOSECTOR))
|
|
{
|
|
AActor *link = act->Sector->thinglist;
|
|
|
|
while (link != NULL)
|
|
{
|
|
PredictionSectorListBackup.Push(link);
|
|
link = link->snext;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
// Values too small to be usable for lerping can be considered "off".
|
|
bool CanLerp = (!(cl_predict_lerpscale < 0.01f) && (ticdup == 1)), DoLerp = false, NoInterpolateOld = R_GetViewInterpolationStatus();
|
|
for (int i = gametic; i < maxtic; ++i)
|
|
{
|
|
if (!NoInterpolateOld)
|
|
R_RebuildViewInterpolation(player);
|
|
|
|
player->cmd = localcmds[i % LOCALCMDTICS];
|
|
P_PlayerThink (player);
|
|
player->mo->Tick ();
|
|
|
|
if (CanLerp && PredictionLast.gametic > 0 && i == PredictionLast.gametic && !NoInterpolateOld)
|
|
{
|
|
// Z is not compared as lifts will alter this with no apparent change
|
|
// Make lerping less picky by only testing whole units
|
|
DoLerp = (int)PredictionLast.pos.X != (int)player->mo->X() || (int)PredictionLast.pos.Y != (int)player->mo->Y();
|
|
|
|
// Aditional Debug information
|
|
if (developer >= DMSG_NOTIFY && DoLerp)
|
|
{
|
|
DPrintf(DMSG_NOTIFY, "Lerp! Ltic (%d) && Ptic (%d) | Lx (%f) && Px (%f) | Ly (%f) && Py (%f)\n",
|
|
PredictionLast.gametic, i,
|
|
(PredictionLast.pos.X), (player->mo->X()),
|
|
(PredictionLast.pos.Y), (player->mo->Y()));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CanLerp)
|
|
{
|
|
if (NoInterpolateOld)
|
|
P_PredictionLerpReset();
|
|
|
|
else if (DoLerp)
|
|
{
|
|
// If lerping is already in effect, use the previous camera postion so the view doesn't suddenly snap
|
|
PredictionLerpFrom = (PredictionLerptics == 0) ? PredictionLast : PredictionLerpResult;
|
|
PredictionLerptics = 1;
|
|
}
|
|
|
|
PredictionLast.gametic = maxtic - 1;
|
|
PredictionLast.pos = player->mo->Pos();
|
|
//PredictionLast.portalgroup = player->mo->Sector->PortalGroup;
|
|
|
|
if (PredictionLerptics > 0)
|
|
{
|
|
if (PredictionLerpFrom.gametic > 0 &&
|
|
P_LerpCalculate(player->mo, PredictionLerpFrom, PredictionLast, PredictionLerpResult, (float)PredictionLerptics * cl_predict_lerpscale))
|
|
{
|
|
PredictionLerptics++;
|
|
player->mo->SetXYZ(PredictionLerpResult.pos);
|
|
}
|
|
else
|
|
{
|
|
PredictionLerptics = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void P_UnPredictPlayer ()
|
|
{
|
|
player_t *player = &players[consoleplayer];
|
|
|
|
if (player->cheats & CF_PREDICTING)
|
|
{
|
|
unsigned int i;
|
|
AActor *act = player->mo;
|
|
|
|
if (act != PredictionActor)
|
|
{
|
|
// Q: Can this happen? If yes, can we continue?
|
|
}
|
|
|
|
AActor *savedcamera = player->camera;
|
|
|
|
auto &actInvSel = act->PointerVar<AActor*>(NAME_InvSel);
|
|
auto InvSel = actInvSel;
|
|
int inventorytics = player->inventorytics;
|
|
const bool settings_controller = player->settings_controller;
|
|
|
|
*player = PredictionPlayerBackup;
|
|
|
|
player->settings_controller = settings_controller;
|
|
// Restore the camera instead of using the backup's copy, because spynext/prev
|
|
// could cause it to change during prediction.
|
|
player->camera = savedcamera;
|
|
|
|
FLinkContext ctx;
|
|
// Unlink from all list, including those which are not being handled by UnlinkFromWorld.
|
|
auto sectorportal_list = act->touching_sectorportallist;
|
|
auto lineportal_list = act->touching_lineportallist;
|
|
act->touching_sectorportallist = nullptr;
|
|
act->touching_lineportallist = nullptr;
|
|
|
|
act->UnlinkFromWorld(&ctx);
|
|
memcpy(&act->snext, PredictionActorBackupArray.Data(), PredictionActorBackupArray.Size() - ((uint8_t *)&act->snext - (uint8_t *)act));
|
|
|
|
// The blockmap ordering needs to remain unchanged, too.
|
|
// Restore sector links and refrences.
|
|
// [ED850] This is somewhat of a duplicate of LinkToWorld(), but we need to keep every thing the same,
|
|
// otherwise we end up fixing bugs in blockmap logic (i.e undefined behaviour with polyobject collisions),
|
|
// which we really don't want to do here.
|
|
if (!(act->flags & MF_NOSECTOR))
|
|
{
|
|
sector_t *sec = act->Sector;
|
|
AActor *me, *next;
|
|
AActor **link;// , **prev;
|
|
|
|
// The thinglist is just a pointer chain. We are restoring the exact same things, so we can NULL the head safely
|
|
sec->thinglist = NULL;
|
|
|
|
for (i = PredictionSectorListBackup.Size(); i-- > 0;)
|
|
{
|
|
me = PredictionSectorListBackup[i];
|
|
link = &sec->thinglist;
|
|
next = *link;
|
|
if ((me->snext = next))
|
|
next->sprev = &me->snext;
|
|
me->sprev = link;
|
|
*link = me;
|
|
}
|
|
|
|
act->touching_sectorlist = RestoreNodeList(act, ctx.sector_list, §or_t::touching_thinglist, PredictionTouchingSectors_sprev_Backup, PredictionTouchingSectorsBackup);
|
|
act->touching_rendersectors = RestoreNodeList(act, ctx.render_list, §or_t::touching_renderthings, PredictionRenderSectors_sprev_Backup, PredictionRenderSectorsBackup);
|
|
act->touching_sectorportallist = RestoreNodeList(act, sectorportal_list, §or_t::sectorportal_thinglist, PredictionPortalSectors_sprev_Backup, PredictionPortalSectorsBackup);
|
|
act->touching_lineportallist = RestoreNodeList(act, lineportal_list, &FLinePortal::lineportal_thinglist, PredictionPortalLines_sprev_Backup, PredictionPortalLinesBackup);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
actInvSel = InvSel;
|
|
player->inventorytics = inventorytics;
|
|
}
|
|
}
|
|
|
|
void player_t::Serialize(FSerializer &arc)
|
|
{
|
|
FString skinname;
|
|
|
|
arc("class", cls)
|
|
("mo", mo)
|
|
("camera", camera)
|
|
("playerstate", playerstate)
|
|
("cmd", cmd);
|
|
|
|
if (arc.isReading())
|
|
{
|
|
ReadUserInfo(arc, userinfo, skinname);
|
|
}
|
|
else
|
|
{
|
|
WriteUserInfo(arc, userinfo);
|
|
}
|
|
|
|
arc("desiredfov", DesiredFOV)
|
|
("fov", FOV)
|
|
("viewz", viewz)
|
|
("viewheight", viewheight)
|
|
("deltaviewheight", deltaviewheight)
|
|
("bob", bob)
|
|
("vel", Vel)
|
|
("centering", centering)
|
|
("health", health)
|
|
("inventorytics", inventorytics)
|
|
("fragcount", fragcount)
|
|
("spreecount", spreecount)
|
|
("multicount", multicount)
|
|
("lastkilltime", lastkilltime)
|
|
("readyweapon", ReadyWeapon)
|
|
("pendingweapon", PendingWeapon)
|
|
("cheats", cheats)
|
|
("refire", refire)
|
|
("inconsistant", inconsistant)
|
|
("killcount", killcount)
|
|
("itemcount", itemcount)
|
|
("secretcount", secretcount)
|
|
("damagecount", damagecount)
|
|
("bonuscount", bonuscount)
|
|
("hazardcount", hazardcount)
|
|
("poisoncount", poisoncount)
|
|
("poisoner", poisoner)
|
|
("attacker", attacker)
|
|
("extralight", extralight)
|
|
("fixedcolormap", fixedcolormap)
|
|
("fixedlightlevel", fixedlightlevel)
|
|
("morphTics", morphTics)
|
|
("morphedplayerclass", MorphedPlayerClass)
|
|
("morphstyle", MorphStyle)
|
|
("morphexitflash", MorphExitFlash)
|
|
("premorphweapon", PremorphWeapon)
|
|
("chickenpeck", chickenPeck)
|
|
("jumptics", jumpTics)
|
|
("respawntime", respawn_time)
|
|
("airfinished", air_finished)
|
|
("turnticks", turnticks)
|
|
("oldbuttons", oldbuttons)
|
|
("hazardtype", hazardtype)
|
|
("hazardinterval", hazardinterval)
|
|
("bot", Bot)
|
|
("blendr", BlendR)
|
|
("blendg", BlendG)
|
|
("blendb", BlendB)
|
|
("blenda", BlendA)
|
|
("weaponstate", WeaponState)
|
|
("logtext", LogText)
|
|
("conversionnpc", ConversationNPC)
|
|
("conversionpc", ConversationPC)
|
|
("conversionnpcangle", ConversationNPCAngle)
|
|
("conversionfacetalker", ConversationFaceTalker)
|
|
.Array("frags", frags, MAXPLAYERS)
|
|
("psprites", psprites)
|
|
("currentplayerclass", CurrentPlayerClass)
|
|
("crouchfactor", crouchfactor)
|
|
("crouching", crouching)
|
|
("crouchdir", crouchdir)
|
|
("crouchviewdelta", crouchviewdelta)
|
|
("original_cmd", original_cmd)
|
|
("original_oldbuttons", original_oldbuttons)
|
|
("poisontype", poisontype)
|
|
("poisonpaintype", poisonpaintype)
|
|
("timefreezer", timefreezer)
|
|
("settings_controller", settings_controller)
|
|
("onground", onground)
|
|
("musinfoactor", MUSINFOactor)
|
|
("musinfotics", MUSINFOtics);
|
|
|
|
if (arc.isWriting ())
|
|
{
|
|
// 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;
|
|
original_oldbuttons = ~0;
|
|
}
|
|
if (skinname.IsNotEmpty())
|
|
{
|
|
userinfo.SkinChanged(skinname, CurrentPlayerClass);
|
|
}
|
|
}
|
|
|
|
bool P_IsPlayerTotallyFrozen(const player_t *player)
|
|
{
|
|
return
|
|
gamestate == GS_TITLELEVEL ||
|
|
player->cheats & CF_TOTALLYFROZEN ||
|
|
((level.flags2 & LEVEL2_FROZEN) && player->timefreezer == 0);
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// native members
|
|
//
|
|
//==========================================================================
|
|
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, mo)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, playerstate)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, original_oldbuttons)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, cls)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, DesiredFOV)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, FOV)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, viewz)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, viewheight)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, deltaviewheight)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, bob)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, Vel)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, centering)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, turnticks)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, attackdown)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, usedown)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, oldbuttons)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, health)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, inventorytics)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, CurrentPlayerClass)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, frags)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, fragcount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, lastkilltime)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, multicount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, spreecount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, WeaponState)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, ReadyWeapon)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, PendingWeapon)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, psprites)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, cheats)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, timefreezer)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, refire)
|
|
DEFINE_FIELD_NAMED_X(PlayerInfo, player_t, inconsistant, inconsistent)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, waiting)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, killcount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, itemcount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, secretcount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, damagecount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, bonuscount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, hazardcount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, hazardinterval)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, hazardtype)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, poisoncount)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, poisontype)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, poisonpaintype)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, poisoner)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, attacker)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, extralight)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, fixedcolormap)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, fixedlightlevel)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, morphTics)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, MorphedPlayerClass)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, MorphStyle)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, MorphExitFlash)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, PremorphWeapon)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, chickenPeck)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, jumpTics)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, onground)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, respawn_time)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, camera)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, air_finished)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, LastDamageType)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, MUSINFOactor)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, MUSINFOtics)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, settings_controller)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, crouching)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, crouchdir)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, Bot)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, BlendR)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, BlendG)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, BlendB)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, BlendA)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, LogText)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, MinPitch)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, MaxPitch)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, crouchfactor)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, crouchoffset)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, crouchviewdelta)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, ConversationNPC)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, ConversationPC)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, ConversationNPCAngle)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, ConversationFaceTalker)
|
|
DEFINE_FIELD_NAMED_X(PlayerInfo, player_t, cmd.ucmd, cmd)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, original_cmd)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, userinfo)
|
|
DEFINE_FIELD_X(PlayerInfo, player_t, weapons)
|
|
DEFINE_FIELD_NAMED_X(PlayerInfo, player_t, cmd.ucmd.buttons, buttons)
|
|
|
|
DEFINE_FIELD_X(UserCmd, usercmd_t, buttons)
|
|
DEFINE_FIELD_X(UserCmd, usercmd_t, pitch)
|
|
DEFINE_FIELD_X(UserCmd, usercmd_t, yaw)
|
|
DEFINE_FIELD_X(UserCmd, usercmd_t, roll)
|
|
DEFINE_FIELD_X(UserCmd, usercmd_t, forwardmove)
|
|
DEFINE_FIELD_X(UserCmd, usercmd_t, sidemove)
|
|
DEFINE_FIELD_X(UserCmd, usercmd_t, upmove)
|
|
|
|
DEFINE_FIELD(FPlayerClass, Type)
|
|
DEFINE_FIELD(FPlayerClass, Flags)
|
|
DEFINE_FIELD(FPlayerClass, Skins)
|