raze/source/games/exhumed/src/player.cpp
Mitchell Richters 3d6f1e1a04 - Exhumed: Store dedicated player velocity in Player struct.
* Changes implementation in 67c7dd65f9.
* Cannot rely on the actor's velocity alone as parts of the game modify this, such as getting hit by lava dudes, but game nullifies this at the start of the player's tic.
2023-03-16 12:17:42 +11:00

2819 lines
81 KiB
C++

//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT
This file is part of PCExhumed.
PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//-------------------------------------------------------------------------
#include "ns.h"
#include "player.h"
#include "aistuff.h"
#include "exhumed.h"
#include "names.h"
#include "engine.h"
#include "sequence.h"
#include "view.h"
#include "input.h"
#include "status.h"
#include "sound.h"
#include "sound.h"
#include "buildtiles.h"
#include "gstrings.h"
#include "gamestate.h"
#include "mapinfo.h"
#include "automap.h"
#include "interpolate.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
BEGIN_PS_NS
extern int nStatusSeqOffset;
int obobangle = 0, bobangle = 0;
static actionSeq PlayerSeq[] = {
{18, 0}, {0, 0}, {9, 0}, {27, 0}, {63, 0},
{72, 0}, {54, 0}, {45, 0}, {54, 0}, {81, 0},
{90, 0}, {99, 0}, {108, 0}, {8, 0}, {0, 0},
{139, 0}, {117, 1}, {119, 1}, {120, 1}, {121, 1},
{122, 1}
};
static const uint8_t nHeightTemplate[] = { 0, 0, 0, 0, 0, 0, 7, 7, 7, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0 };
static constexpr double nActionEyeLevel[] = {
-55.0, -55.0, -55.0, -55.0, -55.0, -55.0, -32.5,
-32.5, -32.5, -32.5, -32.5, -32.5, -32.5, -55.0,
-55.0, -55.0, -55.0, -55.0, -55.0, -55.0, -55.0
};
static const uint16_t nGunLotag[] = { 52, 53, 54, 55, 56, 57 };
static const uint16_t nGunPicnum[] = { 57, 488, 490, 491, 878, 899, 3455 };
static const int16_t nItemText[] = {
-1, -1, -1, -1, -1, -1, 18, 20, 19, 13, -1, 10, 1, 0, 2, -1, 3,
-1, 4, 5, 9, 6, 7, 8, -1, 11, -1, 13, 12, 14, 15, -1, 16, 17,
-1, -1, -1, 21, 22, -1, -1, -1, -1, -1, -1, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
int nLocalPlayer = 0;
Player PlayerList[kMaxPlayers];
TObjPtr<DExhumedActor*> nNetStartSprite[kMaxPlayers] = { };
double nStandHeight;
int PlayerCount;
int nNetStartSprites;
int nCurStartSprite;
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
size_t MarkPlayers()
{
for (auto& p : PlayerList)
{
GC::Mark(p.pActor);
GC::Mark(p.pDoppleSprite);
GC::Mark(p.pPlayerFloorSprite);
GC::Mark(p.pPlayerGrenade);
GC::Mark(p.pTarget);
}
GC::MarkArray(nNetStartSprite, kMaxPlayers);
return 6 * kMaxPlayers;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetSavePoint(int nPlayer, const DVector3& pos, sectortype* pSector, DAngle nAngle)
{
PlayerList[nPlayer].sPlayerSave.pos = pos;
PlayerList[nPlayer].sPlayerSave.pSector = pSector;
PlayerList[nPlayer].sPlayerSave.nAngle = nAngle;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void feebtag(const DVector3& pos, sectortype* pSector, DExhumedActor **nSprite, int nVal2, double deflen)
{
*nSprite = nullptr;
auto startwall = pSector->walls.Data();
int nWalls = pSector->walls.Size();
int var_20 = nVal2 & 2;
int var_14 = nVal2 & 1;
while (1)
{
if (pSector != nullptr)
{
ExhumedSectIterator it(pSector);
while (auto pActor = it.Next())
{
int nStat = pActor->spr.statnum;
if (nStat >= 900 && !(pActor->spr.cstat & CSTAT_SPRITE_INVISIBLE))
{
auto diff = pActor->spr.pos - pos;
if (diff.Z < 20 && diff.Z > -100)
{
double len = diff.XY().Length();
if (len < deflen && ((nStat != 950 && nStat != 949) || !(var_14 & 1)) && ((nStat != 912 && nStat != 913) || !(var_20 & 2)))
{
deflen = len;
*nSprite = pActor;
}
}
}
}
}
nWalls--;
if (nWalls < 0)
return;
pSector = startwall->nextSector();
startwall++;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void InitPlayer()
{
for (int i = 0; i < kMaxPlayers; i++) {
PlayerList[i].pActor = nullptr;
PlayerList[i].Angles = {};
PlayerList[i].pPlayerPushSect = nullptr;
PlayerList[i].pPlayerViewSect = nullptr;
}
}
void InitPlayerKeys(int nPlayer)
{
PlayerList[nPlayer].keys = 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void InitPlayerInventory(int nPlayer)
{
memset(&PlayerList[nPlayer], 0, sizeof(Player));
PlayerList[nPlayer].nItem = -1;
PlayerList[nPlayer].nPlayerSwear = 4;
ResetPlayerWeapons(nPlayer);
PlayerList[nPlayer].nLives = kDefaultLives;
PlayerList[nPlayer].pActor = nullptr;
PlayerList[nPlayer].Angles = {};
PlayerList[nPlayer].nRun = -1;
PlayerList[nPlayer].nPistolClip = 6;
PlayerList[nPlayer].nPlayerClip = 0;
PlayerList[nPlayer].nCurrentWeapon = 0;
if (nPlayer == nLocalPlayer) {
automapMode = am_off;
}
PlayerList[nPlayer].nPlayerScore = 0;
auto pixels = GetRawPixels(tileGetTextureID(kTile3571 + nPlayer));
PlayerList[nPlayer].nPlayerColor = pixels[tileWidth(nPlayer + kTile3571) * tileHeight(nPlayer + kTile3571) / 2];
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int GetPlayerFromActor(DExhumedActor* pActor)
{
return RunData[pActor->spr.intowner].nObjIndex;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void RestartPlayer(int nPlayer)
{
auto plr = &PlayerList[nPlayer];
auto pActor = plr->pActor;
DExhumedActor* pDopSprite = plr->pDoppleSprite;
DExhumedActor* floorsprt;
if (pActor)
{
runlist_DoSubRunRec(pActor->spr.intowner);
runlist_FreeRun(pActor->spr.lotag - 1);
ChangeActorStat(pActor, 0);
plr->pActor = nullptr;
plr->Angles = {};
DExhumedActor* pFloorSprite = plr->pPlayerFloorSprite;
if (pFloorSprite != nullptr) {
DeleteActor(pFloorSprite);
}
if (pDopSprite)
{
runlist_DoSubRunRec(pDopSprite->spr.intowner);
runlist_FreeRun(pDopSprite->spr.lotag - 1);
DeleteActor(pDopSprite);
}
}
pActor = GrabBody();
ChangeActorSect(pActor, plr->sPlayerSave.pSector);
ChangeActorStat(pActor, 100);
auto pDActor = insertActor(pActor->sector(), 100);
plr->pDoppleSprite = pDActor;
if (nTotalPlayers > 1)
{
DExhumedActor* nNStartSprite = nNetStartSprite[nCurStartSprite];
nCurStartSprite++;
if (nCurStartSprite >= nNetStartSprites) {
nCurStartSprite = 0;
}
pActor->spr.pos = nNStartSprite->spr.pos;
ChangeActorSect(pActor, nNStartSprite->sector());
pActor->spr.Angles.Yaw = nNStartSprite->spr.Angles.Yaw;
floorsprt = insertActor(pActor->sector(), 0);
floorsprt->spr.pos = pActor->spr.pos;
floorsprt->spr.scale = DVector2(1, 1);
floorsprt->spr.cstat = CSTAT_SPRITE_ALIGNMENT_FLOOR;
floorsprt->spr.picnum = nPlayer + kTile3571;
}
else
{
pActor->spr.pos.XY() = plr->sPlayerSave.pos.XY();
pActor->spr.pos.Z = plr->sPlayerSave.pSector->floorz;
pActor->spr.Angles.Yaw = plr->sPlayerSave.nAngle;
floorsprt = nullptr;
}
pActor->backuploc();
plr->pPlayerFloorSprite = floorsprt;
pActor->spr.cstat = CSTAT_SPRITE_BLOCK_ALL;
pActor->spr.shade = -12;
pActor->clipdist = 14.5;
pActor->spr.pal = 0;
pActor->spr.scale = DVector2(0.625, 0.625);
pActor->spr.xoffset = 0;
pActor->spr.yoffset = 0;
pActor->spr.picnum = seq_GetSeqPicnum(kSeqJoe, 18, 0);
pActor->vel.X = 0;
pActor->vel.Y = 0;
pActor->vel.Z = 0;
nStandHeight = GetActorHeight(pActor);
pActor->spr.hitag = 0;
pActor->spr.extra = -1;
pActor->spr.lotag = runlist_HeadRun() + 1;
pDActor->spr.pos = pActor->spr.pos;
pDActor->spr.scale = pActor->spr.scale;
pDActor->spr.xoffset = 0;
pDActor->spr.yoffset = 0;
pDActor->spr.shade = pActor->spr.shade;
pDActor->spr.Angles.Yaw = pActor->spr.Angles.Yaw;
pDActor->spr.cstat = pActor->spr.cstat;
pDActor->spr.lotag = runlist_HeadRun() + 1;
plr->nAction = 0;
plr->nHealth = 800; // TODO - define
if (nNetPlayerCount) {
plr->nHealth = 1600; // TODO - define
}
plr->nSeqSize = 0;
plr->pActor = pActor;
plr->Angles = {};
plr->Angles.initialize(plr->pActor);
plr->bIsMummified = false;
if (plr->invincibility >= 0) {
plr->invincibility = 0;
}
plr->nTorch = 0;
plr->nMaskAmount = 0;
SetTorch(nPlayer, 0);
plr->nInvisible = 0;
plr->bIsFiring = 0;
plr->nSeqSize2 = 0;
plr->pPlayerViewSect = plr->sPlayerSave.pSector;
plr->nState = 0;
plr->nDouble = 0;
plr->nSeq = kSeqJoe;
plr->nPlayerPushSound = -1;
plr->nNextWeapon = -1;
if (plr->nCurrentWeapon == 7) {
plr->nCurrentWeapon = plr->nLastWeapon;
}
plr->nLastWeapon = 0;
plr->nAir = 100;
if (!(currentLevel->gameflags & LEVEL_EX_MULTI))
{
RestoreMinAmmo(nPlayer);
}
else
{
ResetPlayerWeapons(nPlayer);
plr->nMagic = 0;
}
plr->pPlayerGrenade = nullptr;
pActor->oviewzoffset = pActor->viewzoffset = -55.;
dVertPan[nPlayer] = 0;
nTemperature[nPlayer] = 0;
plr->nThrust.Zero();
plr->nDestVertPan = plr->pActor->PrevAngles.Pitch = plr->pActor->spr.Angles.Pitch = nullAngle;
plr->nBreathTimer = 90;
plr->nTauntTimer = RandomSize(3) + 3;
pDActor->spr.intowner = runlist_AddRunRec(pDActor->spr.lotag - 1, nPlayer, 0xA0000);
pActor->spr.intowner = runlist_AddRunRec(pActor->spr.lotag - 1, nPlayer, 0xA0000);
if (plr->nRun < 0) {
plr->nRun = runlist_AddRunRec(NewRun, nPlayer, 0xA0000);
}
BuildRa(nPlayer);
if (nPlayer == nLocalPlayer)
{
RestoreGreenPal();
plr->bPlayerPan = plr->bLockPan = false;
}
plr->ototalvel = plr->totalvel = 0;
PlayerList[nPlayer].nCurrentItem = -1;
plr->nDeathType = 0;
nQuake[nPlayer] = 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int GrabPlayer()
{
if (PlayerCount >= kMaxPlayers) {
return -1;
}
return PlayerCount++;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void StartDeathSeq(int nPlayer, int nVal)
{
FreeRa(nPlayer);
auto pActor = PlayerList[nPlayer].pActor;
PlayerList[nPlayer].nHealth = 0;
int nLotag = pActor->sector()->lotag;
if (nLotag > 0) {
runlist_SignalRun(nLotag - 1, nPlayer, &ExhumedAI::EnterSector);
}
if (PlayerList[nPlayer].pPlayerGrenade)
{
ThrowGrenade(nPlayer, 0, -10000);
}
else
{
if (nNetPlayerCount)
{
int nWeapon = PlayerList[nPlayer].nCurrentWeapon;
if (nWeapon > kWeaponSword && nWeapon <= kWeaponRing)
{
auto pSector = pActor->sector();
if (pSector->pBelow != nullptr) {
pSector = pSector->pBelow;
}
auto pGunActor = GrabBodyGunSprite();
ChangeActorSect(pGunActor, pSector);
pGunActor->spr.pos = { pActor->spr.pos.X, pActor->spr.pos.Y, pSector->floorz - 2 };
ChangeActorStat(pGunActor, nGunLotag[nWeapon] + 900);
pGunActor->spr.picnum = nGunPicnum[nWeapon];
BuildItemAnim(pGunActor);
}
}
}
StopFiringWeapon(nPlayer);
PlayerList[nPlayer].pActor->PrevAngles.Pitch = PlayerList[nPlayer].pActor->spr.Angles.Pitch = nullAngle;
pActor->oviewzoffset = pActor->viewzoffset = -55;
PlayerList[nPlayer].nInvisible = 0;
dVertPan[nPlayer] = 15;
pActor->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE;
SetNewWeaponImmediate(nPlayer, -2);
if (pActor->sector()->Damage <= 0)
{
PlayerList[nPlayer].nDeathType = nVal;
}
else
{
PlayerList[nPlayer].nDeathType = 2;
}
nVal *= 2;
if (nVal || !(pActor->sector()->Flag & kSectUnderwater))
{
PlayerList[nPlayer].nAction = nVal + 17;
}
else {
PlayerList[nPlayer].nAction = 16;
}
PlayerList[nPlayer].nSeqSize = 0;
pActor->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
if (nTotalPlayers == 1)
{
if (!(currentLevel->gameflags & LEVEL_EX_TRAINING)) { // if not on the training level
PlayerList[nPlayer].nLives--;
}
if (PlayerList[nPlayer].nLives < 0) {
PlayerList[nPlayer].nLives = 0;
}
}
PlayerList[nPlayer].ototalvel = PlayerList[nPlayer].totalvel = 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int AddAmmo(int nPlayer, int nWeapon, int nAmmoAmount)
{
if (!nAmmoAmount) {
nAmmoAmount = 1;
}
int nCurAmmo = PlayerList[nPlayer].nAmmo[nWeapon];
if (nCurAmmo >= 300 && nAmmoAmount > 0) {
return 0;
}
nAmmoAmount = nCurAmmo + nAmmoAmount;
if (nAmmoAmount > 300) {
nAmmoAmount = 300;
}
PlayerList[nPlayer].nAmmo[nWeapon] = nAmmoAmount;
if (nWeapon == 1)
{
if (!PlayerList[nPlayer].nPistolClip) {
PlayerList[nPlayer].nPistolClip = 6;
}
}
return 1;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetPlayerMummified(int nPlayer, int bIsMummified)
{
DExhumedActor* pActor = PlayerList[nPlayer].pActor;
pActor->vel.Y = 0;
pActor->vel.X = 0;
PlayerList[nPlayer].bIsMummified = bIsMummified;
if (bIsMummified)
{
PlayerList[nPlayer].nAction = 13;
PlayerList[nPlayer].nSeq = kSeqMummy;
}
else
{
PlayerList[nPlayer].nAction = 0;
PlayerList[nPlayer].nSeq = kSeqJoe;
}
PlayerList[nPlayer].nSeqSize = 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void ShootStaff(int nPlayer)
{
PlayerList[nPlayer].nAction = 15;
PlayerList[nPlayer].nSeqSize = 0;
PlayerList[nPlayer].nSeq = kSeqJoe;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void PlayAlert(const char *str)
{
StatusMessage(300, str);
PlayLocalSound(StaticSound[kSound63], 0);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void pickupMessage(int no)
{
no = nItemText[no];
if (no != -1)
{
FStringf label("TXT_EX_PICKUP%d", no + 1);
auto str = GStrings[label];
if (str) Printf(PRINT_NOTIFY, "%s\n", str);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void UpdatePlayerSpriteAngle(Player* pPlayer)
{
if (pPlayer->pActor) inita = pPlayer->pActor->spr.Angles.Yaw.Normalized360();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void AIPlayer::Draw(RunListEvent* ev)
{
int nPlayer = RunData[ev->nRun].nObjIndex;
assert(nPlayer >= 0 && nPlayer < kMaxPlayers);
int nAction = PlayerList[nPlayer].nAction;
seq_PlotSequence(ev->nParam, SeqOffsets[PlayerList[nPlayer].nSeq] + PlayerSeq[nAction].a, PlayerList[nPlayer].nSeqSize, PlayerSeq[nAction].b);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void AIPlayer::RadialDamage(RunListEvent* ev)
{
int nPlayer = RunData[ev->nRun].nObjIndex;
assert(nPlayer >= 0 && nPlayer < kMaxPlayers);
auto pPlayerActor = PlayerList[nPlayer].pActor;
if (PlayerList[nPlayer].nHealth <= 0)
{
return;
}
ev->nDamage = runlist_CheckRadialDamage(pPlayerActor);
Damage(ev);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void AIPlayer::Damage(RunListEvent* ev)
{
int nDamage = ev->nDamage;
int nPlayer = RunData[ev->nRun].nObjIndex;
auto pPlayerActor = PlayerList[nPlayer].pActor;
int nAction = PlayerList[nPlayer].nAction;
DExhumedActor* pDopple = PlayerList[nPlayer].pDoppleSprite;
if (!nDamage) {
return;
}
DExhumedActor* pActor2 = (!ev->isRadialEvent()) ? ev->pOtherActor : ev->pRadialActor->pTarget.Get();
// ok continue case 0x80000 as normal, loc_1C57C
if (!PlayerList[nPlayer].nHealth) {
return;
}
if (!PlayerList[nPlayer].invincibility)
{
PlayerList[nPlayer].nHealth -= nDamage;
if (nPlayer == nLocalPlayer)
{
TintPalette(nDamage, 0, 0);
}
}
if (PlayerList[nPlayer].nHealth > 0)
{
if (nDamage > 40 || (totalmoves & 0xF) < 2)
{
if (PlayerList[nPlayer].invincibility) {
return;
}
if (pPlayerActor->sector()->Flag & kSectUnderwater)
{
if (nAction != 12)
{
PlayerList[nPlayer].nSeqSize = 0;
PlayerList[nPlayer].nAction = 12;
return;
}
}
else
{
if (nAction != 4)
{
PlayerList[nPlayer].nSeqSize = 0;
PlayerList[nPlayer].nAction = 4;
if (pActor2)
{
PlayerList[nPlayer].nPlayerSwear--;
if (PlayerList[nPlayer].nPlayerSwear <= 0)
{
D3PlayFX(StaticSound[kSound52], pDopple);
PlayerList[nPlayer].nPlayerSwear = RandomSize(3) + 4;
}
}
}
}
}
return;
}
else
{
// player has died
if (pActor2 && pActor2->spr.statnum == 100)
{
int nPlayer2 = GetPlayerFromActor(pActor2);
if (nPlayer2 == nPlayer) // player caused their own death
{
PlayerList[nPlayer].nPlayerScore--;
}
else
{
PlayerList[nPlayer].nPlayerScore++;
}
}
else if (pActor2 == nullptr)
{
PlayerList[nPlayer].nPlayerScore--;
}
if (ev->isRadialEvent())
{
for (int i = 122; i <= 131; i++)
{
BuildCreatureChunk(pPlayerActor, seq_GetSeqPicnum(kSeqJoe, i, 0));
}
StartDeathSeq(nPlayer, 1);
}
else
{
StartDeathSeq(nPlayer, 0);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool CheckMovingBlocks(int nPlayer, Collision& nMove, DVector3& spr_pos, sectortype* spr_sect)
{
auto pPlayerActor = PlayerList[nPlayer].pActor;
double const zz = pPlayerActor->vel.Z;
if (nMove.type == kHitSector || nMove.type == kHitWall)
{
sectortype* sect;
DAngle nNormal = nullAngle;
if (nMove.type == kHitSector)
{
sect = nMove.hitSector;
// Hm... Normal calculation here was broken.
}
else //if (nMove.type == kHitWall)
{
sect = nMove.hitWall->nextSector();
nNormal = GetWallNormal(nMove.hitWall);
}
// moving blocks - move this to a separate function!
if (sect != nullptr)
{
if ((sect->hitag == 45) && bTouchFloor)
{
auto nDiff = absangle(nNormal, pPlayerActor->spr.Angles.Yaw + DAngle180);
if (nDiff <= DAngle45)
{
PlayerList[nPlayer].pPlayerPushSect = sect;
DVector2 vel = PlayerList[nPlayer].vel;
auto nMyAngle = vel.Angle().Normalized360();
setsectinterpolate(sect);
MoveSector(sect, nMyAngle, vel);
if (PlayerList[nPlayer].nPlayerPushSound <= -1)
{
PlayerList[nPlayer].nPlayerPushSound = 1;
int nBlock = PlayerList[nPlayer].pPlayerPushSect->extra;
DExhumedActor* pBlockActor = sBlockInfo[nBlock].pActor;
D3PlayFX(StaticSound[kSound23], pBlockActor, 0x4000);
}
else
{
pPlayerActor->spr.pos = spr_pos;
ChangeActorSect(pPlayerActor, spr_sect);
}
movesprite(pPlayerActor, vel, zz, -20, CLIPMASK0);
return true;
}
}
}
}
return false;
}
//---------------------------------------------------------------------------
//
// this function is pure spaghetti madness... :(
//
//---------------------------------------------------------------------------
void AIPlayer::Tick(RunListEvent* ev)
{
int var_48 = 0;
int var_40;
bool mplevel = (currentLevel->gameflags & LEVEL_EX_MULTI);
int nPlayer = RunData[ev->nRun].nObjIndex;
assert(nPlayer >= 0 && nPlayer < kMaxPlayers);
auto pPlayerActor = PlayerList[nPlayer].pActor;
DExhumedActor* pDopple = PlayerList[nPlayer].pDoppleSprite;
int nAction = PlayerList[nPlayer].nAction;
int nActionB = PlayerList[nPlayer].nAction;
pPlayerActor->vel.XY() = PlayerList[nPlayer].vel;
if (PlayerList[nPlayer].nCurrentItem > -1)
{
UseItem(nPlayer, PlayerList[nPlayer].nCurrentItem);
PlayerList[nPlayer].nCurrentItem = -1;
}
pPlayerActor->spr.picnum = seq_GetSeqPicnum(PlayerList[nPlayer].nSeq, PlayerSeq[nHeightTemplate[nAction]].a, PlayerList[nPlayer].nSeqSize);
pDopple->spr.picnum = pPlayerActor->spr.picnum;
if (PlayerList[nPlayer].nTorch > 0)
{
PlayerList[nPlayer].nTorch--;
if (PlayerList[nPlayer].nTorch == 0)
{
SetTorch(nPlayer, 0);
}
else
{
if (nPlayer != nLocalPlayer)
{
nFlashDepth = 5;
AddFlash(pPlayerActor->sector(), pPlayerActor->spr.pos, 0);
}
}
}
if (PlayerList[nPlayer].nDouble > 0)
{
PlayerList[nPlayer].nDouble--;
if (PlayerList[nPlayer].nDouble == 150 && nPlayer == nLocalPlayer) {
PlayAlert(GStrings("TXT_EX_WEAPONEX"));
}
}
if (PlayerList[nPlayer].nInvisible > 0)
{
PlayerList[nPlayer].nInvisible--;
if (PlayerList[nPlayer].nInvisible == 0)
{
pPlayerActor->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE; // set visible
DExhumedActor* pFloorSprite = PlayerList[nPlayer].pPlayerFloorSprite;
if (pFloorSprite != nullptr) {
pFloorSprite->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE; // set visible
}
}
else if (PlayerList[nPlayer].nInvisible == 150 && nPlayer == nLocalPlayer)
{
PlayAlert(GStrings("TXT_EX_INVISEX"));
}
}
if (PlayerList[nPlayer].invincibility > 0)
{
PlayerList[nPlayer].invincibility--;
if (PlayerList[nPlayer].invincibility == 150 && nPlayer == nLocalPlayer) {
PlayAlert(GStrings("TXT_EX_INVINCEX"));
}
}
if (nQuake[nPlayer] != 0)
{
nQuake[nPlayer] = -nQuake[nPlayer];
if (nQuake[nPlayer] > 0)
{
nQuake[nPlayer] -= 2.;
if (nQuake[nPlayer] < 0)
nQuake[nPlayer] = 0;
}
}
PlayerList[nPlayer].Angles.doViewYaw(&PlayerList[nLocalPlayer].input);
// loc_1A494:
if (SyncInput())
{
PlayerList[nPlayer].pActor->spr.Angles.Yaw += DAngle::fromDeg(PlayerList[nPlayer].input.avel);
}
PlayerList[nPlayer].Angles.doYawKeys(&PlayerList[nLocalPlayer].input);
UpdatePlayerSpriteAngle(&PlayerList[nPlayer]);
// player.zvel is modified within Gravity()
double zVel = pPlayerActor->vel.Z;
Gravity(pPlayerActor);
if (pPlayerActor->vel.Z >= 6500/256. && zVel < 6500 / 256.)
{
D3PlayFX(StaticSound[kSound17], pPlayerActor);
}
// loc_1A4E6
auto pSector = pPlayerActor->sector();
int nSectFlag = PlayerList[nPlayer].pPlayerViewSect->Flag;
auto playerPos = pPlayerActor->spr.pos.XY();
DVector2 vect = PlayerList[nPlayer].vel;
double zz = pPlayerActor->vel.Z;
if (pPlayerActor->vel.Z > 32)
pPlayerActor->vel.Z = 32;
if (PlayerList[nPlayer].bIsMummified)
{
vect *= 0.5;
}
auto spr_pos = pPlayerActor->spr.pos;
auto spr_sect = pPlayerActor->sector();
// TODO
// nSectFlag & kSectUnderwater;
zVel = pPlayerActor->vel.Z;
Collision nMove;
nMove.setNone();
if (bSlipMode)
{
pPlayerActor->spr.pos += vect;
SetActor(pPlayerActor, pPlayerActor->spr.pos);
pPlayerActor->spr.pos.Z = pPlayerActor->sector()->floorz;
}
else
{
nMove = movesprite(pPlayerActor, vect, zz, -20, CLIPMASK0);
auto pPlayerSect = pPlayerActor->sector();
pushmove(pPlayerActor->spr.pos, &pPlayerSect, pPlayerActor->clipdist, 20, -20, CLIPMASK0);
if (pPlayerSect != pPlayerActor->sector()) {
ChangeActorSect(pPlayerActor, pPlayerSect);
}
}
// loc_1A6E4
if (inside(pPlayerActor->spr.pos.X, pPlayerActor->spr.pos.Y, pPlayerActor->sector()) != 1)
{
ChangeActorSect(pPlayerActor, spr_sect);
pPlayerActor->spr.pos.XY() = spr_pos.XY();
if (zVel < pPlayerActor->vel.Z) {
pPlayerActor->vel.Z = zVel;
}
}
// int _bTouchFloor = bTouchFloor;
int bUnderwater = pPlayerActor->sector()->Flag & kSectUnderwater;
if (bUnderwater)
{
PlayerList[nPlayer].nThrust /= 2;
}
// Trigger Ramses?
if ((pPlayerActor->sector()->Flag & 0x8000) && bTouchFloor)
{
if (nTotalPlayers <= 1)
{
pPlayerActor->spr.Angles = DRotator(nullAngle, GetAngleToSprite(pPlayerActor, pSpiritSprite), nullAngle);
pPlayerActor->backupang();
PlayerList[nPlayer].vel.Zero();
pPlayerActor->vel.Zero();
if (nFreeze < 1)
{
nFreeze = 1;
StopAllSounds();
StopLocalSound();
InitSpiritHead();
PlayerList[nPlayer].nDestVertPan = nullAngle;
pPlayerActor->spr.Angles.Pitch = currentLevel->ex_ramses_horiz;
}
}
else
{
LevelFinished();
}
return;
}
if (nMove.type || nMove.exbits)
{
if (bTouchFloor)
{
// Damage stuff..
PlayerList[nPlayer].nThrust /= 2;
if (nPlayer == nLocalPlayer)
{
double zVelB = zVel;
if (zVelB < 0) {
zVelB = -zVelB;
}
if (zVelB > 2 && !PlayerList[nPlayer].pActor->spr.Angles.Pitch.Sgn() && cl_slopetilting) {
PlayerList[nPlayer].nDestVertPan = nullAngle;
}
}
if (zVel >= 6500 / 256.)
{
pPlayerActor->vel.XY() *= 0.25;
runlist_DamageEnemy(pPlayerActor, nullptr, ((int(zVel * 256) - 6500) >> 7) + 10);
if (PlayerList[nPlayer].nHealth <= 0)
{
pPlayerActor->vel.X = 0;
pPlayerActor->vel.Y = 0;
StopActorSound(pPlayerActor);
PlayFXAtXYZ(StaticSound[kSoundJonFDie], pPlayerActor->spr.pos, CHANF_NONE, 1); // CHECKME
}
else
{
D3PlayFX(StaticSound[kSound27] | 0x2000, pPlayerActor);
}
}
}
if (CheckMovingBlocks(nPlayer, nMove, spr_pos, spr_sect))
goto sectdone;
}
// loc_1AB46:
if (PlayerList[nPlayer].nPlayerPushSound > -1)
{
if (PlayerList[nPlayer].pPlayerPushSect != nullptr)
{
StopActorSound(sBlockInfo[PlayerList[nPlayer].pPlayerPushSect->extra].pActor);
}
PlayerList[nPlayer].nPlayerPushSound = -1;
}
sectdone:
if (!PlayerList[nPlayer].bPlayerPan && !PlayerList[nPlayer].bLockPan)
{
PlayerList[nPlayer].nDestVertPan = maphoriz((pPlayerActor->spr.pos.Z - spr_pos.Z) * 2.);
}
playerPos -= pPlayerActor->spr.pos.XY();
PlayerList[nPlayer].ototalvel = PlayerList[nPlayer].totalvel;
PlayerList[nPlayer].totalvel = int(playerPos.Length() * worldtoint);
auto pViewSect = pPlayerActor->sector();
double EyeZ = pPlayerActor->getOffsetZ() + nQuake[nPlayer];
while (1)
{
double nCeilZ = pViewSect->ceilingz;
if (EyeZ >= nCeilZ)
break;
if (pViewSect->pAbove == nullptr)
break;
pViewSect = pViewSect->pAbove;
}
// Do underwater sector check
if (bUnderwater)
{
if (pViewSect != pPlayerActor->sector())
{
if (nMove.type == kHitWall)
{
auto pos = pPlayerActor->spr.pos;
ChangeActorSect(pPlayerActor, pViewSect);
double fz = pViewSect->floorz - 20;
pPlayerActor->spr.pos = DVector3(spr_pos.XY(), fz);
auto coll = movesprite(pPlayerActor, vect, 0, 0, CLIPMASK0);
if (coll.type == kHitWall)
{
ChangeActorSect(pPlayerActor, pPlayerActor->sector());
pPlayerActor->spr.pos = pos;
}
else
{
pPlayerActor->spr.pos.Z = fz-1;
D3PlayFX(StaticSound[kSound42], pPlayerActor);
}
}
}
}
// loc_1ADAF
PlayerList[nPlayer].pPlayerViewSect = pViewSect;
PlayerList[nPlayer].nPlayerD = (pPlayerActor->spr.pos - spr_pos);
int var_5C = pViewSect->Flag & kSectUnderwater;
auto actions = PlayerList[nPlayer].input.actions;
// loc_1AEF5:
if (PlayerList[nPlayer].nHealth > 0)
{
if (PlayerList[nPlayer].nMaskAmount > 0)
{
PlayerList[nPlayer].nMaskAmount--;
if (PlayerList[nPlayer].nMaskAmount == 150 && nPlayer == nLocalPlayer) {
PlayAlert(GStrings("TXT_EX_MASKEX"));
}
}
if (!PlayerList[nPlayer].invincibility)
{
// Handle air
PlayerList[nPlayer].nBreathTimer--;
if (PlayerList[nPlayer].nBreathTimer <= 0)
{
PlayerList[nPlayer].nBreathTimer = 90;
// if underwater
if (var_5C)
{
if (PlayerList[nPlayer].nMaskAmount > 0)
{
D3PlayFX(StaticSound[kSound30], pPlayerActor);
PlayerList[nPlayer].nAir = 100;
}
else
{
PlayerList[nPlayer].nAir -= 25;
if (PlayerList[nPlayer].nAir > 0)
{
D3PlayFX(StaticSound[kSound25], pPlayerActor);
}
else
{
PlayerList[nPlayer].nHealth += (PlayerList[nPlayer].nAir << 2);
if (PlayerList[nPlayer].nHealth <= 0)
{
PlayerList[nPlayer].nHealth = 0;
StartDeathSeq(nPlayer, 0);
}
PlayerList[nPlayer].nAir = 0;
if (PlayerList[nPlayer].nHealth < 300)
{
D3PlayFX(StaticSound[kSound79], pPlayerActor);
}
else
{
D3PlayFX(StaticSound[kSound19], pPlayerActor);
}
}
}
DoBubbles(nPlayer);
}
}
}
// loc_1B0B9
if (var_5C) // if underwater
{
if (PlayerList[nPlayer].nTorch > 0)
{
PlayerList[nPlayer].nTorch = 0;
SetTorch(nPlayer, 0);
}
}
else
{
auto pTmpSect = pPlayerActor->sector();
if (PlayerList[nPlayer].totalvel > 25 && pPlayerActor->spr.pos.Z > pTmpSect->floorz)
{
if (pTmpSect->Depth && !pTmpSect->Speed && !pTmpSect->Damage)
{
D3PlayFX(StaticSound[kSound42], pPlayerActor);
}
}
// CHECKME - wrong place?
if (nSectFlag & kSectUnderwater)
{
if (PlayerList[nPlayer].nAir < 50)
{
D3PlayFX(StaticSound[kSound14], pPlayerActor);
}
PlayerList[nPlayer].nBreathTimer = 1;
}
PlayerList[nPlayer].nBreathTimer--;
if (PlayerList[nPlayer].nBreathTimer <= 0)
{
PlayerList[nPlayer].nBreathTimer = 90;
}
if (PlayerList[nPlayer].nAir < 100)
{
PlayerList[nPlayer].nAir = 100;
}
}
// loc_1B1EB
DExhumedActor* pFloorActor = PlayerList[nPlayer].pPlayerFloorSprite;
if (nTotalPlayers > 1 && pFloorActor)
{
pFloorActor->spr.pos.XY() = pPlayerActor->spr.pos.XY();
if (pFloorActor->sector() != pPlayerActor->sector())
{
ChangeActorSect(pFloorActor, pPlayerActor->sector());
}
pFloorActor->spr.pos.Z = pPlayerActor->sector()->floorz;
}
int var_30 = 0;
if (PlayerList[nPlayer].nHealth >= 800)
{
var_30 = 2;
}
if (PlayerList[nPlayer].nMagic >= 1000)
{
var_30 |= 1;
}
// code to handle item pickup?
HitInfo near;
// neartag finds the nearest sector, wall, and sprite which has its hitag and/or lotag set to a value.
neartag(pPlayerActor->spr.pos, pPlayerActor->sector(), pPlayerActor->spr.Angles.Yaw, near, 128., NT_Hitag | NT_NoSpriteCheck);
DExhumedActor* pActorB;
feebtag(pPlayerActor->spr.pos, pPlayerActor->sector(), &pActorB, var_30, 48);
// Item pickup code
if (pActorB != nullptr && pActorB->spr.statnum >= 900)
{
int var_8C = 16;
int var_88 = 9;
int var_70 = pActorB->spr.statnum - 900;
int var_44 = 0;
// item lotags start at 6 (1-5 reserved?) so 0-offset them
int itemtype = var_70 - 6;
if (itemtype <= 54)
{
switch (itemtype)
{
do_default:
default:
{
// loc_1B3C7
// CHECKME - is order of evaluation correct?
if (!mplevel || (var_70 >= 25 && (var_70 <= 25 || var_70 == 50)))
{
// If this is an anim we need to properly destroy it so we need to do some proper detection and not wild guesses.
if (pActorB->nRun == pActorB->nDamage && pActorB->nRun != 0 && pActorB->nPhase == ITEM_MAGIC)
DestroyAnim(pActorB);
else
DeleteActor(pActorB);
}
else
{
StartRegenerate(pActorB);
}
do_default_b:
// loc_1BA74
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
}
case 0: // Speed Loader
{
if (AddAmmo(nPlayer, 1, pActorB->spr.hitag))
{
var_88 = StaticSound[kSoundAmmoPickup];
goto do_default;
}
break;
}
case 1: // Fuel Canister
{
if (AddAmmo(nPlayer, 3, pActorB->spr.hitag))
{
var_88 = StaticSound[kSoundAmmoPickup];
goto do_default;
}
break;
}
case 2: // M - 60 Ammo Belt
{
if (AddAmmo(nPlayer, 2, pActorB->spr.hitag))
{
var_88 = StaticSound[kSoundAmmoPickup];
CheckClip(nPlayer);
goto do_default;
}
break;
}
case 3: // Grenade
case 21:
case 49:
{
if (AddAmmo(nPlayer, 4, 1))
{
var_88 = StaticSound[kSoundAmmoPickup];
if (!(PlayerList[nPlayer].nPlayerWeapons & 0x10))
{
PlayerList[nPlayer].nPlayerWeapons |= 0x10;
SetNewWeaponIfBetter(nPlayer, 4);
}
if (var_70 == 55)
{
pActorB->spr.cstat = CSTAT_SPRITE_INVISIBLE;
DestroyItemAnim(pActorB);
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
}
else
{
goto do_default;
}
}
break;
}
case 4: // Pickable item
case 9: // Pickable item
case 10: // Reserved
case 18:
case 25:
case 28:
case 29:
case 30:
case 33:
case 34:
case 35:
case 36:
case 37:
case 38:
case 45:
case 52:
{
goto do_default;
}
case 5: // Map
{
GrabMap();
goto do_default;
}
case 6: // Berry Twig
{
if (pActorB->spr.hitag == 0) {
break;
}
var_88 = 20;
int edx = 40;
if (edx <= 0 || (!(var_30 & 2)))
{
if (!PlayerList[nPlayer].invincibility || edx > 0)
{
PlayerList[nPlayer].nHealth += edx;
if (PlayerList[nPlayer].nHealth > 800)
{
PlayerList[nPlayer].nHealth = 800;
}
else
{
if (PlayerList[nPlayer].nHealth < 0)
{
var_88 = -1;
StartDeathSeq(nPlayer, 0);
}
}
}
if (var_70 == 12)
{
pActorB->spr.hitag = 0;
pActorB->spr.picnum++;
ChangeActorStat(pActorB, 0);
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
}
else
{
if (var_70 != 14)
{
var_88 = 21;
}
else
{
var_44 = var_8C;
var_88 = 22;
var_8C = 0;
}
goto do_default;
}
}
break;
}
case 7: // Blood Bowl
{
int edx = 160;
// Same code as case 6 now till break
if (edx <= 0 || (!(var_30 & 2)))
{
if (!PlayerList[nPlayer].invincibility || edx > 0)
{
PlayerList[nPlayer].nHealth += edx;
if (PlayerList[nPlayer].nHealth > 800)
{
PlayerList[nPlayer].nHealth = 800;
}
else
{
if (PlayerList[nPlayer].nHealth < 0)
{
var_88 = -1;
StartDeathSeq(nPlayer, 0);
}
}
}
if (var_70 == 12)
{
pActorB->spr.hitag = 0;
pActorB->spr.picnum++;
ChangeActorStat(pActorB, 0);
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
}
else
{
if (var_70 != 14)
{
var_88 = 21;
}
else
{
var_44 = var_8C;
var_88 = 22;
var_8C = 0;
}
goto do_default;
}
}
break;
}
case 8: // Cobra Venom Bowl
{
int edx = -200;
// Same code as case 6 and 7 from now till break
if (edx <= 0 || (!(var_30 & 2)))
{
if (!PlayerList[nPlayer].invincibility || edx > 0)
{
PlayerList[nPlayer].nHealth += edx;
if (PlayerList[nPlayer].nHealth > 800)
{
PlayerList[nPlayer].nHealth = 800;
}
else
{
if (PlayerList[nPlayer].nHealth < 0)
{
var_88 = -1;
StartDeathSeq(nPlayer, 0);
}
}
}
if (var_70 == 12)
{
pActorB->spr.hitag = 0;
pActorB->spr.picnum++;
ChangeActorStat(pActorB, 0);
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
}
else
{
if (var_70 != 14)
{
var_88 = 21;
}
else
{
var_44 = var_8C;
var_88 = 22;
var_8C = 0;
}
goto do_default;
}
}
break;
}
case 11: // Bubble Nest
{
PlayerList[nPlayer].nAir += 10;
if (PlayerList[nPlayer].nAir > 100) {
PlayerList[nPlayer].nAir = 100; // TODO - constant
}
if (PlayerList[nPlayer].nBreathTimer < 89)
{
D3PlayFX(StaticSound[kSound13], pPlayerActor);
}
PlayerList[nPlayer].nBreathTimer = 90;
break;
}
case 12: // Still Beating Heart
{
if (GrabItem(nPlayer, kItemHeart)) {
goto do_default;
}
break;
}
case 13: // Scarab amulet(Invicibility)
{
if (GrabItem(nPlayer, kItemInvincibility)) {
goto do_default;
}
break;
}
case 14: // Severed Slave Hand(double damage)
{
if (GrabItem(nPlayer, kItemDoubleDamage)) {
goto do_default;
}
break;
}
case 15: // Unseen eye(Invisibility)
{
if (GrabItem(nPlayer, kItemInvisibility)) {
goto do_default;
}
break;
}
case 16: // Torch
{
if (GrabItem(nPlayer, kItemTorch)) {
goto do_default;
}
break;
}
case 17: // Sobek Mask
{
if (GrabItem(nPlayer, kItemMask)) {
goto do_default;
}
break;
}
case 19: // Extra Life
{
var_88 = -1;
if (PlayerList[nPlayer].nLives >= kMaxPlayerLives) {
break;
}
PlayerList[nPlayer].nLives++;
var_8C = 32;
var_44 = 32;
goto do_default;
}
// FIXME - lots of repeated code from here down!!
case 20: // sword pickup??
{
var_40 = 0;
int ebx = 0;
// loc_1B75D
int var_18 = 1 << var_40;
int weapons = PlayerList[nPlayer].nPlayerWeapons;
if (weapons & var_18)
{
if (mplevel)
{
AddAmmo(nPlayer, WeaponInfo[var_40].nAmmoType, ebx);
}
}
else
{
weapons = var_40;
SetNewWeaponIfBetter(nPlayer, weapons);
PlayerList[nPlayer].nPlayerWeapons |= var_18;
AddAmmo(nPlayer, WeaponInfo[weapons].nAmmoType, ebx);
var_88 = StaticSound[kSound72];
}
if (var_40 == 2) {
CheckClip(nPlayer);
}
if (var_70 <= 50) {
goto do_default;
}
pActorB->spr.cstat = CSTAT_SPRITE_INVISIBLE;
DestroyItemAnim(pActorB);
////
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
/////
}
case 22: // .357 Magnum Revolver
case 46:
{
var_40 = 1;
int ebx = 6;
// loc_1B75D
int var_18 = 1 << var_40;
int weapons = PlayerList[nPlayer].nPlayerWeapons;
if (weapons & var_18)
{
if (mplevel)
{
AddAmmo(nPlayer, WeaponInfo[var_40].nAmmoType, ebx);
}
}
else
{
weapons = var_40;
SetNewWeaponIfBetter(nPlayer, weapons);
PlayerList[nPlayer].nPlayerWeapons |= var_18;
AddAmmo(nPlayer, WeaponInfo[weapons].nAmmoType, ebx);
var_88 = StaticSound[kSound72];
}
if (var_40 == 2) {
CheckClip(nPlayer);
}
if (var_70 <= 50) {
goto do_default;
}
pActorB->spr.cstat = CSTAT_SPRITE_INVISIBLE;
DestroyItemAnim(pActorB);
////
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
/////
}
case 23: // M - 60 Machine Gun
case 47:
{
var_40 = 2;
int ebx = 24;
// loc_1B75D
int var_18 = 1 << var_40;
int weapons = PlayerList[nPlayer].nPlayerWeapons;
if (weapons & var_18)
{
if (mplevel)
{
AddAmmo(nPlayer, WeaponInfo[var_40].nAmmoType, ebx);
}
}
else
{
weapons = var_40;
SetNewWeaponIfBetter(nPlayer, weapons);
PlayerList[nPlayer].nPlayerWeapons |= var_18;
AddAmmo(nPlayer, WeaponInfo[weapons].nAmmoType, ebx);
var_88 = StaticSound[kSound72];
}
if (var_40 == 2) {
CheckClip(nPlayer);
}
if (var_70 <= 50) {
goto do_default;
}
pActorB->spr.cstat = CSTAT_SPRITE_INVISIBLE;
DestroyItemAnim(pActorB);
////
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
/////
}
case 24: // Flame Thrower
case 48:
{
var_40 = 3;
int ebx = 100;
// loc_1B75D
int var_18 = 1 << var_40;
int weapons = PlayerList[nPlayer].nPlayerWeapons;
if (weapons & var_18)
{
if (mplevel)
{
AddAmmo(nPlayer, WeaponInfo[var_40].nAmmoType, ebx);
}
}
else
{
weapons = var_40;
SetNewWeaponIfBetter(nPlayer, weapons);
PlayerList[nPlayer].nPlayerWeapons |= var_18;
AddAmmo(nPlayer, WeaponInfo[weapons].nAmmoType, ebx);
var_88 = StaticSound[kSound72];
}
if (var_40 == 2) {
CheckClip(nPlayer);
}
if (var_70 <= 50) {
goto do_default;
}
pActorB->spr.cstat = CSTAT_SPRITE_INVISIBLE;
DestroyItemAnim(pActorB);
////
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
/////
}
case 26: // Cobra Staff
case 50:
{
var_40 = 5;
int ebx = 20;
// loc_1B75D
int var_18 = 1 << var_40;
int weapons = PlayerList[nPlayer].nPlayerWeapons;
if (weapons & var_18)
{
if (mplevel)
{
AddAmmo(nPlayer, WeaponInfo[var_40].nAmmoType, ebx);
}
}
else
{
weapons = var_40;
SetNewWeaponIfBetter(nPlayer, weapons);
PlayerList[nPlayer].nPlayerWeapons |= var_18;
AddAmmo(nPlayer, WeaponInfo[weapons].nAmmoType, ebx);
var_88 = StaticSound[kSound72];
}
if (var_40 == 2) {
CheckClip(nPlayer);
}
if (var_70 <= 50) {
goto do_default;
}
pActorB->spr.cstat = CSTAT_SPRITE_INVISIBLE;
DestroyItemAnim(pActorB);
////
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
/////
}
case 27: // Eye of Ra Gauntlet
case 51:
{
var_40 = 6;
int ebx = 2;
// loc_1B75D
int var_18 = 1 << var_40;
int weapons = PlayerList[nPlayer].nPlayerWeapons;
if (weapons & var_18)
{
if (mplevel)
{
AddAmmo(nPlayer, WeaponInfo[var_40].nAmmoType, ebx);
}
}
else
{
weapons = var_40;
SetNewWeaponIfBetter(nPlayer, weapons);
PlayerList[nPlayer].nPlayerWeapons |= var_18;
AddAmmo(nPlayer, WeaponInfo[weapons].nAmmoType, ebx);
var_88 = StaticSound[kSound72];
}
if (var_40 == 2) {
CheckClip(nPlayer);
}
if (var_70 <= 50) {
goto do_default;
}
pActorB->spr.cstat = CSTAT_SPRITE_INVISIBLE;
DestroyItemAnim(pActorB);
////
// loc_1BA74: - repeated block, see in default case
if (nPlayer == nLocalPlayer)
{
if (nItemText[var_70] > -1 && nTotalPlayers == 1)
{
pickupMessage(var_70);
}
TintPalette(var_44 * 4, var_8C * 4, 0);
if (var_88 > -1)
{
PlayLocalSound(var_88, 0);
}
}
break;
/////
}
case 31: // Cobra staff ammo
{
if (AddAmmo(nPlayer, 5, 1)) {
var_88 = StaticSound[kSoundAmmoPickup];
goto do_default;
}
break;
}
case 32: // Raw Energy
{
if (AddAmmo(nPlayer, 6, pActorB->spr.hitag)) {
var_88 = StaticSound[kSoundAmmoPickup];
goto do_default;
}
break;
}
case 39: // Power key
case 40: // Time key
case 41: // War key
case 42: // Earth key
{
int keybit = 4096 << (itemtype - 39);
var_88 = -1;
if (!(PlayerList[nPlayer].keys & keybit))
{
PlayerList[nPlayer].keys |= keybit;
if (nTotalPlayers > 1)
{
goto do_default_b;
}
else
{
goto do_default;
}
}
break;
}
case 43: // Magical Essence
case 44: // ?
{
if (PlayerList[nPlayer].nMagic >= 1000) {
break;
}
var_88 = StaticSound[kSoundMana1];
PlayerList[nPlayer].nMagic += 100;
if (PlayerList[nPlayer].nMagic >= 1000) {
PlayerList[nPlayer].nMagic = 1000;
}
goto do_default;
}
case 53: // Scarab (Checkpoint)
{
if (nLocalPlayer == nPlayer)
{
pActorB->nIndex2++;
pActorB->nAction &= 0xEF;
pActorB->nIndex = 0;
ChangeActorStat(pActorB, 899);
}
SetSavePoint(nPlayer, pPlayerActor->spr.pos, pPlayerActor->sector(), pPlayerActor->spr.Angles.Yaw);
break;
}
case 54: // Golden Sarcophagus (End Level)
{
if (!bInDemo)
{
LevelFinished();
}
DestroyItemAnim(pActorB);
DeleteActor(pActorB);
break;
}
}
}
}
// CORRECT ? // loc_1BAF9:
if (bTouchFloor)
{
if (pPlayerActor->sector()->lotag > 0)
{
runlist_SignalRun(pPlayerActor->sector()->lotag - 1, nPlayer, &ExhumedAI::TouchFloor);
}
}
if (pSector != pPlayerActor->sector())
{
if (pSector->lotag > 0)
{
runlist_SignalRun(pSector->lotag - 1, nPlayer, &ExhumedAI::EnterSector);
}
if (pPlayerActor->sector()->lotag > 0)
{
runlist_SignalRun(pPlayerActor->sector()->lotag - 1, nPlayer, &ExhumedAI::LeaveSector);
}
}
if (!PlayerList[nPlayer].bIsMummified)
{
if (actions & SB_OPEN)
{
ClearSpaceBar(nPlayer);
int tag;
if (near.hitWall != nullptr && (tag = near.hitWall->lotag) > 0)
{
runlist_SignalRun(tag - 1, nPlayer, &ExhumedAI::Use);
}
if (near.hitSector != nullptr && (tag = near.hitSector->lotag) > 0)
{
runlist_SignalRun(tag - 1, nPlayer, &ExhumedAI::Use);
}
}
// was int var_38 = buttons & 0x8
if (actions & SB_FIRE)
{
FireWeapon(nPlayer);
}
else
{
StopFiringWeapon(nPlayer);
}
// loc_1BC57:
// CHECKME - are we finished with 'nSector' variable at this point? if so, maybe set it to pPlayerActor->spr.sector so we can make this code a bit neater. Don't assume pPlayerActor->spr.sector == nSector here!!
if (nStandHeight > (pPlayerActor->sector()->floorz - pPlayerActor->sector()->ceilingz)) {
var_48 = 1;
}
// Jumping
if (actions & SB_JUMP)
{
if (bUnderwater)
{
pPlayerActor->vel.Z = -8;
nActionB = 10;
}
else if (bTouchFloor)
{
if (nAction < 6 || nAction > 8)
{
pPlayerActor->vel.Z = -14;
nActionB = 3;
}
}
// goto loc_1BE70:
}
else if (actions & SB_CROUCH)
{
if (bUnderwater)
{
pPlayerActor->vel.Z = 8;
nActionB = 10;
}
else
{
if (pPlayerActor->viewzoffset < -32.5) {
pPlayerActor->viewzoffset += ((-32.5 - pPlayerActor->viewzoffset) * 0.5);
}
loc_1BD2E:
if (PlayerList[nPlayer].totalvel < 1) {
nActionB = 6;
}
else {
nActionB = 7;
}
}
// goto loc_1BE70:
}
else
{
if (PlayerList[nPlayer].nHealth > 0)
{
pPlayerActor->viewzoffset += (nActionEyeLevel[nAction] - pPlayerActor->viewzoffset) * 0.5;
if (bUnderwater)
{
if (PlayerList[nPlayer].totalvel <= 1)
nActionB = 9;
else
nActionB = 10;
}
else
{
// CHECKME - confirm branching in this area is OK
if (var_48)
{
goto loc_1BD2E;
}
else
{
if (PlayerList[nPlayer].totalvel <= 1) {
nActionB = 0;//bUnderwater; // this is just setting to 0
}
else if (PlayerList[nPlayer].totalvel <= 30) {
nActionB = 2;
}
else
{
nActionB = 1;
}
}
}
}
// loc_1BE30
if (actions & SB_FIRE) // was var_38
{
if (bUnderwater)
{
nActionB = 11;
}
else
{
if (nActionB != 2 && nActionB != 1)
{
nActionB = 5;
}
}
}
}
// loc_1BE70:
// Handle player pressing number keys to change weapon
uint8_t var_90 = PlayerList[nPlayer].input.getNewWeapon();
if (var_90)
{
var_90--;
if (PlayerList[nPlayer].nPlayerWeapons & (1 << var_90))
{
SetNewWeapon(nPlayer, var_90);
}
}
}
else // player is mummified
{
if (actions & SB_FIRE)
{
FireWeapon(nPlayer);
}
if (nAction != 15)
{
if (PlayerList[nPlayer].totalvel <= 1)
{
nActionB = 13;
}
else
{
nActionB = 14;
}
}
}
// loc_1BF09
if (nActionB != nAction && nAction != 4)
{
nAction = nActionB;
PlayerList[nPlayer].nAction = nActionB;
PlayerList[nPlayer].nSeqSize = 0;
}
Player* pPlayer = &PlayerList[nPlayer];
if (SyncInput())
{
pPlayer->pActor->spr.Angles.Pitch += DAngle::fromDeg(PlayerList[nPlayer].input.horz);
}
pPlayer->Angles.doPitchKeys(&PlayerList[nLocalPlayer].input);
if (actions & (SB_AIM_UP | SB_AIM_DOWN) || PlayerList[nPlayer].input.horz)
{
pPlayer->nDestVertPan = pPlayer->pActor->spr.Angles.Pitch;
pPlayer->bPlayerPan = pPlayer->bLockPan = true;
}
else if (actions & (SB_LOOK_UP | SB_LOOK_DOWN | SB_CENTERVIEW))
{
pPlayer->nDestVertPan = pPlayer->pActor->spr.Angles.Pitch;
pPlayer->bPlayerPan = pPlayer->bLockPan = false;
}
if (PlayerList[nPlayer].totalvel > 20)
{
pPlayer->bPlayerPan = false;
}
if (cl_slopetilting && !pPlayer->bPlayerPan && !pPlayer->bLockPan)
{
if (double nVertPan = deltaangle(pPlayer->pActor->spr.Angles.Pitch, pPlayer->nDestVertPan).Tan() * 32.)
{
pPlayer->pActor->spr.Angles.Pitch += maphoriz(abs(nVertPan) >= 4 ? clamp(nVertPan, -4., 4.) : nVertPan * 2.);
}
}
}
else // else, player's health is less than 0
{
// loc_1C0E9
if (actions & SB_OPEN)
{
ClearSpaceBar(nPlayer);
if (nAction >= 16)
{
if (nPlayer == nLocalPlayer)
{
StopAllSounds();
StopLocalSound();
GrabPalette();
}
PlayerList[nPlayer].nCurrentWeapon = PlayerList[nPlayer].nPlayerOldWeapon;
if (PlayerList[nPlayer].nLives && nNetTime)
{
if (nAction != 20)
{
pPlayerActor->spr.picnum = seq_GetSeqPicnum(kSeqJoe, 120, 0);
pPlayerActor->spr.cstat = 0;
pPlayerActor->spr.pos.Z = pPlayerActor->sector()->floorz;
}
// will invalidate nPlayerSprite
RestartPlayer(nPlayer);
pPlayerActor = PlayerList[nPlayer].pActor;
pDopple = PlayerList[nPlayer].pDoppleSprite;
}
else
{
DoGameOverScene(currentLevel->levelNumber == 20);
return;
}
}
}
}
// loc_1C201:
if (nLocalPlayer == nPlayer)
{
pLocalEyeSect = PlayerList[nLocalPlayer].pPlayerViewSect;
CheckAmbience(pLocalEyeSect);
}
int var_AC = SeqOffsets[PlayerList[nPlayer].nSeq] + PlayerSeq[nAction].a;
seq_MoveSequence(pPlayerActor, var_AC, PlayerList[nPlayer].nSeqSize);
PlayerList[nPlayer].nSeqSize++;
if (PlayerList[nPlayer].nSeqSize >= SeqSize[var_AC])
{
PlayerList[nPlayer].nSeqSize = 0;
switch (PlayerList[nPlayer].nAction)
{
default:
break;
case 3:
PlayerList[nPlayer].nSeqSize = SeqSize[var_AC] - 1;
break;
case 4:
PlayerList[nPlayer].nAction = 0;
break;
case 16:
PlayerList[nPlayer].nSeqSize = SeqSize[var_AC] - 1;
if (pPlayerActor->spr.pos.Z < pPlayerActor->sector()->floorz)
{
pPlayerActor->spr.pos.Z++;
}
if (!RandomSize(5))
{
sectortype* mouthSect;
auto pos = WheresMyMouth(nPlayer, &mouthSect);
BuildAnim(nullptr, 71, 0, DVector3(pos.XY(), pPlayerActor->spr.pos.Z + 15), mouthSect, 1.171875, 128);
}
break;
case 17:
PlayerList[nPlayer].nAction = 18;
break;
case 19:
pPlayerActor->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
PlayerList[nPlayer].nAction = 20;
break;
}
}
// loc_1C3B4:
if (nPlayer == nLocalPlayer)
{
initpos = pPlayerActor->spr.pos;
initsectp = pPlayerActor->sector();
inita = pPlayerActor->spr.Angles.Yaw;
}
if (!PlayerList[nPlayer].nHealth)
{
PlayerList[nPlayer].nThrust.Zero();
if (pPlayerActor->viewzoffset >= -11)
{
pPlayerActor->viewzoffset = -11;
dVertPan[nPlayer] = 0;
}
else
{
if (PlayerList[nPlayer].pActor->spr.Angles.Pitch.Sgn() > 0)
{
pPlayerActor->spr.Angles.Pitch = nullAngle;
pPlayerActor->viewzoffset -= dVertPan[nPlayer];
}
else
{
PlayerList[nPlayer].pActor->spr.Angles.Pitch -= maphoriz(dVertPan[nPlayer]);
if (PlayerList[nPlayer].pActor->spr.Angles.Pitch.Degrees() <= 38)
{
PlayerList[nPlayer].pActor->spr.Angles.Pitch = DAngle::fromDeg(-37.72);
}
else if (PlayerList[nPlayer].pActor->spr.Angles.Pitch.Sgn() >= 0)
{
if (!(pPlayerActor->sector()->Flag & kSectUnderwater))
{
SetNewWeapon(nPlayer, PlayerList[nPlayer].nDeathType + 8);
}
}
dVertPan[nPlayer]--;
}
}
}
// loc_1C4E1
pDopple->spr.pos = pPlayerActor->spr.pos;
if (pPlayerActor->sector()->pAbove != nullptr)
{
pDopple->spr.Angles.Yaw = pPlayerActor->spr.Angles.Yaw;
ChangeActorSect(pDopple, pPlayerActor->sector()->pAbove);
pDopple->spr.cstat = CSTAT_SPRITE_BLOCK_ALL;
}
else
{
pDopple->spr.cstat = CSTAT_SPRITE_INVISIBLE;
}
MoveWeapons(nPlayer);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
FSerializer& Serialize(FSerializer& arc, const char* keyname, Player& w, Player* def)
{
if (arc.BeginObject(keyname))
{
arc("health", w.nHealth)
("at2", w.nSeqSize)
("action", w.nAction)
("sprite", w.pActor)
("mummy", w.bIsMummified)
("invincible", w.invincibility)
("air", w.nAir)
("seq", w.nSeq)
("item", w.nItem)
("maskamount", w.nMaskAmount)
("keys", w.keys)
("magic", w.nMagic)
.Array("items", w.items, countof(w.items))
.Array("ammo", w.nAmmo, countof(w.nAmmo))
("weapon", w.nCurrentWeapon)
("isfiring", w.bIsFiring)
("field3f", w.nSeqSize2)
("field38", w.nNextWeapon)
("field3a", w.nState)
("field3c", w.nLastWeapon)
("seq", w.nSeq)
("horizon", w.Angles)
("angle", w.Angles)
("lives", w.nLives)
("double", w.nDouble)
("invisible", w.nInvisible)
("torch", w.nTorch)
("breathtimer", w.nBreathTimer)
("playerswear", w.nPlayerSwear)
("pushsect", w.pPlayerPushSect)
("deathtype", w.nDeathType)
("score", w.nPlayerScore)
("color", w.nPlayerColor)
("dx", w.nPlayerD.X)
("dy", w.nPlayerD.Y)
("pistolclip", w.nPistolClip)
("thrustx", w.nThrust.X)
("thrusty", w.nThrust.Y)
("dopplesprite", w.pDoppleSprite)
("oldweapon", w.nPlayerOldWeapon)
("clip", w.nPlayerClip)
("pushsound", w.nPlayerPushSound)
("taunttimer", w.nTauntTimer)
("weapons", w.nPlayerWeapons)
("viewsect", w.pPlayerViewSect)
("floorspr", w.pPlayerFloorSprite)
("save", w.sPlayerSave)
("totalvel", w.totalvel)
("grenade", w.pPlayerGrenade)
.EndObject();
}
return arc;
}
FSerializer& Serialize(FSerializer& arc, const char* keyname, PlayerSave& w, PlayerSave* def)
{
if (arc.BeginObject(keyname))
{
arc("pos", w.pos)
("sector", w.pSector)
("angle", w.nAngle)
.EndObject();
}
return arc;
}
void SerializePlayer(FSerializer& arc)
{
if (arc.BeginObject("player"))
{
arc ("bobangle", bobangle)
("standheight", nStandHeight)
("playercount", PlayerCount)
("netstartsprites", nNetStartSprites)
("localplayer", nLocalPlayer)
("curstartsprite", nCurStartSprite)
.Array("netstartsprite", nNetStartSprite, kMaxPlayers)
.Array("list", PlayerList, PlayerCount);
arc.EndObject();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
DEFINE_FIELD_X(ExhumedPlayer, Player, nHealth);
DEFINE_FIELD_X(ExhumedPlayer, Player, nLives);
DEFINE_FIELD_X(ExhumedPlayer, Player, nDouble);
DEFINE_FIELD_X(ExhumedPlayer, Player, nInvisible);
DEFINE_FIELD_X(ExhumedPlayer, Player, nTorch);
DEFINE_FIELD_X(ExhumedPlayer, Player, nSeqSize);
DEFINE_FIELD_X(ExhumedPlayer, Player, nAction);
DEFINE_FIELD_X(ExhumedPlayer, Player, pActor);
DEFINE_FIELD_X(ExhumedPlayer, Player, bIsMummified);
DEFINE_FIELD_X(ExhumedPlayer, Player, invincibility);
DEFINE_FIELD_X(ExhumedPlayer, Player, nAir);
DEFINE_FIELD_X(ExhumedPlayer, Player, nSeq);
DEFINE_FIELD_X(ExhumedPlayer, Player, nMaskAmount);
DEFINE_FIELD_X(ExhumedPlayer, Player, keys);
DEFINE_FIELD_X(ExhumedPlayer, Player, nMagic);
DEFINE_FIELD_X(ExhumedPlayer, Player, nItem);
DEFINE_FIELD_X(ExhumedPlayer, Player, items);
DEFINE_FIELD_X(ExhumedPlayer, Player, nAmmo); // TODO - kMaxWeapons?
DEFINE_FIELD_X(ExhumedPlayer, Player, nPlayerWeapons);
DEFINE_FIELD_X(ExhumedPlayer, Player, nCurrentWeapon);
DEFINE_FIELD_X(ExhumedPlayer, Player, nSeqSize2);
DEFINE_FIELD_X(ExhumedPlayer, Player, bIsFiring);
DEFINE_FIELD_X(ExhumedPlayer, Player, nNextWeapon);
DEFINE_FIELD_X(ExhumedPlayer, Player, nState);
DEFINE_FIELD_X(ExhumedPlayer, Player, nLastWeapon);
DEFINE_FIELD_X(ExhumedPlayer, Player, nRun);
DEFINE_FIELD_X(ExhumedPlayer, Player, bPlayerPan);
DEFINE_FIELD_X(ExhumedPlayer, Player, bLockPan);
DEFINE_ACTION_FUNCTION(_Exhumed, GetViewPlayer)
{
ACTION_RETURN_POINTER(&PlayerList[nLocalPlayer]);
}
DEFINE_ACTION_FUNCTION(_Exhumed, GetPistolClip)
{
ACTION_RETURN_INT(PlayerList[nLocalPlayer].nPistolClip);
}
DEFINE_ACTION_FUNCTION(_Exhumed, GetPlayerClip)
{
ACTION_RETURN_INT(PlayerList[nLocalPlayer].nPlayerClip);
}
DEFINE_ACTION_FUNCTION(_ExhumedPlayer, IsUnderwater)
{
PARAM_SELF_STRUCT_PROLOGUE(Player);
auto nLocalPlayer = self - PlayerList;
ACTION_RETURN_BOOL(PlayerList[nLocalPlayer].pPlayerViewSect->Flag & kSectUnderwater);
}
DEFINE_ACTION_FUNCTION(_ExhumedPlayer, GetAngle)
{
PARAM_SELF_STRUCT_PROLOGUE(Player);
ACTION_RETURN_INT(self->pActor->spr.Angles.Yaw.Buildang());
}
END_PS_NS