mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-11-24 19:30:26 +01:00
5889 lines
200 KiB
C++
5889 lines
200 KiB
C++
401
|
|
%{
|
|
|
|
#include "Entities/StdH/StdH.h"
|
|
|
|
#include <Engine/Build.h>
|
|
#include <Engine/Network/Network.h>
|
|
#include <locale.h>
|
|
|
|
#include "Models/Player/SeriousSam/Player.h"
|
|
#include "Models/Player/SeriousSam/Body.h"
|
|
#include "Models/Player/SeriousSam/Head.h"
|
|
|
|
#include "Entities/PlayerMarker.h"
|
|
#include "Entities/PlayerWeapons.h"
|
|
#include "Entities/PlayerAnimator.h"
|
|
#include "Entities/PlayerView.h"
|
|
#include "Entities/MovingBrush.h"
|
|
#include "Entities/Switch.h"
|
|
#include "Entities/MessageHolder.h"
|
|
#include "Entities/Camera.h"
|
|
#include "Entities/WorldLink.h"
|
|
#include "Entities/HealthItem.h"
|
|
#include "Entities/ArmorItem.h"
|
|
#include "Entities/WeaponItem.h"
|
|
#include "Entities/AmmoItem.h"
|
|
#include "Entities/MessageItem.h"
|
|
#include "Entities/AmmoPack.h"
|
|
#include "Entities/KeyItem.h"
|
|
#include "Entities/MusicHolder.h"
|
|
#include "Entities/EnemyBase.h"
|
|
#include "Entities/PlayerActionMarker.h"
|
|
#include "Entities/BasicEffects.h"
|
|
#include "Entities/BackgroundViewer.h"
|
|
#include "Entities/WorldSettingsController.h"
|
|
|
|
extern void JumpFromBouncer(CEntity *penToBounce, CEntity *penBouncer);
|
|
// from game
|
|
#define GRV_SHOWEXTRAS (1L<<0) // add extra stuff like console, weapon, pause
|
|
|
|
%}
|
|
|
|
uses "Entities/WorldLink";
|
|
|
|
enum PlayerViewType {
|
|
0 PVT_PLAYEREYES "",
|
|
1 PVT_PLAYERAUTOVIEW "",
|
|
2 PVT_SCENECAMERA "",
|
|
3 PVT_3RDPERSONVIEW "",
|
|
};
|
|
|
|
enum PlayerState {
|
|
0 PST_STAND "",
|
|
1 PST_CROUCH "",
|
|
2 PST_SWIM "",
|
|
3 PST_DIVE "",
|
|
4 PST_FALL "",
|
|
};
|
|
|
|
// event for starting cinematic camera sequence
|
|
event ECameraStart {
|
|
CEntityPointer penCamera, // the camera
|
|
};
|
|
|
|
// event for ending cinematic camera sequence
|
|
event ECameraStop {
|
|
CEntityPointer penCamera, // the camera
|
|
};
|
|
|
|
|
|
// sent when needs to rebirth
|
|
event ERebirth {
|
|
};
|
|
|
|
// sent when player was disconnected from game
|
|
event EDisconnected {
|
|
};
|
|
|
|
// starts automatic player actions
|
|
event EAutoAction {
|
|
CEntityPointer penFirstMarker,
|
|
};
|
|
|
|
%{
|
|
extern void DrawHUD( const CPlayer *penPlayerCurrent, CDrawPort *pdpCurrent, BOOL bSnooping);
|
|
extern void InitHUD(void);
|
|
extern void EndHUD(void);
|
|
|
|
static CTimerValue _tvProbingLast;
|
|
|
|
// used to render certain entities only for certain players (like picked items, etc.)
|
|
ULONG _ulPlayerRenderingMask = 0;
|
|
|
|
#define NAME name
|
|
|
|
const FLOAT _fBlowUpAmmount = 70.0f;
|
|
|
|
// computer message adding flags
|
|
#define CMF_READ (1L<<0)
|
|
#define CMF_ANALYZE (1L<<1)
|
|
|
|
struct MarkerDistance {
|
|
public:
|
|
FLOAT md_fMinD;
|
|
CPlayerMarker *md_ppm;
|
|
void Clear(void);
|
|
};
|
|
|
|
int qsort_CompareMarkerDistance(const void *pv0, const void *pv1)
|
|
{
|
|
MarkerDistance &md0 = *(MarkerDistance*)pv0;
|
|
MarkerDistance &md1 = *(MarkerDistance*)pv1;
|
|
if( md0.md_fMinD<md1.md_fMinD) return +1;
|
|
else if( md0.md_fMinD>md1.md_fMinD) return -1;
|
|
else return 0;
|
|
}
|
|
|
|
static inline FLOAT IntensityAtDistance( FLOAT fFallOff, FLOAT fHotSpot, FLOAT fDistance)
|
|
{
|
|
// intensity is zero if further than fall-off range
|
|
if( fDistance>fFallOff) return 0.0f;
|
|
// intensity is maximum if closer than hot-spot range
|
|
if( fDistance<fHotSpot) return 1.0f;
|
|
// interpolate if between fall-off and hot-spot range
|
|
return (fFallOff-fDistance)/(fFallOff-fHotSpot);
|
|
}
|
|
|
|
static CTString MakeEmptyString(INDEX ctLen, char ch=' ')
|
|
{
|
|
char ach[2];
|
|
ach[0] = ch;
|
|
ach[1] = 0;
|
|
CTString strSpaces;
|
|
for (INDEX i=0; i<ctLen; i++) {
|
|
strSpaces+=ach;
|
|
}
|
|
return strSpaces;
|
|
}
|
|
|
|
// take a two line string and align into one line of minimum given length
|
|
static int _ctAlignWidth = 20;
|
|
static CTString AlignString(const CTString &strOrg)
|
|
{
|
|
// split into two lines
|
|
CTString strL = strOrg;
|
|
strL.OnlyFirstLine();
|
|
CTString strR = strOrg;
|
|
strR.RemovePrefix(strL);
|
|
strR.DeleteChar(0);
|
|
|
|
// get their lengths
|
|
INDEX iLenL = strL.LengthNaked();
|
|
INDEX iLenR = strR.LengthNaked();
|
|
|
|
// find number of spaces to insert
|
|
INDEX ctSpaces = _ctAlignWidth-(iLenL+iLenR);
|
|
if (ctSpaces<1) {
|
|
ctSpaces=1;
|
|
}
|
|
|
|
// make aligned string
|
|
return strL+MakeEmptyString(ctSpaces)+strR;
|
|
}
|
|
|
|
static CTString CenterString(const CTString &str)
|
|
{
|
|
INDEX ctSpaces = (_ctAlignWidth-str.LengthNaked())/2;
|
|
if (ctSpaces<0) {
|
|
ctSpaces=0;
|
|
}
|
|
return MakeEmptyString(ctSpaces)+str;
|
|
}
|
|
|
|
static CTString PadStringRight(const CTString &str, INDEX iLen)
|
|
{
|
|
INDEX ctSpaces = iLen-str.LengthNaked();
|
|
if (ctSpaces<0) {
|
|
ctSpaces=0;
|
|
}
|
|
return str+MakeEmptyString(ctSpaces);
|
|
}
|
|
|
|
static CTString PadStringLeft(const CTString &str, INDEX iLen)
|
|
{
|
|
INDEX ctSpaces = iLen-str.LengthNaked();
|
|
if (ctSpaces<0) {
|
|
ctSpaces=0;
|
|
}
|
|
return MakeEmptyString(ctSpaces)+str;
|
|
}
|
|
|
|
static void KillAllEnemies(CEntity *penKiller)
|
|
{
|
|
// for each entity in the world
|
|
{FOREACHINDYNAMICCONTAINER(penKiller->GetWorld()->wo_cenEntities, CEntity, iten) {
|
|
CEntity *pen = iten;
|
|
if (IsDerivedFromClass(pen, "Enemy Base") && !IsOfClass(pen, "Devil")) {
|
|
CEnemyBase *penEnemy = (CEnemyBase *)pen;
|
|
if (penEnemy->m_penEnemy==NULL) {
|
|
continue;
|
|
}
|
|
penKiller->InflictDirectDamage(pen, penKiller, DMT_BULLET,
|
|
penEnemy->GetHealth()+1, pen->GetPlacement().pl_PositionVector, FLOAT3D(0,1,0));
|
|
}
|
|
}}
|
|
}
|
|
|
|
|
|
#define HEADING_MAX 45.0f
|
|
#define PITCH_MAX 90.0f
|
|
#define BANKING_MAX 45.0f
|
|
|
|
// player flags
|
|
#define PLF_INITIALIZED (1UL<<0) // set when player entity is ready to function
|
|
#define PLF_VIEWROTATIONCHANGED (1UL<<1) // for adjusting view rotation separately from legs
|
|
#define PLF_JUMPALLOWED (1UL<<2) // if jumping is allowed
|
|
#define PLF_SYNCWEAPON (1UL<<3) // weapon model needs to be synchronized before rendering
|
|
#define PLF_AUTOMOVEMENTS (1UL<<4) // complete automatic control of movements
|
|
#define PLF_DONTRENDER (1UL<<5) // don't render view (used at end of level)
|
|
#define PLF_CHANGINGLEVEL (1UL<<6) // mark that we next are to appear at start of new level
|
|
#define PLF_APPLIEDACTION (1UL<<7) // used to detect when player is not connected
|
|
#define PLF_NOTCONNECTED (1UL<<8) // set if the player is not connected
|
|
#define PLF_LEVELSTARTED (1UL<<9) // marks that level start time was recorded
|
|
#define PLF_RESPAWNINPLACE (1UL<<10) // don't move to marker when respawning (for current death only)
|
|
|
|
// defines representing flags used to fill player buttoned actions
|
|
#define PLACT_FIRE (1L<<0)
|
|
#define PLACT_RELOAD (1L<<1)
|
|
#define PLACT_WEAPON_NEXT (1L<<2)
|
|
#define PLACT_WEAPON_PREV (1L<<3)
|
|
#define PLACT_WEAPON_FLIP (1L<<4)
|
|
#define PLACT_USE (1L<<5)
|
|
#define PLACT_COMPUTER (1L<<6)
|
|
#define PLACT_3RD_PERSON_VIEW (1L<<7)
|
|
#define PLACT_CENTER_VIEW (1L<<8)
|
|
#define PLACT_SELECT_WEAPON_SHIFT (9)
|
|
#define PLACT_SELECT_WEAPON_MASK (0x1FL<<PLACT_SELECT_WEAPON_SHIFT)
|
|
|
|
#define MAX_WEAPONS 30
|
|
|
|
#define PICKEDREPORT_TIME (2.0f) // how long (picked-up) message stays on screen
|
|
|
|
// is player spying another player
|
|
//extern TIME _tmSnoopingStarted;
|
|
//extern CEntity *_penTargeting;
|
|
|
|
|
|
struct PlayerControls {
|
|
FLOAT3D aRotation;
|
|
FLOAT3D aViewRotation;
|
|
FLOAT3D vTranslation;
|
|
|
|
BOOL bMoveForward;
|
|
BOOL bMoveBackward;
|
|
BOOL bMoveLeft;
|
|
BOOL bMoveRight;
|
|
BOOL bMoveUp;
|
|
BOOL bMoveDown;
|
|
|
|
BOOL bTurnLeft;
|
|
BOOL bTurnRight;
|
|
BOOL bTurnUp;
|
|
BOOL bTurnDown;
|
|
BOOL bTurnBankingLeft;
|
|
BOOL bTurnBankingRight;
|
|
BOOL bCenterView;
|
|
|
|
BOOL bLookLeft;
|
|
BOOL bLookRight;
|
|
BOOL bLookUp;
|
|
BOOL bLookDown;
|
|
BOOL bLookBankingLeft;
|
|
BOOL bLookBankingRight;
|
|
|
|
BOOL bSelectWeapon[MAX_WEAPONS+1];
|
|
BOOL bWeaponNext;
|
|
BOOL bWeaponPrev;
|
|
BOOL bWeaponFlip;
|
|
|
|
BOOL bWalk;
|
|
BOOL bStrafe;
|
|
BOOL bStrafeFB;
|
|
BOOL bFire;
|
|
BOOL bReload;
|
|
BOOL bUse;
|
|
BOOL bComputer;
|
|
BOOL bUseOrComputer;
|
|
BOOL bUseOrComputerLast; // for internal use
|
|
BOOL b3rdPersonView;
|
|
};
|
|
|
|
static struct PlayerControls pctlCurrent;
|
|
|
|
// cheats
|
|
static INDEX cht_iGoToMarker = -1;
|
|
static INDEX cht_bKillAll = FALSE;
|
|
static INDEX cht_bGiveAll = FALSE;
|
|
static INDEX cht_bOpen = FALSE;
|
|
static INDEX cht_bAllMessages= FALSE;
|
|
static INDEX cht_bRefresh = FALSE;
|
|
INDEX cht_bGod = FALSE;
|
|
INDEX cht_bFly = FALSE;
|
|
INDEX cht_bGhost = FALSE;
|
|
INDEX cht_bInvisible = FALSE;
|
|
FLOAT cht_fTranslationMultiplier = 1.0f;
|
|
INDEX cht_bEnable = 0;
|
|
|
|
// interface control
|
|
static INDEX hud_bShowAll = TRUE; // used internaly in menu/console
|
|
INDEX hud_bShowWeapon = TRUE;
|
|
INDEX hud_bShowMessages = TRUE;
|
|
INDEX hud_bShowInfo = TRUE;
|
|
INDEX hud_bShowLatency = FALSE;
|
|
INDEX hud_iShowPlayers = -1; // auto
|
|
INDEX hud_iSortPlayers = -1; // auto
|
|
FLOAT hud_fOpacity = 0.9f;
|
|
FLOAT hud_fScaling = 1.0f;
|
|
FLOAT hud_tmWeaponsOnScreen = 3.0f;
|
|
FLOAT hud_tmLatencySnapshot = 1.0f;
|
|
|
|
FLOAT plr_fBreathingStrength = 0.0f;
|
|
extern FLOAT plr_tmSnoopingTime;
|
|
INDEX cht_bKillFinalBoss = FALSE;
|
|
INDEX cht_bDebugFinalBoss = FALSE;
|
|
INDEX cht_bDumpFinalBossData = FALSE;
|
|
INDEX cht_bDebugFinalBossAnimations = FALSE;
|
|
INDEX cht_bDumpPlayerShading = FALSE;
|
|
|
|
FLOAT wpn_fRecoilSpeed[17] = {0};
|
|
FLOAT wpn_fRecoilLimit[17] = {0};
|
|
FLOAT wpn_fRecoilDampUp[17] = {0};
|
|
FLOAT wpn_fRecoilDampDn[17] = {0};
|
|
FLOAT wpn_fRecoilOffset[17] = {0};
|
|
FLOAT wpn_fRecoilFactorP[17] = {0};
|
|
FLOAT wpn_fRecoilFactorZ[17] = {0};
|
|
|
|
// misc
|
|
static FLOAT plr_fAcceleration = 100.0f;
|
|
static FLOAT plr_fDeceleration = 60.0f;
|
|
static FLOAT plr_fSpeedForward = 10.0f;
|
|
static FLOAT plr_fSpeedBackward = 10.0f;
|
|
static FLOAT plr_fSpeedSide = 10.0f;
|
|
static FLOAT plr_fSpeedUp = 11.0f;
|
|
static FLOAT plr_fViewHeightStand = 1.9f;
|
|
static FLOAT plr_fViewHeightCrouch = 0.7f;
|
|
static FLOAT plr_fViewHeightSwim = 0.4f;
|
|
static FLOAT plr_fViewHeightDive = 0.0f;
|
|
FLOAT plr_fViewDampFactor = 0.4f;
|
|
FLOAT plr_fViewDampLimitGroundUp = 0.1f;
|
|
FLOAT plr_fViewDampLimitGroundDn = 0.4f;
|
|
FLOAT plr_fViewDampLimitWater = 0.1f;
|
|
static FLOAT plr_fFrontClipDistance = 0.25f;
|
|
static FLOAT plr_fFOV = 90.0f;
|
|
static FLOAT net_tmLatencyAvg;
|
|
INDEX plr_bRenderPicked = FALSE;
|
|
INDEX plr_bRenderPickedParticles = FALSE;
|
|
INDEX plr_bOnlySam = FALSE;
|
|
CTString plr_strLastLevel = "";
|
|
INDEX ent_bReportBrokenChains = FALSE;
|
|
FLOAT ent_tmMentalIn = 0.5f;
|
|
FLOAT ent_tmMentalOut = 0.75f;
|
|
FLOAT ent_tmMentalFade = 0.5f;
|
|
|
|
// prediction control vars
|
|
FLOAT cli_fPredictPlayersRange = 0.0f;
|
|
FLOAT cli_fPredictItemsRange = 3.0f;
|
|
FLOAT cli_tmPredictFoe = 10.0f;
|
|
FLOAT cli_tmPredictAlly = 10.0f;
|
|
FLOAT cli_tmPredictEnemy = 10.0f;
|
|
|
|
static FLOAT plr_fSwimSoundDelay = 0.8f;
|
|
static FLOAT plr_fDiveSoundDelay = 1.6f;
|
|
static FLOAT plr_fWalkSoundDelay = 0.5f;
|
|
static FLOAT plr_fRunSoundDelay = 0.3f;
|
|
|
|
static FLOAT ctl_tmComputerDoubleClick = 0.5f; // double click delay for calling computer
|
|
static FLOAT _tmLastUseOrCompPressed = -10.0f; // for computer doubleclick
|
|
|
|
// speeds for button rotation
|
|
static FLOAT ctl_fButtonRotationSpeedH = 300.0f;
|
|
static FLOAT ctl_fButtonRotationSpeedP = 150.0f;
|
|
static FLOAT ctl_fButtonRotationSpeedB = 150.0f;
|
|
// modifier for axis strafing
|
|
static FLOAT ctl_fAxisStrafingModifier = 1.0f;
|
|
|
|
// !=NULL if some player wants to call computer
|
|
DECL_DLL class CPlayer *cmp_ppenPlayer = NULL;
|
|
// !=NULL for rendering computer on secondary display in dualhead
|
|
DECL_DLL class CPlayer *cmp_ppenDHPlayer = NULL;
|
|
// set to update current message in background mode (for dualhead)
|
|
DECL_DLL BOOL cmp_bUpdateInBackground = FALSE;
|
|
// set for initial calling computer without rendering game
|
|
DECL_DLL BOOL cmp_bInitialStart = FALSE;
|
|
|
|
// game sets this for player hud and statistics and hiscore sound playing
|
|
DECL_DLL INDEX plr_iHiScore = 0.0f;
|
|
|
|
// these define address and size of player controls structure
|
|
DECL_DLL void *ctl_pvPlayerControls = &pctlCurrent;
|
|
DECL_DLL const SLONG ctl_slPlayerControlsSize = sizeof(pctlCurrent);
|
|
|
|
// called to compose action packet from current controls
|
|
DECL_DLL void ctl_ComposeActionPacket(const CPlayerCharacter &pc, CPlayerAction &paAction, BOOL bPreScan)
|
|
{
|
|
// allow double axis controls
|
|
paAction.pa_aRotation += paAction.pa_aViewRotation;
|
|
|
|
CPlayerSettings *pps = (CPlayerSettings *)pc.pc_aubAppearance;
|
|
// CPrintF("compose: prescan %d, x:%g\n", bPreScan, paAction.pa_aRotation(1));
|
|
// if strafing
|
|
if (pctlCurrent.bStrafe) {
|
|
// move rotation left/right into translation left/right
|
|
paAction.pa_vTranslation(1) = -paAction.pa_aRotation(1)*ctl_fAxisStrafingModifier;
|
|
paAction.pa_aRotation(1) = 0;
|
|
}
|
|
// if strafing forward/backward
|
|
if (pctlCurrent.bStrafeFB) {
|
|
// move rotation up/down into translation forward/backward
|
|
paAction.pa_vTranslation(3) = -paAction.pa_aRotation(2)*ctl_fAxisStrafingModifier;
|
|
paAction.pa_aRotation(2) = 0;
|
|
}
|
|
|
|
// if centering view
|
|
if (pctlCurrent.bCenterView) {
|
|
// don't allow moving view up/down
|
|
paAction.pa_aRotation(2) = 0;
|
|
}
|
|
|
|
// multiply axis actions with speed
|
|
paAction.pa_vTranslation(1) *= plr_fSpeedSide;
|
|
paAction.pa_vTranslation(2) *= plr_fSpeedUp;
|
|
if (paAction.pa_vTranslation(3)<0) {
|
|
paAction.pa_vTranslation(3) *= plr_fSpeedForward;
|
|
} else {
|
|
paAction.pa_vTranslation(3) *= plr_fSpeedBackward;
|
|
}
|
|
|
|
// find local player, if any
|
|
CPlayer *penThis = NULL;
|
|
INDEX ctPlayers = CEntity::GetMaxPlayers();
|
|
for (INDEX iPlayer = 0; iPlayer<ctPlayers; iPlayer++) {
|
|
CPlayer *pen=(CPlayer *)CEntity::GetPlayerEntity(iPlayer);
|
|
if (pen!=NULL && pen->en_pcCharacter==pc) {
|
|
penThis = pen;
|
|
break;
|
|
}
|
|
}
|
|
// if not found
|
|
if (penThis==NULL) {
|
|
// do nothing
|
|
return;
|
|
}
|
|
// accumulate local rotation
|
|
penThis->m_aLocalRotation +=paAction.pa_aRotation;
|
|
penThis->m_aLocalViewRotation+=paAction.pa_aViewRotation;
|
|
penThis->m_vLocalTranslation +=paAction.pa_vTranslation;
|
|
|
|
// if prescanning
|
|
if (bPreScan) {
|
|
// no button checking
|
|
return;
|
|
}
|
|
|
|
// add button movement/rotation/look actions to the axis actions
|
|
if(pctlCurrent.bMoveForward || (pctlCurrent.bStrafeFB&&pctlCurrent.bTurnUp) ) paAction.pa_vTranslation(3) -= plr_fSpeedForward;
|
|
if(pctlCurrent.bMoveBackward || (pctlCurrent.bStrafeFB&&pctlCurrent.bTurnDown)) paAction.pa_vTranslation(3) += plr_fSpeedBackward;
|
|
if(pctlCurrent.bMoveLeft || (pctlCurrent.bStrafe&&pctlCurrent.bTurnLeft) ) paAction.pa_vTranslation(1) -= plr_fSpeedSide;
|
|
if(pctlCurrent.bMoveRight || (pctlCurrent.bStrafe&&pctlCurrent.bTurnRight)) paAction.pa_vTranslation(1) += plr_fSpeedSide;
|
|
if(pctlCurrent.bMoveUp ) paAction.pa_vTranslation(2) += plr_fSpeedUp;
|
|
if(pctlCurrent.bMoveDown ) paAction.pa_vTranslation(2) -= plr_fSpeedUp;
|
|
|
|
const FLOAT fQuantum = _pTimer->TickQuantum;
|
|
if(pctlCurrent.bTurnLeft && !pctlCurrent.bStrafe) penThis->m_aLocalRotation(1) += ctl_fButtonRotationSpeedH*fQuantum;
|
|
if(pctlCurrent.bTurnRight && !pctlCurrent.bStrafe) penThis->m_aLocalRotation(1) -= ctl_fButtonRotationSpeedH*fQuantum;
|
|
if(pctlCurrent.bTurnUp && !pctlCurrent.bStrafeFB) penThis->m_aLocalRotation(2) += ctl_fButtonRotationSpeedP*fQuantum;
|
|
if(pctlCurrent.bTurnDown && !pctlCurrent.bStrafeFB) penThis->m_aLocalRotation(2) -= ctl_fButtonRotationSpeedP*fQuantum;
|
|
if(pctlCurrent.bTurnBankingLeft ) penThis->m_aLocalRotation(3) += ctl_fButtonRotationSpeedB*fQuantum;
|
|
if(pctlCurrent.bTurnBankingRight ) penThis->m_aLocalRotation(3) -= ctl_fButtonRotationSpeedB*fQuantum;
|
|
|
|
if(pctlCurrent.bLookLeft ) penThis->m_aLocalViewRotation(1) += ctl_fButtonRotationSpeedH*fQuantum;
|
|
if(pctlCurrent.bLookRight ) penThis->m_aLocalViewRotation(1) -= ctl_fButtonRotationSpeedH*fQuantum;
|
|
if(pctlCurrent.bLookUp ) penThis->m_aLocalViewRotation(2) += ctl_fButtonRotationSpeedP*fQuantum;
|
|
if(pctlCurrent.bLookDown ) penThis->m_aLocalViewRotation(2) -= ctl_fButtonRotationSpeedP*fQuantum;
|
|
if(pctlCurrent.bLookBankingLeft ) penThis->m_aLocalViewRotation(3) += ctl_fButtonRotationSpeedB*fQuantum;
|
|
if(pctlCurrent.bLookBankingRight ) penThis->m_aLocalViewRotation(3) -= ctl_fButtonRotationSpeedB*fQuantum;
|
|
|
|
// use current accumulated rotation
|
|
paAction.pa_aRotation = penThis->m_aLocalRotation;
|
|
paAction.pa_aViewRotation = penThis->m_aLocalViewRotation;
|
|
//paAction.pa_vTranslation = penThis->m_vLocalTranslation;
|
|
|
|
// if walking
|
|
if(pctlCurrent.bWalk) {
|
|
// make forward/backward and sidestep speeds slower
|
|
paAction.pa_vTranslation(3) /= 2.0f;
|
|
paAction.pa_vTranslation(1) /= 2.0f;
|
|
}
|
|
|
|
// reset all button actions
|
|
paAction.pa_ulButtons = 0;
|
|
|
|
// set weapon selection bits
|
|
for(INDEX i=1; i<MAX_WEAPONS; i++) {
|
|
if(pctlCurrent.bSelectWeapon[i]) {
|
|
paAction.pa_ulButtons = i<<PLACT_SELECT_WEAPON_SHIFT;
|
|
break;
|
|
}
|
|
}
|
|
// set button pressed flags
|
|
if(pctlCurrent.bWeaponNext) paAction.pa_ulButtons |= PLACT_WEAPON_NEXT;
|
|
if(pctlCurrent.bWeaponPrev) paAction.pa_ulButtons |= PLACT_WEAPON_PREV;
|
|
if(pctlCurrent.bWeaponFlip) paAction.pa_ulButtons |= PLACT_WEAPON_FLIP;
|
|
if(pctlCurrent.bFire) paAction.pa_ulButtons |= PLACT_FIRE;
|
|
if(pctlCurrent.bReload) paAction.pa_ulButtons |= PLACT_RELOAD;
|
|
if(pctlCurrent.bUse) paAction.pa_ulButtons |= PLACT_USE;
|
|
if(pctlCurrent.bComputer) paAction.pa_ulButtons |= PLACT_COMPUTER;
|
|
if(pctlCurrent.b3rdPersonView) paAction.pa_ulButtons |= PLACT_3RD_PERSON_VIEW;
|
|
if(pctlCurrent.bCenterView) paAction.pa_ulButtons |= PLACT_CENTER_VIEW;
|
|
|
|
// if userorcomp just pressed
|
|
if(pctlCurrent.bUseOrComputer && !pctlCurrent.bUseOrComputerLast) {
|
|
// if double-click is off
|
|
if (ctl_tmComputerDoubleClick==0 || (pps->ps_ulFlags&PSF_COMPSINGLECLICK)) {
|
|
// press both
|
|
paAction.pa_ulButtons |= PLACT_USE|PLACT_COMPUTER;
|
|
// if double-click is on
|
|
} else {
|
|
// if double click
|
|
if (_pTimer->GetRealTimeTick()<=_tmLastUseOrCompPressed+ctl_tmComputerDoubleClick) {
|
|
// computer pressed
|
|
paAction.pa_ulButtons |= PLACT_COMPUTER;
|
|
// if single click
|
|
} else {
|
|
// use pressed
|
|
paAction.pa_ulButtons |= PLACT_USE;
|
|
}
|
|
}
|
|
_tmLastUseOrCompPressed = _pTimer->GetRealTimeTick();
|
|
}
|
|
// remember old userorcomp pressed state
|
|
pctlCurrent.bUseOrComputerLast = pctlCurrent.bUseOrComputer;
|
|
};
|
|
|
|
void CPlayer_Precache(void)
|
|
{
|
|
CDLLEntityClass *pdec = &CPlayer_DLLClass;
|
|
|
|
// precache view
|
|
extern void CPlayerView_Precache(void);
|
|
CPlayerView_Precache();
|
|
|
|
// precache all player sounds
|
|
pdec->PrecacheSound(SOUND_WATER_ENTER );
|
|
pdec->PrecacheSound(SOUND_WATER_LEAVE );
|
|
pdec->PrecacheSound(SOUND_WALK_L );
|
|
pdec->PrecacheSound(SOUND_WALK_R );
|
|
pdec->PrecacheSound(SOUND_WALK_SAND_L );
|
|
pdec->PrecacheSound(SOUND_WALK_SAND_R );
|
|
pdec->PrecacheSound(SOUND_SWIM_L );
|
|
pdec->PrecacheSound(SOUND_SWIM_R );
|
|
pdec->PrecacheSound(SOUND_DIVE_L );
|
|
pdec->PrecacheSound(SOUND_DIVE_R );
|
|
pdec->PrecacheSound(SOUND_DIVEIN );
|
|
pdec->PrecacheSound(SOUND_DIVEOUT );
|
|
pdec->PrecacheSound(SOUND_DROWN );
|
|
pdec->PrecacheSound(SOUND_INHALE0 );
|
|
pdec->PrecacheSound(SOUND_JUMP );
|
|
pdec->PrecacheSound(SOUND_LAND );
|
|
pdec->PrecacheSound(SOUND_WOUNDWEAK );
|
|
pdec->PrecacheSound(SOUND_WOUNDMEDIUM );
|
|
pdec->PrecacheSound(SOUND_WOUNDSTRONG );
|
|
pdec->PrecacheSound(SOUND_WOUNDWATER );
|
|
pdec->PrecacheSound(SOUND_DEATH );
|
|
pdec->PrecacheSound(SOUND_DEATHWATER );
|
|
pdec->PrecacheSound(SOUND_WATERAMBIENT );
|
|
pdec->PrecacheSound(SOUND_WATERBUBBLES );
|
|
pdec->PrecacheSound(SOUND_WATERWALK_L );
|
|
pdec->PrecacheSound(SOUND_WATERWALK_R );
|
|
pdec->PrecacheSound(SOUND_INHALE1 );
|
|
pdec->PrecacheSound(SOUND_INHALE2 );
|
|
pdec->PrecacheSound(SOUND_INFO );
|
|
// pdec->PrecacheSound(SOUND_HIGHSCORE );
|
|
pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_TELEPORT);
|
|
|
|
pdec->PrecacheModel(MODEL_FLESH);
|
|
pdec->PrecacheModel(MODEL_FLESH_APPLE);
|
|
pdec->PrecacheModel(MODEL_FLESH_BANANA);
|
|
pdec->PrecacheModel(MODEL_FLESH_BURGER);
|
|
pdec->PrecacheTexture(TEXTURE_FLESH_RED);
|
|
pdec->PrecacheTexture(TEXTURE_FLESH_GREEN);
|
|
pdec->PrecacheTexture(TEXTURE_FLESH_APPLE);
|
|
pdec->PrecacheTexture(TEXTURE_FLESH_BANANA);
|
|
pdec->PrecacheTexture(TEXTURE_FLESH_BURGER);
|
|
pdec->PrecacheTexture(TEXTURE_FLESH_LOLLY);
|
|
pdec->PrecacheTexture(TEXTURE_FLESH_ORANGE);
|
|
|
|
pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BLOODSPILL);
|
|
pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BLOODSTAIN);
|
|
pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BLOODSTAINGROW);
|
|
pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BLOODEXPLODE);
|
|
}
|
|
|
|
void CPlayer_OnInitClass(void)
|
|
{
|
|
// clear current player controls
|
|
memset(&pctlCurrent, 0, sizeof(pctlCurrent));
|
|
// declare player control variables
|
|
_pShell->DeclareSymbol("user INDEX ctl_bMoveForward;", &pctlCurrent.bMoveForward);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bMoveBackward;", &pctlCurrent.bMoveBackward);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bMoveLeft;", &pctlCurrent.bMoveLeft);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bMoveRight;", &pctlCurrent.bMoveRight);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bMoveUp;", &pctlCurrent.bMoveUp);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bMoveDown;", &pctlCurrent.bMoveDown);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bTurnLeft;", &pctlCurrent.bTurnLeft);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bTurnRight;", &pctlCurrent.bTurnRight);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bTurnUp;", &pctlCurrent.bTurnUp);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bTurnDown;", &pctlCurrent.bTurnDown);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bTurnBankingLeft;", &pctlCurrent.bTurnBankingLeft);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bTurnBankingRight;", &pctlCurrent.bTurnBankingRight);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bCenterView;", &pctlCurrent.bCenterView);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bLookLeft;", &pctlCurrent.bLookLeft);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bLookRight;", &pctlCurrent.bLookRight);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bLookUp;", &pctlCurrent.bLookUp);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bLookDown;", &pctlCurrent.bLookDown);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bLookBankingLeft;", &pctlCurrent.bLookBankingLeft);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bLookBankingRight;", &pctlCurrent.bLookBankingRight );
|
|
_pShell->DeclareSymbol("user INDEX ctl_bWalk;", &pctlCurrent.bWalk);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bStrafe;", &pctlCurrent.bStrafe);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bStrafeFB;", &pctlCurrent.bStrafeFB);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bFire;", &pctlCurrent.bFire);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bReload;", &pctlCurrent.bReload);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bUse;", &pctlCurrent.bUse);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bComputer;", &pctlCurrent.bComputer);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bUseOrComputer;", &pctlCurrent.bUseOrComputer);
|
|
_pShell->DeclareSymbol("user INDEX ctl_b3rdPersonView;", &pctlCurrent.b3rdPersonView);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bWeaponNext;", &pctlCurrent.bWeaponNext);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bWeaponPrev;", &pctlCurrent.bWeaponPrev);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bWeaponFlip;", &pctlCurrent.bWeaponFlip);
|
|
_pShell->DeclareSymbol("user INDEX ctl_bSelectWeapon[30+1];", &pctlCurrent.bSelectWeapon);
|
|
_pShell->DeclareSymbol("persistent user FLOAT ctl_tmComputerDoubleClick;", &ctl_tmComputerDoubleClick);
|
|
_pShell->DeclareSymbol("persistent user FLOAT ctl_fButtonRotationSpeedH;", &ctl_fButtonRotationSpeedH);
|
|
_pShell->DeclareSymbol("persistent user FLOAT ctl_fButtonRotationSpeedP;", &ctl_fButtonRotationSpeedP);
|
|
_pShell->DeclareSymbol("persistent user FLOAT ctl_fButtonRotationSpeedB;", &ctl_fButtonRotationSpeedB);
|
|
_pShell->DeclareSymbol("persistent user FLOAT ctl_fAxisStrafingModifier;", &ctl_fAxisStrafingModifier);
|
|
|
|
_pShell->DeclareSymbol("user FLOAT plr_fSwimSoundDelay;", &plr_fSwimSoundDelay);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fDiveSoundDelay;", &plr_fDiveSoundDelay);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fWalkSoundDelay;", &plr_fWalkSoundDelay);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fRunSoundDelay;", &plr_fRunSoundDelay);
|
|
|
|
_pShell->DeclareSymbol("persistent user FLOAT cli_fPredictPlayersRange;",&cli_fPredictPlayersRange);
|
|
_pShell->DeclareSymbol("persistent user FLOAT cli_fPredictItemsRange;", &cli_fPredictItemsRange );
|
|
_pShell->DeclareSymbol("persistent user FLOAT cli_tmPredictFoe;", &cli_tmPredictFoe );
|
|
_pShell->DeclareSymbol("persistent user FLOAT cli_tmPredictAlly;", &cli_tmPredictAlly );
|
|
_pShell->DeclareSymbol("persistent user FLOAT cli_tmPredictEnemy;", &cli_tmPredictEnemy );
|
|
|
|
_pShell->DeclareSymbol(" INDEX hud_bShowAll;", &hud_bShowAll);
|
|
_pShell->DeclareSymbol("user INDEX hud_bShowInfo;", &hud_bShowInfo);
|
|
_pShell->DeclareSymbol("user const FLOAT net_tmLatencyAvg;", &net_tmLatencyAvg);
|
|
_pShell->DeclareSymbol("persistent user INDEX hud_bShowLatency;", &hud_bShowLatency);
|
|
_pShell->DeclareSymbol("persistent user INDEX hud_iShowPlayers;", &hud_iShowPlayers);
|
|
_pShell->DeclareSymbol("persistent user INDEX hud_iSortPlayers;", &hud_iSortPlayers);
|
|
_pShell->DeclareSymbol("persistent user INDEX hud_bShowWeapon;", &hud_bShowWeapon);
|
|
_pShell->DeclareSymbol("persistent user INDEX hud_bShowMessages;",&hud_bShowMessages);
|
|
_pShell->DeclareSymbol("persistent user FLOAT hud_fScaling;", &hud_fScaling);
|
|
_pShell->DeclareSymbol("persistent user FLOAT hud_fOpacity;", &hud_fOpacity);
|
|
_pShell->DeclareSymbol("persistent user FLOAT hud_tmWeaponsOnScreen;", &hud_tmWeaponsOnScreen);
|
|
_pShell->DeclareSymbol("persistent user FLOAT hud_tmLatencySnapshot;", &hud_tmLatencySnapshot);
|
|
_pShell->DeclareSymbol("persistent user FLOAT plr_fBreathingStrength;", &plr_fBreathingStrength);
|
|
_pShell->DeclareSymbol("INDEX cht_bKillFinalBoss;", &cht_bKillFinalBoss);
|
|
_pShell->DeclareSymbol("INDEX cht_bDebugFinalBoss;", &cht_bDebugFinalBoss);
|
|
_pShell->DeclareSymbol("INDEX cht_bDumpFinalBossData;", &cht_bDumpFinalBossData);
|
|
_pShell->DeclareSymbol("INDEX cht_bDebugFinalBossAnimations;", &cht_bDebugFinalBossAnimations);
|
|
_pShell->DeclareSymbol("INDEX cht_bDumpPlayerShading;", &cht_bDumpPlayerShading);
|
|
|
|
_pShell->DeclareSymbol("persistent user FLOAT wpn_fRecoilSpeed[17];", &wpn_fRecoilSpeed);
|
|
_pShell->DeclareSymbol("persistent user FLOAT wpn_fRecoilLimit[17];", &wpn_fRecoilLimit);
|
|
_pShell->DeclareSymbol("persistent user FLOAT wpn_fRecoilDampUp[17];", &wpn_fRecoilDampUp);
|
|
_pShell->DeclareSymbol("persistent user FLOAT wpn_fRecoilDampDn[17];", &wpn_fRecoilDampDn);
|
|
_pShell->DeclareSymbol("persistent user FLOAT wpn_fRecoilOffset[17];", &wpn_fRecoilOffset);
|
|
_pShell->DeclareSymbol("persistent user FLOAT wpn_fRecoilFactorP[17];", &wpn_fRecoilFactorP);
|
|
_pShell->DeclareSymbol("persistent user FLOAT wpn_fRecoilFactorZ[17];", &wpn_fRecoilFactorZ);
|
|
|
|
// cheats
|
|
_pShell->DeclareSymbol("user INDEX cht_bGod;", &cht_bGod);
|
|
_pShell->DeclareSymbol("user INDEX cht_bFly;", &cht_bFly);
|
|
_pShell->DeclareSymbol("user INDEX cht_bGhost;", &cht_bGhost);
|
|
_pShell->DeclareSymbol("user INDEX cht_bInvisible;", &cht_bInvisible);
|
|
_pShell->DeclareSymbol("user INDEX cht_bGiveAll;", &cht_bGiveAll);
|
|
_pShell->DeclareSymbol("user INDEX cht_bKillAll;", &cht_bKillAll);
|
|
_pShell->DeclareSymbol("user INDEX cht_bOpen;", &cht_bOpen);
|
|
_pShell->DeclareSymbol("user INDEX cht_bAllMessages;", &cht_bAllMessages);
|
|
_pShell->DeclareSymbol("user FLOAT cht_fTranslationMultiplier ;", &cht_fTranslationMultiplier);
|
|
_pShell->DeclareSymbol("user INDEX cht_bRefresh;", &cht_bRefresh);
|
|
// this one is masqueraded cheat enable variable
|
|
_pShell->DeclareSymbol("INDEX cht_bEnable;", &cht_bEnable);
|
|
|
|
// this cheat is always enabled
|
|
_pShell->DeclareSymbol("user INDEX cht_iGoToMarker;", &cht_iGoToMarker);
|
|
|
|
// player speed and view parameters, not declared except in internal build
|
|
#if 0
|
|
_pShell->DeclareSymbol("user FLOAT plr_fViewHeightStand;", &plr_fViewHeightStand);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fViewHeightCrouch;",&plr_fViewHeightCrouch);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fViewHeightSwim;", &plr_fViewHeightSwim);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fViewHeightDive;", &plr_fViewHeightDive);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fViewDampFactor;", &plr_fViewDampFactor);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fViewDampLimitGroundUp;", &plr_fViewDampLimitGroundUp);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fViewDampLimitGroundDn;", &plr_fViewDampLimitGroundDn);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fViewDampLimitWater;", &plr_fViewDampLimitWater);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fAcceleration;", &plr_fAcceleration);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fDeceleration;", &plr_fDeceleration);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fSpeedForward;", &plr_fSpeedForward);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fSpeedBackward;", &plr_fSpeedBackward);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fSpeedSide;", &plr_fSpeedSide);
|
|
_pShell->DeclareSymbol("user FLOAT plr_fSpeedUp;", &plr_fSpeedUp);
|
|
#endif
|
|
_pShell->DeclareSymbol("persistent user FLOAT plr_fFOV;", (void*) &plr_fFOV);
|
|
_pShell->DeclareSymbol("persistent user FLOAT plr_fFrontClipDistance;", (void*) &plr_fFrontClipDistance);
|
|
_pShell->DeclareSymbol("persistent user INDEX plr_bRenderPicked;", (void*) &plr_bRenderPicked);
|
|
_pShell->DeclareSymbol("persistent user INDEX plr_bRenderPickedParticles;", (void*) &plr_bRenderPickedParticles);
|
|
_pShell->DeclareSymbol("persistent user INDEX plr_bOnlySam;", (void*) &plr_bOnlySam);
|
|
_pShell->DeclareSymbol("persistent user INDEX ent_bReportBrokenChains;", (void*) &ent_bReportBrokenChains);
|
|
_pShell->DeclareSymbol("persistent user FLOAT ent_tmMentalIn ;", (void*) &ent_tmMentalIn );
|
|
_pShell->DeclareSymbol("persistent user FLOAT ent_tmMentalOut ;", (void*) &ent_tmMentalOut );
|
|
_pShell->DeclareSymbol("persistent user FLOAT ent_tmMentalFade;", (void*) &ent_tmMentalFade);
|
|
_pShell->DeclareSymbol("persistent user CTString plr_strLastLevel;", (void*) &plr_strLastLevel);
|
|
|
|
// player appearance interface
|
|
_pShell->DeclareSymbol("INDEX SetPlayerAppearance(INDEX, INDEX, INDEX, INDEX);", (void *)&SetPlayerAppearance);
|
|
|
|
// call player weapons persistant variable initialization
|
|
extern void CPlayerWeapons_Init(void);
|
|
CPlayerWeapons_Init();
|
|
|
|
// initialize HUD
|
|
InitHUD();
|
|
|
|
// precache
|
|
CPlayer_Precache();
|
|
}
|
|
|
|
// clean up
|
|
void CPlayer_OnEndClass(void)
|
|
{
|
|
EndHUD();
|
|
}
|
|
|
|
CTString GetDifficultyString(void)
|
|
{
|
|
if (GetSP()->sp_bMental) { return TRANS("Mental"); }
|
|
|
|
switch (GetSP()->sp_gdGameDifficulty) {
|
|
case CSessionProperties::GD_TOURIST: return TRANS("Tourist");
|
|
case CSessionProperties::GD_EASY: return TRANS("Easy");
|
|
default:
|
|
case CSessionProperties::GD_NORMAL: return TRANS("Normal");
|
|
case CSessionProperties::GD_HARD: return TRANS("Hard");
|
|
case CSessionProperties::GD_EXTREME: return TRANS("Serious");
|
|
}
|
|
}
|
|
// armor & health constants getters
|
|
|
|
FLOAT MaxArmor(void)
|
|
{
|
|
if (GetSP()->sp_gdGameDifficulty<=CSessionProperties::GD_EASY) {
|
|
return 300;
|
|
} else {
|
|
return 200;
|
|
}
|
|
}
|
|
FLOAT TopArmor(void)
|
|
{
|
|
if (GetSP()->sp_gdGameDifficulty<=CSessionProperties::GD_EASY) {
|
|
return 200;
|
|
} else {
|
|
return 100;
|
|
}
|
|
}
|
|
FLOAT MaxHealth(void)
|
|
{
|
|
if (GetSP()->sp_gdGameDifficulty<=CSessionProperties::GD_EASY) {
|
|
return 300;
|
|
} else {
|
|
return 200;
|
|
}
|
|
}
|
|
FLOAT TopHealth(void)
|
|
{
|
|
if (GetSP()->sp_gdGameDifficulty<=CSessionProperties::GD_EASY) {
|
|
return 200;
|
|
} else {
|
|
return 100;
|
|
}
|
|
}
|
|
|
|
// info structure
|
|
static EntityInfo eiPlayerGround = {
|
|
EIBT_FLESH, 80.0f,
|
|
0.0f, 1.7f, 0.0f, // source (eyes)
|
|
0.0f, 1.0f, 0.0f, // target (body)
|
|
};
|
|
static EntityInfo eiPlayerCrouch = {
|
|
EIBT_FLESH, 80.0f,
|
|
0.0f, 1.2f, 0.0f, // source (eyes)
|
|
0.0f, 0.7f, 0.0f, // target (body)
|
|
};
|
|
static EntityInfo eiPlayerSwim = {
|
|
EIBT_FLESH, 40.0f,
|
|
0.0f, 0.0f, 0.0f, // source (eyes)
|
|
0.0f, 0.0f, 0.0f, // target (body)
|
|
};
|
|
|
|
|
|
// animation light specific
|
|
#define LIGHT_ANIM_MINIGUN 2
|
|
#define LIGHT_ANIM_TOMMYGUN 3
|
|
#define LIGHT_ANIM_COLT_SHOTGUN 4
|
|
#define LIGHT_ANIM_NONE 5
|
|
|
|
const char *NameForState(PlayerState pst)
|
|
{
|
|
switch(pst) {
|
|
case PST_STAND: return "stand";
|
|
case PST_CROUCH: return "crouch";
|
|
case PST_FALL: return "fall";
|
|
case PST_SWIM: return "swim";
|
|
case PST_DIVE: return "dive";
|
|
default: return "???";
|
|
};
|
|
}
|
|
|
|
|
|
// print explanation on how a player died
|
|
void PrintPlayerDeathMessage(CPlayer *ppl, const EDeath &eDeath)
|
|
{
|
|
CTString strMyName = ppl->GetPlayerName();
|
|
CEntity *penKiller = eDeath.eLastDamage.penInflictor;
|
|
// if killed by a valid entity
|
|
if (penKiller!=NULL) {
|
|
// if killed by a player
|
|
if (IsOfClass(penKiller, "Player")) {
|
|
// if not self
|
|
if (penKiller!=ppl) {
|
|
CTString strKillerName = ((CPlayer*)penKiller)->GetPlayerName();
|
|
|
|
if(eDeath.eLastDamage.dmtType==DMT_TELEPORT) {
|
|
CPrintF(TRANS("%s telefragged %s\n"), (const char*)strKillerName, (const char*)strMyName);
|
|
} else if(eDeath.eLastDamage.dmtType==DMT_CLOSERANGE) {
|
|
CPrintF(TRANS("%s cut %s into pieces\n"), (const char*)strKillerName, (const char*)strMyName);
|
|
} else if(eDeath.eLastDamage.dmtType==DMT_BULLET) {
|
|
CPrintF(TRANS("%s poured lead into %s\n"), (const char*)strKillerName, (const char*)strMyName);
|
|
} else if(eDeath.eLastDamage.dmtType==DMT_PROJECTILE || eDeath.eLastDamage.dmtType==DMT_EXPLOSION) {
|
|
CPrintF(TRANS("%s blew %s away\n"), (const char*)strKillerName, (const char*)strMyName);
|
|
} else if(eDeath.eLastDamage.dmtType==DMT_CANNONBALL) {
|
|
CPrintF(TRANS("%s smashed %s with a cannon\n"), (const char*)strKillerName, (const char*)strMyName);
|
|
} else if(eDeath.eLastDamage.dmtType==DMT_CANNONBALL_EXPLOSION) {
|
|
CPrintF(TRANS("%s nuked %s\n"), (const char*)strKillerName, (const char*)strMyName);
|
|
} else {
|
|
CPrintF(TRANS("%s killed %s\n"), (const char*)strKillerName, (const char*)strMyName);
|
|
}
|
|
} else {
|
|
// make message from damage type
|
|
switch(eDeath.eLastDamage.dmtType) {
|
|
case DMT_DROWNING: CPrintF(TRANS("%s drowned\n"), (const char*)strMyName); break;
|
|
case DMT_BURNING: CPrintF(TRANS("%s burst into flames\n"), (const char*)strMyName); break;
|
|
case DMT_SPIKESTAB: CPrintF(TRANS("%s fell into a spike-hole\n"), (const char*)strMyName); break;
|
|
case DMT_FREEZING: CPrintF(TRANS("%s has frozen\n"), (const char*)strMyName); break;
|
|
case DMT_ACID: CPrintF(TRANS("%s dissolved\n"), (const char*)strMyName); break;
|
|
case DMT_PROJECTILE:
|
|
case DMT_EXPLOSION:
|
|
CPrintF(TRANS("%s blew himself away\n"), (const char*)strMyName); break;
|
|
default: CPrintF(TRANS("%s has committed suicide\n"), (const char*)strMyName);
|
|
}
|
|
}
|
|
// if killed by an enemy
|
|
} else if (IsDerivedFromClass(penKiller, "Enemy Base")) {
|
|
// check for telefrag first
|
|
if(eDeath.eLastDamage.dmtType==DMT_TELEPORT) {
|
|
CPrintF(TRANS("%s was telefragged\n"), (const char*)strMyName);
|
|
return;
|
|
}
|
|
// describe how this enemy killed player
|
|
CPrintF("%s\n", (const char*)((CEnemyBase*)penKiller)->GetPlayerKillDescription(strMyName, eDeath));
|
|
|
|
// if killed by some other entity
|
|
} else {
|
|
// make message from damage type
|
|
switch(eDeath.eLastDamage.dmtType) {
|
|
case DMT_SPIKESTAB: CPrintF(TRANS("%s was pierced\n"), (const char*)strMyName); break;
|
|
case DMT_BRUSH: CPrintF(TRANS("%s was squashed\n"), (const char*)strMyName); break;
|
|
case DMT_ABYSS: CPrintF(TRANS("%s went over the edge\n"), (const char*)strMyName); break;
|
|
case DMT_IMPACT: CPrintF(TRANS("%s swashed\n"), (const char*)strMyName); break;
|
|
case DMT_HEAT: CPrintF(TRANS("%s stood in the sun for too long\n"), (const char*)strMyName); break;
|
|
default: CPrintF(TRANS("%s passed away\n"), (const char*)strMyName);
|
|
}
|
|
}
|
|
// if no entity pointer (shouldn't happen)
|
|
} else {
|
|
CPrintF(TRANS("%s is missing in action\n"), (const char*)strMyName);
|
|
}
|
|
}
|
|
|
|
%}
|
|
|
|
class export CPlayer : CPlayerEntity {
|
|
name "Player";
|
|
thumbnail "";
|
|
features "ImplementsOnInitClass", "ImplementsOnEndClass", "CanBePredictable";
|
|
|
|
properties:
|
|
1 CTString m_strName "Name" = "<unnamed player>",
|
|
2 COLOR m_ulLastButtons = 0x0, // buttons last pressed
|
|
3 FLOAT m_fArmor = 0.0f, // armor
|
|
4 CTString m_strGroup = "", // group name for world change
|
|
5 INDEX m_ulKeys = 0, // mask for all picked-up keys
|
|
6 FLOAT m_fMaxHealth = 1, // default health supply player can have
|
|
7 INDEX m_ulFlags = 0, // various flags
|
|
|
|
16 CEntityPointer m_penWeapons, // player weapons
|
|
17 CEntityPointer m_penAnimator, // player animator
|
|
18 CEntityPointer m_penView, // player view
|
|
19 CEntityPointer m_pen3rdPersonView, // player 3rd person view
|
|
20 INDEX m_iViewState=PVT_PLAYEREYES, // view state
|
|
21 INDEX m_iLastViewState=PVT_PLAYEREYES, // last view state
|
|
|
|
26 CAnimObject m_aoLightAnimation, // light animation object
|
|
27 FLOAT m_fDamageAmmount = 0.0f, // how much was last wound
|
|
28 FLOAT m_tmWoundedTime = 0.0f, // when was last wound
|
|
29 FLOAT m_tmScreamTime = 0.0f, // when was last wound sound played
|
|
|
|
34 enum PlayerState m_pstState = PST_STAND, // current player state
|
|
35 FLOAT m_fFallTime = 0.0f, // time passed when falling
|
|
36 FLOAT m_fSwimTime = 0.0f, // time when started swimming
|
|
45 FLOAT m_tmOutOfWater = 0.0f, // time when got out of water last time
|
|
37 FLOAT m_tmMoveSound = 0.0f, // last time move sound was played
|
|
38 BOOL m_bMoveSoundLeft = TRUE, // left or right walk channel is current
|
|
39 FLOAT m_tmNextAmbientOnce = 0.0f, // next time to play local ambient sound
|
|
43 FLOAT m_tmMouthSoundLast = 0.0f, // time last played some repeating mouth sound
|
|
|
|
40 CEntityPointer m_penCamera, // camera for current cinematic sequence, or null
|
|
41 CTString m_strCenterMessage="", // center message
|
|
42 FLOAT m_tmCenterMessageEnd = 0.0f, // last time to show centered message
|
|
48 BOOL m_bPendingMessage = FALSE, // message sound pending to be played
|
|
47 FLOAT m_tmMessagePlay = 0.0f, // when to play the message sound
|
|
49 FLOAT m_tmAnalyseEnd = 0.0f, // last time to show analysation
|
|
50 BOOL m_bComputerInvoked = FALSE, // set if computer was invoked at least once
|
|
57 FLOAT m_tmAnimateInbox = -100.0f, // show animation of inbox icon animation
|
|
|
|
44 CEntityPointer m_penMainMusicHolder,
|
|
|
|
51 FLOAT m_tmLastDamage = -1.0f,
|
|
52 FLOAT m_fMaxDamageAmmount = 0.0f,
|
|
53 FLOAT3D m_vDamage = FLOAT3D(0,0,0),
|
|
54 FLOAT m_tmSpraySpawned = -1.0f,
|
|
55 FLOAT m_fSprayDamage = 0.0f,
|
|
56 CEntityPointer m_penSpray,
|
|
|
|
// sounds
|
|
60 CSoundObject m_soWeapon0,
|
|
61 CSoundObject m_soWeapon1,
|
|
62 CSoundObject m_soWeapon2,
|
|
63 CSoundObject m_soWeapon3,
|
|
|
|
70 CSoundObject m_soMouth, // breating, yelling etc.
|
|
71 CSoundObject m_soFootL, // walking etc.
|
|
72 CSoundObject m_soFootR,
|
|
73 CSoundObject m_soBody, // splashing etc.
|
|
74 CSoundObject m_soLocalAmbientLoop, // local ambient that only this player hears
|
|
75 CSoundObject m_soLocalAmbientOnce, // local ambient that only this player hears
|
|
76 CSoundObject m_soMessage, // message sounds
|
|
77 CSoundObject m_soHighScore, // high score sound
|
|
78 CSoundObject m_soSpeech, // for quotes
|
|
|
|
81 INDEX m_iMana = 0, // current score worth for killed player
|
|
94 FLOAT m_fManaFraction = 0.0f,// fractional part of mana, for slow increase with time
|
|
84 INDEX m_iHighScore = 0, // internal hiscore for demo playing
|
|
85 INDEX m_iBeatenHighScore = 0, // hiscore that was beaten
|
|
89 FLOAT m_tmLatency = 0.0f, // player-server latency (in seconds)
|
|
// for latency averaging
|
|
88 FLOAT m_tmLatencyLastAvg = 0.0f,
|
|
87 FLOAT m_tmLatencyAvgSum = 0.0f,
|
|
86 INDEX m_ctLatencyAvg = 0,
|
|
|
|
96 BOOL m_bEndOfLevel = FALSE,
|
|
97 BOOL m_bEndOfGame = FALSE,
|
|
98 INDEX m_iMayRespawn = 0, // must get to 2 to be able to respawn
|
|
99 FLOAT m_tmSpawned = 0.0f, // when player was spawned
|
|
100 FLOAT3D m_vDied = FLOAT3D(0,0,0), // where player died (for respawn in-place)
|
|
101 FLOAT3D m_aDied = FLOAT3D(0,0,0),
|
|
|
|
// statistics
|
|
103 FLOAT m_tmEstTime = 0.0f, // time estimated for this level
|
|
105 INDEX m_iTimeScore = 0,
|
|
106 INDEX m_iStartTime = 0, // game start time (ansi c time_t type)
|
|
107 INDEX m_iEndTime = 0, // game end time (ansi c time_t type)
|
|
108 FLOAT m_tmLevelStarted = 0.0f, // game time when level started
|
|
93 CTString m_strLevelStats = "", // detailed statistics for each level
|
|
|
|
// auto action vars
|
|
110 CEntityPointer m_penActionMarker, // current marker for auto actions
|
|
111 FLOAT m_fAutoSpeed = 0.0f, // speed to go towards the marker
|
|
112 INDEX m_iAutoOrgWeapon = 0, // original weapon for autoactions
|
|
113 FLOAT3D m_vAutoSpeed = FLOAT3D(0,0,0),
|
|
114 FLOAT m_tmSpiritStart = 0.0f,
|
|
115 FLOAT m_tmFadeStart = 0.0f,
|
|
|
|
// 'picked up' display vars
|
|
120 FLOAT m_tmLastPicked = -10000.0f, // when something was last picked up
|
|
121 CTString m_strPickedName = "", // name of item picked
|
|
122 FLOAT m_fPickedAmmount = 0.0f, // total picked ammount
|
|
123 FLOAT m_fPickedMana = 0.0f, // total picked mana
|
|
|
|
// shaker values
|
|
130 INDEX m_iLastHealth = 0,
|
|
131 INDEX m_iLastArmor = 0,
|
|
132 INDEX m_iLastAmmo = 0,
|
|
135 FLOAT m_tmHealthChanged = -9,
|
|
136 FLOAT m_tmArmorChanged = -9,
|
|
137 FLOAT m_tmAmmoChanged = -9,
|
|
|
|
138 FLOAT m_tmMinigunAutoFireStart = -1.0f,
|
|
|
|
150 FLOAT3D m_vLastStain = FLOAT3D(0,0,0), // where last stain was left
|
|
|
|
// for mouse lag elimination via prescanning
|
|
151 ANGLE3D m_aLastRotation = FLOAT3D(0,0,0),
|
|
152 ANGLE3D m_aLastViewRotation = FLOAT3D(0,0,0),
|
|
153 FLOAT3D m_vLastTranslation = FLOAT3D(0,0,0),
|
|
154 ANGLE3D m_aLocalRotation = FLOAT3D(0,0,0),
|
|
155 ANGLE3D m_aLocalViewRotation = FLOAT3D(0,0,0),
|
|
156 FLOAT3D m_vLocalTranslation = FLOAT3D(0,0,0),
|
|
|
|
|
|
{
|
|
ShellLaunchData ShellLaunchData_array; // array of data describing flying empty shells
|
|
INDEX m_iFirstEmptySLD; // index of last added empty shell
|
|
|
|
ULONG ulButtonsNow; ULONG ulButtonsBefore;
|
|
ULONG ulNewButtons;
|
|
ULONG ulReleasedButtons;
|
|
|
|
// listener
|
|
CSoundListener sliSound;
|
|
// light
|
|
CLightSource m_lsLightSource;
|
|
|
|
TIME m_tmPredict; // time to predict the entity to
|
|
|
|
// all messages in the inbox
|
|
CDynamicStackArray<CCompMessageID> m_acmiMessages;
|
|
INDEX m_ctUnreadMessages;
|
|
|
|
// statistics
|
|
PlayerStats m_psLevelStats;
|
|
PlayerStats m_psLevelTotal;
|
|
PlayerStats m_psGameStats;
|
|
PlayerStats m_psGameTotal;
|
|
|
|
CModelObject m_moRender; // model object to render - this one can be customized
|
|
}
|
|
|
|
components:
|
|
1 class CLASS_PLAYER_WEAPONS "Classes\\PlayerWeapons.ecl",
|
|
2 class CLASS_PLAYER_ANIMATOR "Classes\\PlayerAnimator.ecl",
|
|
3 class CLASS_PLAYER_VIEW "Classes\\PlayerView.ecl",
|
|
4 class CLASS_BASIC_EFFECT "Classes\\BasicEffect.ecl",
|
|
5 class CLASS_BLOOD_SPRAY "Classes\\BloodSpray.ecl",
|
|
|
|
// sounds
|
|
150 sound SOUND_WATER_ENTER "Sounds\\Player\\WaterEnter.wav",
|
|
151 sound SOUND_WATER_LEAVE "Sounds\\Player\\WaterLeave.wav",
|
|
152 sound SOUND_WALK_L "Sounds\\Player\\WalkL.wav",
|
|
153 sound SOUND_WALK_R "Sounds\\Player\\WalkR.wav",
|
|
154 sound SOUND_SWIM_L "Sounds\\Player\\SwimL.wav",
|
|
155 sound SOUND_SWIM_R "Sounds\\Player\\SwimR.wav",
|
|
156 sound SOUND_DIVE_L "Sounds\\Player\\Dive.wav",
|
|
157 sound SOUND_DIVE_R "Sounds\\Player\\Dive.wav",
|
|
158 sound SOUND_DIVEIN "Sounds\\Player\\DiveIn.wav",
|
|
159 sound SOUND_DIVEOUT "Sounds\\Player\\DiveOut.wav",
|
|
160 sound SOUND_DROWN "Sounds\\Player\\Drown.wav",
|
|
161 sound SOUND_INHALE0 "Sounds\\Player\\Inhale00.wav",
|
|
162 sound SOUND_JUMP "Sounds\\Player\\Jump.wav",
|
|
163 sound SOUND_LAND "Sounds\\Player\\Land.wav",
|
|
166 sound SOUND_DEATH "Sounds\\Player\\Death.wav",
|
|
167 sound SOUND_DEATHWATER "Sounds\\Player\\DeathWater.wav",
|
|
168 sound SOUND_WATERAMBIENT "Sounds\\Player\\Underwater.wav",
|
|
169 sound SOUND_WATERBUBBLES "Sounds\\Player\\Bubbles.wav",
|
|
170 sound SOUND_WATERWALK_L "Sounds\\Player\\WalkWaterL.wav",
|
|
171 sound SOUND_WATERWALK_R "Sounds\\Player\\WalkWaterR.wav",
|
|
172 sound SOUND_INHALE1 "Sounds\\Player\\Inhale01.wav",
|
|
173 sound SOUND_INHALE2 "Sounds\\Player\\Inhale02.wav",
|
|
174 sound SOUND_INFO "Sounds\\Player\\Info.wav",
|
|
175 sound SOUND_WALK_SAND_L "Sounds\\Player\\WalkSandL.wav",
|
|
176 sound SOUND_WALK_SAND_R "Sounds\\Player\\WalkSandR.wav",
|
|
//178 sound SOUND_HIGHSCORE "Sounds\\Player\\HighScore.wav",
|
|
180 sound SOUND_WOUNDWEAK "Sounds\\Player\\WoundWeak.wav",
|
|
181 sound SOUND_WOUNDMEDIUM "Sounds\\Player\\WoundMedium.wav",
|
|
182 sound SOUND_WOUNDSTRONG "Sounds\\Player\\WoundStrong.wav",
|
|
185 sound SOUND_WOUNDWATER "Sounds\\Player\\WoundWater.wav",
|
|
|
|
// ************** FLESH PARTS **************
|
|
210 model MODEL_FLESH "Models\\Effects\\Debris\\Flesh\\Flesh.mdl",
|
|
211 model MODEL_FLESH_APPLE "Models\\Effects\\Debris\\Fruits\\Apple.mdl",
|
|
212 model MODEL_FLESH_BANANA "Models\\Effects\\Debris\\Fruits\\Banana.mdl",
|
|
213 model MODEL_FLESH_BURGER "Models\\Effects\\Debris\\Fruits\\CheeseBurger.mdl",
|
|
214 model MODEL_FLESH_LOLLY "Models\\Effects\\Debris\\Fruits\\LollyPop.mdl",
|
|
215 model MODEL_FLESH_ORANGE "Models\\Effects\\Debris\\Fruits\\Orange.mdl",
|
|
|
|
220 texture TEXTURE_FLESH_RED "Models\\Effects\\Debris\\Flesh\\FleshRed.tex",
|
|
221 texture TEXTURE_FLESH_GREEN "Models\\Effects\\Debris\\Flesh\\FleshGreen.tex",
|
|
222 texture TEXTURE_FLESH_APPLE "Models\\Effects\\Debris\\Fruits\\Apple.tex",
|
|
223 texture TEXTURE_FLESH_BANANA "Models\\Effects\\Debris\\Fruits\\Banana.tex",
|
|
224 texture TEXTURE_FLESH_BURGER "Models\\Effects\\Debris\\Fruits\\CheeseBurger.tex",
|
|
225 texture TEXTURE_FLESH_LOLLY "Models\\Effects\\Debris\\Fruits\\LollyPop.tex",
|
|
226 texture TEXTURE_FLESH_ORANGE "Models\\Effects\\Debris\\Fruits\\Orange.tex",
|
|
|
|
|
|
functions:
|
|
|
|
void AddBouble( FLOAT3D vPos, FLOAT3D vSpeedRelative)
|
|
{
|
|
ShellLaunchData &sld = m_asldData[m_iFirstEmptySLD];
|
|
sld.sld_vPos = vPos;
|
|
const FLOATmatrix3D &m = GetRotationMatrix();
|
|
FLOAT3D vUp( m(1,2), m(2,2), m(3,2));
|
|
sld.sld_vUp = vUp;
|
|
sld.sld_vSpeed = vSpeedRelative*m;
|
|
sld.sld_tmLaunch = _pTimer->CurrentTick();
|
|
sld.sld_estType = ESL_BUBBLE;
|
|
// move to next shell position
|
|
m_iFirstEmptySLD = (m_iFirstEmptySLD+1) % MAX_FLYING_SHELLS;
|
|
}
|
|
|
|
|
|
void ClearShellLaunchData( void)
|
|
{
|
|
// clear flying shells data array
|
|
m_iFirstEmptySLD = 0;
|
|
for( INDEX iShell=0; iShell<MAX_FLYING_SHELLS; iShell++)
|
|
{
|
|
m_asldData[iShell].sld_tmLaunch = -100.0f;
|
|
}
|
|
}
|
|
|
|
void CPlayer(void)
|
|
{
|
|
// clear flying shells data array
|
|
ClearShellLaunchData();
|
|
m_tmPredict = 0;
|
|
}
|
|
|
|
class CPlayerWeapons *GetPlayerWeapons(void)
|
|
{
|
|
ASSERT(m_penWeapons!=NULL);
|
|
return (CPlayerWeapons *)&*m_penWeapons;
|
|
}
|
|
class CPlayerAnimator *GetPlayerAnimator(void)
|
|
{
|
|
ASSERT(m_penAnimator!=NULL);
|
|
return (CPlayerAnimator *)&*m_penAnimator;
|
|
}
|
|
|
|
CPlayerSettings *GetSettings(void)
|
|
{
|
|
return (CPlayerSettings *)en_pcCharacter.pc_aubAppearance;
|
|
}
|
|
|
|
export void Copy(CEntity &enOther, ULONG ulFlags)
|
|
{
|
|
CPlayerEntity::Copy(enOther, ulFlags);
|
|
CPlayer *penOther = (CPlayer *)(&enOther);
|
|
m_moRender.Copy(penOther->m_moRender);
|
|
m_psLevelStats = penOther->m_psLevelStats;
|
|
m_psLevelTotal = penOther->m_psLevelTotal;
|
|
m_psGameStats = penOther->m_psGameStats ;
|
|
m_psGameTotal = penOther->m_psGameTotal ;
|
|
|
|
// if creating predictor
|
|
if (ulFlags©_PREDICTOR)
|
|
{
|
|
// copy positions of launched empty shells
|
|
memcpy( m_asldData, penOther->m_asldData, sizeof( m_asldData));
|
|
m_iFirstEmptySLD = penOther->m_iFirstEmptySLD;
|
|
// all messages in the inbox
|
|
m_acmiMessages.Clear();
|
|
m_ctUnreadMessages = 0;
|
|
//m_lsLightSource;
|
|
SetupLightSource(); //? is this ok !!!!
|
|
|
|
// if normal copying
|
|
} else {
|
|
// copy messages
|
|
m_acmiMessages = penOther->m_acmiMessages;
|
|
m_ctUnreadMessages = penOther->m_ctUnreadMessages;
|
|
}
|
|
}
|
|
|
|
// update smoothed (average latency)
|
|
void UpdateLatency(FLOAT tmLatencyNow)
|
|
{
|
|
TIME tmNow = _pTimer->GetHighPrecisionTimer().GetSeconds();
|
|
|
|
// if not enough time passed
|
|
if (tmNow<m_tmLatencyLastAvg+hud_tmLatencySnapshot) {
|
|
// just sum
|
|
m_tmLatencyAvgSum += tmLatencyNow;
|
|
m_ctLatencyAvg++;
|
|
|
|
// if enough time passed
|
|
} else {
|
|
// calculate average
|
|
m_tmLatency = m_tmLatencyAvgSum/m_ctLatencyAvg;
|
|
// reset counters
|
|
m_tmLatencyAvgSum = 0.0f;
|
|
m_ctLatencyAvg = 0;
|
|
m_tmLatencyLastAvg = tmNow;
|
|
}
|
|
|
|
if (_pNetwork->IsPlayerLocal(this)) {
|
|
en_tmPing = m_tmLatency;
|
|
net_tmLatencyAvg = en_tmPing;
|
|
}
|
|
}
|
|
|
|
// check character data for invalid values
|
|
void ValidateCharacter(void)
|
|
{
|
|
// if in single player or flyover
|
|
if (GetSP()->sp_bSinglePlayer) {
|
|
// always use default model
|
|
CPlayerSettings *pps = (CPlayerSettings *)en_pcCharacter.pc_aubAppearance;
|
|
memset(pps->ps_achModelFile, 0, sizeof(pps->ps_achModelFile));
|
|
}
|
|
}
|
|
|
|
void CheckHighScore(void)
|
|
{
|
|
// if not playing a demo
|
|
if (!_pNetwork->IsPlayingDemo()) {
|
|
// update our local high score with the external
|
|
if (plr_iHiScore>m_iHighScore) {
|
|
m_iHighScore = plr_iHiScore;
|
|
}
|
|
}
|
|
|
|
// if current score is better than highscore
|
|
if (m_psGameStats.ps_iScore>m_iHighScore) {
|
|
// if it is a highscore greater than the last one beaten
|
|
if (m_iHighScore>m_iBeatenHighScore) {
|
|
// remember that it was beaten
|
|
m_iBeatenHighScore = m_iHighScore;
|
|
// tell that to player
|
|
m_soHighScore.Set3DParameters(25.0f, 5.0f, 1.0f, 1.0f);
|
|
//PlaySound(m_soHighScore, SOUND_HIGHSCORE, 0); !!!!####!!!!
|
|
}
|
|
}
|
|
}
|
|
|
|
CTString GetPredictName(void) const
|
|
{
|
|
if (IsPredicted()) {
|
|
return "PREDICTED";
|
|
} else if (IsPredictor()) {
|
|
return "predictor";
|
|
} else if (GetFlags()&ENF_WILLBEPREDICTED){
|
|
return "WILLBEPREDICTED";
|
|
} else {
|
|
return "no prediction";
|
|
}
|
|
}
|
|
/* Write to stream. */
|
|
void Write_t( CTStream *ostr) // throw char *
|
|
{
|
|
CPlayerEntity::Write_t(ostr);
|
|
// save array of messages
|
|
ostr->WriteID_t("MSGS");
|
|
INDEX ctMsg = m_acmiMessages.Count();
|
|
(*ostr)<<ctMsg;
|
|
for(INDEX iMsg=0; iMsg<ctMsg; iMsg++) {
|
|
m_acmiMessages[iMsg].Write_t(*ostr);
|
|
}
|
|
ostr->Write_t(&m_psLevelStats, sizeof(m_psLevelStats));
|
|
ostr->Write_t(&m_psLevelTotal, sizeof(m_psLevelTotal));
|
|
ostr->Write_t(&m_psGameStats , sizeof(m_psGameStats ));
|
|
ostr->Write_t(&m_psGameTotal , sizeof(m_psGameTotal ));
|
|
}
|
|
/* Read from stream. */
|
|
void Read_t( CTStream *istr) // throw char *
|
|
{
|
|
CPlayerEntity::Read_t(istr);
|
|
// clear flying shells data array
|
|
ClearShellLaunchData();
|
|
// load array of messages
|
|
istr->ExpectID_t("MSGS");
|
|
INDEX ctMsg;
|
|
(*istr)>>ctMsg;
|
|
m_acmiMessages.Clear();
|
|
m_ctUnreadMessages = 0;
|
|
if( ctMsg>0) {
|
|
m_acmiMessages.Push(ctMsg);
|
|
for(INDEX iMsg=0; iMsg<ctMsg; iMsg++) {
|
|
m_acmiMessages[iMsg].Read_t(*istr);
|
|
if (!m_acmiMessages[iMsg].cmi_bRead) {
|
|
m_ctUnreadMessages++;
|
|
}
|
|
}
|
|
}
|
|
|
|
istr->Read_t(&m_psLevelStats, sizeof(m_psLevelStats));
|
|
istr->Read_t(&m_psLevelTotal, sizeof(m_psLevelTotal));
|
|
istr->Read_t(&m_psGameStats , sizeof(m_psGameStats ));
|
|
istr->Read_t(&m_psGameTotal , sizeof(m_psGameTotal ));
|
|
|
|
// set your real appearance if possible
|
|
ValidateCharacter();
|
|
CTString strDummy;
|
|
SetPlayerAppearance(&m_moRender, &en_pcCharacter, strDummy, /*bPreview=*/FALSE);
|
|
m_ulFlags |= PLF_SYNCWEAPON;
|
|
// setup light source
|
|
SetupLightSource();
|
|
};
|
|
|
|
/* Get static light source information. */
|
|
CLightSource *GetLightSource(void)
|
|
{
|
|
if (!IsPredictor()) {
|
|
return &m_lsLightSource;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
};
|
|
|
|
// called by other entities to set time prediction parameter
|
|
void SetPredictionTime(TIME tmAdvance) // give time interval in advance to set
|
|
{
|
|
m_tmPredict = _pTimer->CurrentTick()+tmAdvance;
|
|
}
|
|
|
|
// called by engine to get the upper time limit
|
|
TIME GetPredictionTime(void) // return moment in time up to which to predict this entity
|
|
{
|
|
return m_tmPredict;
|
|
}
|
|
|
|
// get maximum allowed range for predicting this entity
|
|
FLOAT GetPredictionRange(void)
|
|
{
|
|
return cli_fPredictPlayersRange;
|
|
}
|
|
|
|
// add to prediction any entities that this entity depends on
|
|
void AddDependentsToPrediction(void)
|
|
{
|
|
m_penWeapons->AddToPrediction();
|
|
m_penAnimator->AddToPrediction();
|
|
m_penView->AddToPrediction();
|
|
m_pen3rdPersonView->AddToPrediction();
|
|
}
|
|
|
|
// get in-game time for statistics
|
|
TIME GetStatsInGameTimeLevel(void)
|
|
{
|
|
if(m_bEndOfLevel) {
|
|
return m_psLevelStats.ps_tmTime;
|
|
} else {
|
|
return _pNetwork->GetGameTime()-m_tmLevelStarted;
|
|
}
|
|
}
|
|
TIME GetStatsInGameTimeGame(void)
|
|
{
|
|
if(m_bEndOfLevel) {
|
|
return m_psGameStats.ps_tmTime;
|
|
} else {
|
|
return m_psGameStats.ps_tmTime + (_pNetwork->GetGameTime()-m_tmLevelStarted);
|
|
}
|
|
}
|
|
|
|
FLOAT GetStatsRealWorldTime(void)
|
|
{
|
|
time_t timeNow;
|
|
if(m_bEndOfLevel) {
|
|
timeNow = m_iEndTime;
|
|
} else {
|
|
time(&timeNow);
|
|
}
|
|
return (FLOAT)difftime( timeNow, m_iStartTime);
|
|
}
|
|
|
|
CTString GetStatsRealWorldStarted(void)
|
|
{
|
|
struct tm *newtime;
|
|
STUBBED("this isn't 64-bit clean");
|
|
time_t t = (time_t) m_iStartTime;
|
|
newtime = localtime(&t);
|
|
|
|
setlocale(LC_ALL, "");
|
|
CTString strTimeline;
|
|
char achTimeLine[256];
|
|
strftime( achTimeLine, sizeof(achTimeLine)-1, "%a %x %H:%M", newtime);
|
|
strTimeline = achTimeLine;
|
|
setlocale(LC_ALL, "C");
|
|
return strTimeline;
|
|
}
|
|
|
|
// fill in player statistics
|
|
export void GetStats( CTString &strStats, const CompStatType csType, INDEX ctCharsPerRow)
|
|
{
|
|
|
|
// get proper type of stats
|
|
if( csType==CST_SHORT) {
|
|
GetShortStats(strStats);
|
|
} else {
|
|
ASSERT(csType==CST_DETAIL);
|
|
|
|
strStats = "\n";
|
|
_ctAlignWidth = Min(ctCharsPerRow, INDEX(60));
|
|
|
|
if (GetSP()->sp_bCooperative) {
|
|
if (GetSP()->sp_bSinglePlayer) {
|
|
GetDetailStatsSP(strStats, 0);
|
|
} else {
|
|
GetDetailStatsCoop(strStats);
|
|
}
|
|
} else {
|
|
GetDetailStatsDM(strStats);
|
|
}
|
|
}
|
|
}
|
|
|
|
// get short one-line statistics - used for savegame descriptions and similar
|
|
void GetShortStats(CTString &strStats)
|
|
{
|
|
strStats.PrintF( TRANS("%s %s Score: %d Kills: %d/%d"),
|
|
(const char*)GetDifficultyString(), (const char*)TimeToString(GetStatsInGameTimeLevel()),
|
|
m_psLevelStats.ps_iScore, m_psLevelStats.ps_iKills, m_psLevelTotal.ps_iKills);
|
|
}
|
|
|
|
// get detailed statistics for deathmatch game
|
|
void GetDetailStatsDM(CTString &strStats)
|
|
{
|
|
extern INDEX SetAllPlayersStats( INDEX iSortKey);
|
|
extern CPlayer *_apenPlayers[NET_MAXGAMEPLAYERS];
|
|
// determine type of game
|
|
const BOOL bFragMatch = GetSP()->sp_bUseFrags;
|
|
|
|
// fill players table
|
|
const INDEX ctPlayers = SetAllPlayersStats(bFragMatch?5:3); // sort by frags or by score
|
|
|
|
// get time elapsed since the game start
|
|
strStats+=AlignString(CTString(0, "^cFFFFFF%s:^r\n%s", (const char*)TRANS("TIME"), (const char*)TimeToString(_pNetwork->GetGameTime())));
|
|
strStats+="\n";
|
|
|
|
// find maximum frags/score that one player has
|
|
INDEX iMaxFrags = LowerLimit(INDEX(0));
|
|
INDEX iMaxScore = LowerLimit(INDEX(0));
|
|
{for(INDEX iPlayer=0; iPlayer<ctPlayers; iPlayer++) {
|
|
CPlayer *penPlayer = _apenPlayers[iPlayer];
|
|
iMaxFrags = Max(iMaxFrags, penPlayer->m_psLevelStats.ps_iKills);
|
|
iMaxScore = Max(iMaxScore, penPlayer->m_psLevelStats.ps_iScore);
|
|
}}
|
|
|
|
// print game limits
|
|
const CSessionProperties &sp = *GetSP();
|
|
if (sp.sp_iTimeLimit>0) {
|
|
FLOAT fTimeLeft = ClampDn(sp.sp_iTimeLimit*60.0f - _pNetwork->GetGameTime(), 0.0f);
|
|
strStats+=AlignString(CTString(0, "^cFFFFFF%s:^r\n%s", TRANS("TIME LEFT"), (const char*)TimeToString(fTimeLeft)));
|
|
strStats+="\n";
|
|
}
|
|
if (bFragMatch && sp.sp_iFragLimit>0) {
|
|
INDEX iFragsLeft = ClampDn(sp.sp_iFragLimit-iMaxFrags, INDEX(0));
|
|
strStats+=AlignString(CTString(0, "^cFFFFFF%s:^r\n%d", TRANS("FRAGS LEFT"), iFragsLeft));
|
|
strStats+="\n";
|
|
}
|
|
if (!bFragMatch && sp.sp_iScoreLimit>0) {
|
|
INDEX iScoreLeft = ClampDn(sp.sp_iScoreLimit-iMaxScore, INDEX(0));
|
|
strStats+=AlignString(CTString(0, "^cFFFFFF%s:^r\n%d", TRANS("SCORE LEFT"), iScoreLeft));
|
|
strStats+="\n";
|
|
}
|
|
strStats += "\n";
|
|
|
|
CTString strRank = TRANS("NO.");
|
|
CTString strFrag = bFragMatch ? TRANS("FRAGS"):TRANS("SCORE");
|
|
CTString strPing = TRANS("PING");
|
|
CTString strName = TRANS("PLAYER");
|
|
INDEX ctRankChars = Max(strRank.Length(), INDEX(3)) ;
|
|
INDEX ctFragChars = Max(strFrag.Length(), INDEX(7)) ;
|
|
INDEX ctPingChars = Max(strPing.Length(), INDEX(5)) ;
|
|
INDEX ctNameChars = Max(strName.Length(), INDEX(20));
|
|
|
|
// header
|
|
strStats += "^cFFFFFF";
|
|
strStats += PadStringRight(strRank, ctRankChars)+" ";
|
|
strStats += PadStringLeft (strFrag, ctFragChars)+" ";
|
|
strStats += PadStringLeft (strPing, ctPingChars)+" ";
|
|
strStats += PadStringRight(strName, ctNameChars)+" ";
|
|
strStats += "^r";
|
|
strStats += "\n\n";
|
|
{for(INDEX iPlayer=0; iPlayer<ctPlayers; iPlayer++) {
|
|
CTString strLine;
|
|
CPlayer *penPlayer = _apenPlayers[iPlayer];
|
|
INDEX iPing = ceil(penPlayer->en_tmPing*1000.0f);
|
|
INDEX iScore = bFragMatch ? penPlayer->m_psLevelStats.ps_iKills : penPlayer->m_psLevelStats.ps_iScore;
|
|
CTString strName = penPlayer->GetPlayerName();
|
|
|
|
strStats += PadStringRight(CTString(0, "%d", iPlayer+1), ctRankChars)+" ";
|
|
strStats += PadStringLeft (CTString(0, "%d", iScore), ctFragChars)+" ";
|
|
strStats += PadStringLeft (CTString(0, "%d", iPing), ctPingChars)+" ";
|
|
strStats += PadStringRight(strName, ctNameChars)+" ";
|
|
strStats += "\n";
|
|
}}
|
|
}
|
|
|
|
// get singleplayer statistics
|
|
void GetDetailStatsCoop(CTString &strStats)
|
|
{
|
|
// first put in your full stats
|
|
strStats += "^b"+CenterString(TRANS("YOUR STATS"))+"^r\n";
|
|
strStats+="\n";
|
|
GetDetailStatsSP(strStats, 1);
|
|
|
|
// get stats from all players
|
|
extern INDEX SetAllPlayersStats( INDEX iSortKey);
|
|
extern CPlayer *_apenPlayers[NET_MAXGAMEPLAYERS];
|
|
const INDEX ctPlayers = SetAllPlayersStats(3); // sort by score
|
|
|
|
// for each player
|
|
PlayerStats psSquadLevel = PlayerStats();
|
|
PlayerStats psSquadGame = PlayerStats();
|
|
{for( INDEX iPlayer=0; iPlayer<ctPlayers; iPlayer++) {
|
|
CPlayer *penPlayer = _apenPlayers[iPlayer];
|
|
// add values to squad stats
|
|
ASSERT( penPlayer!=NULL);
|
|
PlayerStats psLevel = penPlayer->m_psLevelStats;
|
|
PlayerStats psGame = penPlayer->m_psGameStats ;
|
|
psSquadLevel.ps_iScore += psLevel.ps_iScore ;
|
|
psSquadLevel.ps_iKills += psLevel.ps_iKills ;
|
|
psSquadLevel.ps_iDeaths += psLevel.ps_iDeaths ;
|
|
psSquadLevel.ps_iSecrets += psLevel.ps_iSecrets ;
|
|
psSquadGame.ps_iScore += psGame.ps_iScore ;
|
|
psSquadGame.ps_iKills += psGame.ps_iKills ;
|
|
psSquadGame.ps_iDeaths += psGame.ps_iDeaths ;
|
|
psSquadGame.ps_iSecrets += psGame.ps_iSecrets ;
|
|
}}
|
|
|
|
// add squad stats
|
|
strStats+="\n";
|
|
strStats += "^b"+CenterString(TRANS("SQUAD TOTAL"))+"^r\n";
|
|
strStats+="\n";
|
|
strStats+=CTString(0, "^cFFFFFF%s^r", TranslateConst(en_pwoWorld->GetName(), 0));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d", TRANS("SCORE"), psSquadLevel.ps_iScore));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d", TRANS("DEATHS"), psSquadLevel.ps_iDeaths));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d/%d", TRANS("KILLS"), psSquadLevel.ps_iKills, m_psLevelTotal.ps_iKills));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d/%d", TRANS("SECRETS"), psSquadLevel.ps_iSecrets, m_psLevelTotal.ps_iSecrets));
|
|
strStats+="\n";
|
|
strStats+="\n";
|
|
strStats+=CTString("^cFFFFFF")+TRANS("TOTAL")+"^r\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d", TRANS("SCORE"), psSquadGame.ps_iScore));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d", TRANS("DEATHS"), psSquadGame.ps_iDeaths));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d/%d", TRANS("KILLS"), psSquadGame.ps_iKills, m_psGameTotal.ps_iKills));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d/%d", TRANS("SECRETS"), psSquadGame.ps_iSecrets, m_psGameTotal.ps_iSecrets));
|
|
strStats+="\n";
|
|
strStats+="\n";
|
|
|
|
|
|
strStats+="\n";
|
|
strStats += "^b"+CenterString(TRANS("OTHER PLAYERS"))+"^r\n";
|
|
strStats+="\n";
|
|
|
|
// for each player
|
|
{for(INDEX iPlayer=0; iPlayer<ctPlayers; iPlayer++) {
|
|
CPlayer *penPlayer = _apenPlayers[iPlayer];
|
|
// if this one
|
|
if (penPlayer==this) {
|
|
// skip it
|
|
continue;
|
|
}
|
|
// add his stats short
|
|
strStats+="^cFFFFFF"+CenterString(penPlayer->GetPlayerName())+"^r\n\n";
|
|
penPlayer->GetDetailStatsSP(strStats, 2);
|
|
strStats+="\n";
|
|
}}
|
|
}
|
|
|
|
// get singleplayer statistics
|
|
void GetDetailStatsSP(CTString &strStats, INDEX iCoopType)
|
|
{
|
|
if (iCoopType<=1) {
|
|
if (m_bEndOfGame) {
|
|
if (GetWorld()->wo_fnmFileName==plr_strLastLevel) {
|
|
strStats+=CTString(TRANS("^f4YOU HAVE FINISHED THE LIMITED EDITION!^F\n"))+
|
|
TRANS("8 more levels are available in\nthe full version of\n'Serious Sam: The First Encounter'!\n\n");
|
|
} else if (GetSP()->sp_gdGameDifficulty==CSessionProperties::GD_EXTREME) {
|
|
strStats+=TRANS("^f4SERIOUS GAME FINISHED,\nMENTAL MODE IS NOW ENABLED!^F\n\n");
|
|
} else if (GetSP()->sp_bMental) {
|
|
strStats+=TRANS("^f4YOU HAVE MASTERED THE GAME!^F\n\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iCoopType<=1) {
|
|
// report total score info
|
|
strStats+=AlignString(CTString(0, "^cFFFFFF%s:^r\n%d", TRANS("TOTAL SCORE"), m_psGameStats.ps_iScore));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, "^cFFFFFF%s:^r\n%s", TRANS("DIFFICULTY"), (const char*)GetDifficultyString()));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, "^cFFFFFF%s:^r\n%s", TRANS("STARTED"), (const char*)GetStatsRealWorldStarted()));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, "^cFFFFFF%s:^r\n%s", TRANS("PLAYING TIME"), (const char*)TimeToString(GetStatsRealWorldTime())));
|
|
strStats+="\n";
|
|
if( m_psGameStats.ps_iScore<=plr_iHiScore) {
|
|
strStats+=AlignString(CTString(0, "^cFFFFFF%s:^r\n%d", TRANS("HI-SCORE"), plr_iHiScore));
|
|
} else {
|
|
strStats+=TRANS("YOU BEAT THE HI-SCORE!");
|
|
}
|
|
strStats+="\n\n";
|
|
}
|
|
|
|
// report this level statistics
|
|
strStats+=CTString(0, "^cFFFFFF%s^r", TranslateConst(en_pwoWorld->GetName(), 0));
|
|
strStats+="\n";
|
|
if (iCoopType<=1) {
|
|
if( m_bEndOfLevel) {
|
|
strStats+=AlignString(CTString(0, " %s:\n%s", TRANS("ESTIMATED TIME"), (const char*)TimeToString(m_tmEstTime)));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d", TRANS("TIME BONUS"), m_iTimeScore));
|
|
strStats+="\n";
|
|
strStats+="\n";
|
|
}
|
|
// } else {
|
|
// strStats+=CTString("^cFFFFFF")+TRANS("THIS LEVEL")+"^r\n";
|
|
}
|
|
strStats+=AlignString(CTString(0, " %s:\n%d", TRANS("SCORE"), m_psLevelStats.ps_iScore));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d/%d", TRANS("KILLS"), m_psLevelStats.ps_iKills, m_psLevelTotal.ps_iKills));
|
|
strStats+="\n";
|
|
if (iCoopType>=1) {
|
|
strStats+=AlignString(CTString(0, " %s:\n%d", TRANS("DEATHS"), m_psLevelStats.ps_iDeaths, m_psLevelTotal.ps_iDeaths));
|
|
strStats+="\n";
|
|
}
|
|
strStats+=AlignString(CTString(0, " %s:\n%d/%d", TRANS("SECRETS"), m_psLevelStats.ps_iSecrets, m_psLevelTotal.ps_iSecrets));
|
|
strStats+="\n";
|
|
if (iCoopType<=1) {
|
|
strStats+=AlignString(CTString(0, " %s:\n%s", TRANS("TIME"), (const char*)TimeToString(GetStatsInGameTimeLevel())));
|
|
strStats+="\n";
|
|
}
|
|
strStats+="\n";
|
|
|
|
// report total game statistics
|
|
strStats+=CTString("^cFFFFFF")+TRANS("TOTAL")+"^r";
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d", TRANS("SCORE"), m_psGameStats.ps_iScore));
|
|
strStats+="\n";
|
|
strStats+=AlignString(CTString(0, " %s:\n%d/%d", TRANS("KILLS"), m_psGameStats.ps_iKills, m_psGameTotal.ps_iKills));
|
|
strStats+="\n";
|
|
if (iCoopType>=1) {
|
|
strStats+=AlignString(CTString(0, " %s:\n%d", TRANS("DEATHS"), m_psGameStats.ps_iDeaths, m_psGameTotal.ps_iDeaths));
|
|
strStats+="\n";
|
|
}
|
|
strStats+=AlignString(CTString(0, " %s:\n%d/%d", TRANS("SECRETS"), m_psGameStats.ps_iSecrets, m_psGameTotal.ps_iSecrets));
|
|
strStats+="\n";
|
|
if (iCoopType<=1) {
|
|
strStats+=AlignString(CTString(0, " %s:\n%s", TRANS("GAME TIME"),(const char*) TimeToString(GetStatsInGameTimeGame())));
|
|
strStats+="\n";
|
|
}
|
|
strStats+="\n";
|
|
|
|
// set per level outputs
|
|
if (iCoopType<=1) {
|
|
if(m_strLevelStats!="") {
|
|
strStats += CTString("^cFFFFFF")+TRANS("Per level statistics") +"^r\n\n" + m_strLevelStats;
|
|
}
|
|
}
|
|
}
|
|
|
|
// provide info for GameSpy enumeration
|
|
void GetGameSpyPlayerInfo( INDEX iPlayer, CTString &strOut)
|
|
{
|
|
CTString strKey;
|
|
strKey.PrintF("\\player_%d\\%s", iPlayer, (const char*)GetPlayerName());
|
|
strOut+=strKey;
|
|
if (GetSP()->sp_bUseFrags) {
|
|
strKey.PrintF("\\frags_%d\\%d", iPlayer, m_psLevelStats.ps_iKills);
|
|
strOut+=strKey;
|
|
} else {
|
|
strKey.PrintF("\\frags_%d\\%d", iPlayer, m_psLevelStats.ps_iScore);
|
|
strOut+=strKey;
|
|
}
|
|
strKey.PrintF("\\ping_%d\\%d", iPlayer, INDEX(ceil(en_tmPing*1000.0f)));
|
|
strOut+=strKey;
|
|
};
|
|
|
|
// check if message is in inbox
|
|
BOOL HasMessage( const CTFileName &fnmMessage)
|
|
{
|
|
ULONG ulHash = fnmMessage.GetHash();
|
|
INDEX ctMsg = m_acmiMessages.Count();
|
|
for(INDEX iMsg=0; iMsg<ctMsg; iMsg++) {
|
|
if (m_acmiMessages[iMsg].cmi_ulHash == ulHash &&
|
|
m_acmiMessages[iMsg].cmi_fnmFileName == fnmMessage) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// receive a computer message and put it in inbox if not already there
|
|
void ReceiveComputerMessage(const CTFileName &fnmMessage, ULONG ulFlags)
|
|
{
|
|
// if already received
|
|
if (HasMessage(fnmMessage)) {
|
|
// do nothing
|
|
return;
|
|
}
|
|
// add it to array
|
|
CCompMessageID &cmi = m_acmiMessages.Push();
|
|
cmi.NewMessage(fnmMessage);
|
|
cmi.cmi_bRead = ulFlags&CMF_READ;
|
|
if (!(ulFlags&CMF_READ)) {
|
|
m_ctUnreadMessages++;
|
|
cmp_bUpdateInBackground = TRUE;
|
|
}
|
|
if (!(ulFlags&CMF_READ) && (ulFlags&CMF_ANALYZE)) {
|
|
m_tmAnalyseEnd = _pTimer->CurrentTick()+2.0f;
|
|
m_soMessage.Set3DParameters(25.0f, 5.0f, 1.0f, 1.0f);
|
|
PlaySound(m_soMessage, SOUND_INFO, SOF_3D|SOF_VOLUMETRIC|SOF_LOCAL);
|
|
}
|
|
}
|
|
|
|
void SayVoiceMessage(const CTFileName &fnmMessage)
|
|
{
|
|
if (GetSettings()->ps_ulFlags&PSF_NOQUOTES) {
|
|
return;
|
|
}
|
|
SetSpeakMouthPitch();
|
|
PlaySound( m_soSpeech, fnmMessage, SOF_3D|SOF_VOLUMETRIC);
|
|
}
|
|
|
|
// receive all messages in one directory - cheat
|
|
void CheatAllMessagesDir(const CTString &strDir)
|
|
{
|
|
// list the directory
|
|
CDynamicStackArray<CTFileName> afnmDir;
|
|
MakeDirList(afnmDir, strDir, CTFileName(CTString("*.txt")), DLI_RECURSIVE);
|
|
|
|
// for each file in the directory
|
|
for (INDEX i=0; i<afnmDir.Count(); i++) {
|
|
CTFileName fnm = afnmDir[i];
|
|
// add the message
|
|
ReceiveComputerMessage(fnm, 0);
|
|
}
|
|
}
|
|
|
|
// receive all messages - cheat
|
|
void CheatAllMessages(void)
|
|
{
|
|
CheatAllMessagesDir("Data\\Messages\\information\\");
|
|
CheatAllMessagesDir("Data\\Messages\\weapons\\");
|
|
CheatAllMessagesDir("Data\\Messages\\enemies\\");
|
|
CheatAllMessagesDir("Data\\Messages\\background\\");
|
|
CheatAllMessagesDir("Data\\Messages\\statistics\\");
|
|
}
|
|
|
|
// mark that an item was picked
|
|
void ItemPicked(const CTString &strName, FLOAT fAmmount)
|
|
{
|
|
// if nothing picked too long
|
|
if (_pTimer->CurrentTick() > m_tmLastPicked+PICKEDREPORT_TIME) {
|
|
// kill the name
|
|
m_strPickedName = "";
|
|
// reset picked mana
|
|
m_fPickedMana = 0;
|
|
}
|
|
// if different than last picked
|
|
if (m_strPickedName!=strName) {
|
|
// remember name
|
|
m_strPickedName = strName;
|
|
// reset picked ammount
|
|
m_fPickedAmmount = 0;
|
|
}
|
|
// increase ammount
|
|
m_fPickedAmmount+=fAmmount;
|
|
m_tmLastPicked = _pTimer->CurrentTick();
|
|
}
|
|
|
|
// Setup light source
|
|
void SetupLightSource(void)
|
|
{
|
|
// setup light source
|
|
CLightSource lsNew;
|
|
lsNew.ls_ulFlags = LSF_NONPERSISTENT|LSF_DYNAMIC;
|
|
lsNew.ls_rHotSpot = 1.0f;
|
|
lsNew.ls_colColor = C_WHITE;
|
|
lsNew.ls_rFallOff = 2.5f;
|
|
lsNew.ls_plftLensFlare = NULL;
|
|
lsNew.ls_ubPolygonalMask = 0;
|
|
lsNew.ls_paoLightAnimation = &m_aoLightAnimation;
|
|
|
|
m_lsLightSource.ls_penEntity = this;
|
|
m_lsLightSource.SetLightSource(lsNew);
|
|
};
|
|
|
|
// play light animation
|
|
void PlayLightAnim(INDEX iAnim, ULONG ulFlags) {
|
|
if (m_aoLightAnimation.GetData()!=NULL) {
|
|
m_aoLightAnimation.PlayAnim(iAnim, ulFlags);
|
|
}
|
|
};
|
|
|
|
|
|
BOOL AdjustShadingParameters(FLOAT3D &vLightDirection, COLOR &colLight, COLOR &colAmbient)
|
|
{
|
|
if( cht_bDumpPlayerShading)
|
|
{
|
|
ANGLE3D a3dHPB;
|
|
DirectionVectorToAngles(-vLightDirection, a3dHPB);
|
|
UBYTE ubAR, ubAG, ubAB;
|
|
UBYTE ubCR, ubCG, ubCB;
|
|
ColorToRGB(colAmbient, ubAR, ubAG, ubAB);
|
|
ColorToRGB(colLight, ubCR, ubCG, ubCB);
|
|
CPrintF("Ambient: %d,%d,%d, Color: %d,%d,%d, Direction HPB (%g,%g,%g)\n",
|
|
ubAR, ubAG, ubAB, ubCR, ubCG, ubCB, a3dHPB(1), a3dHPB(2), a3dHPB(3));
|
|
}
|
|
|
|
return CPlayerEntity::AdjustShadingParameters(vLightDirection, colLight, colAmbient);
|
|
};
|
|
|
|
// get a different model object for rendering
|
|
CModelObject *GetModelForRendering(void)
|
|
{
|
|
// if not yet initialized
|
|
if(!(m_ulFlags&PLF_INITIALIZED)) {
|
|
// return base model
|
|
return GetModelObject();
|
|
}
|
|
|
|
// lerp player viewpoint
|
|
CPlacement3D plView;
|
|
plView.Lerp(en_plLastViewpoint, en_plViewpoint, _pTimer->GetLerpFactor());
|
|
// body and head attachment animation
|
|
((CPlayerAnimator&)*m_penAnimator).BodyAndHeadOrientation(plView);
|
|
((CPlayerAnimator&)*m_penAnimator).OnPreRender();
|
|
// synchronize your appearance with the default model
|
|
m_moRender.Synchronize(*GetModelObject());
|
|
if (m_ulFlags&PLF_SYNCWEAPON) {
|
|
m_ulFlags &= ~PLF_SYNCWEAPON;
|
|
GetPlayerAnimator()->SyncWeapon();
|
|
}
|
|
|
|
FLOAT tmNow = _pTimer->GetLerpedCurrentTick();
|
|
|
|
FLOAT fFading = 1.0f;
|
|
if (m_tmFadeStart!=0) {
|
|
FLOAT fFactor = (tmNow-m_tmFadeStart)/5.0f;
|
|
fFactor = Clamp(fFactor, 0.0f, 1.0f);
|
|
fFading*=fFactor;
|
|
}
|
|
|
|
// if invunerable after spawning
|
|
FLOAT tmSpawnInvulnerability = GetSP()->sp_tmSpawnInvulnerability;
|
|
if (tmSpawnInvulnerability>0 && tmNow-m_tmSpawned<tmSpawnInvulnerability) {
|
|
// blink fast
|
|
FLOAT fDelta = tmNow-m_tmSpawned;
|
|
fFading *= 0.75f+0.25f*Sin(fDelta/0.5f*360);
|
|
}
|
|
|
|
COLOR colAlpha = m_moRender.mo_colBlendColor;
|
|
colAlpha = (colAlpha&0xffffff00) + (COLOR(fFading*0xff)&0xff);
|
|
m_moRender.mo_colBlendColor = colAlpha;
|
|
|
|
// if not connected
|
|
if (m_ulFlags&PLF_NOTCONNECTED) {
|
|
// pluse slowly
|
|
fFading *= 0.25f+0.25f*Sin(tmNow/2.0f*360);
|
|
}
|
|
|
|
// use the appearance for rendering
|
|
return &m_moRender;
|
|
}
|
|
|
|
// wrapper for action marker getting
|
|
class CPlayerActionMarker *GetActionMarker(void) {
|
|
return (CPlayerActionMarker *)&*m_penActionMarker;
|
|
}
|
|
|
|
// find main music holder if not remembered
|
|
void FindMusicHolder(void)
|
|
{
|
|
if (m_penMainMusicHolder==NULL) {
|
|
m_penMainMusicHolder = _pNetwork->GetEntityWithName("MusicHolder", 0);
|
|
}
|
|
}
|
|
|
|
// update per-level stats
|
|
void UpdateLevelStats(void)
|
|
{
|
|
// clear stats for this level
|
|
m_psLevelStats = PlayerStats();
|
|
|
|
// get music holder
|
|
if (m_penMainMusicHolder==NULL) {
|
|
return;
|
|
}
|
|
CMusicHolder &mh = (CMusicHolder&)*m_penMainMusicHolder;
|
|
|
|
// assure proper count enemies in current world
|
|
if (mh.m_ctEnemiesInWorld==0) {
|
|
mh.CountEnemies();
|
|
}
|
|
// set totals for level and increment for game
|
|
m_psLevelTotal.ps_iKills = mh.m_ctEnemiesInWorld;
|
|
m_psGameTotal.ps_iKills += mh.m_ctEnemiesInWorld;
|
|
m_psLevelTotal.ps_iSecrets = mh.m_ctSecretsInWorld;
|
|
m_psGameTotal.ps_iSecrets += mh.m_ctSecretsInWorld;
|
|
}
|
|
|
|
// check if there is fuss
|
|
BOOL IsFuss(void)
|
|
{
|
|
// if no music holder
|
|
if (m_penMainMusicHolder==NULL) {
|
|
// no fuss
|
|
return FALSE;
|
|
}
|
|
// if no enemies - no fuss
|
|
return ((CMusicHolder*)&*m_penMainMusicHolder)->m_cenFussMakers.Count()>0;
|
|
}
|
|
|
|
void SetDefaultMouthPitch(void)
|
|
{
|
|
m_soMouth.Set3DParameters(50.0f, 10.0f, 1.0f, 1.0f);
|
|
}
|
|
void SetRandomMouthPitch(FLOAT fMin, FLOAT fMax)
|
|
{
|
|
m_soMouth.Set3DParameters(50.0f, 10.0f, 1.0f, Lerp(fMin, fMax, FRnd()));
|
|
}
|
|
void SetSpeakMouthPitch(void)
|
|
{
|
|
m_soSpeech.Set3DParameters(50.0f, 10.0f, 2.0f, 1.0f);
|
|
}
|
|
|
|
void ApplyShaking(CPlacement3D &plViewer)
|
|
{
|
|
CWorldSettingsController *pwsc = NULL;
|
|
// obtain bcg viewer
|
|
CBackgroundViewer *penBcgViewer = (CBackgroundViewer *) GetWorld()->GetBackgroundViewer();
|
|
if( penBcgViewer != NULL) {
|
|
// obtain world settings controller
|
|
pwsc = (CWorldSettingsController *) &*penBcgViewer->m_penWorldSettingsController;
|
|
}
|
|
if (pwsc==NULL || pwsc->m_tmShakeStarted<0) {
|
|
return;
|
|
}
|
|
|
|
TIME tm = _pTimer->GetLerpedCurrentTick()-pwsc->m_tmShakeStarted;
|
|
if (tm<0) {
|
|
return;
|
|
}
|
|
FLOAT fDistance = (plViewer.pl_PositionVector-pwsc->m_vShakePos).Length();
|
|
FLOAT fIntensity = IntensityAtDistance(pwsc->m_fShakeFalloff, 0, fDistance);
|
|
FLOAT fShakeY = SinFast(tm*pwsc->m_tmShakeFrequencyY*360.0f)*
|
|
exp(-tm*(pwsc->m_fShakeFade))*
|
|
fIntensity*pwsc->m_fShakeIntensityY;
|
|
FLOAT fShakeB = SinFast(tm*pwsc->m_tmShakeFrequencyB*360.0f)*
|
|
exp(-tm*(pwsc->m_fShakeFade))*
|
|
fIntensity*pwsc->m_fShakeIntensityB;
|
|
FLOAT fShakeZ = SinFast(tm*pwsc->m_tmShakeFrequencyZ*360.0f)*
|
|
exp(-tm*(pwsc->m_fShakeFade))*
|
|
fIntensity*pwsc->m_fShakeIntensityZ;
|
|
plViewer.pl_PositionVector(2) += fShakeY;
|
|
plViewer.pl_PositionVector(3) += fShakeZ;
|
|
plViewer.pl_OrientationAngle(3) += fShakeB;
|
|
}
|
|
|
|
COLOR GetWorldGlaring(void)
|
|
{
|
|
CWorldSettingsController *pwsc = NULL;
|
|
// obtain bcg viewer
|
|
CBackgroundViewer *penBcgViewer = (CBackgroundViewer *) GetWorld()->GetBackgroundViewer();
|
|
if( penBcgViewer != NULL) {
|
|
// obtain world settings controller
|
|
pwsc = (CWorldSettingsController *) &*penBcgViewer->m_penWorldSettingsController;
|
|
}
|
|
if (pwsc==NULL || pwsc->m_tmGlaringStarted<0) {
|
|
return 0;
|
|
}
|
|
TIME tm = _pTimer->GetLerpedCurrentTick();
|
|
FLOAT fRatio = CalculateRatio(tm, pwsc->m_tmGlaringStarted, pwsc->m_tmGlaringEnded,
|
|
pwsc->m_fGlaringFadeInRatio, pwsc->m_fGlaringFadeOutRatio);
|
|
COLOR colResult = C_WHITE|(UBYTE(fRatio*255.0f));
|
|
return colResult;
|
|
}
|
|
|
|
/************************************************************
|
|
* RENDER GAME VIEW *
|
|
************************************************************/
|
|
|
|
// setup viewing parameters for viewing from player or camera
|
|
void SetupView(CDrawPort *pdp, CAnyProjection3D &apr, CEntity *&penViewer,
|
|
CPlacement3D &plViewer, COLOR &colBlend, BOOL bCamera)
|
|
{
|
|
// read the exact placement of the view for this tick
|
|
GetLerpedAbsoluteViewPlacement(plViewer);
|
|
ASSERT(IsValidFloat(plViewer.pl_OrientationAngle(1))&&IsValidFloat(plViewer.pl_OrientationAngle(2))&&IsValidFloat(plViewer.pl_OrientationAngle(3)) );
|
|
// get current entity that the player views from
|
|
penViewer = GetViewEntity();
|
|
|
|
INDEX iViewState = m_iViewState;
|
|
|
|
if (m_penCamera!=NULL && bCamera) {
|
|
iViewState = PVT_SCENECAMERA;
|
|
plViewer = m_penCamera->GetLerpedPlacement();
|
|
penViewer = m_penCamera;
|
|
}
|
|
|
|
// init projection parameters
|
|
CPerspectiveProjection3D prPerspectiveProjection;
|
|
plr_fFOV = Clamp( plr_fFOV, 1.0f, 160.0f);
|
|
ANGLE aFOV = plr_fFOV;
|
|
// disable zoom in deathmatch
|
|
if (!GetSP()->sp_bCooperative) {
|
|
aFOV = 90.0f;
|
|
}
|
|
if (m_pstState==PST_DIVE && iViewState == PVT_PLAYEREYES) {
|
|
TIME tmNow = _pTimer->GetLerpedCurrentTick();
|
|
aFOV+=sin(tmNow*0.79f)*2.0f;
|
|
}
|
|
ApplyShaking(plViewer);
|
|
|
|
colBlend = 0;
|
|
if (iViewState == PVT_SCENECAMERA) {
|
|
CCamera *pcm = (CCamera*)&*m_penCamera;
|
|
prPerspectiveProjection.FOVL() =
|
|
Lerp(pcm->m_fLastFOV, pcm->m_fFOV, _pTimer->GetLerpFactor());
|
|
if (pcm->m_tmDelta>0.001f) {
|
|
FLOAT fFactor = (_pTimer->GetLerpedCurrentTick()-pcm->m_tmAtMarker)/pcm->m_tmDelta;
|
|
fFactor = Clamp( fFactor, 0.0f, 1.0f);
|
|
colBlend = LerpColor( pcm->m_colFade0, pcm->m_colFade1, fFactor);
|
|
} else {
|
|
colBlend = pcm->m_colFade0;
|
|
}
|
|
} else {
|
|
prPerspectiveProjection.FOVL() = aFOV;
|
|
}
|
|
prPerspectiveProjection.ScreenBBoxL() = FLOATaabbox2D(
|
|
FLOAT2D(0.0f, 0.0f),
|
|
FLOAT2D((FLOAT)pdp->GetWidth(), (FLOAT)pdp->GetHeight())
|
|
);
|
|
// determine front clip plane
|
|
plr_fFrontClipDistance = Clamp( plr_fFrontClipDistance, 0.05f, 0.30f);
|
|
FLOAT fFCD = plr_fFrontClipDistance;
|
|
// adjust front clip plane if swimming
|
|
if( m_pstState==PST_SWIM && iViewState==PVT_PLAYEREYES) { fFCD *= 0.6666f; }
|
|
prPerspectiveProjection.FrontClipDistanceL() = fFCD;
|
|
prPerspectiveProjection.AspectRatioL() = 1.0f;
|
|
// set up viewer position
|
|
apr = prPerspectiveProjection;
|
|
apr->ViewerPlacementL() = plViewer;
|
|
}
|
|
|
|
// listen from a given viewer
|
|
void ListenFromEntity(CEntity *penListener, const CPlacement3D &plSound)
|
|
{
|
|
FLOATmatrix3D mRotation;
|
|
MakeRotationMatrixFast(mRotation, plSound.pl_OrientationAngle);
|
|
sliSound.sli_vPosition = plSound.pl_PositionVector;
|
|
sliSound.sli_mRotation = mRotation;
|
|
sliSound.sli_fVolume = 1.0f;
|
|
sliSound.sli_vSpeed = en_vCurrentTranslationAbsolute;
|
|
sliSound.sli_penEntity = penListener;
|
|
if (m_pstState == PST_DIVE) {
|
|
sliSound.sli_fFilter = 20.0f;
|
|
} else {
|
|
sliSound.sli_fFilter = 0.0f;
|
|
}
|
|
INDEX iEnv = 0;
|
|
|
|
CBrushSector *pbsc = penListener->GetSectorFromPoint(plSound.pl_PositionVector);
|
|
|
|
// for each sector around listener
|
|
if (pbsc!=NULL) {
|
|
iEnv = pbsc->GetEnvironmentType();
|
|
}
|
|
|
|
// get the environment
|
|
CEnvironmentType &et = GetWorld()->wo_aetEnvironmentTypes[iEnv];
|
|
sliSound.sli_iEnvironmentType = et.et_iType;
|
|
sliSound.sli_fEnvironmentSize = et.et_fSize;
|
|
_pSound->Listen(sliSound);
|
|
}
|
|
|
|
// render dummy view (not connected yet)
|
|
void RenderDummyView(CDrawPort *pdp)
|
|
{
|
|
// clear screen
|
|
pdp->Fill( C_BLACK|CT_OPAQUE);
|
|
|
|
// if not single player
|
|
if (!GetSP()->sp_bSinglePlayer) {
|
|
// print a message
|
|
PIX pixDPWidth = pdp->GetWidth();
|
|
PIX pixDPHeight = pdp->GetHeight();
|
|
FLOAT fScale = (FLOAT)pixDPWidth/640.0f;
|
|
pdp->SetFont( _pfdDisplayFont);
|
|
pdp->SetTextScaling( fScale);
|
|
pdp->SetTextAspect( 1.0f);
|
|
CTString strMsg;
|
|
strMsg.PrintF(TRANS("%s connected"), (const char*)GetPlayerName());
|
|
pdp->PutTextCXY( strMsg, pixDPWidth*0.5f, pixDPHeight*0.5f, C_GREEN|CT_OPAQUE);
|
|
}
|
|
}
|
|
|
|
// render view from player
|
|
void RenderPlayerView(CDrawPort *pdp, BOOL bShowExtras)
|
|
{
|
|
// setup view settings
|
|
CAnyProjection3D apr;
|
|
CEntity *penViewer;
|
|
CPlacement3D plViewer;
|
|
COLOR colBlend;
|
|
SetupView(pdp, apr, penViewer, plViewer, colBlend, FALSE);
|
|
|
|
// render the view
|
|
ASSERT(IsValidFloat(plViewer.pl_OrientationAngle(1))&&IsValidFloat(plViewer.pl_OrientationAngle(2))&&IsValidFloat(plViewer.pl_OrientationAngle(3)));
|
|
_ulPlayerRenderingMask = 1<<GetMyPlayerIndex();
|
|
RenderView(*en_pwoWorld, *penViewer, apr, *pdp);
|
|
_ulPlayerRenderingMask = 0;
|
|
|
|
// listen from here
|
|
ListenFromEntity(this, plViewer);
|
|
|
|
if(hud_bShowAll && bShowExtras) {
|
|
// let the player entity render its interface
|
|
CPlacement3D plLight(_vViewerLightDirection, ANGLE3D(0,0,0));
|
|
plLight.AbsoluteToRelative(plViewer);
|
|
RenderHUD( *(CPerspectiveProjection3D *)(CProjection3D *)apr, pdp,
|
|
plLight.pl_PositionVector, _colViewerLight, _colViewerAmbient,
|
|
penViewer==this && (GetFlags()&ENF_ALIVE));
|
|
}
|
|
|
|
// determine and cache main drawport, size and relative scale
|
|
PIX pixDPWidth = pdp->GetWidth();
|
|
PIX pixDPHeight = pdp->GetHeight();
|
|
FLOAT fScale = (FLOAT)pixDPWidth/640.0f;
|
|
|
|
// print center message
|
|
if (_pTimer->CurrentTick()<m_tmCenterMessageEnd) {
|
|
pdp->SetFont( _pfdDisplayFont);
|
|
pdp->SetTextScaling( fScale);
|
|
pdp->SetTextAspect( 1.0f);
|
|
pdp->PutTextCXY( m_strCenterMessage, pixDPWidth*0.5f, pixDPHeight*0.85f, C_WHITE|0xDD);
|
|
// print picked item
|
|
} else if (_pTimer->CurrentTick()<m_tmLastPicked+PICKEDREPORT_TIME) {
|
|
pdp->SetFont( _pfdDisplayFont);
|
|
pdp->SetTextScaling( fScale);
|
|
pdp->SetTextAspect( 1.0f);
|
|
CTString strPicked;
|
|
if (m_fPickedAmmount==0) {
|
|
strPicked = m_strPickedName;
|
|
} else {
|
|
strPicked.PrintF("%s +%d", (const char*)m_strPickedName, int(m_fPickedAmmount));
|
|
}
|
|
pdp->PutTextCXY( strPicked, pixDPWidth*0.5f, pixDPHeight*0.82f, C_WHITE|0xDD);
|
|
if (!GetSP()->sp_bCooperative && !GetSP()->sp_bUseFrags && m_fPickedMana>=1) {
|
|
CTString strValue;
|
|
strValue.PrintF("%s +%d", TRANS("Value"), INDEX(m_fPickedMana));
|
|
pdp->PutTextCXY( strValue, pixDPWidth*0.5f, pixDPHeight*0.85f, C_WHITE|0xDD);
|
|
}
|
|
}
|
|
|
|
if (_pTimer->CurrentTick()<m_tmAnalyseEnd) {
|
|
pdp->SetFont( _pfdDisplayFont);
|
|
pdp->SetTextScaling( fScale);
|
|
pdp->SetTextAspect( 1.0f);
|
|
UBYTE ubA = int(sin(_pTimer->CurrentTick()*10.0f)*127+128);
|
|
pdp->PutTextCXY( TRANS("Analyzing..."), pixDPWidth*0.5f, pixDPHeight*0.2f, C_GREEN|ubA);
|
|
}
|
|
}
|
|
|
|
// render view from camera
|
|
void RenderCameraView(CDrawPort *pdp, BOOL bListen)
|
|
{
|
|
CDrawPort dpCamera;
|
|
CDrawPort *pdpCamera = pdp;
|
|
if (m_penCamera!=NULL && ((CCamera&)*m_penCamera).m_bWideScreen) {
|
|
pdp->MakeWideScreen(&dpCamera);
|
|
pdpCamera = &dpCamera;
|
|
}
|
|
|
|
pdp->Unlock();
|
|
pdpCamera->Lock();
|
|
|
|
// setup view settings
|
|
CAnyProjection3D apr;
|
|
CEntity *penViewer;
|
|
CPlacement3D plViewer;
|
|
COLOR colBlend;
|
|
SetupView(pdpCamera, apr, penViewer, plViewer, colBlend, TRUE);
|
|
|
|
// render the view
|
|
ASSERT(IsValidFloat(plViewer.pl_OrientationAngle(1))&&IsValidFloat(plViewer.pl_OrientationAngle(2))&&IsValidFloat(plViewer.pl_OrientationAngle(3)));
|
|
_ulPlayerRenderingMask = 1<<GetMyPlayerIndex();
|
|
RenderView(*en_pwoWorld, *penViewer, apr, *pdpCamera);
|
|
_ulPlayerRenderingMask = 0;
|
|
|
|
|
|
// listen from there if needed
|
|
if (bListen) {
|
|
ListenFromEntity(penViewer, plViewer);
|
|
}
|
|
|
|
pdpCamera->Unlock();
|
|
pdp->Lock();
|
|
|
|
// camera fading
|
|
if ((colBlend&CT_AMASK)!=0) {
|
|
pdp->Fill(colBlend);
|
|
}
|
|
|
|
// print center message
|
|
if (_pTimer->CurrentTick()<m_tmCenterMessageEnd) {
|
|
PIX pixDPWidth = pdp->GetWidth();
|
|
PIX pixDPHeight = pdp->GetHeight();
|
|
FLOAT fScale = (FLOAT)pixDPWidth/640.0f;
|
|
pdp->SetFont( _pfdDisplayFont);
|
|
pdp->SetTextScaling( fScale);
|
|
pdp->SetTextAspect( 1.0f);
|
|
pdp->PutTextCXY( m_strCenterMessage, pixDPWidth*0.5f, pixDPHeight*0.85f, C_WHITE|0xDD);
|
|
}
|
|
}
|
|
|
|
void RenderGameView(CDrawPort *pdp, void *pvUserData)
|
|
{
|
|
BOOL bShowExtras = (size_t(pvUserData)&GRV_SHOWEXTRAS);
|
|
|
|
// if not yet initialized
|
|
if(!(m_ulFlags&PLF_INITIALIZED) || (m_ulFlags&PLF_DONTRENDER)) {
|
|
// render dummy view on the right drawport
|
|
CDrawPort dpView(pdp, TRUE);
|
|
if(dpView.Lock()) {
|
|
RenderDummyView(&dpView);
|
|
dpView.Unlock();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// if rendering real game view (not thumbnail, or similar)
|
|
if (pvUserData!=0) {
|
|
// if rendered a game view recently
|
|
CTimerValue tvNow = _pTimer->GetHighPrecisionTimer();
|
|
if ((tvNow-_tvProbingLast).GetSeconds()<0.1) {
|
|
// allow probing
|
|
_pGfx->gl_bAllowProbing = TRUE;
|
|
}
|
|
_tvProbingLast = tvNow;
|
|
}
|
|
|
|
// CPrintF("%s: render\n", GetPredictName());
|
|
|
|
// check for dualhead
|
|
BOOL bDualHead =
|
|
pdp->IsDualHead() &&
|
|
GetSP()->sp_gmGameMode!=CSessionProperties::GM_FLYOVER &&
|
|
m_penActionMarker==NULL;
|
|
|
|
// if dualhead, or no camera active
|
|
if (bDualHead||m_penCamera==NULL) {
|
|
// make left player view
|
|
CDrawPort dpView(pdp, TRUE);
|
|
if(dpView.Lock()) {
|
|
// draw it
|
|
RenderPlayerView(&dpView, bShowExtras);
|
|
dpView.Unlock();
|
|
}
|
|
}
|
|
|
|
// if camera active
|
|
if (m_penCamera!=NULL) {
|
|
// make left or right camera view
|
|
CDrawPort dpView(pdp, m_penActionMarker!=NULL);
|
|
if(dpView.Lock()) {
|
|
// draw it, listen if not dualhead
|
|
RenderCameraView(&dpView, !bDualHead);
|
|
dpView.Unlock();
|
|
}
|
|
// if camera is not active
|
|
} else {
|
|
// if dualhead
|
|
if (bDualHead) {
|
|
// render computer on secondary display
|
|
cmp_ppenDHPlayer = this;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/************************************************************
|
|
* PRE/DO/POST MOVING *
|
|
************************************************************/
|
|
|
|
// premoving for soft player up-down movement
|
|
void PreMoving(void) {
|
|
/*CPrintF("pos(%s): %g,%g,%g\n", GetPredictName(),
|
|
GetPlacement().pl_PositionVector(1),
|
|
GetPlacement().pl_PositionVector(2),
|
|
GetPlacement().pl_PositionVector(3));
|
|
*/
|
|
|
|
((CPlayerAnimator&)*m_penAnimator).StoreLast();
|
|
CPlayerEntity::PreMoving();
|
|
};
|
|
|
|
// do moving
|
|
void DoMoving(void) {
|
|
CPlayerEntity::DoMoving();
|
|
((CPlayerAnimator&)*m_penAnimator).AnimateBanking();
|
|
|
|
if (m_penView!=NULL) {
|
|
((CPlayerView&)*m_penView).DoMoving();
|
|
}
|
|
if (m_pen3rdPersonView!=NULL) {
|
|
((CPlayerView&)*m_pen3rdPersonView).DoMoving();
|
|
}
|
|
};
|
|
|
|
|
|
// postmoving for soft player up-down movement
|
|
void PostMoving(void)
|
|
{
|
|
CPlayerEntity::PostMoving();
|
|
// never allow a player to be removed from the list of movers
|
|
en_ulFlags &= ~ENF_INRENDERING;
|
|
|
|
((CPlayerAnimator&)*m_penAnimator).AnimateSoftEyes();
|
|
//((CPlayerAnimator&)*m_penAnimator).AnimateRecoilPitch();
|
|
|
|
// slowly increase mana with time, faster if player is not moving
|
|
m_fManaFraction +=
|
|
ClampDn( 1.0f-en_vCurrentTranslationAbsolute.Length()/20.0f, 0.0f) * 20.0f
|
|
* _pTimer->TickQuantum;
|
|
INDEX iNewMana = m_fManaFraction;
|
|
m_iMana += iNewMana;
|
|
m_fManaFraction -= iNewMana;
|
|
|
|
// if in tourist mode
|
|
if (GetSP()->sp_gdGameDifficulty==CSessionProperties::GD_TOURIST) {
|
|
// slowly increase health with time
|
|
FLOAT fHealth = GetHealth();
|
|
FLOAT fTopHealth = TopHealth();
|
|
if (fHealth<fTopHealth) {
|
|
SetHealth(ClampUp(fHealth+_pTimer->TickQuantum, fTopHealth)); // one unit per second
|
|
}
|
|
}
|
|
|
|
// update ray hit for weapon target
|
|
GetPlayerWeapons()->UpdateTargetingInfo();
|
|
|
|
if (m_pen3rdPersonView!=NULL) {
|
|
((CPlayerView&)*m_pen3rdPersonView).PostMoving();
|
|
}
|
|
if (m_penView!=NULL) {
|
|
((CPlayerView&)*m_penView).PostMoving();
|
|
}
|
|
|
|
// if didn't have any action in this tick
|
|
if (!(m_ulFlags&PLF_APPLIEDACTION)) {
|
|
// means we are not connected
|
|
SetUnconnected();
|
|
}
|
|
|
|
// clear action indicator
|
|
m_ulFlags&=~PLF_APPLIEDACTION;
|
|
}
|
|
|
|
// set player parameters for unconnected state (between the server loads and player reconnects)
|
|
void SetUnconnected(void)
|
|
{
|
|
if (m_ulFlags&PLF_NOTCONNECTED) {
|
|
return;
|
|
}
|
|
m_ulFlags |= PLF_NOTCONNECTED;
|
|
|
|
// reset to a dummy state
|
|
ForceFullStop();
|
|
SetPhysicsFlags(GetPhysicsFlags() & ~(EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY));
|
|
SetCollisionFlags(GetCollisionFlags() & ~((ECBI_BRUSH|ECBI_MODEL)<<ECB_TEST));
|
|
en_plLastViewpoint.pl_OrientationAngle = en_plViewpoint.pl_OrientationAngle = ANGLE3D(0,0,0);
|
|
|
|
StartModelAnim(PLAYER_ANIM_STAND, 0);
|
|
GetPlayerAnimator()->BodyAnimationTemplate(
|
|
BODY_ANIM_NORMALWALK, BODY_ANIM_COLT_STAND, BODY_ANIM_SHOTGUN_STAND, BODY_ANIM_MINIGUN_STAND,
|
|
AOF_LOOPING|AOF_NORESTART);
|
|
}
|
|
|
|
// set player parameters for connected state
|
|
void SetConnected(void)
|
|
{
|
|
if (!(m_ulFlags&PLF_NOTCONNECTED)) {
|
|
return;
|
|
}
|
|
m_ulFlags &= ~PLF_NOTCONNECTED;
|
|
|
|
SetPhysicsFlags(GetPhysicsFlags() | (EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY));
|
|
SetCollisionFlags(GetCollisionFlags() | ((ECBI_BRUSH|ECBI_MODEL)<<ECB_TEST));
|
|
}
|
|
|
|
// check if player is connected or not
|
|
BOOL IsConnected(void) const
|
|
{
|
|
return !(m_ulFlags&PLF_NOTCONNECTED);
|
|
}
|
|
|
|
// create a checksum value for sync-check
|
|
void ChecksumForSync(ULONG &ulCRC, INDEX iExtensiveSyncCheck)
|
|
{
|
|
CPlayerEntity::ChecksumForSync(ulCRC, iExtensiveSyncCheck);
|
|
CRC_AddLONG(ulCRC, m_psLevelStats.ps_iScore);
|
|
CRC_AddLONG(ulCRC, m_iMana);
|
|
if (iExtensiveSyncCheck>0) {
|
|
CRC_AddFLOAT(ulCRC, m_fManaFraction);
|
|
}
|
|
CRC_AddFLOAT(ulCRC, m_fArmor);
|
|
}
|
|
|
|
|
|
// dump sync data to text file
|
|
void DumpSync_t(CTStream &strm, INDEX iExtensiveSyncCheck) // throw char *
|
|
{
|
|
CPlayerEntity::DumpSync_t(strm, iExtensiveSyncCheck);
|
|
strm.FPrintF_t("Score: %d\n", m_psLevelStats.ps_iScore);
|
|
strm.FPrintF_t("m_iMana: %d\n", m_iMana);
|
|
strm.FPrintF_t("m_fManaFraction: %g(%08x)\n", m_fManaFraction, (ULONG&)m_fManaFraction);
|
|
strm.FPrintF_t("m_fArmor: %g(%08x)\n", m_fArmor, (ULONG&)m_fArmor);
|
|
}
|
|
|
|
/************************************************************
|
|
* DAMAGE OVERRIDE (PLAYER HAS ARMOR) *
|
|
************************************************************/
|
|
|
|
|
|
// leave stain
|
|
virtual void LeaveStain( BOOL bGrow)
|
|
{
|
|
ESpawnEffect ese;
|
|
FLOAT3D vPoint;
|
|
FLOATplane3D vPlaneNormal;
|
|
FLOAT fDistanceToEdge;
|
|
// get your size
|
|
FLOATaabbox3D box;
|
|
GetBoundingBox(box);
|
|
|
|
// on plane
|
|
if( GetNearestPolygon(vPoint, vPlaneNormal, fDistanceToEdge)) {
|
|
// if near to polygon and away from last stain point
|
|
if( (vPoint-GetPlacement().pl_PositionVector).Length()<0.5f
|
|
&& (m_vLastStain-vPoint).Length()>1.0f ) {
|
|
m_vLastStain = vPoint;
|
|
FLOAT fStretch = box.Size().Length();
|
|
ese.colMuliplier = C_WHITE|CT_OPAQUE;
|
|
// stain
|
|
if (bGrow) {
|
|
ese.betType = BET_BLOODSTAINGROW;
|
|
ese.vStretch = FLOAT3D( fStretch*1.5f, fStretch*1.5f, 1.0f);
|
|
} else {
|
|
ese.betType = BET_BLOODSTAIN;
|
|
ese.vStretch = FLOAT3D( fStretch*0.75f, fStretch*0.75f, 1.0f);
|
|
}
|
|
ese.vNormal = FLOAT3D( vPlaneNormal);
|
|
ese.vDirection = FLOAT3D( 0, 0, 0);
|
|
FLOAT3D vPos = vPoint+ese.vNormal/50.0f*(FRnd()+0.5f);
|
|
CEntityPointer penEffect = CreateEntity( CPlacement3D(vPos, ANGLE3D(0,0,0)), CLASS_BASIC_EFFECT);
|
|
penEffect->Initialize(ese);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
void DamageImpact(enum DamageType dmtType,
|
|
FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection)
|
|
{
|
|
// if exploded
|
|
if (GetRenderType()!=RT_MODEL) {
|
|
// do nothing
|
|
return;
|
|
}
|
|
|
|
if (dmtType == DMT_ABYSS || dmtType == DMT_SPIKESTAB) {
|
|
return;
|
|
}
|
|
|
|
fDamageAmmount = Clamp(fDamageAmmount, 0.0f, 5000.0f);
|
|
|
|
FLOAT fKickDamage = fDamageAmmount;
|
|
if( (dmtType == DMT_EXPLOSION) || (dmtType == DMT_IMPACT) || (dmtType == DMT_CANNONBALL_EXPLOSION) )
|
|
{
|
|
fKickDamage*=1.5;
|
|
}
|
|
if (dmtType==DMT_DROWNING || dmtType==DMT_CLOSERANGE) {
|
|
fKickDamage /= 10;
|
|
}
|
|
|
|
// get passed time since last damage
|
|
TIME tmNow = _pTimer->CurrentTick();
|
|
TIME tmDelta = tmNow-m_tmLastDamage;
|
|
m_tmLastDamage = tmNow;
|
|
|
|
// fade damage out
|
|
if (tmDelta>=_pTimer->TickQuantum*3) {
|
|
m_vDamage=FLOAT3D(0,0,0);
|
|
}
|
|
// add new damage
|
|
FLOAT3D vDirectionFixed;
|
|
if (vDirection.ManhattanNorm()>0.5f) {
|
|
vDirectionFixed = vDirection;
|
|
} else {
|
|
vDirectionFixed = -en_vGravityDir;
|
|
}
|
|
FLOAT3D vDamageOld = m_vDamage;
|
|
m_vDamage+=(vDirectionFixed/*-en_vGravityDir/2*/)*fKickDamage;
|
|
|
|
FLOAT fOldLen = vDamageOld.Length();
|
|
FLOAT fNewLen = m_vDamage.Length();
|
|
FLOAT fOldRootLen = Sqrt(fOldLen);
|
|
FLOAT fNewRootLen = Sqrt(fNewLen);
|
|
|
|
FLOAT fMassFactor = 200.0f/((EntityInfo*)GetEntityInfo())->fMass;
|
|
|
|
if( !(en_ulFlags & ENF_ALIVE))
|
|
{
|
|
fMassFactor /= 3;
|
|
}
|
|
|
|
switch( dmtType)
|
|
{
|
|
case DMT_CLOSERANGE:
|
|
case DMT_DROWNING:
|
|
case DMT_IMPACT:
|
|
case DMT_BRUSH:
|
|
case DMT_BURNING:
|
|
// do nothing
|
|
break;
|
|
default:
|
|
{
|
|
if(fOldLen != 0.0f)
|
|
{
|
|
// cancel last push
|
|
GiveImpulseTranslationAbsolute( -vDamageOld/fOldRootLen*fMassFactor);
|
|
}
|
|
|
|
/*
|
|
FLOAT3D vImpuls = m_vDamage/fNewRootLen*fMassFactor;
|
|
CPrintF( "Applied absolute translation impuls: (%g%g%g)\n",
|
|
vImpuls(1),vImpuls(2),vImpuls(3));*/
|
|
|
|
// push it back
|
|
GiveImpulseTranslationAbsolute( m_vDamage/fNewRootLen*fMassFactor);
|
|
}
|
|
}
|
|
|
|
if( m_fMaxDamageAmmount<fDamageAmmount)
|
|
{
|
|
m_fMaxDamageAmmount = fDamageAmmount;
|
|
}
|
|
// if it has no spray, or if this damage overflows it
|
|
if ((m_tmSpraySpawned<=_pTimer->CurrentTick()-_pTimer->TickQuantum*8 ||
|
|
m_fSprayDamage+fDamageAmmount>50.0f)) {
|
|
|
|
// spawn blood spray
|
|
CPlacement3D plSpray = CPlacement3D( vHitPoint, ANGLE3D(0, 0, 0));
|
|
m_penSpray = CreateEntity( plSpray, CLASS_BLOOD_SPRAY);
|
|
m_penSpray->SetParent( this);
|
|
ESpawnSpray eSpawnSpray;
|
|
|
|
if( m_fMaxDamageAmmount > 10.0f)
|
|
{
|
|
eSpawnSpray.fDamagePower = 3.0f;
|
|
}
|
|
else if(m_fSprayDamage+fDamageAmmount>50.0f)
|
|
{
|
|
eSpawnSpray.fDamagePower = 2.0f;
|
|
}
|
|
else
|
|
{
|
|
eSpawnSpray.fDamagePower = 1.0f;
|
|
}
|
|
|
|
eSpawnSpray.sptType = SPT_BLOOD;
|
|
eSpawnSpray.fSizeMultiplier = 1.0f;
|
|
|
|
// setup direction of spray
|
|
FLOAT3D vHitPointRelative = vHitPoint - GetPlacement().pl_PositionVector;
|
|
FLOAT3D vReflectingNormal;
|
|
GetNormalComponent( vHitPointRelative, en_vGravityDir, vReflectingNormal);
|
|
vReflectingNormal.Normalize();
|
|
|
|
vReflectingNormal(1)/=5.0f;
|
|
|
|
FLOAT3D vProjectedComponent = vReflectingNormal*(vDirection%vReflectingNormal);
|
|
FLOAT3D vSpilDirection = vDirection-vProjectedComponent*2.0f-en_vGravityDir*0.5f;
|
|
|
|
eSpawnSpray.vDirection = vSpilDirection;
|
|
eSpawnSpray.penOwner = this;
|
|
|
|
// initialize spray
|
|
m_penSpray->Initialize( eSpawnSpray);
|
|
m_tmSpraySpawned = _pTimer->CurrentTick();
|
|
m_fSprayDamage = 0.0f;
|
|
m_fMaxDamageAmmount = 0.0f;
|
|
}
|
|
m_fSprayDamage+=fDamageAmmount;
|
|
}
|
|
/* Receive damage */
|
|
void ReceiveDamage( CEntity *penInflictor, enum DamageType dmtType,
|
|
FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection)
|
|
{
|
|
// don't harm yourself with knife or with rocket in easy/tourist mode
|
|
if( penInflictor==this && (dmtType==DMT_CLOSERANGE ||
|
|
((dmtType==DMT_EXPLOSION||dmtType==DMT_CANNONBALL_EXPLOSION||dmtType==DMT_PROJECTILE) &&
|
|
GetSP()->sp_gdGameDifficulty<=CSessionProperties::GD_EASY)) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if not connected
|
|
if (m_ulFlags&PLF_NOTCONNECTED) {
|
|
// noone can harm you
|
|
return;
|
|
}
|
|
|
|
// god mode -> no one can harm you
|
|
if( cht_bGod && CheatsEnabled() ) { return; }
|
|
|
|
// if invunerable after spawning
|
|
FLOAT tmSpawnInvulnerability = GetSP()->sp_tmSpawnInvulnerability;
|
|
if (tmSpawnInvulnerability>0 && _pTimer->CurrentTick()-m_tmSpawned<tmSpawnInvulnerability) {
|
|
// ignore damage
|
|
return;
|
|
}
|
|
|
|
// check for friendly fire
|
|
if (!GetSP()->sp_bFriendlyFire && GetSP()->sp_bCooperative) {
|
|
if (IsOfClass(penInflictor, "Player") && penInflictor!=this) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// adjust for difficulty
|
|
FLOAT fDifficultyDamage = GetSP()->sp_fDamageStrength;
|
|
if( fDifficultyDamage<=1.0f || penInflictor!=this) {
|
|
fDamageAmmount *= fDifficultyDamage;
|
|
}
|
|
|
|
// ignore zero damages
|
|
if (fDamageAmmount<=0) {
|
|
return;
|
|
}
|
|
|
|
FLOAT fSubHealth, fSubArmor;
|
|
if( dmtType == DMT_DROWNING) {
|
|
// drowning
|
|
fSubHealth = fDamageAmmount;
|
|
}
|
|
else {
|
|
// damage and armor
|
|
fSubArmor = fDamageAmmount*2.0f/3.0f; // 2/3 on armor damage
|
|
fSubHealth = fDamageAmmount - fSubArmor; // 1/3 on health damage
|
|
m_fArmor -= fSubArmor; // decrease armor
|
|
if( m_fArmor<0) { // armor below zero -> add difference to health damage
|
|
fSubHealth -= m_fArmor;
|
|
m_fArmor = 0.0f;
|
|
}
|
|
}
|
|
|
|
// if any damage
|
|
if( fSubHealth>0) {
|
|
// if camera is active
|
|
if (m_penCamera!=NULL) {
|
|
// if the camera has onbreak
|
|
CEntity *penOnBreak = ((CCamera&)*m_penCamera).m_penOnBreak;
|
|
if (penOnBreak!=NULL) {
|
|
// trigger it
|
|
SendToTarget(penOnBreak, EET_TRIGGER, this);
|
|
// if it doesn't
|
|
} else {
|
|
// just deactivate camera
|
|
m_penCamera = NULL;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// if the player is doing autoactions
|
|
if (m_penActionMarker!=NULL) {
|
|
// ignore all damage
|
|
return;
|
|
}
|
|
|
|
DamageImpact(dmtType, fSubHealth, vHitPoint, vDirection);
|
|
|
|
// receive damage
|
|
CPlayerEntity::ReceiveDamage( penInflictor, dmtType, fSubHealth, vHitPoint, vDirection);
|
|
|
|
// red screen and hit translation
|
|
if( fDamageAmmount>1.0f) {
|
|
// !!!! this is obsolete, DamageImpact is used instead!
|
|
if( dmtType==DMT_EXPLOSION || dmtType==DMT_PROJECTILE || dmtType==DMT_BULLET
|
|
|| dmtType==DMT_IMPACT || dmtType==DMT_CANNONBALL || dmtType==DMT_CANNONBALL_EXPLOSION) {
|
|
// GiveImpulseTranslationAbsolute( vDirection*(fDamageAmmount/7.5f)
|
|
// -en_vGravityDir*(fDamageAmmount/15.0f));
|
|
}
|
|
if( GetFlags()&ENF_ALIVE) {
|
|
m_fDamageAmmount += fDamageAmmount;
|
|
m_tmWoundedTime = _pTimer->CurrentTick();
|
|
}
|
|
}
|
|
|
|
// yell (this hurts)
|
|
ESound eSound;
|
|
eSound.EsndtSound = SNDT_PLAYER;
|
|
eSound.penTarget = this;
|
|
SendEventInRange( eSound, FLOATaabbox3D( GetPlacement().pl_PositionVector, 10.0f));
|
|
|
|
// play hurting sound
|
|
if( dmtType==DMT_DROWNING) {
|
|
SetRandomMouthPitch( 0.9f, 1.1f);
|
|
PlaySound( m_soMouth, SOUND_DROWN, SOF_3D);
|
|
if(_pNetwork->IsPlayerLocal(this)) {IFeel_PlayEffect("WoundWater");}
|
|
m_tmMouthSoundLast = _pTimer->CurrentTick();
|
|
PlaySound( m_soLocalAmbientOnce, SOUND_WATERBUBBLES, SOF_3D|SOF_VOLUMETRIC|SOF_LOCAL);
|
|
m_soLocalAmbientOnce.Set3DParameters( 25.0f, 5.0f, 2.0f, Lerp(0.5f, 1.5f, FRnd()) );
|
|
SpawnBubbles( 10+INDEX(FRnd()*10));
|
|
} else if( m_fDamageAmmount>1.0f) {
|
|
// if not dead
|
|
if (GetFlags()&ENF_ALIVE) {
|
|
// determine corresponding sound
|
|
INDEX iSound;
|
|
const char *strIFeel = NULL;
|
|
if( m_fDamageAmmount<5.0f) {
|
|
iSound = SOUND_WOUNDWEAK;
|
|
strIFeel = "WoundWeak";
|
|
}
|
|
else if( m_fDamageAmmount<25.0f) {
|
|
iSound = SOUND_WOUNDMEDIUM;
|
|
strIFeel = "WoundMedium";
|
|
}
|
|
else {
|
|
iSound = SOUND_WOUNDSTRONG;
|
|
strIFeel = "WoundStrong";
|
|
}
|
|
if( m_pstState==PST_DIVE) {
|
|
iSound = SOUND_WOUNDWATER;
|
|
strIFeel = "WoundWater";
|
|
} // override for diving
|
|
SetRandomMouthPitch( 0.9f, 1.1f);
|
|
// give some pause inbetween screaming
|
|
TIME tmNow = _pTimer->CurrentTick();
|
|
if( (tmNow-m_tmScreamTime) > 1.0f) {
|
|
m_tmScreamTime = tmNow;
|
|
PlaySound( m_soMouth, iSound, SOF_3D);
|
|
if(_pNetwork->IsPlayerLocal(this)) {IFeel_PlayEffect(strIFeel);}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// should this player blow up (spawn debris)
|
|
BOOL ShouldBlowUp(void)
|
|
{
|
|
// blow up if
|
|
return
|
|
// allowed
|
|
GetSP()->sp_bGibs &&
|
|
// dead and
|
|
(GetHealth()<=0) &&
|
|
// has received large enough damage lately and
|
|
(m_vDamage.Length() > _fBlowUpAmmount) &&
|
|
// is not blown up already
|
|
GetRenderType()==RT_MODEL;
|
|
};
|
|
|
|
// spawn body parts
|
|
void BlowUp(void)
|
|
{
|
|
FLOAT3D vNormalizedDamage = m_vDamage-m_vDamage*(_fBlowUpAmmount/m_vDamage.Length());
|
|
vNormalizedDamage /= Sqrt(vNormalizedDamage.Length());
|
|
vNormalizedDamage *= 0.75f;
|
|
|
|
FLOAT3D vBodySpeed = en_vCurrentTranslationAbsolute-en_vGravityDir*(en_vGravityDir%en_vCurrentTranslationAbsolute);
|
|
const FLOAT fBlowUpSize = 2.0f;
|
|
|
|
// readout blood type
|
|
const INDEX iBloodType = GetSP()->sp_iBlood;
|
|
// determine debris texture (color)
|
|
ULONG ulFleshTexture = TEXTURE_FLESH_GREEN;
|
|
ULONG ulFleshModel = MODEL_FLESH;
|
|
if( iBloodType==2) { ulFleshTexture = TEXTURE_FLESH_RED; }
|
|
// spawn debris
|
|
Debris_Begin( EIBT_FLESH, DPT_BLOODTRAIL, BET_BLOODSTAIN, fBlowUpSize, vNormalizedDamage, vBodySpeed, 1.0f, 0.0f);
|
|
for( INDEX iDebris=0; iDebris<4; iDebris++) {
|
|
// flowerpower mode?
|
|
if( iBloodType==3) {
|
|
switch( IRnd()%5) {
|
|
case 1: { ulFleshModel = MODEL_FLESH_APPLE; ulFleshTexture = TEXTURE_FLESH_APPLE; break; }
|
|
case 2: { ulFleshModel = MODEL_FLESH_BANANA; ulFleshTexture = TEXTURE_FLESH_BANANA; break; }
|
|
case 3: { ulFleshModel = MODEL_FLESH_BURGER; ulFleshTexture = TEXTURE_FLESH_BURGER; break; }
|
|
case 4: { ulFleshModel = MODEL_FLESH_LOLLY; ulFleshTexture = TEXTURE_FLESH_LOLLY; break; }
|
|
default: { ulFleshModel = MODEL_FLESH_ORANGE; ulFleshTexture = TEXTURE_FLESH_ORANGE; break; }
|
|
}
|
|
}
|
|
Debris_Spawn( this, this, ulFleshModel, ulFleshTexture, 0, 0, 0, IRnd()%4, 0.5f,
|
|
FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
|
|
}
|
|
|
|
// leave a stain beneath
|
|
LeaveStain(FALSE);
|
|
|
|
// hide yourself (must do this after spawning debris)
|
|
SwitchToEditorModel();
|
|
|
|
FLOAT fSpeedOrg = en_vCurrentTranslationAbsolute.Length();
|
|
const FLOAT fSpeedMax = 30.0f;
|
|
if (fSpeedOrg>fSpeedMax) {
|
|
en_vCurrentTranslationAbsolute *= fSpeedMax/fSpeedOrg;
|
|
}
|
|
|
|
// SetPhysicsFlags(EPF_MODEL_IMMATERIAL);
|
|
// SetCollisionFlags(ECF_IMMATERIAL);
|
|
};
|
|
|
|
/************************************************************
|
|
* OVERRIDEN FUNCTIONS *
|
|
************************************************************/
|
|
/* Entity info */
|
|
void *GetEntityInfo(void) {
|
|
switch (m_pstState) {
|
|
case PST_STAND: case PST_FALL:
|
|
return &eiPlayerGround;
|
|
break;
|
|
case PST_CROUCH:
|
|
return &eiPlayerCrouch;
|
|
break;
|
|
case PST_SWIM: case PST_DIVE:
|
|
return &eiPlayerSwim;
|
|
break;
|
|
}
|
|
return &eiPlayerGround;
|
|
};
|
|
|
|
|
|
/* Receive item */
|
|
BOOL ReceiveItem(const CEntityEvent &ee)
|
|
{
|
|
// *********** HEALTH ***********
|
|
if( ee.ee_slEvent == EVENTCODE_EHealth)
|
|
{
|
|
// determine old and new health values
|
|
FLOAT fHealthOld = GetHealth();
|
|
FLOAT fHealthNew = fHealthOld + ((EHealth&)ee).fHealth;
|
|
if( ((EHealth&)ee).bOverTopHealth) {
|
|
fHealthNew = ClampUp( fHealthNew, MaxHealth());
|
|
} else {
|
|
fHealthNew = ClampUp( fHealthNew, TopHealth());
|
|
}
|
|
|
|
// if value can be changed
|
|
if( ceil(fHealthNew) > ceil(fHealthOld)) {
|
|
// receive it
|
|
SetHealth(fHealthNew);
|
|
ItemPicked( TRANS("Health"), ((EHealth&)ee).fHealth);
|
|
m_iMana += (INDEX)(((EHealth&)ee).fHealth);
|
|
m_fPickedMana += ((EHealth&)ee).fHealth;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// *********** ARMOR ***********
|
|
else if( ee.ee_slEvent == EVENTCODE_EArmor)
|
|
{
|
|
// determine old and new health values
|
|
FLOAT fArmorOld = m_fArmor;
|
|
FLOAT fArmorNew = fArmorOld + ((EArmor&)ee).fArmor;
|
|
if( ((EArmor&)ee).bOverTopArmor) {
|
|
fArmorNew = ClampUp( fArmorNew, MaxArmor());
|
|
} else {
|
|
fArmorNew = ClampUp( fArmorNew, TopArmor());
|
|
}
|
|
// if value can be changed
|
|
if( ceil(fArmorNew) > ceil(fArmorOld)) {
|
|
// receive it
|
|
m_fArmor = fArmorNew;
|
|
ItemPicked( TRANS("Armor"), ((EArmor&)ee).fArmor);
|
|
m_iMana += (INDEX)(((EArmor&)ee).fArmor);
|
|
m_fPickedMana += ((EArmor&)ee).fArmor;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// *********** MESSAGE ***********
|
|
else if (ee.ee_slEvent == EVENTCODE_EMessageItem) {
|
|
EMessageItem &eMI = (EMessageItem &)ee;
|
|
ReceiveComputerMessage(eMI.fnmMessage, CMF_ANALYZE);
|
|
ItemPicked(TRANS("Ancient papyrus"), 0);
|
|
return TRUE;
|
|
}
|
|
|
|
// *********** WEAPON ***********
|
|
else if (ee.ee_slEvent == EVENTCODE_EWeaponItem) {
|
|
return ((CPlayerWeapons&)*m_penWeapons).ReceiveWeapon(ee);
|
|
}
|
|
|
|
// *********** AMMO ***********
|
|
else if (ee.ee_slEvent == EVENTCODE_EAmmoItem) {
|
|
return ((CPlayerWeapons&)*m_penWeapons).ReceiveAmmo(ee);
|
|
}
|
|
|
|
else if (ee.ee_slEvent == EVENTCODE_EAmmoPackItem) {
|
|
return ((CPlayerWeapons&)*m_penWeapons).ReceivePackAmmo(ee);
|
|
}
|
|
// *********** KEYS ***********
|
|
else if (ee.ee_slEvent == EVENTCODE_EKey) {
|
|
// make key mask
|
|
ULONG ulKey = 1<<INDEX(((EKey&)ee).kitType);
|
|
EKey &eKey = (EKey&)ee;
|
|
if(eKey.kitType == KIT_ANKHGOLDDUMMY || eKey.kitType == KIT_SCARABDUMMY)
|
|
{
|
|
ulKey = 0;
|
|
}
|
|
// if key is already in inventory
|
|
if (m_ulKeys&ulKey) {
|
|
// ignore it
|
|
return FALSE;
|
|
// if key is not in inventory
|
|
} else {
|
|
// pick it up
|
|
m_ulKeys |= ulKey;
|
|
CTString strKey = GetKeyName(((EKey&)ee).kitType);
|
|
ItemPicked(strKey, 0);
|
|
// if in cooperative
|
|
if (GetSP()->sp_bCooperative && !GetSP()->sp_bSinglePlayer) {
|
|
CPrintF(TRANS("^cFFFFFF%s - %s^r\n"), (const char*)GetPlayerName(), (const char*)strKey);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// nothing picked
|
|
return FALSE;
|
|
};
|
|
|
|
|
|
|
|
// Change Player view
|
|
void ChangePlayerView()
|
|
{
|
|
// change from eyes to 3rd person
|
|
if (m_iViewState == PVT_PLAYEREYES) {
|
|
// spawn 3rd person view camera
|
|
ASSERT(m_pen3rdPersonView == NULL);
|
|
if (m_pen3rdPersonView == NULL) {
|
|
m_pen3rdPersonView = CreateEntity(GetPlacement(), CLASS_PLAYER_VIEW);
|
|
EViewInit eInit;
|
|
eInit.penOwner = this;
|
|
eInit.penCamera = NULL;
|
|
eInit.vtView = VT_3RDPERSONVIEW;
|
|
eInit.bDeathFixed = FALSE;
|
|
m_pen3rdPersonView ->Initialize(eInit);
|
|
}
|
|
|
|
m_iViewState = PVT_3RDPERSONVIEW;
|
|
|
|
// change from 3rd person to eyes
|
|
} else if (m_iViewState == PVT_3RDPERSONVIEW) {
|
|
m_iViewState = PVT_PLAYEREYES;
|
|
|
|
// kill 3rd person view
|
|
if (m_pen3rdPersonView != NULL) {
|
|
((CPlayerView&)*m_pen3rdPersonView).SendEvent(EEnd());
|
|
m_pen3rdPersonView = NULL;
|
|
}
|
|
}
|
|
};
|
|
|
|
// if computer is pressed
|
|
void ComputerPressed(void)
|
|
{
|
|
// call computer
|
|
if (cmp_ppenPlayer==NULL && _pNetwork->IsPlayerLocal(this)) {
|
|
cmp_ppenPlayer = this;
|
|
}
|
|
m_bComputerInvoked = TRUE;
|
|
// clear analyses message
|
|
m_tmAnalyseEnd = 0;
|
|
m_bPendingMessage = FALSE;
|
|
m_tmMessagePlay = 0;
|
|
}
|
|
|
|
|
|
|
|
// if use is pressed
|
|
void UsePressed(BOOL bOrComputer)
|
|
{
|
|
// cast ray from weapon
|
|
CPlayerWeapons *penWeapons = GetPlayerWeapons();
|
|
CEntity *pen = penWeapons->m_penRayHit;
|
|
BOOL bSomethingToUse = FALSE;
|
|
|
|
// if hit
|
|
if (pen!=NULL) {
|
|
// check switch/messageholder relaying by moving brush
|
|
if (IsOfClass( pen, "Moving Brush")) {
|
|
if (((CMovingBrush&)*pen).m_penSwitch!=NULL) {
|
|
pen = ((CMovingBrush&)*pen).m_penSwitch;
|
|
}
|
|
}
|
|
|
|
// if switch and near enough
|
|
if (IsOfClass( pen, "Switch") && penWeapons->m_fRayHitDistance < 2.0f) {
|
|
CSwitch &enSwitch = (CSwitch&)*pen;
|
|
// if switch is useable
|
|
if (enSwitch.m_bUseable) {
|
|
// send it a trigger event
|
|
SendToTarget(pen, EET_TRIGGER, this);
|
|
bSomethingToUse = TRUE;
|
|
}
|
|
}
|
|
|
|
// if analyzable
|
|
if (IsOfClass( pen, "MessageHolder")
|
|
&& penWeapons->m_fRayHitDistance<((CMessageHolder*)&*pen)->m_fDistance
|
|
&& ((CMessageHolder*)&*pen)->m_bActive) {
|
|
const CTFileName &fnmMessage = ((CMessageHolder*)&*pen)->m_fnmMessage;
|
|
// if player doesn't have that message in database
|
|
if (!HasMessage(fnmMessage)) {
|
|
// add the message
|
|
ReceiveComputerMessage(fnmMessage, CMF_ANALYZE);
|
|
bSomethingToUse = TRUE;
|
|
}
|
|
}
|
|
}
|
|
// if nothing usable under cursor, and may call computer
|
|
if (!bSomethingToUse && bOrComputer) {
|
|
// call computer
|
|
ComputerPressed();
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************
|
|
* PLAYER ACTIONS *
|
|
************************************************************/
|
|
void SetGameEnd(void)
|
|
{
|
|
_pNetwork->SetGameFinished();
|
|
// start console for first player possible
|
|
for(INDEX iPlayer=0; iPlayer<GetMaxPlayers(); iPlayer++) {
|
|
CEntity *pen = GetPlayerEntity(iPlayer);
|
|
if (pen!=NULL) {
|
|
if (cmp_ppenPlayer==NULL && _pNetwork->IsPlayerLocal(pen)) {
|
|
cmp_ppenPlayer = (CPlayer*)pen;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// check if game should be finished
|
|
void CheckGameEnd(void)
|
|
{
|
|
BOOL bFinished = FALSE;
|
|
// if time limit is out
|
|
INDEX iTimeLimit = GetSP()->sp_iTimeLimit;
|
|
if (iTimeLimit>0 && _pTimer->CurrentTick()>=iTimeLimit*60.0f) {
|
|
bFinished = TRUE;
|
|
}
|
|
// if frag limit is out
|
|
INDEX iFragLimit = GetSP()->sp_iFragLimit;
|
|
if (iFragLimit>0 && m_psLevelStats.ps_iKills>=iFragLimit) {
|
|
bFinished = TRUE;
|
|
}
|
|
// if score limit is out
|
|
INDEX iScoreLimit = GetSP()->sp_iScoreLimit;
|
|
if (iScoreLimit>0 && m_psLevelStats.ps_iScore>=iScoreLimit) {
|
|
bFinished = TRUE;
|
|
}
|
|
|
|
if (bFinished) {
|
|
SetGameEnd();
|
|
}
|
|
}
|
|
|
|
// Preapply the action packet for local mouselag elimination
|
|
void PreapplyAction( const CPlayerAction &paAction)
|
|
{
|
|
}
|
|
|
|
// Called to apply player action to player entity each tick.
|
|
void ApplyAction( const CPlayerAction &paOriginal, FLOAT tmLatency)
|
|
{
|
|
if(!(m_ulFlags&PLF_INITIALIZED)) { return; }
|
|
// CPrintF("---APPLY: %g\n", paOriginal.pa_aRotation(1));
|
|
|
|
// if was not connected
|
|
if (m_ulFlags&PLF_NOTCONNECTED) {
|
|
// set connected state
|
|
SetConnected();
|
|
}
|
|
// mark that the player is connected
|
|
m_ulFlags |= PLF_APPLIEDACTION;
|
|
|
|
// make a copy of action for adjustments
|
|
CPlayerAction paAction = paOriginal;
|
|
//CPrintF("applying(%s-%08x): %g\n", GetPredictName(), int(paAction.pa_llCreated),
|
|
// paAction.pa_vTranslation(3));
|
|
|
|
// calculate delta from last received actions
|
|
ANGLE3D aDeltaRotation = paAction.pa_aRotation -m_aLastRotation;
|
|
ANGLE3D aDeltaViewRotation = paAction.pa_aViewRotation-m_aLastViewRotation;
|
|
//FLOAT3D vDeltaTranslation = paAction.pa_vTranslation -m_vLastTranslation;
|
|
m_aLastRotation = paAction.pa_aRotation;
|
|
m_aLastViewRotation = paAction.pa_aViewRotation;
|
|
//m_vLastTranslation = paAction.pa_vTranslation;
|
|
paAction.pa_aRotation = aDeltaRotation;
|
|
paAction.pa_aViewRotation = aDeltaViewRotation;
|
|
//paAction.pa_vTranslation = vDeltaTranslation;
|
|
|
|
// adjust rotations per tick
|
|
paAction.pa_aRotation /= _pTimer->TickQuantum;
|
|
paAction.pa_aViewRotation /= _pTimer->TickQuantum;
|
|
|
|
// adjust prediction for remote players only
|
|
CEntity *penMe = this;
|
|
if (IsPredictor()) {
|
|
penMe = penMe->GetPredicted();
|
|
}
|
|
SetPredictable(!_pNetwork->IsPlayerLocal(penMe));
|
|
|
|
// check for end of game
|
|
if (!IsPredictor()) {
|
|
CheckGameEnd();
|
|
}
|
|
|
|
// limit speeds against abusing
|
|
paAction.pa_vTranslation(1) = Clamp( paAction.pa_vTranslation(1), -plr_fSpeedSide, plr_fSpeedSide);
|
|
paAction.pa_vTranslation(2) = Clamp( paAction.pa_vTranslation(2), -plr_fSpeedUp, plr_fSpeedUp);
|
|
paAction.pa_vTranslation(3) = Clamp( paAction.pa_vTranslation(3), -plr_fSpeedForward, plr_fSpeedBackward);
|
|
|
|
// if speeds are like walking
|
|
if (Abs(paAction.pa_vTranslation(3))< plr_fSpeedForward/1.99f
|
|
&&Abs(paAction.pa_vTranslation(1))< plr_fSpeedSide/1.99f) {
|
|
// don't allow falling
|
|
en_fStepDnHeight = 1.5f;
|
|
|
|
// if speeds are like running
|
|
} else {
|
|
// allow falling
|
|
en_fStepDnHeight = -1;
|
|
}
|
|
|
|
// limit diagonal speed against abusing
|
|
FLOAT3D &v = paAction.pa_vTranslation;
|
|
FLOAT fDiag = Sqrt(v(1)*v(1)+v(3)*v(3));
|
|
if (fDiag>0.01f) {
|
|
FLOAT fDiagLimited = Min(fDiag, plr_fSpeedForward);
|
|
FLOAT fFactor = fDiagLimited/fDiag;
|
|
v(1)*=fFactor;
|
|
v(3)*=fFactor;
|
|
}
|
|
|
|
ulButtonsNow = paAction.pa_ulButtons;
|
|
ulButtonsBefore = m_ulLastButtons;
|
|
ulNewButtons = ulButtonsNow&~ulButtonsBefore;
|
|
ulReleasedButtons = (~ulButtonsNow)&(ulButtonsBefore);
|
|
|
|
m_ulLastButtons = ulButtonsNow; // remember last buttons
|
|
en_plLastViewpoint = en_plViewpoint; // remember last view point for lerping
|
|
|
|
// if alive
|
|
if (GetFlags() & ENF_ALIVE) {
|
|
// if not in auto-action mode
|
|
if (m_penActionMarker==NULL) {
|
|
// apply actions
|
|
AliveActions(paAction);
|
|
// if in auto-action mode
|
|
} else {
|
|
// do automatic actions
|
|
AutoActions(paAction);
|
|
}
|
|
// if not alive rotate camera view and rebirth on fire
|
|
} else {
|
|
DeathActions(paAction);
|
|
}
|
|
|
|
if (Abs(_pTimer->CurrentTick()-m_tmAnalyseEnd)<_pTimer->TickQuantum*2) {
|
|
m_tmAnalyseEnd = 0;
|
|
m_bPendingMessage = TRUE;
|
|
m_tmMessagePlay = 0;
|
|
}
|
|
if (m_bPendingMessage && !IsFuss()) {
|
|
m_bPendingMessage = FALSE;
|
|
m_tmMessagePlay = _pTimer->CurrentTick()+1.0f;
|
|
m_tmAnimateInbox = _pTimer->CurrentTick();
|
|
}
|
|
if (Abs(_pTimer->CurrentTick()-m_tmMessagePlay)<_pTimer->TickQuantum*2) {
|
|
m_bPendingMessage = FALSE;
|
|
m_tmAnalyseEnd = 0;
|
|
|
|
if (!m_bComputerInvoked && GetSP()->sp_bSinglePlayer) {
|
|
PrintCenterMessage(this, this,
|
|
TRANS("Press USE to read the message!"), 5.0f, MSS_NONE);
|
|
}
|
|
}
|
|
|
|
// wanna cheat a bit?
|
|
if (CheatsEnabled()) {
|
|
Cheats();
|
|
}
|
|
|
|
// if teleporting to marker (this cheat is enabled in all versions)
|
|
if (cht_iGoToMarker>0 && (GetFlags()&ENF_ALIVE)) {
|
|
// rebirth player, and it will teleport
|
|
m_iLastViewState = m_iViewState;
|
|
SendEvent(ERebirth());
|
|
}
|
|
|
|
// keep latency for eventual printout
|
|
UpdateLatency(tmLatency);
|
|
|
|
// check if highscore has changed
|
|
CheckHighScore();
|
|
};
|
|
|
|
|
|
// Called when player is disconnected
|
|
void Disconnect(void)
|
|
{
|
|
// remember name
|
|
m_strName = GetPlayerName();
|
|
// clear the character, so we don't get re-connected to same entity
|
|
en_pcCharacter = CPlayerCharacter();
|
|
// make main loop exit
|
|
SendEvent(EDisconnected());
|
|
};
|
|
|
|
// Called when player character is changed
|
|
void CharacterChanged(const CPlayerCharacter &pcNew)
|
|
{
|
|
// remember original character
|
|
CPlayerCharacter pcOrg = en_pcCharacter;
|
|
|
|
// set the new character
|
|
en_pcCharacter = pcNew;
|
|
ValidateCharacter();
|
|
|
|
// if the name has changed
|
|
if (pcOrg.GetName()!=pcNew.GetName()) {
|
|
// report that
|
|
CPrintF(TRANS("%s is now known as %s\n"),
|
|
(const char*)pcOrg.GetNameForPrinting(), (const char*)pcNew.GetNameForPrinting());
|
|
}
|
|
|
|
// if the team has changed
|
|
if (pcOrg.GetTeam()!=pcNew.GetTeam()) {
|
|
// report that
|
|
CPrintF(TRANS("%s switched to team %s\n"),
|
|
(const char*) pcNew.GetNameForPrinting(), (const char*)pcNew.GetTeamForPrinting());
|
|
}
|
|
|
|
// if appearance changed
|
|
CPlayerSettings *ppsOrg = (CPlayerSettings *)pcOrg.pc_aubAppearance;
|
|
CPlayerSettings *ppsNew = (CPlayerSettings *)pcNew.pc_aubAppearance;
|
|
if (memcmp(ppsOrg->ps_achModelFile, ppsNew->ps_achModelFile, sizeof(ppsOrg->ps_achModelFile))!=0) {
|
|
// update your real appearance if possible
|
|
CTString strNewLook;
|
|
BOOL bSuccess = SetPlayerAppearance(&m_moRender, &en_pcCharacter, strNewLook, /*bPreview=*/FALSE);
|
|
// if succeeded
|
|
if (bSuccess) {
|
|
// report that
|
|
CPrintF(TRANS("%s now appears as %s\n"),
|
|
(const char*)pcNew.GetNameForPrinting(),(const char*) strNewLook);
|
|
// if failed
|
|
} else {
|
|
// report that
|
|
CPrintF(TRANS("Cannot change appearance for %s: setting '%s' is unavailable\n"),
|
|
(const char*)pcNew.GetNameForPrinting(), (const char*)ppsNew->GetModelFilename());
|
|
}
|
|
// attach weapon to new appearance
|
|
GetPlayerAnimator()->SyncWeapon();
|
|
}
|
|
|
|
BOOL b3RDPersonOld = ppsOrg->ps_ulFlags&PSF_PREFER3RDPERSON;
|
|
BOOL b3RDPersonNew = ppsNew->ps_ulFlags&PSF_PREFER3RDPERSON;
|
|
if ((b3RDPersonOld && !b3RDPersonNew && m_iViewState==PVT_3RDPERSONVIEW)
|
|
||(b3RDPersonNew && !b3RDPersonOld && m_iViewState==PVT_PLAYEREYES) ) {
|
|
ChangePlayerView();
|
|
}
|
|
};
|
|
|
|
|
|
// Alive actions
|
|
void AliveActions(const CPlayerAction &pa)
|
|
{
|
|
CPlayerAction paAction = pa;
|
|
|
|
// if camera is active
|
|
if (m_penCamera!=NULL) {
|
|
// ignore keyboard/mouse/joystick commands
|
|
paAction.pa_vTranslation = FLOAT3D(0,0,0);
|
|
paAction.pa_aRotation = ANGLE3D(0,0,0);
|
|
paAction.pa_aViewRotation = ANGLE3D(0,0,0);
|
|
// if fire or use is pressed
|
|
if (ulNewButtons&(PLACT_FIRE|PLACT_USE)) {
|
|
// stop camera
|
|
m_penCamera=NULL;
|
|
}
|
|
} else {
|
|
ButtonsActions(paAction);
|
|
}
|
|
|
|
// do the actions
|
|
ActiveActions(paAction);
|
|
|
|
// if less than few seconds elapsed since last damage
|
|
FLOAT tmSinceWounding = _pTimer->CurrentTick() - m_tmWoundedTime;
|
|
if( tmSinceWounding<4.0f) {
|
|
// decrease damage ammount
|
|
m_fDamageAmmount *= 1.0f - tmSinceWounding/4.0f;
|
|
} else {
|
|
// reset damage ammount
|
|
m_fDamageAmmount = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Auto-actions
|
|
void AutoActions(const CPlayerAction &pa)
|
|
{
|
|
// if fire, use or computer is pressed
|
|
if (ulNewButtons&(PLACT_FIRE|PLACT_USE|PLACT_COMPUTER)) {
|
|
if (m_penCamera!=NULL) {
|
|
CEntity *penOnBreak = ((CCamera&)*m_penCamera).m_penOnBreak;
|
|
if (penOnBreak!=NULL) {
|
|
SendToTarget(penOnBreak, EET_TRIGGER, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
CPlayerAction paAction = pa;
|
|
// ignore keyboard/mouse/joystick commands
|
|
paAction.pa_vTranslation = FLOAT3D(0,0,0);
|
|
paAction.pa_aRotation = ANGLE3D(0,0,0);
|
|
paAction.pa_aViewRotation = ANGLE3D(0,0,0);
|
|
|
|
// if moving towards the marker is enabled
|
|
if (m_fAutoSpeed>0) {
|
|
FLOAT3D vDelta =
|
|
m_penActionMarker->GetPlacement().pl_PositionVector-
|
|
GetPlacement().pl_PositionVector;
|
|
FLOAT fDistance = vDelta.Length();
|
|
if (fDistance>0.1f) {
|
|
vDelta/=fDistance;
|
|
ANGLE aDH = GetRelativeHeading(vDelta);
|
|
|
|
// if should hit the marker exactly
|
|
FLOAT fSpeed = m_fAutoSpeed;
|
|
if (GetActionMarker()->m_paaAction==PAA_RUNANDSTOP) {
|
|
// adjust speed
|
|
fSpeed = Min(fSpeed, fDistance/_pTimer->TickQuantum);
|
|
}
|
|
// adjust rotation
|
|
if (Abs(aDH)>5.0f) {
|
|
if (fSpeed>m_fAutoSpeed-0.1f) {
|
|
aDH = Clamp(aDH, -30.0f, 30.0f);
|
|
}
|
|
paAction.pa_aRotation = ANGLE3D(aDH/_pTimer->TickQuantum,0,0);
|
|
}
|
|
// set forward speed
|
|
paAction.pa_vTranslation = FLOAT3D(0,0,-fSpeed);
|
|
}
|
|
} else {
|
|
paAction.pa_vTranslation = m_vAutoSpeed;
|
|
}
|
|
|
|
CPlayerActionMarker *ppam = GetActionMarker();
|
|
ASSERT( ppam != NULL);
|
|
if( ppam->m_paaAction == PAA_LOGO_FIRE_MINIGUN)
|
|
{
|
|
if( m_tmMinigunAutoFireStart != -1)
|
|
{
|
|
FLOAT tmDelta = _pTimer->CurrentTick()-m_tmMinigunAutoFireStart;
|
|
FLOAT aDH=0.0f;
|
|
FLOAT aDP=0.0f;
|
|
if( tmDelta>=0.0f && tmDelta<=0.75f)
|
|
{
|
|
aDH = 0.0f;
|
|
}
|
|
else if( tmDelta>=0.75f)
|
|
{
|
|
FLOAT fDT = tmDelta-0.75f;
|
|
aDH = 1.0f*cos(fDT+PI/2.0f);
|
|
aDP = 0.5f*cos(fDT);
|
|
}
|
|
paAction.pa_aRotation = ANGLE3D(aDH/_pTimer->TickQuantum, aDP/_pTimer->TickQuantum,0);
|
|
}
|
|
}
|
|
|
|
// do the actions
|
|
if (!(m_ulFlags&PLF_AUTOMOVEMENTS)) {
|
|
ActiveActions(paAction);
|
|
}
|
|
}
|
|
|
|
void GetLerpedWeaponPosition( FLOAT3D vRel, CPlacement3D &pl)
|
|
{
|
|
pl = CPlacement3D( vRel, ANGLE3D(0,0,0));
|
|
CPlacement3D plView;
|
|
GetLerpedAbsoluteViewPlacement(plView);
|
|
pl.RelativeToAbsolute( plView);
|
|
}
|
|
|
|
void SpawnBubbles( INDEX ctBubbles)
|
|
{
|
|
for( INDEX iBouble=0; iBouble<ctBubbles; iBouble++)
|
|
{
|
|
FLOAT3D vRndRel = FLOAT3D( (FRnd()-0.5f)*0.25f, -0.25f, -0.5f+FRnd()/10.0f);
|
|
ANGLE3D aDummy = ANGLE3D(0,0,0);
|
|
CPlacement3D plMouth = CPlacement3D( vRndRel, aDummy);
|
|
|
|
plMouth.RelativeToAbsolute( en_plViewpoint);
|
|
plMouth.RelativeToAbsolute( GetPlacement());
|
|
FLOAT3D vRndSpd = FLOAT3D( (FRnd()-0.5f)*0.25f, (FRnd()-0.5f)*0.25f, (FRnd()-0.5f)*0.25f);
|
|
AddBouble( plMouth.pl_PositionVector, vRndSpd);
|
|
}
|
|
}
|
|
|
|
void ActiveActions(const CPlayerAction &paAction)
|
|
{
|
|
// translation
|
|
FLOAT3D vTranslation = paAction.pa_vTranslation;
|
|
// turbo speed cheat
|
|
if (cht_fTranslationMultiplier && CheatsEnabled()) {
|
|
vTranslation *= cht_fTranslationMultiplier;
|
|
}
|
|
|
|
// enable faster moving if holding knife in DM
|
|
if( ((CPlayerWeapons&)*m_penWeapons).m_iCurrentWeapon==WEAPON_KNIFE &&
|
|
!GetSP()->sp_bCooperative)
|
|
{
|
|
vTranslation*=1.30f;
|
|
}
|
|
|
|
en_fAcceleration = plr_fAcceleration;
|
|
en_fDeceleration = plr_fDeceleration;
|
|
|
|
if( !GetSP()->sp_bCooperative)
|
|
{
|
|
vTranslation(1)*=1.35f;
|
|
vTranslation(3)*=1.35f;
|
|
//en_fDeceleration *= 0.8f;
|
|
}
|
|
|
|
CContentType &ctUp = GetWorld()->wo_actContentTypes[en_iUpContent];
|
|
CContentType &ctDn = GetWorld()->wo_actContentTypes[en_iDnContent];
|
|
|
|
PlayerState pstWanted = PST_STAND;
|
|
BOOL bUpSwimable = (ctUp.ct_ulFlags&CTF_SWIMABLE) && en_fImmersionFactor<=0.99f;
|
|
BOOL bDnSwimable = (ctDn.ct_ulFlags&CTF_SWIMABLE) && en_fImmersionFactor>=0.5f;
|
|
|
|
// if considerably inside swimable content
|
|
if (bUpSwimable || bDnSwimable) {
|
|
// allow jumping
|
|
m_ulFlags|=PLF_JUMPALLOWED;
|
|
//CPrintF("swimable %f", en_fImmersionFactor);
|
|
// if totaly inside
|
|
if (en_fImmersionFactor>=0.99f || bUpSwimable) {
|
|
// want to dive
|
|
pstWanted = PST_DIVE;
|
|
// if only partially inside
|
|
} else {
|
|
// want to swim
|
|
pstWanted = PST_SWIM;
|
|
}
|
|
// if not in swimable content
|
|
} else {
|
|
// if has reference
|
|
if (en_penReference!=NULL) {
|
|
// reset fall timer
|
|
m_fFallTime = 0.0f;
|
|
|
|
// if no reference
|
|
} else {
|
|
// increase fall time
|
|
m_fFallTime += _pTimer->TickQuantum;
|
|
}
|
|
// if not wanting to jump
|
|
if (vTranslation(2)<0.1f) {
|
|
// allow jumping
|
|
m_ulFlags|=PLF_JUMPALLOWED;
|
|
}
|
|
|
|
// if falling
|
|
if (m_fFallTime >= 0.5f) {
|
|
// wants to fall
|
|
pstWanted = PST_FALL;
|
|
// if not falling
|
|
} else {
|
|
// if holding down and really not in air
|
|
if (vTranslation(2)<-0.01f/* && m_fFallTime<0.5f*/) {
|
|
// wants to crouch
|
|
pstWanted = PST_CROUCH;
|
|
// if not holding down
|
|
} else {
|
|
// wants to stand
|
|
pstWanted = PST_STAND;
|
|
}
|
|
}
|
|
}
|
|
//CPrintF("c - %s w - %s", NameForState(m_pstState), NameForState(pstWanted));
|
|
|
|
// flying mode - rotate whole player
|
|
if (!(GetPhysicsFlags()&EPF_TRANSLATEDBYGRAVITY)) {
|
|
SetDesiredRotation(paAction.pa_aRotation);
|
|
StartModelAnim(PLAYER_ANIM_STAND, AOF_LOOPING|AOF_NORESTART);
|
|
SetDesiredTranslation(vTranslation);
|
|
// normal mode
|
|
} else {
|
|
PlayerState pstOld = m_pstState;
|
|
|
|
// if different state needed
|
|
if (pstWanted!=m_pstState) {
|
|
// check state wanted
|
|
switch(pstWanted) {
|
|
// if wanting to stand
|
|
case PST_STAND: {
|
|
// if can stand here
|
|
if (ChangeCollisionBoxIndexNow(PLAYER_COLLISION_BOX_STAND)) {
|
|
en_plViewpoint.pl_PositionVector(2) = plr_fViewHeightStand;
|
|
if (m_pstState==PST_CROUCH) {
|
|
((CPlayerAnimator&)*m_penAnimator).Rise();
|
|
} else {
|
|
((CPlayerAnimator&)*m_penAnimator).Stand();
|
|
}
|
|
m_pstState = PST_STAND;
|
|
}
|
|
} break;
|
|
// if wanting to crouch
|
|
case PST_CROUCH: {
|
|
// if can crouch here
|
|
if (ChangeCollisionBoxIndexNow(PLAYER_COLLISION_BOX_CROUCH)) {
|
|
m_pstState = PST_CROUCH;
|
|
en_plViewpoint.pl_PositionVector(2) = plr_fViewHeightCrouch;
|
|
((CPlayerAnimator&)*m_penAnimator).Crouch();
|
|
}
|
|
} break;
|
|
// if wanting to swim
|
|
case PST_SWIM: {
|
|
// if can swim here
|
|
if (ChangeCollisionBoxIndexNow(PLAYER_COLLISION_BOX_SWIMSMALL)) {
|
|
ChangeCollisionBoxIndexWhenPossible(PLAYER_COLLISION_BOX_SWIM);
|
|
m_pstState = PST_SWIM;
|
|
en_plViewpoint.pl_PositionVector(2) = plr_fViewHeightSwim;
|
|
((CPlayerAnimator&)*m_penAnimator).Swim();
|
|
m_fSwimTime = _pTimer->CurrentTick();
|
|
}
|
|
} break;
|
|
// if wanting to dive
|
|
case PST_DIVE: {
|
|
// if can dive here
|
|
if (ChangeCollisionBoxIndexNow(PLAYER_COLLISION_BOX_SWIMSMALL)) {
|
|
ChangeCollisionBoxIndexWhenPossible(PLAYER_COLLISION_BOX_SWIM);
|
|
m_pstState = PST_DIVE;
|
|
en_plViewpoint.pl_PositionVector(2) = plr_fViewHeightDive;
|
|
((CPlayerAnimator&)*m_penAnimator).Swim();
|
|
}
|
|
} break;
|
|
// if wanting to fall
|
|
case PST_FALL: {
|
|
// if can fall here
|
|
if (ChangeCollisionBoxIndexNow(PLAYER_COLLISION_BOX_STAND)) {
|
|
m_pstState = PST_FALL;
|
|
en_plViewpoint.pl_PositionVector(2) = plr_fViewHeightStand;
|
|
((CPlayerAnimator&)*m_penAnimator).Fall();
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
// if state changed
|
|
if (m_pstState!=pstOld) {
|
|
// check water entering/leaving
|
|
BOOL bWasInWater = (pstOld==PST_SWIM||pstOld==PST_DIVE);
|
|
BOOL bIsInWater = (m_pstState==PST_SWIM||m_pstState==PST_DIVE);
|
|
// if entered water
|
|
if (bIsInWater && !bWasInWater) {
|
|
PlaySound(m_soBody, SOUND_WATER_ENTER, SOF_3D);
|
|
// if left water
|
|
} else if (!bIsInWater && bWasInWater) {
|
|
PlaySound(m_soBody, SOUND_WATER_LEAVE, SOF_3D);
|
|
m_tmOutOfWater = _pTimer->CurrentTick();
|
|
//CPrintF("gotout ");
|
|
// if in water
|
|
} else if (bIsInWater) {
|
|
// if dived in
|
|
if (pstOld==PST_SWIM && m_pstState == PST_DIVE) {
|
|
PlaySound(m_soFootL, SOUND_DIVEIN, SOF_3D);
|
|
if(_pNetwork->IsPlayerLocal(this)) {IFeel_PlayEffect("DiveIn");}
|
|
m_bMoveSoundLeft = TRUE;
|
|
m_tmMoveSound = _pTimer->CurrentTick();
|
|
// if dived out
|
|
} else if (m_pstState==PST_SWIM && pstOld==PST_DIVE) {
|
|
PlaySound(m_soFootL, SOUND_DIVEOUT, SOF_3D);
|
|
m_bMoveSoundLeft = TRUE;
|
|
m_tmMoveSound = _pTimer->CurrentTick();
|
|
}
|
|
}
|
|
// if just fell to ground
|
|
if (pstOld==PST_FALL && (m_pstState==PST_STAND||m_pstState==PST_CROUCH)) {
|
|
PlaySound(m_soFootL, SOUND_LAND, SOF_3D);
|
|
if(_pNetwork->IsPlayerLocal(this)) {IFeel_PlayEffect("Land");}
|
|
}
|
|
// change ambience sounds
|
|
if (m_pstState==PST_DIVE) {
|
|
m_soLocalAmbientLoop.Set3DParameters(50.0f, 10.0f, 0.25f, 1.0f);
|
|
PlaySound(m_soLocalAmbientLoop, SOUND_WATERAMBIENT,
|
|
SOF_LOOP|SOF_3D|SOF_VOLUMETRIC|SOF_LOCAL);
|
|
} else if (pstOld==PST_DIVE) {
|
|
m_soLocalAmbientLoop.Stop();
|
|
}
|
|
}
|
|
// if just jumped
|
|
if (en_tmJumped+_pTimer->TickQuantum>=_pTimer->CurrentTick() &&
|
|
en_tmJumped<=_pTimer->CurrentTick() && en_penReference==NULL) {
|
|
// play jump sound
|
|
SetDefaultMouthPitch();
|
|
PlaySound(m_soMouth, SOUND_JUMP, SOF_3D);
|
|
if(_pNetwork->IsPlayerLocal(this)) {IFeel_PlayEffect("Jump");}
|
|
// disallow jumping
|
|
m_ulFlags&=~PLF_JUMPALLOWED;
|
|
}
|
|
|
|
// set density
|
|
if (m_pstState == PST_SWIM || pstWanted == PST_SWIM
|
|
||(pstWanted == PST_DIVE && m_pstState != pstWanted)) {
|
|
en_fDensity = 500.0f; // lower density than water
|
|
} else {
|
|
en_fDensity = 1000.0f; // same density as water
|
|
}
|
|
|
|
if (_pTimer->CurrentTick()>=m_tmNextAmbientOnce)
|
|
{
|
|
if (m_pstState == PST_DIVE)
|
|
{
|
|
PlaySound(m_soLocalAmbientOnce, SOUND_WATERBUBBLES,
|
|
SOF_3D|SOF_VOLUMETRIC|SOF_LOCAL);
|
|
m_soLocalAmbientOnce.Set3DParameters(25.0f, 5.0f, 2.0f, Lerp(0.5f, 1.5f, FRnd()) );
|
|
SpawnBubbles( 5+INDEX(FRnd()*5));
|
|
}
|
|
m_tmNextAmbientOnce = _pTimer->CurrentTick()+5.0f+FRnd();
|
|
}
|
|
|
|
|
|
// if crouching
|
|
if (m_pstState == PST_CROUCH) {
|
|
// go slower
|
|
vTranslation /= 2.5f;
|
|
// don't go down
|
|
vTranslation(2) = 0.0f;
|
|
}
|
|
|
|
// if diving
|
|
if (m_pstState == PST_DIVE) {
|
|
// translate up/down with view pitch
|
|
FLOATmatrix3D mPitch;
|
|
MakeRotationMatrixFast(mPitch, FLOAT3D(0,en_plViewpoint.pl_OrientationAngle(2),0));
|
|
FLOAT fZ = vTranslation(3);
|
|
vTranslation(3) = 0.0f;
|
|
vTranslation += FLOAT3D(0,0,fZ)*mPitch;
|
|
// if swimming
|
|
} else if (m_pstState == PST_SWIM) {
|
|
// translate down with view pitch if large
|
|
FLOATmatrix3D mPitch;
|
|
FLOAT fPitch = en_plViewpoint.pl_OrientationAngle(2);
|
|
if (fPitch>-30.0f) {
|
|
fPitch = 0;
|
|
}
|
|
MakeRotationMatrixFast(mPitch, FLOAT3D(0,fPitch,0));
|
|
FLOAT fZ = vTranslation(3);
|
|
vTranslation(3) = 0.0f;
|
|
vTranslation += FLOAT3D(0,0,fZ)*mPitch;
|
|
}
|
|
|
|
// if swimming or diving
|
|
if (m_pstState == PST_SWIM || m_pstState == PST_DIVE) {
|
|
// up/down is slower than on ground
|
|
vTranslation(2)*=0.5f;
|
|
}
|
|
|
|
// if just started swimming
|
|
if ((m_pstState == PST_SWIM && _pTimer->CurrentTick()<m_fSwimTime+0.5f)
|
|
||_pTimer->CurrentTick()<m_tmOutOfWater+0.5f) {
|
|
// no up/down change
|
|
vTranslation(2)=0;
|
|
//CPrintF(" noup");
|
|
}
|
|
|
|
//CPrintF("\n");
|
|
|
|
// disable consecutive jumps
|
|
if (!(m_ulFlags&PLF_JUMPALLOWED) && vTranslation(2)>0) {
|
|
vTranslation(2) = 0.0f;
|
|
}
|
|
|
|
// set translation
|
|
SetDesiredTranslation(vTranslation);
|
|
|
|
// set pitch and banking from the normal rotation into the view rotation
|
|
en_plViewpoint.Rotate_HPB(ANGLE3D(
|
|
(ANGLE)((FLOAT)paAction.pa_aRotation(1)*_pTimer->TickQuantum),
|
|
(ANGLE)((FLOAT)paAction.pa_aRotation(2)*_pTimer->TickQuantum),
|
|
(ANGLE)((FLOAT)paAction.pa_aRotation(3)*_pTimer->TickQuantum)));
|
|
// pitch and banking boundaries
|
|
RoundViewAngle(en_plViewpoint.pl_OrientationAngle(2), PITCH_MAX);
|
|
RoundViewAngle(en_plViewpoint.pl_OrientationAngle(3), BANKING_MAX);
|
|
|
|
// translation rotate player for heading
|
|
if (vTranslation.Length() > 0.1f) {
|
|
SetDesiredRotation(ANGLE3D(en_plViewpoint.pl_OrientationAngle(1)/_pTimer->TickQuantum, 0.0f, 0.0f));
|
|
if (m_ulFlags&PLF_VIEWROTATIONCHANGED) {
|
|
m_ulFlags&=~PLF_VIEWROTATIONCHANGED;
|
|
FLOATmatrix3D mViewRot;
|
|
MakeRotationMatrixFast(mViewRot, ANGLE3D(en_plViewpoint.pl_OrientationAngle(1),0,0));
|
|
FLOAT3D vTransRel = vTranslation*mViewRot;
|
|
SetDesiredTranslation(vTransRel);
|
|
}
|
|
en_plViewpoint.pl_OrientationAngle(1) = 0.0f;
|
|
|
|
// rotate head, body and legs
|
|
} else {
|
|
m_ulFlags |= PLF_VIEWROTATIONCHANGED;
|
|
SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 0.0f));
|
|
ANGLE aDiff = en_plViewpoint.pl_OrientationAngle(1) - HEADING_MAX;
|
|
if (aDiff > 0.0f) {
|
|
SetDesiredRotation(ANGLE3D(aDiff/_pTimer->TickQuantum, 0.0f, 0.0f));
|
|
}
|
|
aDiff = en_plViewpoint.pl_OrientationAngle(1) + HEADING_MAX;
|
|
if (aDiff < 0.0f) {
|
|
SetDesiredRotation(ANGLE3D(aDiff/_pTimer->TickQuantum, 0.0f, 0.0f));
|
|
}
|
|
RoundViewAngle(en_plViewpoint.pl_OrientationAngle(1), HEADING_MAX);
|
|
}
|
|
|
|
// play moving sounds
|
|
FLOAT fWantSpeed = en_vDesiredTranslationRelative.Length();
|
|
FLOAT fGoesSpeed = en_vCurrentTranslationAbsolute.Length();
|
|
BOOL bOnGround = (m_pstState == PST_STAND)||(m_pstState == PST_CROUCH);
|
|
BOOL bRunning = bOnGround && fWantSpeed>5.0f && fGoesSpeed>5.0f;
|
|
BOOL bWalking = bOnGround && !bRunning && fWantSpeed>2.0f && fGoesSpeed>2.0f;
|
|
BOOL bSwimming = (m_pstState == PST_SWIM) && fWantSpeed>2.0f && fGoesSpeed>2.0f;
|
|
BOOL bDiving = (m_pstState == PST_DIVE) && fWantSpeed>2.0f && fGoesSpeed>2.0f;
|
|
TIME tmNow = _pTimer->CurrentTick();
|
|
INDEX iSoundWalkL = SOUND_WALK_L;
|
|
INDEX iSoundWalkR = SOUND_WALK_R;
|
|
if ((ctDn.ct_ulFlags&CTF_SWIMABLE) && en_fImmersionFactor>=0.1f) {
|
|
iSoundWalkL = SOUND_WATERWALK_L;
|
|
iSoundWalkR = SOUND_WATERWALK_R;
|
|
} else if (en_pbpoStandOn!=NULL &&
|
|
en_pbpoStandOn->bpo_bppProperties.bpp_ubSurfaceType==SURFACE_SAND) {
|
|
iSoundWalkL = SOUND_WALK_SAND_L;
|
|
iSoundWalkR = SOUND_WALK_SAND_R;
|
|
} else if (en_pbpoStandOn!=NULL &&
|
|
en_pbpoStandOn->bpo_bppProperties.bpp_ubSurfaceType==SURFACE_RED_SAND) {
|
|
iSoundWalkL = SOUND_WALK_SAND_L;
|
|
iSoundWalkR = SOUND_WALK_SAND_R;
|
|
}
|
|
else {
|
|
}
|
|
if (bRunning) {
|
|
if (tmNow>m_tmMoveSound+plr_fRunSoundDelay) {
|
|
m_tmMoveSound = tmNow;
|
|
m_bMoveSoundLeft = !m_bMoveSoundLeft;
|
|
if (m_bMoveSoundLeft) {
|
|
PlaySound(m_soFootL, iSoundWalkL, SOF_3D);
|
|
} else {
|
|
PlaySound(m_soFootR, iSoundWalkR, SOF_3D);
|
|
}
|
|
}
|
|
} else if (bWalking) {
|
|
if (tmNow>m_tmMoveSound+plr_fWalkSoundDelay) {
|
|
m_tmMoveSound = tmNow;
|
|
m_bMoveSoundLeft = !m_bMoveSoundLeft;
|
|
if (m_bMoveSoundLeft) {
|
|
PlaySound(m_soFootL, iSoundWalkL, SOF_3D);
|
|
} else {
|
|
PlaySound(m_soFootR, iSoundWalkR, SOF_3D);
|
|
}
|
|
}
|
|
} else if (bDiving) {
|
|
if (tmNow>m_tmMoveSound+plr_fDiveSoundDelay) {
|
|
m_tmMoveSound = tmNow;
|
|
m_bMoveSoundLeft = !m_bMoveSoundLeft;
|
|
if (m_bMoveSoundLeft) {
|
|
PlaySound(m_soFootL, SOUND_DIVE_L, SOF_3D);
|
|
} else {
|
|
PlaySound(m_soFootR, SOUND_DIVE_R, SOF_3D);
|
|
}
|
|
}
|
|
} else if (bSwimming) {
|
|
if (tmNow>m_tmMoveSound+plr_fSwimSoundDelay) {
|
|
m_tmMoveSound = tmNow;
|
|
m_bMoveSoundLeft = !m_bMoveSoundLeft;
|
|
if (m_bMoveSoundLeft) {
|
|
PlaySound(m_soFootL, SOUND_SWIM_L, SOF_3D);
|
|
} else {
|
|
PlaySound(m_soFootR, SOUND_SWIM_R, SOF_3D);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if player is almost out of air
|
|
TIME tmBreathDelay = tmNow-en_tmLastBreathed;
|
|
if (en_tmMaxHoldBreath-tmBreathDelay<20.0f) {
|
|
// play drowning sound once in a while
|
|
if (m_tmMouthSoundLast+2.0f<tmNow) {
|
|
m_tmMouthSoundLast = tmNow;
|
|
SetRandomMouthPitch(0.9f, 1.1f);
|
|
PlaySound(m_soMouth, SOUND_DROWN, SOF_3D);
|
|
}
|
|
}
|
|
|
|
// animate player
|
|
((CPlayerAnimator&)*m_penAnimator).AnimatePlayer();
|
|
}
|
|
};
|
|
|
|
// Round view angle
|
|
void RoundViewAngle(ANGLE &aViewAngle, ANGLE aRound) {
|
|
if (aViewAngle > aRound) {
|
|
aViewAngle = aRound;
|
|
}
|
|
if (aViewAngle < -aRound) {
|
|
aViewAngle = -aRound;
|
|
}
|
|
};
|
|
|
|
// Death actions
|
|
void DeathActions(const CPlayerAction &paAction) {
|
|
// set heading, pitch and banking from the normal rotation into the camera view rotation
|
|
if (m_penView!=NULL) {
|
|
ASSERT((IsPredicted()&&m_penView->IsPredicted()) || (IsPredictor()&&m_penView->IsPredictor())
|
|
|| (!IsPredicted()&&!m_penView->IsPredicted()&&!IsPredictor()&&!m_penView->IsPredictor()));
|
|
en_plViewpoint.pl_PositionVector = FLOAT3D(0, 1, 0);
|
|
en_plViewpoint.pl_OrientationAngle += (ANGLE3D(
|
|
(ANGLE)((FLOAT)paAction.pa_aRotation(1)*_pTimer->TickQuantum),
|
|
(ANGLE)((FLOAT)paAction.pa_aRotation(2)*_pTimer->TickQuantum),
|
|
(ANGLE)((FLOAT)paAction.pa_aRotation(3)*_pTimer->TickQuantum)));
|
|
}
|
|
|
|
// if death is finished and fire just released again and this is not a predictor
|
|
if (m_iMayRespawn==2 && (ulReleasedButtons&PLACT_FIRE) && !IsPredictor()) {
|
|
// if singleplayer
|
|
if( GetSP()->sp_bSinglePlayer) {
|
|
// load quick savegame
|
|
_pShell->Execute("gam_bQuickLoad=1;");
|
|
// if deathmatch or similar
|
|
} else if( !GetSP()->sp_bCooperative) {
|
|
// rebirth
|
|
SendEvent(EEnd());
|
|
// if cooperative
|
|
} else {
|
|
// if holding down reload button
|
|
if (m_ulLastButtons&PLACT_RELOAD) {
|
|
// forbid respawning in-place
|
|
m_ulFlags &= ~PLF_RESPAWNINPLACE;
|
|
}
|
|
// if playing on credits
|
|
if (GetSP()->sp_ctCredits!=0) {
|
|
// if playing on infinite credits or some credits left
|
|
if (GetSP()->sp_ctCredits==-1 || GetSP()->sp_ctCreditsLeft!=0) {
|
|
// decrement credits
|
|
if (GetSP()->sp_ctCredits!=-1) {
|
|
((CSessionProperties*)GetSP())->sp_ctCreditsLeft--;
|
|
}
|
|
|
|
// initiate respawn
|
|
CPrintF(TRANS("%s is riding the gun again\n"), (const char*)GetPlayerName());
|
|
SendEvent(EEnd());
|
|
|
|
// report number of credits left
|
|
if (GetSP()->sp_ctCredits>0) {
|
|
if (GetSP()->sp_ctCreditsLeft==0) {
|
|
CPrintF(TRANS(" no more credits left!\n"));
|
|
} else {
|
|
CPrintF(TRANS(" %d credits left\n"), GetSP()->sp_ctCreditsLeft);
|
|
}
|
|
}
|
|
// if no more credits left
|
|
} else {
|
|
// report that you cannot respawn
|
|
CPrintF(TRANS("%s rests in peace - out of credits\n"), (const char*)GetPlayerName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// check fire released once after death
|
|
if (m_iMayRespawn==1 && !(ulButtonsNow&PLACT_FIRE)) {
|
|
m_iMayRespawn=2;
|
|
}
|
|
};
|
|
|
|
|
|
// Buttons actions
|
|
void ButtonsActions( CPlayerAction &paAction)
|
|
{
|
|
// if selecting a new weapon select it
|
|
if((ulNewButtons&PLACT_SELECT_WEAPON_MASK)!=0) {
|
|
ESelectWeapon eSelect;
|
|
eSelect.iWeapon = (ulNewButtons&PLACT_SELECT_WEAPON_MASK)>>PLACT_SELECT_WEAPON_SHIFT;
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(eSelect);
|
|
}
|
|
|
|
if(ulNewButtons&PLACT_WEAPON_NEXT) {
|
|
ESelectWeapon eSelect;
|
|
eSelect.iWeapon = -1;
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(eSelect);
|
|
}
|
|
if(ulNewButtons&PLACT_WEAPON_PREV) {
|
|
ESelectWeapon eSelect;
|
|
eSelect.iWeapon = -2;
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(eSelect);
|
|
}
|
|
if(ulNewButtons&PLACT_WEAPON_FLIP) {
|
|
ESelectWeapon eSelect;
|
|
eSelect.iWeapon = -3;
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(eSelect);
|
|
}
|
|
|
|
// if fire is pressed
|
|
if (ulNewButtons&PLACT_FIRE) {
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(EFireWeapon());
|
|
}
|
|
// if fire is released
|
|
if (ulReleasedButtons&PLACT_FIRE) {
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(EReleaseWeapon());
|
|
}
|
|
// if reload is pressed
|
|
if (ulReleasedButtons&PLACT_RELOAD) {
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(EReloadWeapon());
|
|
}
|
|
|
|
// if use is pressed
|
|
if (ulNewButtons&PLACT_USE) {
|
|
UsePressed(ulNewButtons&PLACT_COMPUTER);
|
|
// if computer is pressed
|
|
} else if (ulNewButtons&PLACT_COMPUTER) {
|
|
ComputerPressed();
|
|
}
|
|
|
|
// if 3rd person view is pressed
|
|
if (ulNewButtons&PLACT_3RD_PERSON_VIEW) {
|
|
ChangePlayerView();
|
|
}
|
|
|
|
// apply center view
|
|
if( ulButtonsNow&PLACT_CENTER_VIEW) {
|
|
// center view with speed of 45 degrees per 1/20 seconds
|
|
paAction.pa_aRotation(2) += Clamp( -en_plViewpoint.pl_OrientationAngle(2)/_pTimer->TickQuantum, -900.0f, +900.0f);
|
|
}
|
|
};
|
|
|
|
|
|
// check if cheats can be active
|
|
BOOL CheatsEnabled(void)
|
|
{
|
|
return (GetSP()->sp_ctMaxPlayers==1||GetSP()->sp_bQuickTest) && m_penActionMarker==NULL;
|
|
}
|
|
|
|
// Cheats
|
|
void Cheats(void)
|
|
{
|
|
BOOL bFlyOn = cht_bFly || cht_bGhost;
|
|
// fly mode
|
|
BOOL bIsFlying = !(GetPhysicsFlags() & EPF_TRANSLATEDBYGRAVITY);
|
|
if (bFlyOn && !bIsFlying) {
|
|
SetPhysicsFlags(GetPhysicsFlags() & ~(EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY));
|
|
en_plViewpoint.pl_OrientationAngle = ANGLE3D(0, 0, 0);
|
|
} else if (!bFlyOn && bIsFlying) {
|
|
SetPhysicsFlags(GetPhysicsFlags() | EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY);
|
|
en_plViewpoint.pl_OrientationAngle = ANGLE3D(0, 0, 0);
|
|
}
|
|
|
|
// ghost mode
|
|
BOOL bIsGhost = !(GetCollisionFlags() & ((ECBI_BRUSH|ECBI_MODEL)<<ECB_TEST));
|
|
if (cht_bGhost && !bIsGhost) {
|
|
SetCollisionFlags(GetCollisionFlags() & ~((ECBI_BRUSH|ECBI_MODEL)<<ECB_TEST));
|
|
} else if (!cht_bGhost && bIsGhost) {
|
|
SetCollisionFlags(GetCollisionFlags() | ((ECBI_BRUSH|ECBI_MODEL)<<ECB_TEST));
|
|
}
|
|
|
|
// invisible mode
|
|
if (cht_bInvisible) {
|
|
SetFlags(GetFlags() | ENF_INVISIBLE);
|
|
} else {
|
|
SetFlags(GetFlags() & ~ENF_INVISIBLE);
|
|
}
|
|
|
|
// cheat
|
|
if (cht_bGiveAll) {
|
|
cht_bGiveAll = FALSE;
|
|
((CPlayerWeapons&)*m_penWeapons).CheatGiveAll();
|
|
}
|
|
|
|
if (cht_bKillAll) {
|
|
cht_bKillAll = FALSE;
|
|
KillAllEnemies(this);
|
|
}
|
|
|
|
if (cht_bOpen) {
|
|
cht_bOpen = FALSE;
|
|
((CPlayerWeapons&)*m_penWeapons).CheatOpen();
|
|
}
|
|
|
|
if (cht_bAllMessages) {
|
|
cht_bAllMessages = FALSE;
|
|
CheatAllMessages();
|
|
}
|
|
|
|
if (cht_bRefresh) {
|
|
cht_bRefresh = FALSE;
|
|
SetHealth(TopHealth());
|
|
}
|
|
};
|
|
|
|
|
|
/************************************************************
|
|
* END OF PLAYER ACTIONS *
|
|
************************************************************/
|
|
|
|
|
|
// Get current placement that the player views from in absolute space.
|
|
void GetLerpedAbsoluteViewPlacement(CPlacement3D &plView) {
|
|
if (!(m_ulFlags&PLF_INITIALIZED)) {
|
|
plView = GetPlacement();
|
|
return;
|
|
}
|
|
|
|
BOOL bSharpTurning =
|
|
(GetSettings()->ps_ulFlags&PSF_SHARPTURNING) &&
|
|
_pNetwork->IsPlayerLocal((CPlayer*)GetPredictionTail());
|
|
|
|
// lerp player viewpoint
|
|
FLOAT fLerpFactor = _pTimer->GetLerpFactor();
|
|
plView.Lerp(en_plLastViewpoint, en_plViewpoint, fLerpFactor);
|
|
|
|
// moving banking and soft eyes
|
|
((CPlayerAnimator&)*m_penAnimator).ChangeView(plView);
|
|
// body and head attachment animation
|
|
((CPlayerAnimator&)*m_penAnimator).BodyAndHeadOrientation(plView);
|
|
|
|
// return player eyes view
|
|
if (m_iViewState == PVT_PLAYEREYES) {
|
|
CPlacement3D plPosLerped = GetLerpedPlacement();
|
|
if (bSharpTurning) {
|
|
// get your prediction tail
|
|
CPlayer *pen = (CPlayer*)GetPredictionTail();
|
|
// add local rotation
|
|
plView.pl_OrientationAngle = pen->en_plViewpoint.pl_OrientationAngle + (pen->m_aLocalRotation-pen->m_aLastRotation);
|
|
// make sure it doesn't go out of limits
|
|
RoundViewAngle(plView.pl_OrientationAngle(2), PITCH_MAX);
|
|
RoundViewAngle(plView.pl_OrientationAngle(3), BANKING_MAX);
|
|
|
|
// compensate for rotations that happen to the player without his/hers will
|
|
// (rotating brushes, weird gravities...)
|
|
// (these need to be lerped)
|
|
ANGLE3D aCurr = pen->GetPlacement().pl_OrientationAngle;
|
|
ANGLE3D aLast = pen->en_plLastPlacement.pl_OrientationAngle;
|
|
ANGLE3D aDesired = pen->en_aDesiredRotationRelative*_pTimer->TickQuantum;
|
|
FLOATmatrix3D mCurr; MakeRotationMatrixFast(mCurr, aCurr);
|
|
FLOATmatrix3D mLast; MakeRotationMatrixFast(mLast, aLast);
|
|
FLOATmatrix3D mDesired; MakeRotationMatrixFast(mDesired, aDesired);
|
|
mDesired = en_mRotation*(mDesired*!en_mRotation);
|
|
FLOATmatrix3D mForced = !mDesired*mCurr*!mLast; // = aCurr-aLast-aDesired;
|
|
ANGLE3D aForced; DecomposeRotationMatrixNoSnap(aForced, mForced);
|
|
if (aForced.MaxNorm()<1E-2) {
|
|
aForced = ANGLE3D(0,0,0);
|
|
}
|
|
FLOATquat3D qForced; qForced.FromEuler(aForced);
|
|
FLOATquat3D qZero; qZero.FromEuler(ANGLE3D(0,0,0));
|
|
FLOATquat3D qLerped = Slerp(fLerpFactor, qZero, qForced);
|
|
FLOATmatrix3D m;
|
|
qLerped.ToMatrix(m);
|
|
m=m*mDesired*mLast;
|
|
DecomposeRotationMatrixNoSnap(plPosLerped.pl_OrientationAngle, m);
|
|
}
|
|
plView.RelativeToAbsoluteSmooth(plPosLerped);
|
|
// 3rd person view
|
|
} else if (m_iViewState == PVT_3RDPERSONVIEW) {
|
|
plView = m_pen3rdPersonView->GetLerpedPlacement();
|
|
// camera view for player auto actions
|
|
} else if (m_iViewState == PVT_PLAYERAUTOVIEW) {
|
|
plView = m_penView->GetLerpedPlacement();
|
|
// camera view for stored sequences
|
|
} else {
|
|
ASSERTALWAYS("Unknown player view");
|
|
}
|
|
};
|
|
|
|
// Get current entity that the player views from.
|
|
CEntity *GetViewEntity(void) {
|
|
// player eyes
|
|
if (m_iViewState == PVT_PLAYEREYES) {
|
|
return this;
|
|
// 3rd person view
|
|
} else if (m_iViewState == PVT_3RDPERSONVIEW) {
|
|
if (((CPlayerView&)*m_pen3rdPersonView).m_fDistance>2.0f) {
|
|
return m_pen3rdPersonView;
|
|
} else {
|
|
return this;
|
|
}
|
|
// camera
|
|
} else if (m_iViewState == PVT_PLAYERAUTOVIEW) {
|
|
if (((CPlayerView&)*m_penView).m_fDistance>2.0f) {
|
|
return m_penView;
|
|
} else {
|
|
return this;
|
|
}
|
|
// invalid view
|
|
} else {
|
|
ASSERTALWAYS("Unknown player view");
|
|
return NULL;
|
|
}
|
|
};
|
|
|
|
|
|
// Draw player interface on screen.
|
|
void RenderHUD( CPerspectiveProjection3D &prProjection, CDrawPort *pdp,
|
|
FLOAT3D vViewerLightDirection, COLOR colViewerLight, COLOR colViewerAmbient,
|
|
BOOL bRenderWeapon)
|
|
{
|
|
// render weapon models if needed
|
|
BOOL bRenderModels = _pShell->GetINDEX("gfx_bRenderModels");
|
|
if( hud_bShowWeapon && bRenderModels) {
|
|
// render weapons only if view is from player eyes
|
|
((CPlayerWeapons&)*m_penWeapons).RenderWeaponModel(prProjection, pdp,
|
|
vViewerLightDirection, colViewerLight, colViewerAmbient, bRenderWeapon);
|
|
}
|
|
|
|
// render crosshair
|
|
CPlacement3D plView;
|
|
if (m_iViewState == PVT_PLAYEREYES) {
|
|
// player view
|
|
plView = en_plViewpoint;
|
|
plView.RelativeToAbsolute(GetPlacement());
|
|
} else if (m_iViewState == PVT_3RDPERSONVIEW) {
|
|
// camera view
|
|
plView = ((CPlayerView&)*m_pen3rdPersonView).GetPlacement();
|
|
}
|
|
((CPlayerWeapons&)*m_penWeapons).RenderCrosshair(prProjection, pdp, plView);
|
|
|
|
// get your prediction tail
|
|
CPlayer *pen = (CPlayer*)GetPredictionTail();
|
|
// do screen blending
|
|
ULONG ulR=255, ulG=0, ulB=0; // red for wounding
|
|
ULONG ulA = pen->m_fDamageAmmount*5.0f;
|
|
|
|
// if less than few seconds elapsed since last damage
|
|
FLOAT tmSinceWounding = _pTimer->CurrentTick() - pen->m_tmWoundedTime;
|
|
if( tmSinceWounding<4.0f) {
|
|
// decrease damage ammount
|
|
if( tmSinceWounding<0.001f) { ulA = (ulA+64)/2; }
|
|
}
|
|
|
|
// add rest of blend ammount
|
|
ulA = ClampUp( ulA, (ULONG)224);
|
|
if (m_iViewState == PVT_PLAYEREYES) {
|
|
pdp->dp_ulBlendingRA += ulR*ulA;
|
|
pdp->dp_ulBlendingGA += ulG*ulA;
|
|
pdp->dp_ulBlendingBA += ulB*ulA;
|
|
pdp->dp_ulBlendingA += ulA;
|
|
}
|
|
|
|
// add world glaring
|
|
{
|
|
COLOR colGlare = GetWorldGlaring();
|
|
UBYTE ubR, ubG, ubB, ubA;
|
|
ColorToRGBA(colGlare, ubR, ubG, ubB, ubA);
|
|
if (ubA!=0) {
|
|
pdp->dp_ulBlendingRA += ULONG(ubR)*ULONG(ubA);
|
|
pdp->dp_ulBlendingGA += ULONG(ubG)*ULONG(ubA);
|
|
pdp->dp_ulBlendingBA += ULONG(ubB)*ULONG(ubA);
|
|
pdp->dp_ulBlendingA += ULONG(ubA);
|
|
}
|
|
}
|
|
|
|
// do all queued screen blendings
|
|
pdp->BlendScreen();
|
|
|
|
// render status info line (if needed)
|
|
if( hud_bShowInfo) {
|
|
// get player or its predictor
|
|
BOOL bSnooping = FALSE;
|
|
CPlayer *penHUDPlayer = this;
|
|
if (penHUDPlayer->IsPredicted()) {
|
|
penHUDPlayer = (CPlayer *)penHUDPlayer->GetPredictor();
|
|
}
|
|
|
|
// check if snooping is needed
|
|
CPlayerWeapons *pen = (CPlayerWeapons*)&*penHUDPlayer->m_penWeapons;
|
|
TIME tmDelta = _pTimer->CurrentTick() - pen->m_tmSnoopingStarted;
|
|
if( tmDelta<plr_tmSnoopingTime) {
|
|
ASSERT( pen->m_penTargeting!=NULL);
|
|
penHUDPlayer = (CPlayer*)&*pen->m_penTargeting;
|
|
bSnooping = TRUE;
|
|
}
|
|
DrawHUD( penHUDPlayer, pdp, bSnooping);
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************
|
|
* SPECIAL FUNCTIONS *
|
|
************************************************************/
|
|
// try to find start marker for deathmatch (re)spawning
|
|
CEntity *GetDeathmatchStartMarker(void)
|
|
{
|
|
// get number of markers
|
|
CTString strPlayerStart = "Player Start - ";
|
|
INDEX ctMarkers = _pNetwork->GetNumberOfEntitiesWithName(strPlayerStart);
|
|
// if none
|
|
if (ctMarkers==0) {
|
|
// fail
|
|
return NULL;
|
|
}
|
|
// if only one
|
|
if (ctMarkers==1) {
|
|
// get that one
|
|
return _pNetwork->GetEntityWithName(strPlayerStart, 0);
|
|
}
|
|
// if at least two markers found...
|
|
|
|
// create tables of markers and their distances from players
|
|
CStaticArray<MarkerDistance> amdMarkers;
|
|
amdMarkers.New(ctMarkers);
|
|
// for each marker
|
|
{for(INDEX iMarker=0; iMarker<ctMarkers; iMarker++) {
|
|
amdMarkers[iMarker].md_ppm = (CPlayerMarker*)_pNetwork->GetEntityWithName(strPlayerStart, iMarker);
|
|
if (amdMarkers[iMarker].md_ppm==NULL) {
|
|
return NULL; // (if there is any invalidity, fail completely)
|
|
}
|
|
// get min distance from any player
|
|
FLOAT fMinD = UpperLimit(0.0f);
|
|
for (INDEX iPlayer=0; iPlayer<GetMaxPlayers(); iPlayer++) {
|
|
CPlayer *ppl = (CPlayer *)&*GetPlayerEntity(iPlayer);
|
|
if (ppl==NULL) {
|
|
continue;
|
|
}
|
|
FLOAT fD =
|
|
(amdMarkers[iMarker].md_ppm->GetPlacement().pl_PositionVector-
|
|
ppl->GetPlacement().pl_PositionVector).Length();
|
|
if (fD<fMinD) {
|
|
fMinD = fD;
|
|
}
|
|
}
|
|
amdMarkers[iMarker].md_fMinD = fMinD;
|
|
}}
|
|
|
|
// now sort the list
|
|
qsort(&amdMarkers[0], ctMarkers, sizeof(amdMarkers[0]), &qsort_CompareMarkerDistance);
|
|
ASSERT(amdMarkers[0].md_fMinD>=amdMarkers[ctMarkers-1].md_fMinD);
|
|
// choose marker among one of the 50% farthest
|
|
INDEX ctFarMarkers = ctMarkers/2;
|
|
ASSERT(ctFarMarkers>0);
|
|
INDEX iStartMarker = IRnd()%ctFarMarkers;
|
|
// find first next marker that was not used lately
|
|
INDEX iMarker=iStartMarker;
|
|
FOREVER{
|
|
if (_pTimer->CurrentTick()>amdMarkers[iMarker].md_ppm->m_tmLastSpawned+1.0f) {
|
|
break;
|
|
}
|
|
iMarker = (iMarker+1)%ctMarkers;
|
|
if (iMarker==iStartMarker) {
|
|
break;
|
|
}
|
|
}
|
|
// return that
|
|
return amdMarkers[iMarker].md_ppm;
|
|
}
|
|
/************************************************************
|
|
* INITIALIZE PLAYER *
|
|
************************************************************/
|
|
void InitializePlayer() {
|
|
// set viewpoint position inside the entity
|
|
en_plViewpoint.pl_OrientationAngle = ANGLE3D(0,0,0);
|
|
en_plViewpoint.pl_PositionVector = FLOAT3D(0.0f, plr_fViewHeightStand, 0.0f);
|
|
en_plLastViewpoint = en_plViewpoint;
|
|
|
|
// clear properties
|
|
m_ulFlags &= PLF_INITIALIZED|PLF_LEVELSTARTED|PLF_RESPAWNINPLACE; // must not clear initialized flag
|
|
m_fFallTime = 0.0f;
|
|
m_pstState = PST_STAND;
|
|
m_fDamageAmmount = 0.0f;
|
|
m_tmWoundedTime = 0.0f;
|
|
|
|
// initialize animator
|
|
((CPlayerAnimator&)*m_penAnimator).Initialize();
|
|
// restart weapons if needed
|
|
GetPlayerWeapons()->SendEvent(EStart());
|
|
|
|
// set flags
|
|
SetPhysicsFlags(EPF_MODEL_WALKING|EPF_HASLUNGS);
|
|
SetCollisionFlags(ECF_MODEL|((ECBI_PLAYER)<<ECB_IS));
|
|
SetFlags(GetFlags()|ENF_ALIVE);
|
|
// animation
|
|
StartModelAnim(PLAYER_ANIM_STAND, AOF_LOOPING);
|
|
|
|
TeleportPlayer(WLT_FIXED);
|
|
};
|
|
|
|
FLOAT3D GetTeleportingOffset(void)
|
|
{
|
|
// find player index
|
|
INDEX iPlayer = GetMyPlayerIndex();
|
|
|
|
// create offset from marker
|
|
const FLOAT fOffsetY = 0.1f; // how much to offset up (as precaution not to spawn in floor)
|
|
FLOAT3D vOffsetRel = FLOAT3D(0,fOffsetY,0);
|
|
if (GetSP()->sp_bCooperative && !GetSP()->sp_bSinglePlayer) {
|
|
INDEX iRow = iPlayer/4;
|
|
INDEX iCol = iPlayer%4;
|
|
vOffsetRel = FLOAT3D(-3.0f+iCol*2.0f, fOffsetY, -3.0f+iRow*2.0f);
|
|
}
|
|
|
|
return vOffsetRel;
|
|
}
|
|
|
|
void TeleportPlayer(enum WorldLinkType EwltType)
|
|
{
|
|
INDEX iLevel = -1;
|
|
CTString strLevelName = GetWorld()->wo_fnmFileName.FileName();
|
|
strLevelName.ScanF("%02d_", &iLevel);
|
|
if (iLevel>0) {
|
|
((CSessionProperties*)GetSP())->sp_ulLevelsMask|=1<<(iLevel-1);
|
|
}
|
|
|
|
// find player index
|
|
INDEX iPlayer = GetMyPlayerIndex();
|
|
// player placement
|
|
CPlacement3D plSet = GetPlacement();
|
|
// teleport in dummy space to avoid auto teleport frag
|
|
Teleport(CPlacement3D(FLOAT3D(32000.0f+100.0f*iPlayer, 32000.0f, 0), ANGLE3D(0, 0, 0)));
|
|
// force yourself to standing state
|
|
ForceCollisionBoxIndexChange(PLAYER_COLLISION_BOX_STAND);
|
|
en_plViewpoint.pl_PositionVector(2) = plr_fViewHeightStand;
|
|
((CPlayerAnimator&)*m_penAnimator).m_bDisableAnimating = FALSE;
|
|
((CPlayerAnimator&)*m_penAnimator).Stand();
|
|
m_pstState = PST_STAND;
|
|
|
|
// create offset from marker
|
|
FLOAT3D vOffsetRel = GetTeleportingOffset();
|
|
|
|
// no player start initially
|
|
BOOL bSetHealth = FALSE; // for getting health from marker
|
|
BOOL bAdjustHealth = FALSE; // for getting adjusting health to 50-100 interval
|
|
CEntity *pen = NULL;
|
|
if (GetSP()->sp_bCooperative) {
|
|
if (cht_iGoToMarker>=0) {
|
|
// try to find fast go marker
|
|
CTString strPlayerStart;
|
|
strPlayerStart.PrintF("Player Start - %d", (INDEX)cht_iGoToMarker);
|
|
pen = _pNetwork->GetEntityWithName(strPlayerStart, 0);
|
|
pen->SendEvent(ETrigger());
|
|
cht_iGoToMarker = -1;
|
|
bSetHealth = TRUE;
|
|
bAdjustHealth = FALSE;
|
|
// if there is coop respawn marker
|
|
} else if (m_penMainMusicHolder!=NULL && !(m_ulFlags&PLF_CHANGINGLEVEL)) {
|
|
CMusicHolder *pmh = (CMusicHolder *)&*m_penMainMusicHolder;
|
|
if (pmh->m_penRespawnMarker!=NULL) {
|
|
// get it
|
|
pen = pmh->m_penRespawnMarker;
|
|
bSetHealth = TRUE;
|
|
bAdjustHealth = FALSE;
|
|
}
|
|
}
|
|
|
|
// if quick start is enabled (in wed)
|
|
if (pen==NULL && GetSP()->sp_bQuickTest && m_strGroup=="") {
|
|
// try to find quick start marker
|
|
CTString strPlayerStart;
|
|
strPlayerStart.PrintF("Player Quick Start");
|
|
pen = _pNetwork->GetEntityWithName(strPlayerStart, 0);
|
|
bSetHealth = TRUE;
|
|
bAdjustHealth = FALSE;
|
|
}
|
|
// if no start position yet
|
|
if (pen==NULL) {
|
|
// try to find normal start marker
|
|
CTString strPlayerStart;
|
|
strPlayerStart.PrintF("Player Start - %s", (const char*)m_strGroup);
|
|
pen = _pNetwork->GetEntityWithName(strPlayerStart, 0);
|
|
if (m_strGroup=="") {
|
|
bSetHealth = TRUE;
|
|
bAdjustHealth = FALSE;
|
|
} else {
|
|
if (EwltType==WLT_FIXED) {
|
|
bSetHealth = FALSE;
|
|
bAdjustHealth = TRUE;
|
|
} else {
|
|
bSetHealth = FALSE;
|
|
bAdjustHealth = FALSE;
|
|
}
|
|
}
|
|
}
|
|
// if no start position yet
|
|
if (pen==NULL) {
|
|
// try to find normal start marker without group anyway
|
|
CTString strPlayerStart;
|
|
strPlayerStart.PrintF("Player Start - ");
|
|
pen = _pNetwork->GetEntityWithName(strPlayerStart, 0);
|
|
bSetHealth = TRUE;
|
|
bAdjustHealth = FALSE;
|
|
}
|
|
} else {
|
|
bSetHealth = TRUE;
|
|
bAdjustHealth = FALSE;
|
|
// try to find start marker by random
|
|
pen = GetDeathmatchStartMarker();
|
|
if (pen!=NULL) {
|
|
((CPlayerMarker&)*pen).m_tmLastSpawned = _pTimer->CurrentTick();
|
|
}
|
|
}
|
|
|
|
// if respawning in place
|
|
if (m_ulFlags&PLF_RESPAWNINPLACE) {
|
|
m_ulFlags &= ~PLF_RESPAWNINPLACE;
|
|
// set default params
|
|
SetHealth(TopHealth());
|
|
m_iMana = GetSP()->sp_iInitialMana;
|
|
m_fArmor = 0.0f;
|
|
// teleport where you were when you were killed
|
|
Teleport(CPlacement3D(m_vDied, m_aDied));
|
|
|
|
// if start marker is found
|
|
} else if (pen!=NULL) {
|
|
// if there is no respawn marker yet
|
|
if (m_penMainMusicHolder!=NULL) {
|
|
CMusicHolder *pmh = (CMusicHolder *)&*m_penMainMusicHolder;
|
|
if (pmh->m_penRespawnMarker==NULL) {
|
|
// set it
|
|
pmh->m_penRespawnMarker = pen;
|
|
}
|
|
}
|
|
|
|
CPlayerMarker &CpmStart = (CPlayerMarker&)*pen;
|
|
// set player characteristics
|
|
if (bSetHealth) {
|
|
SetHealth(CpmStart.m_fHealth/100.0f*TopHealth());
|
|
m_iMana = GetSP()->sp_iInitialMana;
|
|
m_fArmor = CpmStart.m_fShield;
|
|
} else if (bAdjustHealth) {
|
|
FLOAT fHealth = GetHealth();
|
|
FLOAT fTopHealth = TopHealth();
|
|
if( fHealth < fTopHealth) {
|
|
SetHealth(ClampUp(fHealth+fTopHealth/2.0f, fTopHealth));
|
|
}
|
|
}
|
|
|
|
// if should start in computer
|
|
if (CpmStart.m_bStartInComputer && GetSP()->sp_bSinglePlayer) {
|
|
// mark that
|
|
if (_pNetwork->IsPlayerLocal(this)) {
|
|
cmp_ppenPlayer = this;
|
|
}
|
|
cmp_bInitialStart = TRUE;
|
|
}
|
|
|
|
// start with first message linked to the marker
|
|
CMessageHolder *penMessage = (CMessageHolder *)&*CpmStart.m_penMessage;
|
|
// while there are some messages to add
|
|
while (penMessage!=NULL && IsOfClass(penMessage, "MessageHolder")) {
|
|
const CTFileName &fnmMessage = penMessage->m_fnmMessage;
|
|
// if player doesn't have that message in database
|
|
if (!HasMessage(fnmMessage)) {
|
|
// add the message
|
|
ReceiveComputerMessage(fnmMessage, 0);
|
|
}
|
|
// go to next message holder in list
|
|
penMessage = (CMessageHolder *)&*penMessage->m_penNext;
|
|
}
|
|
|
|
// set weapons
|
|
if (!GetSP()->sp_bCooperative) {
|
|
((CPlayerWeapons&)*m_penWeapons).InitializeWeapons(CpmStart.m_iGiveWeapons, 0, 0,
|
|
CpmStart.m_fMaxAmmoRatio);
|
|
} else {
|
|
((CPlayerWeapons&)*m_penWeapons).InitializeWeapons(CpmStart.m_iGiveWeapons, CpmStart.m_iTakeWeapons,
|
|
GetSP()->sp_bInfiniteAmmo?0:CpmStart.m_iTakeAmmo, CpmStart.m_fMaxAmmoRatio);
|
|
}
|
|
// start position relative to link
|
|
if (EwltType == WLT_RELATIVE) {
|
|
plSet.AbsoluteToRelative(_SwcWorldChange.plLink); // relative to link position
|
|
plSet.RelativeToAbsolute(CpmStart.GetPlacement()); // absolute to start marker position
|
|
Teleport(plSet);
|
|
// fixed start position
|
|
} else if (EwltType == WLT_FIXED) {
|
|
CPlacement3D plNew = CpmStart.GetPlacement();
|
|
vOffsetRel*=CpmStart.en_mRotation;
|
|
plNew.pl_PositionVector += vOffsetRel;
|
|
Teleport(plNew);
|
|
// error -> teleport to zero
|
|
} else {
|
|
ASSERTALWAYS("Unknown world link type");
|
|
Teleport(CPlacement3D(FLOAT3D(0, 0, 0)+vOffsetRel, ANGLE3D(0, 0, 0)));
|
|
}
|
|
// if there is a start trigger target
|
|
if(CpmStart.m_penTarget!=NULL) {
|
|
SendToTarget(CpmStart.m_penTarget, EET_TRIGGER, this);
|
|
}
|
|
|
|
// default start position
|
|
} else {
|
|
// set player characteristics
|
|
SetHealth(TopHealth());
|
|
m_iMana = GetSP()->sp_iInitialMana;
|
|
m_fArmor = 0.0f;
|
|
// set weapons
|
|
((CPlayerWeapons&)*m_penWeapons).InitializeWeapons(0, 0, 0, 0);
|
|
// start position
|
|
Teleport(CPlacement3D(FLOAT3D(0, 0, 0)+vOffsetRel, ANGLE3D(0, 0, 0)));
|
|
}
|
|
// send teleport event to all entities in range
|
|
SendEventInRange(ETeleport(), FLOATaabbox3D(GetPlacement().pl_PositionVector, 200.0f));
|
|
// stop moving
|
|
ForceFullStop();
|
|
|
|
// remember maximum health
|
|
m_fMaxHealth = TopHealth();
|
|
|
|
// if in singleplayer mode
|
|
if (GetSP()->sp_bSinglePlayer && GetSP()->sp_gmGameMode!=CSessionProperties::GM_FLYOVER) {
|
|
// save quick savegame
|
|
_pShell->Execute("gam_bQuickSave=1;");
|
|
}
|
|
// remember level start time
|
|
if (!(m_ulFlags&PLF_LEVELSTARTED)) {
|
|
m_ulFlags |= PLF_LEVELSTARTED;
|
|
m_tmLevelStarted = _pNetwork->GetGameTime();
|
|
}
|
|
// reset model appearance
|
|
CTString strDummy;
|
|
SetPlayerAppearance(GetModelObject(), NULL, strDummy, /*bPreview=*/FALSE);
|
|
ValidateCharacter();
|
|
SetPlayerAppearance(&m_moRender, &en_pcCharacter, strDummy, /*bPreview=*/FALSE);
|
|
GetPlayerAnimator()->SetWeapon();
|
|
m_ulFlags |= PLF_SYNCWEAPON;
|
|
|
|
// spawn teleport effect
|
|
SpawnTeleport();
|
|
// return from editor model (if was fragged into pieces)
|
|
SwitchToModel();
|
|
m_tmSpawned = _pTimer->CurrentTick();
|
|
|
|
en_tmLastBreathed = _pTimer->CurrentTick()+0.1f; // do not take breath when spawned in air
|
|
};
|
|
|
|
// note: set estimated time in advance
|
|
void RecordEndOfLevelData(void)
|
|
{
|
|
// must not be called multiple times
|
|
ASSERT(!m_bEndOfLevel);
|
|
// clear analyses message
|
|
m_tmAnalyseEnd = 0;
|
|
m_bPendingMessage = FALSE;
|
|
m_tmMessagePlay = 0;
|
|
// mark end of level
|
|
m_iMayRespawn = 0;
|
|
m_bEndOfLevel = TRUE;
|
|
// remember end time
|
|
time_t t;
|
|
time(&t);
|
|
m_iEndTime = (INDEX)t;
|
|
// add time score
|
|
TIME tmLevelTime = _pTimer->CurrentTick()-m_tmLevelStarted;
|
|
m_psLevelStats.ps_tmTime = tmLevelTime;
|
|
m_psGameStats.ps_tmTime += tmLevelTime;
|
|
FLOAT fTimeDelta = ClampDn(floorf(m_tmEstTime)-floorf(tmLevelTime), 0.0f);
|
|
m_iTimeScore = floor(fTimeDelta*100.0f);
|
|
m_psLevelStats.ps_iScore+=m_iTimeScore;
|
|
m_psGameStats.ps_iScore+=m_iTimeScore;
|
|
|
|
// record stats for this level and add to global table
|
|
CTString strStats;
|
|
strStats.PrintF(TRANS("%s\n Time: %s\n Score: %9d\n Kills: %03d/%03d\n Secrets: %02d/%02d\n"),
|
|
(const char*)TranslateConst(en_pwoWorld->GetName(), 0), (const char*)TimeToString(tmLevelTime),
|
|
m_psLevelStats.ps_iScore,
|
|
m_psLevelStats.ps_iKills, m_psLevelTotal.ps_iKills,
|
|
m_psLevelStats.ps_iSecrets, m_psLevelTotal.ps_iSecrets);
|
|
m_strLevelStats += strStats;
|
|
}
|
|
|
|
// spawn teleport effect
|
|
void SpawnTeleport(void)
|
|
{
|
|
// if in singleplayer
|
|
if (GetSP()->sp_bSinglePlayer) {
|
|
// no spawn effects
|
|
return;
|
|
}
|
|
ESpawnEffect ese;
|
|
ese.colMuliplier = C_WHITE|CT_OPAQUE;
|
|
ese.betType = BET_TELEPORT;
|
|
ese.vNormal = FLOAT3D(0,1,0);
|
|
FLOATaabbox3D box;
|
|
GetBoundingBox(box);
|
|
FLOAT fEntitySize = box.Size().MaxNorm()*2;
|
|
ese.vStretch = FLOAT3D(fEntitySize, fEntitySize, fEntitySize);
|
|
CEntityPointer penEffect = CreateEntity(GetPlacement(), CLASS_BASIC_EFFECT);
|
|
penEffect->Initialize(ese);
|
|
}
|
|
|
|
|
|
|
|
// render particles
|
|
void RenderParticles(void)
|
|
{
|
|
// render empty shells
|
|
Particles_EmptyShells( this, m_asldData);
|
|
|
|
if (Particle_GetViewer()==this) {
|
|
Particles_ViewerLocal(this);
|
|
}
|
|
|
|
// spirit particles
|
|
if( m_tmSpiritStart != 0.0f)
|
|
{
|
|
Particles_Appearing(this, m_tmSpiritStart);
|
|
}
|
|
}
|
|
|
|
void TeleportToAutoMarker(CPlayerActionMarker *ppam)
|
|
{
|
|
// if we are in coop
|
|
if (GetSP()->sp_bCooperative && !GetSP()->sp_bSinglePlayer) {
|
|
// for each player
|
|
for(INDEX iPlayer=0; iPlayer<GetMaxPlayers(); iPlayer++) {
|
|
CPlayer *ppl = (CPlayer*)GetPlayerEntity(iPlayer);
|
|
if (ppl!=NULL) {
|
|
// put it at marker
|
|
CPlacement3D pl = ppam->GetPlacement();
|
|
FLOAT3D vOffsetRel = ppl->GetTeleportingOffset();
|
|
pl.pl_PositionVector += vOffsetRel*ppam->en_mRotation;
|
|
ppl->Teleport(pl, FALSE);
|
|
// remember new respawn place
|
|
ppl->m_vDied = pl.pl_PositionVector;
|
|
ppl->m_aDied = pl.pl_OrientationAngle;
|
|
}
|
|
}
|
|
|
|
// otherwise
|
|
} else {
|
|
// put yourself at marker
|
|
CPlacement3D pl = ppam->GetPlacement();
|
|
FLOAT3D vOffsetRel = GetTeleportingOffset();
|
|
pl.pl_PositionVector += vOffsetRel*ppam->en_mRotation;
|
|
Teleport(pl, FALSE);
|
|
}
|
|
}
|
|
|
|
// check whether this time we respawn in place or on marker
|
|
void CheckDeathForRespawnInPlace(EDeath eDeath)
|
|
{
|
|
// if respawning in place is not allowed
|
|
if (!GetSP()->sp_bRespawnInPlace) {
|
|
// skip further checks
|
|
return;
|
|
}
|
|
// if killed by a player or enemy
|
|
CEntity *penKiller = eDeath.eLastDamage.penInflictor;
|
|
if (IsOfClass(penKiller, "Player") || IsDerivedFromClass(penKiller, "Enemy Base")) {
|
|
// mark for respawning in place
|
|
m_ulFlags |= PLF_RESPAWNINPLACE;
|
|
m_vDied = GetPlacement().pl_PositionVector;
|
|
m_aDied = GetPlacement().pl_OrientationAngle;
|
|
}
|
|
}
|
|
|
|
procedures:
|
|
/************************************************************
|
|
* WOUNDED *
|
|
************************************************************/
|
|
Wounded(EDamage eDamage) {
|
|
return;
|
|
};
|
|
|
|
|
|
/************************************************************
|
|
* WORLD CHANGE *
|
|
************************************************************/
|
|
WorldChange() {
|
|
// if in single player
|
|
if (GetSP()->sp_bSinglePlayer) {
|
|
// mark world as visited
|
|
CTString strDummy("1");
|
|
SaveStringVar(GetWorld()->wo_fnmFileName.NoExt()+".vis", strDummy);
|
|
}
|
|
// find music holder on new world
|
|
FindMusicHolder();
|
|
// store group name
|
|
m_strGroup = _SwcWorldChange.strGroup;
|
|
TeleportPlayer((WorldLinkType)_SwcWorldChange.iType);
|
|
// setup light source
|
|
SetupLightSource();
|
|
|
|
// update per-level stats
|
|
UpdateLevelStats();
|
|
m_ulFlags |= PLF_INITIALIZED;
|
|
m_ulFlags &= ~PLF_CHANGINGLEVEL;
|
|
return;
|
|
};
|
|
|
|
WorldChangeDead()
|
|
{
|
|
// forbid respawning in-place when changing levels while dead
|
|
m_ulFlags &= ~PLF_RESPAWNINPLACE;
|
|
// if in single player
|
|
if (GetSP()->sp_bSinglePlayer) {
|
|
// mark world as visited
|
|
CTString strDummy("1");
|
|
SaveStringVar(GetWorld()->wo_fnmFileName.NoExt()+".vis", strDummy);
|
|
}
|
|
// find music holder on new world
|
|
FindMusicHolder();
|
|
// store group name
|
|
|
|
autocall Rebirth() EReturn;
|
|
|
|
// setup light source
|
|
SetupLightSource();
|
|
|
|
// update per-level stats
|
|
UpdateLevelStats();
|
|
m_ulFlags |= PLF_INITIALIZED;
|
|
m_ulFlags &= ~PLF_CHANGINGLEVEL;
|
|
return;
|
|
}
|
|
|
|
/************************************************************
|
|
* D E A T H *
|
|
************************************************************/
|
|
Death(EDeath eDeath) {
|
|
// stop firing when dead
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(EReleaseWeapon());
|
|
// stop all looping ifeel effects
|
|
if(_pNetwork->IsPlayerLocal(this))
|
|
{
|
|
IFeel_StopEffect("Minigun_rotate");
|
|
}
|
|
|
|
// if in single player, or if this is a predictor entity
|
|
if (GetSP()->sp_bSinglePlayer || IsPredictor()) {
|
|
// do not print anything
|
|
NOTHING;
|
|
// if in cooperative, but not single player
|
|
} else if (GetSP()->sp_bCooperative) {
|
|
// just print death message, no score updating
|
|
PrintPlayerDeathMessage(this, eDeath);
|
|
// check whether this time we respawn in place or on marker
|
|
CheckDeathForRespawnInPlace(eDeath);
|
|
// increase number of deaths
|
|
m_psLevelStats.ps_iDeaths += 1;
|
|
m_psGameStats.ps_iDeaths += 1;
|
|
// if not in cooperative, and not single player
|
|
} else {
|
|
// print death message
|
|
PrintPlayerDeathMessage(this, eDeath);
|
|
// get the killer pointer
|
|
CEntity *penKiller = eDeath.eLastDamage.penInflictor;
|
|
// initially, not killed by a player
|
|
CPlayer *pplKillerPlayer = NULL;
|
|
|
|
// if killed by some entity
|
|
if (penKiller!=NULL) {
|
|
// if killed by player
|
|
if (IsOfClass(penKiller, "Player")) {
|
|
// if someone other then you
|
|
if (penKiller!=this) {
|
|
pplKillerPlayer = (CPlayer*)penKiller;
|
|
EReceiveScore eScore;
|
|
eScore.iPoints = m_iMana;
|
|
eDeath.eLastDamage.penInflictor->SendEvent(eScore);
|
|
eDeath.eLastDamage.penInflictor->SendEvent(EKilledEnemy());
|
|
// if it was yourself
|
|
} else {
|
|
m_psLevelStats.ps_iScore -= m_iMana;
|
|
m_psGameStats.ps_iScore -= m_iMana;
|
|
m_psLevelStats.ps_iKills -= 1;
|
|
m_psGameStats.ps_iKills -= 1;
|
|
}
|
|
// if killed by non-player
|
|
} else {
|
|
m_psLevelStats.ps_iScore -= m_iMana;
|
|
m_psGameStats.ps_iScore -= m_iMana;
|
|
m_psLevelStats.ps_iKills -= 1;
|
|
m_psGameStats.ps_iKills -= 1;
|
|
}
|
|
// if killed by NULL (shouldn't happen, but anyway)
|
|
} else {
|
|
m_psLevelStats.ps_iScore -= m_iMana;
|
|
m_psGameStats.ps_iScore -= m_iMana;
|
|
m_psLevelStats.ps_iKills -= 1;
|
|
m_psGameStats.ps_iKills -= 1;
|
|
}
|
|
|
|
// if playing scorematch
|
|
if (!GetSP()->sp_bUseFrags) {
|
|
// if killed by a player
|
|
if (pplKillerPlayer!=NULL) {
|
|
// print how much that player gained
|
|
CPrintF(TRANS(" %s: +%d points\n"), (const char*)pplKillerPlayer->GetPlayerName(), m_iMana);
|
|
// if it was a suicide, or an accident
|
|
} else {
|
|
// print how much you lost
|
|
CPrintF(TRANS(" %s: -%d points\n"), (const char*)GetPlayerName(), m_iMana);
|
|
}
|
|
}
|
|
|
|
// increase number of deaths
|
|
m_psLevelStats.ps_iDeaths += 1;
|
|
m_psGameStats.ps_iDeaths += 1;
|
|
}
|
|
|
|
// store last view
|
|
m_iLastViewState = m_iViewState;
|
|
|
|
// mark player as death
|
|
SetFlags(GetFlags()&~ENF_ALIVE);
|
|
// stop player
|
|
SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f));
|
|
SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 0.0f));
|
|
|
|
// remove weapon from hand
|
|
((CPlayerAnimator&)*m_penAnimator).RemoveWeapon();
|
|
// kill weapon animations
|
|
GetPlayerWeapons()->SendEvent(EStop());
|
|
|
|
// if in deathmatch
|
|
if (!GetSP()->sp_bCooperative) {
|
|
// drop current weapon as item so others can pick it
|
|
GetPlayerWeapons()->DropWeapon();
|
|
}
|
|
|
|
|
|
// play death
|
|
INDEX iAnim1;
|
|
INDEX iAnim2;
|
|
if (m_pstState == PST_SWIM || m_pstState == PST_DIVE) {
|
|
iAnim1 = PLAYER_ANIM_DEATH_UNDERWATER;
|
|
iAnim2 = BODY_ANIM_DEATH_UNDERWATER;
|
|
} else if (eDeath.eLastDamage.dmtType==DMT_SPIKESTAB) {
|
|
iAnim1 = PLAYER_ANIM_DEATH_SPIKES;
|
|
iAnim2 = BODY_ANIM_DEATH_SPIKES;
|
|
} else if (eDeath.eLastDamage.dmtType==DMT_ABYSS) {
|
|
iAnim1 = PLAYER_ANIM_ABYSSFALL;
|
|
iAnim2 = BODY_ANIM_ABYSSFALL;
|
|
} else {
|
|
FLOAT3D vFront;
|
|
GetHeadingDirection(0, vFront);
|
|
FLOAT fDamageDir = m_vDamage%vFront;
|
|
if (fDamageDir<0) {
|
|
if (Abs(fDamageDir)<10.0f) {
|
|
iAnim1 = PLAYER_ANIM_DEATH_EASYFALLBACK;
|
|
iAnim2 = BODY_ANIM_DEATH_EASYFALLBACK;
|
|
} else {
|
|
iAnim1 = PLAYER_ANIM_DEATH_BACK;
|
|
iAnim2 = BODY_ANIM_DEATH_BACK;
|
|
}
|
|
} else {
|
|
if (Abs(fDamageDir)<10.0f) {
|
|
iAnim1 = PLAYER_ANIM_DEATH_EASYFALLFORWARD;
|
|
iAnim2 = BODY_ANIM_DEATH_EASYFALLFORWARD;
|
|
} else {
|
|
iAnim1 = PLAYER_ANIM_DEATH_FORWARD;
|
|
iAnim2 = BODY_ANIM_DEATH_FORWARD;
|
|
}
|
|
}
|
|
}
|
|
en_plViewpoint.pl_OrientationAngle = ANGLE3D(0,0,0);
|
|
StartModelAnim(iAnim1, 0);
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(iAnim2, 0);
|
|
|
|
// set physic flags
|
|
SetPhysicsFlags(EPF_MODEL_CORPSE);
|
|
SetCollisionFlags(ECF_CORPSE);
|
|
|
|
// set density to float out of water
|
|
en_fDensity = 400.0f;
|
|
|
|
// play sound
|
|
if (m_pstState==PST_DIVE) {
|
|
SetDefaultMouthPitch();
|
|
PlaySound(m_soMouth, SOUND_DEATHWATER, SOF_3D);
|
|
if(_pNetwork->IsPlayerLocal(this)) {IFeel_PlayEffect("DeathWater");}
|
|
} else {
|
|
SetDefaultMouthPitch();
|
|
PlaySound(m_soMouth, SOUND_DEATH, SOF_3D);
|
|
if(_pNetwork->IsPlayerLocal(this)) {IFeel_PlayEffect("Death");}
|
|
}
|
|
|
|
// initialize death camera view
|
|
ASSERT(m_penView == NULL);
|
|
if (m_penView == NULL) {
|
|
m_penView = CreateEntity(GetPlacement(), CLASS_PLAYER_VIEW);
|
|
EViewInit eInit;
|
|
eInit.penOwner = this;
|
|
eInit.penCamera = NULL;
|
|
eInit.vtView = VT_PLAYERDEATH;
|
|
eInit.bDeathFixed = eDeath.eLastDamage.dmtType==DMT_ABYSS;
|
|
m_penView->Initialize(eInit);
|
|
}
|
|
|
|
if (ShouldBlowUp()) {
|
|
BlowUp();
|
|
} else {
|
|
// leave a stain beneath
|
|
LeaveStain(TRUE);
|
|
}
|
|
|
|
m_iMayRespawn = 0;
|
|
// wait for anim of death
|
|
wait (1.2f) {
|
|
on (EBegin) : {
|
|
// set new view status
|
|
m_iViewState = PVT_PLAYERAUTOVIEW;
|
|
resume;
|
|
}
|
|
// when anim is finished
|
|
on (ETimer) : {
|
|
// allow respawning
|
|
m_iMayRespawn = 1;
|
|
resume;
|
|
}
|
|
// when damaged
|
|
on (EDamage eDamage) : {
|
|
if (eDamage.dmtType==DMT_ABYSS) {
|
|
if (m_penView!=NULL) {
|
|
((CPlayerView*)&*m_penView)->m_bFixed = TRUE;
|
|
}
|
|
}
|
|
// if should blow up now (and not already blown up)
|
|
if (ShouldBlowUp()) {
|
|
// do it
|
|
BlowUp();
|
|
}
|
|
resume;
|
|
}
|
|
on (EDeath) : { resume; }
|
|
// if player pressed fire
|
|
on (EEnd) : {
|
|
// NOTE: predictors must never respawn since player markers for respawning are not predicted
|
|
// if this is not predictor
|
|
if (!IsPredictor()) {
|
|
// stop waiting
|
|
stop;
|
|
}
|
|
}
|
|
// if autoaction is received
|
|
on (EAutoAction eAutoAction) : {
|
|
// if we are in coop
|
|
if (GetSP()->sp_bCooperative && !GetSP()->sp_bSinglePlayer) {
|
|
// if the marker is teleport marker
|
|
if (eAutoAction.penFirstMarker!=NULL &&
|
|
((CPlayerActionMarker*)&*eAutoAction.penFirstMarker)->m_paaAction == PAA_TELEPORT) {
|
|
// teleport there
|
|
TeleportToAutoMarker((CPlayerActionMarker*)&*eAutoAction.penFirstMarker);
|
|
}
|
|
}
|
|
// ignore the actions
|
|
resume;
|
|
}
|
|
on (EDisconnected) : { pass; }
|
|
on (EReceiveScore) : { pass; }
|
|
on (EPreLevelChange) : { pass; }
|
|
on (EPostLevelChange) : { pass; }
|
|
otherwise() : { resume; }
|
|
}
|
|
|
|
return ERebirth();
|
|
};
|
|
|
|
TheEnd() {
|
|
// if not playing demo
|
|
if (!_pNetwork->IsPlayingDemo()) {
|
|
// record high score in single player only
|
|
if (GetSP()->sp_bSinglePlayer) {
|
|
_pShell->Execute("gam_iRecordHighScore=0;");
|
|
}
|
|
}
|
|
// if current difficulty is serious
|
|
if (GetSP()->sp_gdGameDifficulty==CSessionProperties::GD_EXTREME) {
|
|
// activate the mental mode
|
|
_pShell->Execute("sam_bMentalActivated=1;");
|
|
}
|
|
|
|
// stop firing when end
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(EReleaseWeapon());
|
|
|
|
// mark player as dead
|
|
SetFlags(GetFlags()&~ENF_ALIVE);
|
|
// stop player
|
|
SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f));
|
|
SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 0.0f));
|
|
|
|
// look straight
|
|
StartModelAnim(PLAYER_ANIM_STAND, 0);
|
|
((CPlayerAnimator&)*m_penAnimator).BodyAnimationTemplate(
|
|
BODY_ANIM_NORMALWALK, BODY_ANIM_COLT_STAND, BODY_ANIM_SHOTGUN_STAND, BODY_ANIM_MINIGUN_STAND,
|
|
AOF_LOOPING|AOF_NORESTART);
|
|
|
|
en_plViewpoint.pl_OrientationAngle = ANGLE3D(0,0,0);
|
|
|
|
// call computer
|
|
m_bEndOfGame = TRUE;
|
|
SetGameEnd();
|
|
|
|
wait () {
|
|
on (EBegin) : { resume; }
|
|
on (EReceiveScore) : { pass; }
|
|
on (ECenterMessage) : { pass; }
|
|
otherwise() : { resume; }
|
|
}
|
|
};
|
|
|
|
/************************************************************
|
|
* R E B I R T H *
|
|
************************************************************/
|
|
FirstInit() {
|
|
// restore last view
|
|
m_iViewState = m_iLastViewState;
|
|
|
|
// stop and kill camera
|
|
if (m_penView != NULL) {
|
|
((CPlayerView&)*m_penView).SendEvent(EEnd());
|
|
m_penView = NULL;
|
|
}
|
|
|
|
FindMusicHolder();
|
|
|
|
// update per-level stats
|
|
UpdateLevelStats();
|
|
|
|
// initialize player (from PlayerMarker)
|
|
InitializePlayer();
|
|
|
|
// add statistics message
|
|
ReceiveComputerMessage(CTFILENAME("Data\\Messages\\Statistics\\Statistics.txt"), CMF_READ);
|
|
|
|
if (GetSettings()->ps_ulFlags&PSF_PREFER3RDPERSON) {
|
|
ChangePlayerView();
|
|
}
|
|
|
|
return;
|
|
};
|
|
|
|
Rebirth() {
|
|
// restore last view
|
|
m_iViewState = m_iLastViewState;
|
|
// clear ammunition
|
|
if (!(m_ulFlags&PLF_RESPAWNINPLACE)) {
|
|
GetPlayerWeapons()->ClearWeapons();
|
|
}
|
|
|
|
// stop and kill camera
|
|
if (m_penView != NULL) {
|
|
((CPlayerView&)*m_penView).SendEvent(EEnd());
|
|
m_penView = NULL;
|
|
}
|
|
|
|
FindMusicHolder();
|
|
|
|
// initialize player (from PlayerMarker)
|
|
InitializePlayer();
|
|
|
|
return EReturn();
|
|
};
|
|
|
|
|
|
// auto action - go to current marker
|
|
AutoGoToMarker(EVoid)
|
|
{
|
|
ULONG ulFlags = AOF_LOOPING|AOF_NORESTART;
|
|
|
|
INDEX iAnim = GetModelObject()->GetAnim();
|
|
if( iAnim!=PLAYER_ANIM_STAND)
|
|
{
|
|
ulFlags |= AOF_SMOOTHCHANGE;
|
|
}
|
|
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.BodyWalkAnimation();
|
|
if (m_fAutoSpeed>plr_fSpeedForward/2) {
|
|
StartModelAnim(PLAYER_ANIM_RUN, ulFlags);
|
|
} else {
|
|
StartModelAnim(PLAYER_ANIM_NORMALWALK, ulFlags);
|
|
}
|
|
|
|
// while not at marker
|
|
while (
|
|
(m_penActionMarker->GetPlacement().pl_PositionVector-
|
|
GetPlacement().pl_PositionVector).Length()>1.0f) {
|
|
// wait a bit
|
|
autowait(_pTimer->TickQuantum);
|
|
}
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
// auto action - go to current marker and stop there
|
|
AutoGoToMarkerAndStop(EVoid)
|
|
{
|
|
ULONG ulFlags = AOF_LOOPING|AOF_NORESTART;
|
|
|
|
INDEX iAnim = GetModelObject()->GetAnim();
|
|
if( iAnim!=PLAYER_ANIM_STAND)
|
|
{
|
|
ulFlags |= AOF_SMOOTHCHANGE;
|
|
}
|
|
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.BodyWalkAnimation();
|
|
if (m_fAutoSpeed>plr_fSpeedForward/2) {
|
|
StartModelAnim(PLAYER_ANIM_RUN, ulFlags);
|
|
} else {
|
|
StartModelAnim(PLAYER_ANIM_NORMALWALK, ulFlags);
|
|
}
|
|
|
|
// while not at marker
|
|
while (
|
|
(m_penActionMarker->GetPlacement().pl_PositionVector-
|
|
GetPlacement().pl_PositionVector).Length()>m_fAutoSpeed*_pTimer->TickQuantum*2.00f) {
|
|
// wait a bit
|
|
autowait(_pTimer->TickQuantum);
|
|
}
|
|
// disable auto speed
|
|
m_fAutoSpeed = 0.0f;
|
|
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.BodyStillAnimation();
|
|
StartModelAnim(PLAYER_ANIM_STAND, AOF_LOOPING|AOF_NORESTART);
|
|
|
|
// stop moving
|
|
ForceFullStop();
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
// auto action - use an item
|
|
AutoUseItem(EVoid)
|
|
{
|
|
|
|
// start pulling the item
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.BodyPullItemAnimation();
|
|
//StartModelAnim(PLAYER_ANIM_STATUE_PULL, 0);
|
|
|
|
autowait(0.2f);
|
|
|
|
// item appears
|
|
CPlayerActionMarker *ppam = GetActionMarker();
|
|
if (IsOfClass(ppam->m_penItem, "KeyItem")) {
|
|
CModelObject &moItem = ppam->m_penItem->GetModelObject()->GetAttachmentModel(0)->amo_moModelObject;
|
|
GetPlayerAnimator()->SetItem(&moItem);
|
|
}
|
|
|
|
autowait(2.20f-0.2f);
|
|
|
|
// the item is in place
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.BodyRemoveItem();
|
|
// if marker points to a trigger
|
|
if (GetActionMarker()->m_penTrigger!=NULL) {
|
|
// trigger it
|
|
SendToTarget(GetActionMarker()->m_penTrigger, EET_TRIGGER, this);
|
|
}
|
|
|
|
// fake that player has passed through the door controller
|
|
if (GetActionMarker()->m_penDoorController!=NULL) {
|
|
EPass ePass;
|
|
ePass.penOther = this;
|
|
GetActionMarker()->m_penDoorController->SendEvent(ePass);
|
|
}
|
|
|
|
autowait(3.25f-2.20f);
|
|
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.BodyRemoveItem();
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
// auto action - pick an item
|
|
AutoPickItem(EVoid)
|
|
{
|
|
|
|
// start pulling the item
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.BodyPickItemAnimation();
|
|
StartModelAnim(PLAYER_ANIM_KEYLIFT, 0);
|
|
|
|
autowait(1.2f);
|
|
|
|
// if marker points to a trigger
|
|
if (GetActionMarker()->m_penTrigger!=NULL) {
|
|
// trigger it
|
|
SendToTarget(GetActionMarker()->m_penTrigger, EET_TRIGGER, this);
|
|
}
|
|
|
|
// item appears
|
|
CPlayerActionMarker *ppam = GetActionMarker();
|
|
if (IsOfClass(ppam->m_penItem, "KeyItem")) {
|
|
CModelObject &moItem = ppam->m_penItem->GetModelObject()->GetAttachmentModel(0)->amo_moModelObject;
|
|
GetPlayerAnimator()->SetItem(&moItem);
|
|
EPass ePass;
|
|
ePass.penOther = this;
|
|
ppam->m_penItem->SendEvent(ePass);
|
|
}
|
|
|
|
autowait(3.6f-1.2f+GetActionMarker()->m_tmWait);
|
|
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.BodyRemoveItem();
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
AutoFallDown(EVoid)
|
|
{
|
|
StartModelAnim(PLAYER_ANIM_BRIDGEFALLPOSE, 0);
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_BRIDGEFALLPOSE, 0);
|
|
|
|
autowait(GetActionMarker()->m_tmWait);
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
AutoFallToAbys(EVoid)
|
|
{
|
|
StartModelAnim(PLAYER_ANIM_ABYSSFALL, AOF_LOOPING);
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_ABYSSFALL, AOF_LOOPING);
|
|
|
|
autowait(GetActionMarker()->m_tmWait);
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
// auto action - look around
|
|
AutoLookAround(EVoid)
|
|
{
|
|
StartModelAnim(PLAYER_ANIM_BACKPEDAL, 0);
|
|
m_vAutoSpeed = FLOAT3D(0,0,plr_fSpeedForward/4/0.75f);
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_NORMALWALK, 0);
|
|
|
|
autowait(GetModelObject()->GetCurrentAnimLength()/2);
|
|
|
|
m_vAutoSpeed = FLOAT3D(0,0,0);
|
|
|
|
// start looking around
|
|
StartModelAnim(PLAYER_ANIM_STAND, 0);
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_LOOKAROUND, 0);
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
|
|
// wait given time
|
|
autowait(moBody.GetCurrentAnimLength()+0.1f);
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
AutoTeleport(EVoid)
|
|
{
|
|
// teleport there
|
|
TeleportToAutoMarker(GetActionMarker());
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
AutoAppear(EVoid)
|
|
{
|
|
// hide the model
|
|
SwitchToEditorModel();
|
|
|
|
// put it at marker
|
|
Teleport(GetActionMarker()->GetPlacement());
|
|
// make it rotate in spawnpose
|
|
SetPhysicsFlags(GetPhysicsFlags() & ~(EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY));
|
|
m_ulFlags|=PLF_AUTOMOVEMENTS;
|
|
SetDesiredRotation(ANGLE3D(60,0,0));
|
|
StartModelAnim(PLAYER_ANIM_SPAWNPOSE, AOF_LOOPING);
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_SPAWNPOSE, AOF_LOOPING);
|
|
|
|
// start stardust appearing
|
|
m_tmSpiritStart = _pTimer->CurrentTick();
|
|
// wait till it appears
|
|
autowait(5);
|
|
|
|
// start model appearing
|
|
SwitchToModel();
|
|
m_tmFadeStart = _pTimer->CurrentTick();
|
|
// wait till it appears
|
|
autowait(5);
|
|
// fixate full opacity
|
|
COLOR colAlpha = GetModelObject()->mo_colBlendColor;
|
|
GetModelObject()->mo_colBlendColor = colAlpha|0xFF;
|
|
|
|
// put it to normal state
|
|
SetPhysicsFlags(GetPhysicsFlags() | EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY);
|
|
SetDesiredRotation(ANGLE3D(0,0,0));
|
|
m_ulFlags&=~PLF_AUTOMOVEMENTS;
|
|
|
|
// play animation to fall down
|
|
StartModelAnim(PLAYER_ANIM_SPAWN_FALLDOWN, 0);
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_SPAWN_FALLDOWN, 0);
|
|
|
|
autowait(GetModelObject()->GetCurrentAnimLength());
|
|
|
|
// play animation to get up
|
|
StartModelAnim(PLAYER_ANIM_SPAWN_GETUP, AOF_SMOOTHCHANGE);
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_SPAWN_GETUP, AOF_SMOOTHCHANGE);
|
|
|
|
autowait(GetModelObject()->GetCurrentAnimLength());
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
TravellingInBeam()
|
|
{
|
|
// put it at marker
|
|
Teleport(GetActionMarker()->GetPlacement());
|
|
// make it rotate in spawnpose
|
|
SetPhysicsFlags(GetPhysicsFlags() & ~(EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY));
|
|
m_ulFlags|=PLF_AUTOMOVEMENTS;
|
|
SetDesiredRotation(ANGLE3D(60,0,0));
|
|
SetDesiredTranslation(ANGLE3D(0,20.0f,0));
|
|
StartModelAnim(PLAYER_ANIM_SPAWNPOSE, AOF_LOOPING);
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_SPAWNPOSE, AOF_LOOPING);
|
|
// wait till it appears
|
|
autowait(8.0f);
|
|
// switch to model
|
|
SwitchToEditorModel();
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
LogoFireMinigun(EVoid)
|
|
{
|
|
// put it at marker
|
|
CPlacement3D pl = GetActionMarker()->GetPlacement();
|
|
pl.pl_PositionVector += FLOAT3D(0, 0.01f, 0)*GetActionMarker()->en_mRotation;
|
|
Teleport(pl);
|
|
en_plViewpoint.pl_OrientationAngle(1) = 20.0f;
|
|
en_plLastViewpoint.pl_OrientationAngle = en_plViewpoint.pl_OrientationAngle;
|
|
|
|
// stand in pose
|
|
StartModelAnim(PLAYER_ANIM_INTRO, AOF_LOOPING);
|
|
// remember time for rotating view start
|
|
m_tmMinigunAutoFireStart = _pTimer->CurrentTick();
|
|
// wait some time for fade in and to look from left to right with out fireing
|
|
//autowait(0.75f);
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(EFireWeapon());
|
|
autowait(2.5f);
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(EReleaseWeapon());
|
|
|
|
// stop minigun shaking
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_MINIGUN_STAND, 0);
|
|
|
|
autowait(0.5f);
|
|
|
|
// ---------- Apply shake
|
|
CWorldSettingsController *pwsc = NULL;
|
|
// obtain bcg viewer
|
|
CBackgroundViewer *penBcgViewer = (CBackgroundViewer *) GetWorld()->GetBackgroundViewer();
|
|
if( penBcgViewer != NULL)
|
|
{
|
|
pwsc = (CWorldSettingsController *) &*penBcgViewer->m_penWorldSettingsController;
|
|
pwsc->m_tmShakeStarted = _pTimer->CurrentTick();
|
|
pwsc->m_vShakePos = GetPlacement().pl_PositionVector;
|
|
pwsc->m_fShakeFalloff = 250.0f;
|
|
pwsc->m_fShakeFade = 3.0f;
|
|
|
|
pwsc->m_fShakeIntensityZ = 0.1f*2.0f;
|
|
pwsc->m_tmShakeFrequencyZ = 5.0f;
|
|
pwsc->m_fShakeIntensityY = 0.0f;
|
|
pwsc->m_fShakeIntensityB = 0.0f;
|
|
|
|
/*
|
|
pwsc->m_fShakeIntensityY = 0.1f*2.0f;
|
|
pwsc->m_tmShakeFrequencyY = 5.0f;
|
|
pwsc->m_fShakeIntensityB = 2.5f*1.5f;
|
|
pwsc->m_tmShakeFrequencyB = 7.2f;
|
|
*/
|
|
}
|
|
|
|
// stop rotating body
|
|
m_tmMinigunAutoFireStart = -1;
|
|
autowait(10.0f);
|
|
|
|
return EReturn();
|
|
}
|
|
|
|
AutoStoreWeapon(EVoid)
|
|
{
|
|
// store current weapon slowly
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.BodyAnimationTemplate(BODY_ANIM_WAIT,
|
|
BODY_ANIM_COLT_REDRAWSLOW, BODY_ANIM_SHOTGUN_REDRAWSLOW, BODY_ANIM_MINIGUN_REDRAWSLOW,
|
|
0);
|
|
autowait(plan.m_fBodyAnimTime);
|
|
|
|
m_iAutoOrgWeapon = ((CPlayerWeapons&)*m_penWeapons).m_iCurrentWeapon;
|
|
((CPlayerWeapons&)*m_penWeapons).m_iCurrentWeapon = WEAPON_NONE;
|
|
((CPlayerWeapons&)*m_penWeapons).m_iWantedWeapon = WEAPON_NONE;
|
|
|
|
// sync apperances
|
|
GetPlayerAnimator()->SyncWeapon();
|
|
// remove weapon attachment
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.m_iWeaponLast = m_iAutoOrgWeapon;
|
|
plan.RemoveWeapon();
|
|
GetPlayerAnimator()->SyncWeapon();
|
|
|
|
((CPlayerWeapons&)*m_penWeapons).m_iCurrentWeapon = (WeaponType) m_iAutoOrgWeapon;
|
|
plan.BodyAnimationTemplate(BODY_ANIM_WAIT,
|
|
BODY_ANIM_COLT_DEACTIVATETOWALK, BODY_ANIM_SHOTGUN_DEACTIVATETOWALK, BODY_ANIM_MINIGUN_DEACTIVATETOWALK, AOF_SMOOTHCHANGE);
|
|
((CPlayerWeapons&)*m_penWeapons).m_iCurrentWeapon = WEAPON_NONE;
|
|
|
|
autowait(plan.m_fBodyAnimTime);
|
|
|
|
// return to auto-action loop
|
|
return EReturn();
|
|
}
|
|
|
|
// perform player auto actions
|
|
DoAutoActions(EVoid)
|
|
{
|
|
// don't look up/down
|
|
en_plViewpoint.pl_OrientationAngle = ANGLE3D(0,0,0);
|
|
// disable playeranimator animating
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.m_bDisableAnimating = TRUE;
|
|
|
|
// while there is some marker
|
|
while (m_penActionMarker!=NULL && IsOfClass(m_penActionMarker, "PlayerActionMarker")) {
|
|
|
|
// if should wait
|
|
if (GetActionMarker()->m_paaAction==PAA_WAIT) {
|
|
// play still anim
|
|
CModelObject &moBody = GetModelObject()->GetAttachmentModel(PLAYER_ATTACHMENT_TORSO)->amo_moModelObject;
|
|
moBody.PlayAnim(BODY_ANIM_WAIT, AOF_NORESTART|AOF_LOOPING);
|
|
// wait given time
|
|
autowait(GetActionMarker()->m_tmWait);
|
|
|
|
// if should teleport here
|
|
} else if (GetActionMarker()->m_paaAction==PAA_APPEARING) {
|
|
autocall AutoAppear() EReturn;
|
|
} else if (GetActionMarker()->m_paaAction==PAA_TRAVELING_IN_BEAM) {
|
|
autocall TravellingInBeam() EReturn;
|
|
} else if (GetActionMarker()->m_paaAction==PAA_LOGO_FIRE_MINIGUN) {
|
|
autocall LogoFireMinigun() EReturn;
|
|
// if should appear here
|
|
} else if (GetActionMarker()->m_paaAction==PAA_TELEPORT) {
|
|
autocall AutoTeleport() EReturn;
|
|
|
|
// if should wait for trigger
|
|
} else if (GetActionMarker()->m_paaAction==PAA_WAITFOREVER) {
|
|
// wait forever
|
|
wait() {
|
|
on (EBegin) : { resume; }
|
|
otherwise() : { pass; }
|
|
}
|
|
// if should store weapon
|
|
} else if (GetActionMarker()->m_paaAction==PAA_STOREWEAPON) {
|
|
autocall AutoStoreWeapon() EReturn;
|
|
|
|
// if should draw weapon
|
|
} else if (GetActionMarker()->m_paaAction==PAA_DRAWWEAPON) {
|
|
// order playerweapons to select best weapon
|
|
ESelectWeapon eSelect;
|
|
eSelect.iWeapon = -4;
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(eSelect);
|
|
|
|
// if should wait
|
|
} else if (GetActionMarker()->m_paaAction==PAA_LOOKAROUND) {
|
|
autocall AutoLookAround() EReturn;
|
|
|
|
// if should use item
|
|
} else if (GetActionMarker()->m_paaAction==PAA_USEITEM) {
|
|
// use it
|
|
autocall AutoUseItem() EReturn;
|
|
|
|
// if should pick item
|
|
} else if (GetActionMarker()->m_paaAction==PAA_PICKITEM) {
|
|
// pick it
|
|
autocall AutoPickItem() EReturn;
|
|
|
|
// if falling from bridge
|
|
} else if (GetActionMarker()->m_paaAction==PAA_FALLDOWN) {
|
|
// fall
|
|
autocall AutoFallDown() EReturn;
|
|
|
|
// if releasing player
|
|
} else if (GetActionMarker()->m_paaAction==PAA_RELEASEPLAYER) {
|
|
if (m_penCamera!=NULL) {
|
|
((CCamera*)&*m_penCamera)->m_bStopMoving=TRUE;
|
|
}
|
|
m_penCamera = NULL;
|
|
// if currently not having any weapon in hand
|
|
if (GetPlayerWeapons()->m_iCurrentWeapon == WEAPON_NONE) {
|
|
// order playerweapons to select best weapon
|
|
ESelectWeapon eSelect;
|
|
eSelect.iWeapon = -4;
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(eSelect);
|
|
}
|
|
// sync weapon, just in case
|
|
m_ulFlags |= PLF_SYNCWEAPON;
|
|
m_tmSpiritStart = 0;
|
|
|
|
// if start computer
|
|
} else if (GetActionMarker()->m_paaAction==PAA_STARTCOMPUTER) {
|
|
// mark that
|
|
if (_pNetwork->IsPlayerLocal(this) && GetSP()->sp_bSinglePlayer) {
|
|
cmp_ppenPlayer = this;
|
|
cmp_bInitialStart = TRUE;
|
|
}
|
|
|
|
// if start introscroll
|
|
} else if (GetActionMarker()->m_paaAction==PAA_STARTINTROSCROLL) {
|
|
_pShell->Execute("sam_iStartCredits=1;");
|
|
|
|
// if start credits
|
|
} else if (GetActionMarker()->m_paaAction==PAA_STARTCREDITS) {
|
|
_pShell->Execute("sam_iStartCredits=2;");
|
|
|
|
// if stop scroller
|
|
} else if (GetActionMarker()->m_paaAction==PAA_STOPSCROLLER) {
|
|
_pShell->Execute("sam_iStartCredits=-1;");
|
|
|
|
// if should run to the marker
|
|
} else if (GetActionMarker()->m_paaAction==PAA_RUN) {
|
|
// go to it
|
|
m_fAutoSpeed = plr_fSpeedForward*GetActionMarker()->m_fSpeed;
|
|
autocall AutoGoToMarker() EReturn;
|
|
|
|
// if should run to the marker and stop exactly there
|
|
} else if (GetActionMarker()->m_paaAction==PAA_RUNANDSTOP) {
|
|
// go to it
|
|
m_fAutoSpeed = plr_fSpeedForward*GetActionMarker()->m_fSpeed;
|
|
autocall AutoGoToMarkerAndStop() EReturn;
|
|
|
|
// if should record end-of-level stats
|
|
} else if (GetActionMarker()->m_paaAction==PAA_RECORDSTATS) {
|
|
|
|
if ((GetSP()->sp_bSinglePlayer || GetSP()->sp_bPlayEntireGame)) {
|
|
// remeber estimated time
|
|
m_tmEstTime = GetActionMarker()->m_tmWait;
|
|
// record stats
|
|
RecordEndOfLevelData();
|
|
} else {
|
|
SetGameEnd();
|
|
}
|
|
|
|
// if should show statistics to the player
|
|
} else if (GetActionMarker()->m_paaAction==PAA_SHOWSTATS) {
|
|
// !! FIXUP for the 'half' edition of the first encounter
|
|
if (GetWorld()->wo_fnmFileName==plr_strLastLevel) {
|
|
jump TheEnd();
|
|
}
|
|
|
|
// call computer
|
|
if (cmp_ppenPlayer==NULL && _pNetwork->IsPlayerLocal(this) && GetSP()->sp_bSinglePlayer) {
|
|
m_bEndOfLevel = TRUE;
|
|
cmp_ppenPlayer = this;
|
|
m_ulFlags|=PLF_DONTRENDER;
|
|
while(m_bEndOfLevel) {
|
|
wait(_pTimer->TickQuantum) {
|
|
on (ETimer) : { stop; }
|
|
on (EReceiveScore) : { pass; }
|
|
on (ECenterMessage) : { pass; }
|
|
otherwise() : { resume; }
|
|
}
|
|
}
|
|
m_ulFlags&=!PLF_DONTRENDER;
|
|
}
|
|
// if end of entire game
|
|
} else if (GetActionMarker()->m_paaAction==PAA_ENDOFGAME) {
|
|
|
|
// record stats
|
|
jump TheEnd();
|
|
} else if (GetActionMarker()->m_paaAction==PAA_NOGRAVITY) {
|
|
SetPhysicsFlags(GetPhysicsFlags() & ~(EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY));
|
|
if( GetActionMarker()->GetParent() != NULL)
|
|
{
|
|
SetParent(GetActionMarker()->GetParent());
|
|
}
|
|
} else if (GetActionMarker()->m_paaAction==PAA_TURNONGRAVITY) {
|
|
SetPhysicsFlags(GetPhysicsFlags()|EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY);
|
|
SetParent(NULL);
|
|
}
|
|
else if (TRUE) {
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
// if marker points to a trigger
|
|
if (GetActionMarker()->m_penTrigger!=NULL &&
|
|
GetActionMarker()->m_paaAction!=PAA_PICKITEM) {
|
|
// trigger it
|
|
SendToTarget(GetActionMarker()->m_penTrigger, EET_TRIGGER, this);
|
|
}
|
|
|
|
// get next marker
|
|
m_penActionMarker = GetActionMarker()->m_penTarget;
|
|
}
|
|
|
|
// must clear marker, in case it was invalid
|
|
m_penActionMarker = NULL;
|
|
|
|
// enable playeranimator animating
|
|
CPlayerAnimator &plan = (CPlayerAnimator&)*m_penAnimator;
|
|
plan.m_bDisableAnimating = FALSE;
|
|
|
|
// return to main loop
|
|
return EVoid();
|
|
}
|
|
/************************************************************
|
|
* M A I N *
|
|
************************************************************/
|
|
Main(EVoid evoid)
|
|
{
|
|
// remember start time
|
|
time_t t;
|
|
time(&t);
|
|
m_iStartTime = (INDEX)t;
|
|
|
|
m_ctUnreadMessages = 0;
|
|
SetFlags(GetFlags()|ENF_CROSSESLEVELS|ENF_NOTIFYLEVELCHANGE);
|
|
InitAsEditorModel();
|
|
|
|
// set default model for physics etc
|
|
CTString strDummy;
|
|
SetPlayerAppearance(GetModelObject(), NULL, strDummy, /*bPreview=*/FALSE);
|
|
// set your real appearance if possible
|
|
ValidateCharacter();
|
|
SetPlayerAppearance(&m_moRender, &en_pcCharacter, strDummy, /*bPreview=*/FALSE);
|
|
|
|
// if unsuccessful
|
|
if (GetModelObject()->GetData()==NULL) {
|
|
// never proceed with initialization - player cannot work
|
|
return;
|
|
}
|
|
|
|
//const FLOAT fSize = 2.1f/1.85f;
|
|
//GetModelObject()->StretchModel(FLOAT3D(fSize, fSize, fSize));
|
|
ModelChangeNotify();
|
|
|
|
// wait a bit to allow other entities to start
|
|
wait(0.2f) { // this is 4 ticks, it has to be at least more than musicchanger for enemy counting
|
|
on (EBegin) : { resume; }
|
|
on (ETimer) : { stop; }
|
|
on (EDisconnected) : {
|
|
Destroy();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// do not use predictor if not yet initialized
|
|
if (IsPredictor()) { // !!!!####
|
|
Destroy();
|
|
return;
|
|
}
|
|
|
|
// appear
|
|
SwitchToModel();
|
|
m_ulFlags|=PLF_INITIALIZED;
|
|
|
|
// set initial vars
|
|
en_tmMaxHoldBreath = 60.0f;
|
|
en_fDensity = 1000.0f; // same density as water - to be able to dive freely
|
|
|
|
ModelChangeNotify();
|
|
|
|
// spawn weapons
|
|
m_penWeapons = CreateEntity(GetPlacement(), CLASS_PLAYER_WEAPONS);
|
|
EWeaponsInit eInitWeapons;
|
|
eInitWeapons.penOwner = this;
|
|
m_penWeapons->Initialize(eInitWeapons);
|
|
|
|
// spawn animator
|
|
m_penAnimator = CreateEntity(GetPlacement(), CLASS_PLAYER_ANIMATOR);
|
|
EAnimatorInit eInitAnimator;
|
|
eInitAnimator.penPlayer = this;
|
|
m_penAnimator->Initialize(eInitAnimator);
|
|
|
|
// set sound default parameters
|
|
m_soMouth.Set3DParameters(50.0f, 10.0f, 1.0f, 1.0f);
|
|
m_soFootL.Set3DParameters(20.0f, 2.0f, 1.0f, 1.0f);
|
|
m_soFootR.Set3DParameters(20.0f, 2.0f, 1.0f, 1.0f);
|
|
m_soBody.Set3DParameters(25.0f, 5.0f, 1.0f, 1.0f);
|
|
m_soMessage.Set3DParameters(25.0f, 5.0f, 1.0f, 1.0f);
|
|
|
|
// setup light source
|
|
SetupLightSource();
|
|
|
|
// set light animation if available
|
|
try {
|
|
m_aoLightAnimation.SetData_t(CTFILENAME("Animations\\BasicEffects.ani"));
|
|
} catch (char *strError) {
|
|
WarningMessage(TRANS("Cannot load Animations\\BasicEffects.ani: %s"), strError);
|
|
}
|
|
PlayLightAnim(LIGHT_ANIM_NONE, 0);
|
|
|
|
wait() {
|
|
on (EBegin) : { call FirstInit(); }
|
|
on (ERebirth) : { call Rebirth(); }
|
|
on (EDeath eDeath) : { call Death(eDeath); }
|
|
on (EDamage eDamage) : { call Wounded(eDamage); }
|
|
on (EPreLevelChange) : {
|
|
m_ulFlags&=~PLF_INITIALIZED;
|
|
m_ulFlags|=PLF_CHANGINGLEVEL;
|
|
m_ulFlags &= ~PLF_LEVELSTARTED;
|
|
resume;
|
|
}
|
|
on (EPostLevelChange) : {
|
|
if (GetSP()->sp_bSinglePlayer || (GetFlags()&ENF_ALIVE)) {
|
|
call WorldChange();
|
|
} else {
|
|
call WorldChangeDead();
|
|
}
|
|
}
|
|
on (ETakingBreath eTakingBreath ) : {
|
|
SetDefaultMouthPitch();
|
|
if (eTakingBreath.fBreathDelay<0.2f) {
|
|
PlaySound(m_soMouth, SOUND_INHALE0, SOF_3D);
|
|
} else if (eTakingBreath.fBreathDelay<0.8f) {
|
|
PlaySound(m_soMouth, SOUND_INHALE1, SOF_3D);
|
|
} else {
|
|
PlaySound(m_soMouth, SOUND_INHALE2, SOF_3D);
|
|
}
|
|
resume;
|
|
}
|
|
on (ECameraStart eStart) : {
|
|
m_penCamera = eStart.penCamera;
|
|
// stop player
|
|
if (m_penActionMarker==NULL) {
|
|
SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f));
|
|
SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 0.0f));
|
|
}
|
|
// stop firing
|
|
((CPlayerWeapons&)*m_penWeapons).SendEvent(EReleaseWeapon());
|
|
resume;
|
|
}
|
|
on (ECameraStop eCameraStop) : {
|
|
if (m_penCamera==eCameraStop.penCamera) {
|
|
m_penCamera = NULL;
|
|
}
|
|
resume;
|
|
}
|
|
on (ECenterMessage eMsg) : {
|
|
m_strCenterMessage = eMsg.strMessage;
|
|
m_tmCenterMessageEnd = _pTimer->CurrentTick()+eMsg.tmLength;
|
|
if (eMsg.mssSound==MSS_INFO) {
|
|
m_soMessage.Set3DParameters(25.0f, 5.0f, 1.0f, 1.0f);
|
|
PlaySound(m_soMessage, SOUND_INFO, SOF_3D|SOF_VOLUMETRIC|SOF_LOCAL);
|
|
}
|
|
resume;
|
|
}
|
|
on (EComputerMessage eMsg) : {
|
|
ReceiveComputerMessage(eMsg.fnmMessage, CMF_ANALYZE);
|
|
resume;
|
|
}
|
|
on (EVoiceMessage eMsg) : {
|
|
SayVoiceMessage(eMsg.fnmMessage);
|
|
resume;
|
|
}
|
|
on (EAutoAction eAutoAction) : {
|
|
// remember first marker
|
|
m_penActionMarker = eAutoAction.penFirstMarker;
|
|
// do the actions
|
|
call DoAutoActions();
|
|
}
|
|
on (EReceiveScore eScore) : {
|
|
m_psLevelStats.ps_iScore += eScore.iPoints;
|
|
m_psGameStats.ps_iScore += eScore.iPoints;
|
|
m_iMana += eScore.iPoints*GetSP()->sp_fManaTransferFactor;
|
|
CheckHighScore();
|
|
resume;
|
|
}
|
|
on (EKilledEnemy) : {
|
|
m_psLevelStats.ps_iKills += 1;
|
|
m_psGameStats.ps_iKills += 1;
|
|
resume;
|
|
}
|
|
on (ESecretFound) : {
|
|
m_psLevelStats.ps_iSecrets += 1;
|
|
m_psGameStats.ps_iSecrets += 1;
|
|
resume;
|
|
}
|
|
// EEnd should not arrive here
|
|
on (EEnd) : {
|
|
ASSERT(FALSE);
|
|
resume;
|
|
}
|
|
// if player is disconnected
|
|
on (EDisconnected) : {
|
|
// exit the loop
|
|
stop;
|
|
}
|
|
// support for jumping using bouncers
|
|
on (ETouch eTouch) : {
|
|
if (IsOfClass(eTouch.penOther, "Bouncer")) {
|
|
JumpFromBouncer(this, eTouch.penOther);
|
|
// play jump sound
|
|
SetDefaultMouthPitch();
|
|
PlaySound(m_soMouth, SOUND_JUMP, SOF_3D);
|
|
if(_pNetwork->IsPlayerLocal(this)) {IFeel_PlayEffect("Jump");}
|
|
}
|
|
resume;
|
|
}
|
|
}
|
|
|
|
// we get here if the player is disconnected from the server
|
|
|
|
// if we have some keys
|
|
if (!IsPredictor() && m_ulKeys!=0) {
|
|
// find first live player
|
|
CPlayer *penNextPlayer = NULL;
|
|
for(INDEX iPlayer=0; iPlayer<GetMaxPlayers(); iPlayer++) {
|
|
CPlayer *pen = (CPlayer*)&*GetPlayerEntity(iPlayer);
|
|
if (pen!=NULL && pen!=this && (pen->GetFlags()&ENF_ALIVE) && !(pen->GetFlags()&ENF_DELETED) ) {
|
|
penNextPlayer = pen;
|
|
}
|
|
}
|
|
|
|
// if any found
|
|
if (penNextPlayer!=NULL) {
|
|
// transfer keys to that player
|
|
CPrintF(TRANS("%s leaving, all keys transferred to %s\n"),
|
|
(const char*)m_strName, (const char*)penNextPlayer->GetPlayerName());
|
|
penNextPlayer->m_ulKeys |= m_ulKeys;
|
|
}
|
|
}
|
|
|
|
// spawn teleport effect
|
|
SpawnTeleport();
|
|
|
|
// cease to exist
|
|
m_penWeapons->Destroy();
|
|
m_penAnimator->Destroy();
|
|
if (m_penView!=NULL) {
|
|
m_penView->Destroy();
|
|
}
|
|
if (m_pen3rdPersonView!=NULL) {
|
|
m_pen3rdPersonView->Destroy();
|
|
}
|
|
Destroy();
|
|
return;
|
|
};
|
|
};
|