//----------------------------------------------------------------------------- // // 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 "i_system.h" #include "gi.h" #include "m_random.h" #include "p_pspr.h" #include "p_enemy.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 "intermission/intermission.h" #include "c_console.h" #include "doomdef.h" #include "c_dispatch.h" #include "tarray.h" #include "g_level.h" #include "d_net.h" #include "gstrings.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 "r_data/r_translate.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 uint8_t PredictionActorBackup[sizeof(APlayerPawn)]; static TArray PredictionSectorListBackup; static TArray PredictionTouchingSectorsBackup; static TArray PredictionTouchingSectors_sprev_Backup; static TArray PredictionRenderSectorsBackup; static TArray PredictionRenderSectors_sprev_Backup; static TArray PredictionPortalSectorsBackup; static TArray PredictionPortalSectors_sprev_Backup; static TArray PredictionPortalLinesBackup; static TArray PredictionPortalLines_sprev_Backup; // [GRB] Custom player classes TArray 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(); } DEFINE_ACTION_FUNCTION(APlayerPawn, GetPrintableDisplayName) { PARAM_PROLOGUE; PARAM_CLASS(type, AActor); ACTION_RETURN_STRING(type->GetDisplayName()); } bool ValidatePlayerClass(PClassActor *ti, const char *name) { if (ti == NULL) { Printf("Unknown player class '%s'\n", name); return false; } else if (!ti->IsDescendantOf(RUNTIME_CLASS(APlayerPawn))) { 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. // // 16 pixels of bob #define MAXBOB 16. // The player_t constructor. Since LogText is not a POD, we cannot just // memset it all to 0. player_t::player_t() : mo(0), playerstate(0), cls(0), DesiredFOV(0), FOV(0), viewz(0), viewheight(0), deltaviewheight(0), bob(0), Vel(0, 0), centering(0), turnticks(0), attackdown(0), usedown(0), oldbuttons(0), health(0), inventorytics(0), CurrentPlayerClass(0), fragcount(0), lastkilltime(0), multicount(0), spreecount(0), WeaponState(0), ReadyWeapon(0), PendingWeapon(0), psprites(0), cheats(0), timefreezer(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), morphTics(0), MorphedPlayerClass(0), MorphStyle(0), MorphExitFlash(0), PremorphWeapon(0), chickenPeck(0), jumpTics(0), onground(0), respawn_time(0), camera(0), air_finished(0), MUSINFOactor(0), MUSINFOtics(-1), crouching(0), crouchdir(0), Bot(0), BlendR(0), BlendG(0), BlendB(0), BlendA(0), LogText(), crouchfactor(0), crouchoffset(0), crouchviewdelta(0), ConversationNPC(0), ConversationPC(0), ConversationNPCAngle(0.), ConversationFaceTalker(0) { memset (&cmd, 0, sizeof(cmd)); memset (frags, 0, sizeof(frags)); } 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; } // This function supplements the pointer cleanup in dobject.cpp, because // player_t 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_t 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. size_t player_t::FixPointers (const DObject *old, DObject *rep) { APlayerPawn *replacement = static_cast(rep); size_t changed = 0; // The construct *& is used in several of these to avoid the read barriers // that would turn the pointer we want to check to NULL if the old object // is pending deletion. if (mo == old) mo = replacement, changed++; if (*&poisoner == old) poisoner = replacement, changed++; if (*&attacker == old) attacker = replacement, changed++; if (*&camera == old) camera = replacement, changed++; if (*&Bot == old) Bot = static_cast(rep), changed++; if (ReadyWeapon == old) ReadyWeapon = static_cast(rep), changed++; if (PendingWeapon == old) PendingWeapon = static_cast(rep), changed++; if (*&PremorphWeapon == old) PremorphWeapon = static_cast(rep), changed++; if (psprites == old) psprites = static_cast(rep), changed++; if (*&ConversationNPC == old) ConversationNPC = replacement, changed++; if (*&ConversationPC == old) ConversationPC = replacement, changed++; if (*&MUSINFOactor == old) MUSINFOactor = replacement, changed++; return changed; } 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 { int length=Wads.LumpLength(lumpnum); char *data= new char[length+1]; Wads.ReadLump (lumpnum, data); data[length]=0; SetLogText (data); delete[] data; } } 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 static_cast(GetDefaultByType(type))->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(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 *out) { TArray 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); 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); } //========================================================================== // // //========================================================================== bool player_t::GetPainFlash(FName type, PalEntry *color) const { PClass *info = mo->GetClass(); // 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]) == type && std::get<0>(PainFlashes[i])->IsAncestorOf(info)) { *color = std::get<2>(PainFlashes[i]); return true; } } return false; } //=========================================================================== // // 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. // //=========================================================================== void player_t::SendPitchLimits() const { if (this - players == consoleplayer) { Net_WriteByte(DEM_SETPITCHLIMIT); Net_WriteByte(Renderer->GetMaxViewPitch(false)); // up Net_WriteByte(Renderer->GetMaxViewPitch(true)); // down } } bool player_t::HasWeaponsInSlot(int slot) const { for (int i = 0; i < weapons.Slots[slot].Size(); i++) { PClassActor *weap = weapons.Slots[slot].GetWeapon(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 = ((APlayerPawn *)mo->GetDefault())->ViewHeight; 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) { P_SetPsprite(this, PSP_WEAPON, ReadyWeapon->GetUpState()); } if (morphTics) { P_UndoPlayerMorph(this, this); } // player is now alive. // fire E_PlayerRespawned and start the ACS SCRIPT_Respawn. E_PlayerRespawned(int(this - players)); // FBehavior::StaticStartTypedScripts(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()); } //=========================================================================== // // APlayerPawn // //=========================================================================== IMPLEMENT_CLASS(APlayerPawn, false, true) IMPLEMENT_POINTERS_START(APlayerPawn) IMPLEMENT_POINTER(InvFirst) IMPLEMENT_POINTER(InvSel) IMPLEMENT_POINTERS_END void APlayerPawn::Serialize(FSerializer &arc) { Super::Serialize (arc); auto def = (APlayerPawn*)GetDefault(); arc("jumpz", JumpZ, def->JumpZ) ("maxhealth", MaxHealth, def->MaxHealth) ("bonushealth", BonusHealth, def->BonusHealth) ("runhealth", RunHealth, def->RunHealth) ("spawnmask", SpawnMask, def->SpawnMask) ("forwardmove1", ForwardMove1, def->ForwardMove1) ("forwardmove2", ForwardMove2, def->ForwardMove2) ("sidemove1", SideMove1, def->SideMove1) ("sidemove2", SideMove2, def->SideMove2) ("scoreicon", ScoreIcon, def->ScoreIcon) ("invfirst", InvFirst) ("invsel", InvSel) ("morphweapon", MorphWeapon, def->MorphWeapon) ("damagefade", DamageFade, def->DamageFade) ("playerflags", PlayerFlags, def->PlayerFlags) ("flechettetype", FlechetteType, def->FlechetteType) ("gruntspeed", GruntSpeed, def->GruntSpeed) ("fallingscreammin", FallingScreamMinSpeed, def->FallingScreamMinSpeed) ("fallingscreammaxn", FallingScreamMaxSpeed, def->FallingScreamMaxSpeed) ("userange", UseRange, def->UseRange) ("aircapacity", AirCapacity, def->AirCapacity) ("viewheight", ViewHeight, def->ViewHeight) ("viewbob", ViewBob, def->ViewBob) ("fullheight", FullHeight, def->FullHeight); } //=========================================================================== // // APlayerPawn :: MarkPrecacheSounds // //=========================================================================== void APlayerPawn::MarkPrecacheSounds() const { Super::MarkPrecacheSounds(); S_MarkPlayerSounds(GetSoundClass()); } //=========================================================================== // // APlayerPawn :: BeginPlay // //=========================================================================== void APlayerPawn::BeginPlay () { Super::BeginPlay (); ChangeStatNum (STAT_PLAYER); FullHeight = Height; // 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[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. crouchsprite = 0; return; } 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? crouchsprite = 0; } } } //=========================================================================== // // APlayerPawn :: Tick // //=========================================================================== void APlayerPawn::Tick() { if (player != NULL && player->mo == this && player->CanCrouch() && player->playerstate != PST_DEAD) { Height = FullHeight * player->crouchfactor; } else { if (health > 0) Height = FullHeight; } Super::Tick(); } //=========================================================================== // // APlayerPawn :: PostBeginPlay // //=========================================================================== void APlayerPawn::PostBeginPlay() { Super::PostBeginPlay(); SetupWeaponSlots(); // Voodoo dolls: restore original floorz/ceilingz logic if (player == NULL || player->mo != this) { P_FindFloorCeiling(this, FFCF_ONLYSPAWNPOS|FFCF_NOPORTALS); SetZ(floorz); P_FindFloorCeiling(this, FFCF_ONLYSPAWNPOS); } else { player->SendPitchLimits(); } } //=========================================================================== // // APlayerPawn :: SetupWeaponSlots // // Sets up the default weapon slots for this player. If this is also the // local player, determines local modifications and sends those across the // network. Ignores voodoo dolls. // //=========================================================================== void APlayerPawn::SetupWeaponSlots() { if (player != NULL && player->mo == this) { player->weapons.StandardSetup(GetClass()); // If we're the local player, then there's a bit more work to do. // This also applies if we're a bot and this is the net arbitrator. if (player - players == consoleplayer || (player->Bot != NULL && consoleplayer == Net_Arbitrator)) { FWeaponSlots local_slots(player->weapons); if (player->Bot != NULL) { // Bots only need weapons from KEYCONF, not INI modifications. P_PlaybackKeyConfWeapons(&local_slots); } else { local_slots.LocalSetup(GetClass()); } local_slots.SendDifferences(int(player - players), player->weapons); } } } //=========================================================================== // // 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 (InvSel == NULL && (item->ItemFlags & IF_INVBAR)) { 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 (InvSel == item) { InvSel = item->NextInv (); if (InvSel == NULL) { InvSel = item->PrevInv (); } } if (InvFirst == item) { InvFirst = item->NextInv (); if (InvFirst == NULL) { InvFirst = item->PrevInv (); } } if (item == player->PendingWeapon) { player->PendingWeapon = WP_NOCHANGE; } if (item == player->ReadyWeapon) { // If the current weapon is removed, clear the refire counter and pick a new one. pickWeap = true; player->ReadyWeapon = NULL; player->refire = 0; } } 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 ((level.flags2 & LEVEL2_FROZEN) && (player == NULL || player->timefreezer == 0)) { // Time 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_Sound (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(PClassActor *ammotype) { AWeapon *bestMatch = NULL; int bestOrder = INT_MAX; AInventory *item; AWeapon *weap; bool tomed = NULL != FindInventory (PClass::FindActor(NAME_PowerWeaponLevel2), true); // Find the best weapon the player has. for (item = Inventory; item != NULL; item = item->Inventory) { if (!item->IsKindOf(NAME_Weapon)) continue; weap = static_cast (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; // Don't select if if there isn't enough ammo as determined by the weapon's author. if (weap->MinSelAmmo1 > 0 && (weap->Ammo1 == NULL || weap->Ammo1->Amount < weap->MinSelAmmo1)) continue; if (weap->MinSelAmmo2 > 0 && (weap->Ammo2 == NULL || weap->Ammo2->Amount < weap->MinSelAmmo2)) continue; // This weapon is usable! bestOrder = weap->SelectionOrder; bestMatch = weap; } return bestMatch; } DEFINE_ACTION_FUNCTION(APlayerPawn, BestWeapon) { PARAM_SELF_PROLOGUE(APlayerPawn); PARAM_CLASS(ammo, AActor); ACTION_RETURN_POINTER(self->BestWeapon(ammo)); } //=========================================================================== // // 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(PClassActor *ammotype) { AWeapon *best = BestWeapon (ammotype); if (best != NULL) { player->PendingWeapon = best; if (player->ReadyWeapon != NULL) { P_DropWeapon(player); } 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 < PClassActor::AllActorClasses.Size(); ++i) { if (PClassActor::AllActorClasses[i]->IsDescendantOf (PClass::FindActor(NAME_Key))) { AInventory *key = (AInventory*)GetDefaultByType (PClassActor::AllActorClasses[i]); if (key->special1 != 0) { key = (AInventory*)Spawn(PClassActor::AllActorClasses[i]); if (!key->CallTryPickup (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 (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(NAME_Key)) { item->Destroy(); } else if ((dmflags & DF_COOP_LOSE_WEAPONS) && defitem == NULL && item->IsKindOf(NAME_Weapon)) { item->Destroy(); } else if ((dmflags & DF_COOP_LOSE_ARMOR) && item->IsKindOf(NAME_Armor)) { if (defitem == NULL) { item->Destroy(); } else if (item->IsKindOf(NAME_BasicArmor)) { item->IntVar(NAME_SavePercent) = defitem->IntVar(NAME_SavePercent); item->Amount = defitem->Amount; } else if (item->IsKindOf(NAME_HexenArmor)) { double *SlotsTo = (double*)item->ScriptVar(NAME_Slots, nullptr); double *SlotsFrom = (double*)defitem->ScriptVar(NAME_Slots, nullptr); memcpy(SlotsTo, SlotsFrom, 4 * sizeof(double)); } } else if ((dmflags & DF_COOP_LOSE_POWERUPS) && defitem == NULL && item->IsKindOf(NAME_PowerupGiver)) { item->Destroy(); } else if ((dmflags & (DF_COOP_LOSE_AMMO | DF_COOP_HALVE_AMMO)) && item->IsKindOf(NAME_Ammo)) { 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); } //=========================================================================== // // APlayerPawn :: GetSoundClass // //=========================================================================== const char *APlayerPawn::GetSoundClass() const { if (player != NULL && (player->mo == NULL || !(player->mo->flags4 &MF4_NOSKIN)) && (unsigned int)player->userinfo.GetSkin() >= PlayerClasses.Size () && (unsigned)player->userinfo.GetSkin() < Skins.Size()) { return Skins[player->userinfo.GetSkin()].Name.GetChars(); } return SoundClass != NAME_None? SoundClass.GetChars() : "player"; } //=========================================================================== // // APlayerPawn :: GetMaxHealth // // only needed because Boom screwed up Dehacked. // //=========================================================================== int APlayerPawn::GetMaxHealth(bool withupgrades) const { int ret = MaxHealth > 0? MaxHealth : ((i_compatflags&COMPATF_DEHHEALTH)? 100 : deh.MaxHealth); if (withupgrades) ret += stamina + BonusHealth; return ret; } DEFINE_ACTION_FUNCTION(APlayerPawn, GetMaxHealth) { PARAM_SELF_PROLOGUE(APlayerPawn); PARAM_BOOL_DEF(withupgrades); ACTION_RETURN_INT(self->GetMaxHealth(withupgrades)); } //=========================================================================== // // APlayerPawn :: UpdateWaterLevel // // Plays surfacing and diving sounds, as appropriate. // //=========================================================================== bool APlayerPawn::UpdateWaterLevel (bool splash) { int oldlevel = waterlevel; bool retval = Super::UpdateWaterLevel (splash); if (player != NULL) { if (oldlevel < 3 && waterlevel == 3) { // Our head just went under. S_Sound (this, CHAN_VOICE, "*dive", 1, ATTN_NORM); } else if (oldlevel == 3 && waterlevel < 3) { // Our head just came up. if (player->air_finished > level.time) { // We hadn't run out of air yet. S_Sound (this, CHAN_VOICE, "*surface", 1, ATTN_NORM); } // If we were running out of air, then ResetAirSupply() will play *gasp. } } return retval; } //=========================================================================== // // APlayerPawn :: ResetAirSupply // // Gives the player a full "tank" of air. If they had previously completely // run out of air, also plays the *gasp sound. Returns true if the player // was drowning. // //=========================================================================== bool APlayerPawn::ResetAirSupply (bool playgasp) { bool wasdrowning = (player->air_finished < level.time); if (playgasp && wasdrowning) { S_Sound (this, CHAN_VOICE, "*gasp", 1, ATTN_NORM); } if (level.airsupply> 0 && player->mo->AirCapacity > 0) player->air_finished = level.time + int(level.airsupply * player->mo->AirCapacity); else player->air_finished = INT_MAX; return wasdrowning; } DEFINE_ACTION_FUNCTION(APlayerPawn, ResetAirSupply) { PARAM_SELF_PROLOGUE(APlayerPawn); PARAM_BOOL_DEF(playgasp); ACTION_RETURN_BOOL(self->ResetAirSupply(playgasp)); } //=========================================================================== // // Animations // //=========================================================================== void APlayerPawn::PlayIdle () { IFVIRTUAL(APlayerPawn, PlayIdle) { VMValue params[1] = { (DObject*)this }; VMCall(func, params, 1, nullptr, 0); } } void APlayerPawn::PlayAttacking2 () { IFVIRTUAL(APlayerPawn, PlayAttacking2) { VMValue params[1] = { (DObject*)this }; VMCall(func, params, 1, nullptr, 0); } } //=========================================================================== // // APlayerPawn :: GiveDefaultInventory // //=========================================================================== void APlayerPawn::GiveDefaultInventory () { if (player == NULL) return; // HexenArmor must always be the first item in the inventory because // it provides player class based protection that should not affect // any other protection item. auto myclass = GetClass(); GiveInventoryType(PClass::FindActor(NAME_HexenArmor)); auto harmor = FindInventory(NAME_HexenArmor); double *Slots = (double*)harmor->ScriptVar(NAME_Slots, nullptr); double *SlotsIncrement = (double*)harmor->ScriptVar(NAME_SlotsIncrement, nullptr); Slots[4] = HexenArmor[0]; for (int i = 0; i < 4; ++i) { SlotsIncrement[i] = HexenArmor[i + 1]; } // BasicArmor must come right after that. It should not affect any // other protection item as well but needs to process the damage // before the HexenArmor does. auto barmor = (AInventory*)Spawn(NAME_BasicArmor); barmor->BecomeItem (); AddInventory (barmor); // Now add the items from the DECORATE definition auto di = GetDropItems(); while (di) { PClassActor *ti = PClass::FindActor (di->Name); if (ti) { if (!ti->IsDescendantOf(RUNTIME_CLASS(AInventory))) { Printf(TEXTCOLOR_ORANGE "%s is not an inventory item and cannot be given to a player as start item.\n", ti->TypeName.GetChars()); } else { AInventory *item = FindInventory(ti); if (item != NULL) { item->Amount = clamp( item->Amount + (di->Amount ? di->Amount : ((AInventory *)item->GetDefault())->Amount), 0, item->MaxAmount); } else { item = static_cast(Spawn(ti)); item->ItemFlags |= IF_IGNORESKILL; // no skill multiplicators here item->Amount = di->Amount; if (item->IsKindOf(NAME_Weapon)) { // To allow better control any weapon is emptied of // ammo before being given to the player. static_cast(item)->AmmoGive1 = static_cast(item)->AmmoGive2 = 0; } AActor *check; if (!item->CallTryPickup(this, &check)) { if (check != this) { // Player was morphed. This is illegal at game start. // This problem is only detectable when it's too late to do something about it... I_Error("Cannot give morph items when starting a game"); } item->Destroy(); item = NULL; } } if (item != NULL && item->IsKindOf(NAME_Weapon) && static_cast(item)->CheckAmmo(AWeapon::EitherFire, false)) { player->ReadyWeapon = player->PendingWeapon = static_cast (item); } } } di = di->Next; } } void APlayerPawn::ActivateMorphWeapon () { PClassActor *morphweapon = PClass::FindActor (MorphWeapon); player->PendingWeapon = WP_NOCHANGE; if (player->ReadyWeapon != nullptr) { player->GetPSprite(PSP_WEAPON)->y = WEAPONTOP; } if (morphweapon == nullptr || !morphweapon->IsDescendantOf (RUNTIME_CLASS(AWeapon))) { // No weapon at all while morphed! player->ReadyWeapon = nullptr; } else { player->ReadyWeapon = static_cast(player->mo->FindInventory (morphweapon)); if (player->ReadyWeapon == nullptr) { player->ReadyWeapon = static_cast(player->mo->GiveInventoryType (morphweapon)); if (player->ReadyWeapon != nullptr) { player->ReadyWeapon->GivenAsMorphWeapon = true; // flag is used only by new beastweap semantics in P_UndoPlayerMorph } } if (player->ReadyWeapon != nullptr) { P_SetPsprite(player, PSP_WEAPON, player->ReadyWeapon->GetReadyState()); } } if (player->ReadyWeapon != nullptr) { P_SetPsprite(player, PSP_FLASH, nullptr); } player->PendingWeapon = WP_NOCHANGE; } //=========================================================================== // // APlayerPawn :: Die // //=========================================================================== void APlayerPawn::Die (AActor *source, AActor *inflictor, int dmgflags) { Super::Die (source, inflictor, dmgflags); if (player != NULL && player->mo == this) player->bonuscount = 0; if (player != NULL && player->mo != this) { // Make the real player die, too player->mo->CallDie (source, inflictor, dmgflags); } else { if (player != NULL && (dmflags2 & DF2_YES_WEAPONDROP)) { // Voodoo dolls don't drop weapons AWeapon *weap = player->ReadyWeapon; if (weap != NULL) { AInventory *item; // kgDROP - start - modified copy from a_action.cpp auto di = weap->GetDropItems(); if (di != NULL) { while (di != NULL) { if (di->Name != NAME_None) { PClassActor *ti = PClass::FindActor(di->Name); if (ti) P_DropItem (player->mo, ti, di->Amount, di->Probability); } di = di->Next; } } else // kgDROP - end if (weap->SpawnState != NULL && weap->SpawnState != ::GetDefault()->SpawnState) { item = P_DropItem (this, weap->GetClass(), -1, 256); if (item != NULL && item->IsKindOf(NAME_Weapon)) { if (weap->AmmoGive1 && weap->Ammo1) { static_cast(item)->AmmoGive1 = weap->Ammo1->Amount; } if (weap->AmmoGive2 && weap->Ammo2) { static_cast(item)->AmmoGive2 = weap->Ammo2->Amount; } item->ItemFlags |= IF_IGNORESKILL; } } else { item = P_DropItem (this, weap->AmmoType1, -1, 256); if (item != NULL) { item->Amount = weap->Ammo1->Amount; item->ItemFlags |= IF_IGNORESKILL; } item = P_DropItem (this, weap->AmmoType2, -1, 256); if (item != NULL) { item->Amount = weap->Ammo2->Amount; item->ItemFlags |= IF_IGNORESKILL; } } } } if (!multiplayer && level.info->deathsequence != NAME_None) { F_StartIntermission(level.info->deathsequence, FSTATE_EndingGame); } } } //=========================================================================== // // 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; } //---------------------------------------------------------------------------- // // PROC A_SkullPop // //---------------------------------------------------------------------------- DEFINE_ACTION_FUNCTION(AActor, A_SkullPop) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS_DEF(spawntype, APlayerPawn); APlayerPawn *mo; player_t *player; // [GRB] Parameterized version if (spawntype == NULL || !spawntype->IsDescendantOf("PlayerChunk")) { spawntype = PClass::FindActor("BloodySkull"); if (spawntype == NULL) return 0; } self->flags &= ~MF_SOLID; mo = (APlayerPawn *)Spawn (spawntype, self->PosPlusZ(48.), NO_REPLACE); //mo->target = self; mo->Vel.X = pr_skullpop.Random2() / 128.; mo->Vel.Y = pr_skullpop.Random2() / 128.; mo->Vel.Z = 2. + (pr_skullpop() / 1024.); // Attach player mobj to bloody skull player = self->player; self->player = NULL; mo->ObtainInventory (self); mo->player = player; mo->health = self->health; mo->Angles.Yaw = self->Angles.Yaw; if (player != NULL) { player->mo = mo; player->damagecount = 32; } for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i] && players[i].camera == self) { players[i].camera = mo; } } return 0; } //---------------------------------------------------------------------------- // // PROC A_CheckSkullDone // //---------------------------------------------------------------------------- DEFINE_ACTION_FUNCTION(AActor, A_CheckPlayerDone) { PARAM_SELF_PROLOGUE(AActor); if (self->player == NULL) { self->Destroy(); } 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) { if (spritenum == actor->SpawnState->sprite || spritenum == player->mo->crouchsprite) { crouchspriteno = player->mo->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; } } } /* ================== = = P_CalcHeight = = Calculate the walking / running height adjustment = ================== */ void P_CalcHeight (player_t *player) { DAngle angle; double bob; bool still = false; // Regular movement bobbing // (needs to be calculated for gun swing even if not on ground) // 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->cheats & CF_NOCLIP2) { player->bob = 0; } else if ((player->mo->flags & MF_NOGRAVITY) && !player->onground) { player->bob = 0.5; } else { player->bob = player->Vel.LengthSquared(); if (player->bob == 0) { still = true; } else { player->bob *= player->userinfo.GetMoveBob(); if (player->bob > MAXBOB) player->bob = MAXBOB; } } double defaultviewheight = player->mo->ViewHeight + player->crouchviewdelta; if (player->cheats & CF_NOVELOCITY) { player->viewz = player->mo->Z() + defaultviewheight; if (player->viewz > player->mo->ceilingz-4) player->viewz = player->mo->ceilingz-4; return; } if (still) { if (player->health > 0) { angle = level.time / (120 * TICRATE / 35.) * 360.; bob = player->userinfo.GetStillBob() * angle.Sin(); } else { bob = 0; } } else { angle = level.time / (20 * TICRATE / 35.) * 360.; bob = player->bob * angle.Sin() * (player->mo->waterlevel > 1 ? 0.25f : 0.5f); } // 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/2)) { player->viewheight = defaultviewheight/2; if (player->deltaviewheight <= 0) player->deltaviewheight = 1 / 65536.; } if (player->deltaviewheight) { player->deltaviewheight += 0.25; if (!player->deltaviewheight) player->deltaviewheight = 1/65536.; } } if (player->morphTics) { bob = 0; } player->viewz = player->mo->Z() + player->viewheight + (bob * player->mo->ViewBob); // [SP] Allow DECORATE changes to view bobbing speed. 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) { player->viewz = player->mo->ceilingz - 4; } if (player->viewz < player->mo->floorz + 4) { player->viewz = player->mo->floorz + 4; } } DEFINE_ACTION_FUNCTION(APlayerPawn, CalcHeight) { PARAM_SELF_PROLOGUE(APlayerPawn); P_CalcHeight(self->player); return 0; } 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(APlayerPawn); 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->FallingScreamMinSpeed && player->mo->Vel.Z >= -player->mo->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(APlayerPawn); 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(APlayerPawn); 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; IFVIRTUALPTR(player->mo, APlayerPawn, 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 = 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 void BackupNodeList(AActor *act, nodetype *head, nodetype *linktype::*otherlist, TArray &prevbackup, TArray &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 nodetype *RestoreNodeList(AActor *act, nodetype *head, nodetype *linktype::*otherlist, TArray &prevbackup, TArray &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; APlayerPawn *act = player->mo; memcpy(PredictionActorBackup, &act->snext, sizeof(APlayerPawn) - ((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; APlayerPawn *act = player->mo; AActor *savedcamera = player->camera; TObjPtr InvSel = act->InvSel; int inventorytics = player->inventorytics; *player = PredictionPlayerBackup; // 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, includeing 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, PredictionActorBackup, sizeof(APlayerPawn) - ((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; } act->InvSel = 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(APlayerPawn, crouchsprite) DEFINE_FIELD(APlayerPawn, MaxHealth) DEFINE_FIELD(APlayerPawn, BonusHealth) DEFINE_FIELD(APlayerPawn, MugShotMaxHealth) DEFINE_FIELD(APlayerPawn, RunHealth) DEFINE_FIELD(APlayerPawn, PlayerFlags) DEFINE_FIELD(APlayerPawn, InvFirst) DEFINE_FIELD(APlayerPawn, InvSel) DEFINE_FIELD(APlayerPawn, JumpZ) DEFINE_FIELD(APlayerPawn, GruntSpeed) DEFINE_FIELD(APlayerPawn, FallingScreamMinSpeed) DEFINE_FIELD(APlayerPawn, FallingScreamMaxSpeed) DEFINE_FIELD(APlayerPawn, ViewHeight) DEFINE_FIELD(APlayerPawn, ForwardMove1) DEFINE_FIELD(APlayerPawn, ForwardMove2) DEFINE_FIELD(APlayerPawn, SideMove1) DEFINE_FIELD(APlayerPawn, SideMove2) DEFINE_FIELD(APlayerPawn, ScoreIcon) DEFINE_FIELD(APlayerPawn, SpawnMask) DEFINE_FIELD(APlayerPawn, MorphWeapon) DEFINE_FIELD(APlayerPawn, AttackZOffset) DEFINE_FIELD(APlayerPawn, UseRange) DEFINE_FIELD(APlayerPawn, AirCapacity) DEFINE_FIELD(APlayerPawn, FlechetteType) DEFINE_FIELD(APlayerPawn, DamageFade) DEFINE_FIELD(APlayerPawn, ViewBob) DEFINE_FIELD(APlayerPawn, FullHeight) DEFINE_FIELD(APlayerPawn, SoundClass) DEFINE_FIELD(APlayerPawn, Face) DEFINE_FIELD(APlayerPawn, Portrait) DEFINE_FIELD(APlayerPawn, Slot) DEFINE_FIELD(APlayerPawn, HexenArmor) DEFINE_FIELD(APlayerPawn, ColorRangeStart) DEFINE_FIELD(APlayerPawn, ColorRangeEnd) 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)