HLua/dmc/dlls/player.cpp

4888 lines
124 KiB
C++

/***
*
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
* Use, distribution, and modification of this source code and/or resulting
* object code is restricted to non-commercial enhancements to products from
* Valve LLC. All other use, distribution, or modification is prohibited
* without written permission from Valve LLC.
*
****/
/*
===== player.cpp ========================================================
functions dealing with the player
*/
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "player.h"
#include "trains.h"
#include "nodes.h"
#include "weapons.h"
#include "monsters.h"
#include "../engine/shake.h"
#include "decals.h"
#include "gamerules.h"
#include "quake_gun.h"
// #define DUCKFIX
extern bool g_bHaveMOTD;
#include "pm_shared.h"
#include "hltv.h"
extern DLL_GLOBAL ULONG g_ulModelIndexPlayer;
extern DLL_GLOBAL BOOL g_fGameOver;
extern DLL_GLOBAL BOOL g_fDrawLines;
extern unsigned short g_sTeleport;
extern unsigned short g_usPowerUp;
Vector g_vecTeleMins[ MAX_TELES ];
Vector g_vecTeleMaxs[ MAX_TELES ];
int g_iTeleNum;
int gEvilImpulse101;
extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle;
BOOL gInitHUD = TRUE;
extern void CopyToBodyQue(entvars_t* pev);
extern void respawn(entvars_t *pev, BOOL fCopyCorpse);
extern Vector VecBModelOrigin(entvars_t *pevBModel );
extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer );
int MapTextureTypeStepType(char chTextureType);
void AddAmmoNameToAmmoRegistry( const char *szAmmoname );
// the world node graph
extern CGraph WorldGraph;
#define PLAYER_WALLJUMP_SPEED 300 // how fast we can spring off walls
#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump
#define TRAIN_ACTIVE 0x80
#define TRAIN_NEW 0xc0
#define TRAIN_OFF 0x00
#define TRAIN_NEUTRAL 0x01
#define TRAIN_SLOW 0x02
#define TRAIN_MEDIUM 0x03
#define TRAIN_FAST 0x04
#define TRAIN_BACK 0x05
#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes
#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit)
//#define PLAYER_MAX_SAFE_FALL_DIST 20// falling any farther than this many feet will inflict damage
//#define PLAYER_FATAL_FALL_DIST 60// 100% damage inflicted if player falls this many feet
//#define DAMAGE_PER_UNIT_FALLEN (float)( 100 ) / ( ( PLAYER_FATAL_FALL_DIST - PLAYER_MAX_SAFE_FALL_DIST ) * 12 )
//#define MAX_SAFE_FALL_UNITS ( PLAYER_MAX_SAFE_FALL_DIST * 12 )
// Global Savedata for player
TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] =
{
DEFINE_FIELD( CBasePlayer, m_flFlashLightTime, FIELD_TIME ),
DEFINE_FIELD( CBasePlayer, m_iFlashBattery, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_afButtonLast, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_afButtonPressed, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_afButtonReleased, FIELD_INTEGER ),
DEFINE_ARRAY( CBasePlayer, m_rgItems, FIELD_INTEGER, MAX_ITEMS ),
DEFINE_FIELD( CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_flTimeStepSound, FIELD_TIME ),
DEFINE_FIELD( CBasePlayer, m_flTimeWeaponIdle, FIELD_TIME ),
DEFINE_FIELD( CBasePlayer, m_flSwimTime, FIELD_TIME ),
DEFINE_FIELD( CBasePlayer, m_flDuckTime, FIELD_TIME ),
DEFINE_FIELD( CBasePlayer, m_flWallJumpTime, FIELD_TIME ),
DEFINE_FIELD( CBasePlayer, m_flSuitUpdate, FIELD_TIME ),
DEFINE_ARRAY( CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST ),
DEFINE_FIELD( CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER ),
DEFINE_ARRAY( CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT ),
DEFINE_ARRAY( CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT ),
DEFINE_FIELD( CBasePlayer, m_lastDamageAmount, FIELD_INTEGER ),
DEFINE_ARRAY( CBasePlayer, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ),
DEFINE_FIELD( CBasePlayer, m_pActiveItem, FIELD_CLASSPTR ),
DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ),
DEFINE_ARRAY( CBasePlayer, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ),
DEFINE_FIELD( CBasePlayer, m_idrowndmg, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_idrownrestored, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_tSneaking, FIELD_TIME ),
DEFINE_FIELD( CBasePlayer, m_iTrain, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_flFallVelocity, FIELD_FLOAT ),
DEFINE_FIELD( CBasePlayer, m_iTargetVolume, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_iWeaponVolume, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_iExtraSoundTypes, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_iWeaponFlash, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_fLongJump, FIELD_BOOLEAN ),
DEFINE_FIELD( CBasePlayer, m_fInitHUD, FIELD_BOOLEAN ),
DEFINE_FIELD( CBasePlayer, m_tbdPrev, FIELD_TIME ),
DEFINE_FIELD( CBasePlayer, m_pTank, FIELD_EHANDLE ),
DEFINE_FIELD( CBasePlayer, m_iHideHUD, FIELD_INTEGER ),
DEFINE_FIELD( CBasePlayer, m_iFOV, FIELD_INTEGER ),
//DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games
//DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games
//DEFINE_FIELD( CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME ),
//DEFINE_FIELD( CBasePlayer, m_fKnownItem, FIELD_INTEGER ), // reset to zero on load
//DEFINE_FIELD( CBasePlayer, m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache()
//DEFINE_FIELD( CBasePlayer, m_pentSndLast, FIELD_EDICT ), // Don't restore, client needs reset
//DEFINE_FIELD( CBasePlayer, m_flSndRoomtype, FIELD_FLOAT ), // Don't restore, client needs reset
//DEFINE_FIELD( CBasePlayer, m_flSndRange, FIELD_FLOAT ), // Don't restore, client needs reset
//DEFINE_FIELD( CBasePlayer, m_fNewAmmo, FIELD_INTEGER ), // Don't restore, client needs reset
//DEFINE_FIELD( CBasePlayer, m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache()
//DEFINE_FIELD( CBasePlayer, m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache()
//DEFINE_FIELD( CBasePlayer, m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache()
//DEFINE_FIELD( CBasePlayer, m_iStepLeft, FIELD_INTEGER ), // Don't need to restore
//DEFINE_ARRAY( CBasePlayer, m_szTextureName, FIELD_CHARACTER, CBTEXTURENAMEMAX ), // Don't need to restore
//DEFINE_FIELD( CBasePlayer, m_chTextureType, FIELD_CHARACTER ), // Don't need to restore
//DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug
//DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore
//DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset
//DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset
//DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset
//DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset
//DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer
//DEFINE_FIELD( CBasePlayer, m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed
//DEFINE_ARRAY( CBasePlayer, m_rgAmmoLast, FIELD_INTEGER, MAX_AMMO_SLOTS ), // Don't need to restore
//DEFINE_FIELD( CBasePlayer, m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore
//DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't need to restore
};
int giPrecacheGrunt = 0;
int gmsgShake = 0;
int gmsgFade = 0;
int gmsgSelAmmo = 0;
int gmsgFlashlight = 0;
int gmsgFlashBattery = 0;
int gmsgResetHUD = 0;
int gmsgInitHUD = 0;
int gmsgShowGameTitle = 0;
int gmsgCurWeapon = 0;
int gmsgHealth = 0;
int gmsgDamage = 0;
int gmsgBattery = 0;
int gmsgTrain = 0;
int gmsgLogo = 0;
int gmsgWeaponList = 0;
int gmsgAmmoX = 0;
int gmsgHudText = 0;
int gmsgDeathMsg = 0;
int gmsgScoreInfo = 0;
int gmsgTeamInfo = 0;
int gmsgTeamScore = 0;
int gmsgGameMode = 0;
int gmsgMOTD = 0;
int gmsgServerName = 0;
int gmsgAmmoPickup = 0;
int gmsgWeapPickup = 0;
int gmsgItemPickup = 0;
int gmsgHideWeapon = 0;
int gmsgSetCurWeap = 0;
int gmsgSayText = 0;
int gmsgTextMsg = 0;
int gmsgSetFOV = 0;
int gmsgShowMenu = 0;
int gmsgGeigerRange = 0;
// QUAKECLASSIC
int gmsgQItems = 0;
int gmsgStatusText = 0;
int gmsgStatusValue = 0;
void LinkUserMessages( void )
{
// Already taken care of?
if ( gmsgSelAmmo )
{
return;
}
gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo));
gmsgCurWeapon = REG_USER_MSG("CurWeapon", 3);
gmsgGeigerRange = REG_USER_MSG("Geiger", 1);
gmsgFlashlight = REG_USER_MSG("Flashlight", 2);
gmsgFlashBattery = REG_USER_MSG("FlashBat", 1);
gmsgHealth = REG_USER_MSG( "Health", 1 );
gmsgDamage = REG_USER_MSG( "Damage", 12 );
gmsgBattery = REG_USER_MSG( "Battery", 2);
gmsgTrain = REG_USER_MSG( "Train", 1);
gmsgHudText = REG_USER_MSG( "HudText", -1 );
gmsgSayText = REG_USER_MSG( "SayText", -1 );
gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 );
gmsgWeaponList = REG_USER_MSG("WeaponList", -1);
gmsgResetHUD = REG_USER_MSG("ResetHUD", 1); // called every respawn
gmsgInitHUD = REG_USER_MSG("InitHUD", -1 ); // called every time a new player joins the server
gmsgShowGameTitle = REG_USER_MSG("GameTitle", 1);
gmsgDeathMsg = REG_USER_MSG( "DeathMsg", -1 );
gmsgScoreInfo = REG_USER_MSG( "ScoreInfo", 7 );
gmsgTeamInfo = REG_USER_MSG( "TeamInfo", -1 ); // sets the name of a player's team
gmsgTeamScore = REG_USER_MSG( "TeamScore", -1 ); // sets the score of a team on the scoreboard
gmsgGameMode = REG_USER_MSG( "GameMode", 1 );
gmsgMOTD = REG_USER_MSG( "MOTD", -1 );
gmsgServerName = REG_USER_MSG( "ServerName", -1 );
gmsgAmmoPickup = REG_USER_MSG( "AmmoPickup", 2 );
gmsgWeapPickup = REG_USER_MSG( "WeapPickup", 1 );
gmsgItemPickup = REG_USER_MSG( "ItemPickup", -1 );
gmsgHideWeapon = REG_USER_MSG( "HideWeapon", 1 );
gmsgSetFOV = REG_USER_MSG( "SetFOV", 1 );
gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 );
gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake));
gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade));
gmsgAmmoX = REG_USER_MSG("AmmoX", 2);
gmsgQItems = REG_USER_MSG( "QItems", 4 );
gmsgStatusText = REG_USER_MSG("StatusText", -1);
gmsgStatusValue = REG_USER_MSG("StatusValue", 3);
}
LINK_ENTITY_TO_CLASS( player, CBasePlayer );
// QUAKECLASSIC: Play pain sounds
void CBasePlayer :: Pain( CBaseEntity *pAttacker )
{
if (pev->health < 0)
return;
if ( FClassnameIs( pAttacker->pev, "teledeath" ) )
{
EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NORM );
return;
}
// water pain sounds
if (pev->watertype == CONTENT_WATER && pev->waterlevel == 3)
{
UTIL_Bubbles( pev->mins, pev->maxs, 3 );
if ( RANDOM_FLOAT(0,1) > 0.5 )
EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/drown1.wav", 1, ATTN_NORM );
else
EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/drown2.wav", 1, ATTN_NORM );
return;
}
// slime pain sounds
if (pev->watertype == CONTENT_SLIME)
{
if ( RANDOM_FLOAT(0,1) > 0.5 )
EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM );
else
EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM );
return;
}
// lava pain sounds
if (pev->watertype == CONTENT_LAVA)
{
if ( RANDOM_FLOAT(0,1) > 0.5 )
EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM );
else
EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM );
return;
}
// don't make multiple pain sounds right after each other
if (m_flPainSoundFinished > gpGlobals->time)
{
m_bAxHitMe = 0;
return;
}
m_flPainSoundFinished = gpGlobals->time + 0.5;
// ax pain sound
if (m_bAxHitMe == 1)
{
m_bAxHitMe = 0;
EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/axhitbod.wav", 1, ATTN_NORM );
return;
}
// play random sound
switch ( (int)(RANDOM_FLOAT(0,1) * 6) )
{
case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain1.wav", 1, ATTN_NORM ); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain2.wav", 1, ATTN_NORM ); break;
case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain3.wav", 1, ATTN_NORM ); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain4.wav", 1, ATTN_NORM ); break;
case 4: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain5.wav", 1, ATTN_NORM ); break;
case 5: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain6.wav", 1, ATTN_NORM ); break;
}
}
// QUAKECLASSIC: Play a random death sound
void CBasePlayer :: DeathSound( void )
{
// water death sounds
if (pev->waterlevel == 3)
{
EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE);
return;
}
// play random sound
switch ( (int)(RANDOM_FLOAT(0,1) * 5) )
{
case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death1.wav", 1, ATTN_NORM ); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death2.wav", 1, ATTN_NORM ); break;
case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death3.wav", 1, ATTN_NORM ); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death4.wav", 1, ATTN_NORM ); break;
case 4: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death5.wav", 1, ATTN_NORM ); break;
}
}
/*
*
*/
Vector VecVelocityForDamage(float flDamage)
{
Vector vec(RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300));
if (flDamage > -50)
vec = vec * 0.7;
else if (flDamage > -200)
vec = vec * 2;
else
vec = vec * 10;
return vec;
}
#if 0 /*
static void ThrowGib(entvars_t *pev, char *szGibModel, float flDamage)
{
edict_t *pentNew = CREATE_ENTITY();
entvars_t *pevNew = VARS(pentNew);
pevNew->origin = pev->origin;
SET_MODEL(ENT(pevNew), szGibModel);
UTIL_SetSize(pevNew, g_vecZero, g_vecZero);
pevNew->velocity = VecVelocityForDamage(flDamage);
pevNew->movetype = MOVETYPE_BOUNCE;
pevNew->solid = SOLID_NOT;
pevNew->avelocity.x = RANDOM_FLOAT(0,600);
pevNew->avelocity.y = RANDOM_FLOAT(0,600);
pevNew->avelocity.z = RANDOM_FLOAT(0,600);
CHANGE_METHOD(ENT(pevNew), em_think, SUB_Remove);
pevNew->ltime = gpGlobals->time;
pevNew->nextthink = gpGlobals->time + RANDOM_FLOAT(10,20);
pevNew->frame = 0;
pevNew->flags = 0;
}
static void ThrowHead(entvars_t *pev, char *szGibModel, floatflDamage)
{
SET_MODEL(ENT(pev), szGibModel);
pev->frame = 0;
pev->nextthink = -1;
pev->movetype = MOVETYPE_BOUNCE;
pev->takedamage = DAMAGE_NO;
pev->solid = SOLID_NOT;
pev->view_ofs = Vector(0,0,8);
UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,56));
pev->velocity = VecVelocityForDamage(flDamage);
pev->avelocity = RANDOM_FLOAT(-1,1) * Vector(0,600,0);
pev->origin.z -= 24;
ClearBits(pev->flags, FL_ONGROUND);
}
*/
#endif
int TrainSpeed(int iSpeed, int iMax)
{
float fSpeed, fMax;
int iRet = 0;
fMax = (float)iMax;
fSpeed = iSpeed;
fSpeed = fSpeed/fMax;
if (iSpeed < 0)
iRet = TRAIN_BACK;
else if (iSpeed == 0)
iRet = TRAIN_NEUTRAL;
else if (fSpeed < 0.33)
iRet = TRAIN_SLOW;
else if (fSpeed < 0.66)
iRet = TRAIN_MEDIUM;
else
iRet = TRAIN_FAST;
return iRet;
}
// override takehealth
// bitsDamageType indicates type of damage healed.
int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType )
{
// QUAKECLASSIC
if (pev->health <= 0)
return 0;
if ( !(bitsDamageType & DMG_IGNORE_MAXHEALTH) && (pev->health >= pev->max_health) )
return 0;
int iHealAmount = ceil(flHealth);
pev->health += iHealAmount;
if ( !(bitsDamageType & DMG_IGNORE_MAXHEALTH) && (pev->health >= pev->max_health) )
pev->health = pev->max_health;
if (pev->health > 250)
pev->health = 250;
return 1;
}
Vector CBasePlayer :: GetGunPosition( )
{
// UTIL_MakeVectors(pev->v_angle);
// m_HackedGunPos = pev->view_ofs;
Vector origin;
origin = pev->origin + pev->view_ofs;
return origin;
}
//=========================================================
// TraceAttack
//=========================================================
void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType)
{
if ( pev->takedamage )
{
SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood.
TraceBleed( flDamage, vecDir, ptr, bitsDamageType );
AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType );
}
}
void CBasePlayer::RemoveAllItems( BOOL removeSuit )
{
if (m_pActiveItem)
{
ResetAutoaim( );
m_pActiveItem->Holster( );
m_pActiveItem = NULL;
}
m_pLastItem = NULL;
int i;
CBasePlayerItem *pPendingItem;
for (i = 0; i < MAX_ITEM_TYPES; i++)
{
m_pActiveItem = m_rgpPlayerItems[i];
while (m_pActiveItem)
{
pPendingItem = m_pActiveItem->m_pNext;
m_pActiveItem->Drop( );
m_pActiveItem = pPendingItem;
}
m_rgpPlayerItems[i] = NULL;
}
m_pActiveItem = NULL;
pev->viewmodel = 0;
pev->weaponmodel = 0;
if ( removeSuit )
pev->weapons = 0;
else
pev->weapons &= ~WEAPON_ALLWEAPONS;
for ( i = 0; i < MAX_AMMO_SLOTS;i++)
m_rgAmmo[i] = 0;
UpdateClientData();
// send Selected Weapon Message to our client
MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev );
WRITE_BYTE(0);
WRITE_BYTE(0);
WRITE_BYTE(0);
MESSAGE_END();
}
/*
* GLOBALS ASSUMED SET: g_ulModelIndexPlayer
*
* ENTITY_METHOD(PlayerDie)
*/
entvars_t *g_pevLastInflictor; // Set in combat.cpp. Used to pass the damage inflictor for death messages.
// Better solution: Add as parameter to all Killed() functions.
void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib )
{
g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor );
if ( m_pTank != NULL )
{
m_pTank->Use( this, this, USE_OFF, 0 );
m_pTank = NULL;
}
//Remove all powerups and powerup timers
m_flInvincibleFinished = m_flRadsuitFinished = m_flInvisibleFinished = m_flSuperDamageFinished = 0.0;
PowerUpThink();
SetAnimation( PLAYER_DIE );
m_iRespawnFrames = 0;
pev->modelindex = g_ulModelIndexPlayer; // don't use eyes
pev->deadflag = DEAD_DYING;
pev->movetype = MOVETYPE_TOSS;
ClearBits(pev->flags, FL_ONGROUND);
if ( m_iQuakeWeapon == IT_LIGHTNING )
{
PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 );
if ( m_pActiveItem )
((CQuakeGun*)m_pActiveItem)->DestroyEffect();
}
if (pev->velocity.z < 10)
pev->velocity.z += RANDOM_FLOAT(0,300);
// clear out the suit message cache so we don't keep chattering
SetSuitUpdate(NULL, FALSE, 0);
// send "health" update message to zero
m_iClientHealth = 0;
MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev );
WRITE_BYTE( m_iClientHealth );
MESSAGE_END();
// Tell Ammo Hud that the player is dead
MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev );
WRITE_BYTE(0);
WRITE_BYTE(0XFF);
WRITE_BYTE(0xFF);
MESSAGE_END();
// UNDONE: Put this in, but add FFADE_PERMANENT and make fade time 8.8 instead of 4.12
// UTIL_ScreenFade( edict(), Vector(128,0,0), 6, 15, 255, FFADE_OUT | FFADE_MODULATE );
//Quake Player always gibs
if ( pev->health < -40 )
{
pev->solid = SOLID_NOT;
GibMonster(); // This clears pev->model
pev->effects |= EF_NODRAW;
return;
}
DeathSound();
pev->angles.x = 0;
pev->angles.z = 0;
SetThink(&CBasePlayer::PlayerDeathThink);
pev->nextthink = gpGlobals->time + 0.1;
}
// Set the activity based on an event or current state
void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim )
{
int animDesired;
float speed;
char szAnim[64];
speed = pev->velocity.Length2D();
if (pev->flags & FL_FROZEN)
{
speed = 0;
playerAnim = PLAYER_IDLE;
}
switch (playerAnim)
{
case PLAYER_JUMP:
m_IdealActivity = ACT_HOP;
break;
case PLAYER_SUPERJUMP:
m_IdealActivity = ACT_LEAP;
break;
case PLAYER_DIE:
m_IdealActivity = ACT_DIESIMPLE;
m_IdealActivity = GetDeathActivity( );
break;
case PLAYER_ATTACK1:
switch( m_Activity )
{
case ACT_HOVER:
case ACT_SWIM:
case ACT_HOP:
case ACT_LEAP:
case ACT_DIESIMPLE:
m_IdealActivity = m_Activity;
break;
default:
m_IdealActivity = ACT_RANGE_ATTACK1;
break;
}
break;
case PLAYER_IDLE:
case PLAYER_WALK:
if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping
{
m_IdealActivity = m_Activity;
}
else if ( pev->waterlevel > 1 )
{
if ( speed == 0 )
m_IdealActivity = ACT_HOVER;
else
m_IdealActivity = ACT_SWIM;
}
else
{
m_IdealActivity = ACT_WALK;
}
break;
}
switch (m_IdealActivity)
{
case ACT_HOVER:
case ACT_LEAP:
case ACT_SWIM:
case ACT_HOP:
case ACT_DIESIMPLE:
default:
if ( m_Activity == m_IdealActivity)
return;
m_Activity = m_IdealActivity;
animDesired = LookupActivity( m_Activity );
// Already using the desired animation?
if (pev->sequence == animDesired)
return;
pev->gaitsequence = 0;
pev->sequence = animDesired;
pev->frame = 0;
ResetSequenceInfo( );
return;
case ACT_RANGE_ATTACK1:
if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching
strcpy( szAnim, "crouch_shoot_" );
else
strcpy( szAnim, "ref_shoot_" );
strcat( szAnim, m_szAnimExtention );
animDesired = LookupSequence( szAnim );
if (animDesired == -1)
animDesired = 0;
if ( pev->sequence != animDesired || !m_fSequenceLoops )
{
pev->frame = 0;
}
if (!m_fSequenceLoops)
{
pev->effects |= EF_NOINTERP;
}
m_Activity = m_IdealActivity;
pev->sequence = animDesired;
ResetSequenceInfo( );
break;
case ACT_WALK:
if (m_Activity != ACT_RANGE_ATTACK1 || m_fSequenceFinished)
{
if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching
strcpy( szAnim, "crouch_aim_" );
else
strcpy( szAnim, "ref_aim_" );
strcat( szAnim, m_szAnimExtention );
animDesired = LookupSequence( szAnim );
if (animDesired == -1)
animDesired = 0;
m_Activity = ACT_WALK;
}
else
{
animDesired = pev->sequence;
}
}
if ( FBitSet( pev->flags, FL_DUCKING ) )
{
if ( speed == 0)
{
pev->gaitsequence = LookupActivity( ACT_CROUCHIDLE );
// pev->gaitsequence = LookupActivity( ACT_CROUCH );
}
else
{
pev->gaitsequence = LookupActivity( ACT_CROUCH );
}
}
else if ( speed > 220 )
{
pev->gaitsequence = LookupActivity( ACT_RUN );
}
else if (speed > 0)
{
pev->gaitsequence = LookupActivity( ACT_WALK );
}
else
{
// pev->gaitsequence = LookupActivity( ACT_WALK );
pev->gaitsequence = LookupSequence( "deep_idle" );
}
// Already using the desired animation?
if (pev->sequence == animDesired)
return;
//ALERT( at_console, "Set animation to %d\n", animDesired );
// Reset to first frame of desired animation
pev->sequence = animDesired;
pev->frame = 0;
ResetSequenceInfo( );
}
/*
===========
WaterMove
============
*/
#define AIRTIME 12 // lung full of air lasts this many seconds
void CBasePlayer::WaterMove()
{
int air;
if (pev->movetype == MOVETYPE_NOCLIP)
return;
if (pev->health < 0)
return;
// waterlevel 0 - not in water
// waterlevel 1 - feet in water
// waterlevel 2 - waist in water
// waterlevel 3 - head in water
if (pev->waterlevel != 3)
{
// not underwater
// play 'up for air' sound
if (pev->air_finished < gpGlobals->time)
EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade1.wav", 1, ATTN_NORM);
else if (pev->air_finished < gpGlobals->time + 9)
EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade2.wav", 1, ATTN_NORM);
pev->air_finished = gpGlobals->time + AIRTIME;
pev->dmg = 2;
// if we took drowning damage, give it back slowly
if (m_idrowndmg > m_idrownrestored)
{
// set drowning damage bit. hack - dmg_drownrecover actually
// makes the time based damage code 'give back' health over time.
// make sure counter is cleared so we start count correctly.
// NOTE: this actually causes the count to continue restarting
// until all drowning damage is healed.
m_bitsDamageType &= ~DMG_DROWN;
/* m_bitsDamageType |= DMG_DROWNRECOVER;
m_rgbTimeBasedDamage[itbd_DrownRecover] = 0;*/
}
}
else
{ // fully under water
// stop restoring damage while underwater
m_bitsDamageType &= ~DMG_DROWNRECOVER;
m_rgbTimeBasedDamage[itbd_DrownRecover] = 0;
if (pev->air_finished < gpGlobals->time) // drown!
{
if (pev->pain_finished < gpGlobals->time)
{
// take drowning damage
pev->dmg += 1;
if (pev->dmg > 5)
pev->dmg = 5;
TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), pev->dmg, DMG_DROWN);
pev->pain_finished = gpGlobals->time + 1;
// track drowning damage, give it back when
// player finally takes a breath
m_idrowndmg += pev->dmg;
}
}
else
{
m_bitsDamageType &= ~DMG_DROWN;
}
}
if (!pev->waterlevel)
{
if (FBitSet(pev->flags, FL_INWATER))
{
#if 0
// play leave water sound
switch (RANDOM_LONG(0,3))
{
case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break;
case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break;
case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break;
case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break;
}
#endif
ClearBits(pev->flags, FL_INWATER);
}
return;
}
// make bubbles
air = (int)(pev->air_finished - gpGlobals->time);
if (!RANDOM_LONG(0,0x1f) && RANDOM_LONG(0,AIRTIME-1) >= air)
{
switch (RANDOM_LONG(0,3))
{
case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break;
case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break;
case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break;
case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break;
}
}
if (pev->watertype == CONTENT_LAVA) // do damage
{
if (pev->dmgtime < gpGlobals->time)
{
if ( m_iQuakeItems & IT_SUIT )
pev->dmgtime = gpGlobals->time + 1.0;
else
pev->dmgtime = gpGlobals->time + 0.2;
TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 10 * pev->waterlevel, DMG_BURN );
}
}
else if (pev->watertype == CONTENT_SLIME) // do damage
{
if (pev->dmgtime < gpGlobals->time)
{
pev->dmgtime = gpGlobals->time + 1.0;
TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 4 * pev->waterlevel, DMG_ACID );
}
}
if (!FBitSet(pev->flags, FL_INWATER))
{
#if 0
// player enter water sound
if (pev->watertype == CONTENT_WATER)
{
switch (RANDOM_LONG(0,3))
{
case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break;
case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break;
case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break;
case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break;
}
}
#endif
SetBits(pev->flags, FL_INWATER);
pev->dmgtime = 0;
}
if (!FBitSet(pev->flags, FL_WATERJUMP))
pev->velocity = pev->velocity - 0.8 * pev->waterlevel * gpGlobals->frametime * pev->velocity;
}
// TRUE if the player is attached to a ladder
BOOL CBasePlayer::IsOnLadder( void )
{
return (pev->movetype == MOVETYPE_FLY);
}
void CBasePlayer::PlayerDeathThink(void)
{
float flForward;
if (FBitSet(pev->flags, FL_ONGROUND))
{
flForward = pev->velocity.Length() - 20;
if (flForward <= 0)
pev->velocity = g_vecZero;
else
pev->velocity = flForward * pev->velocity.Normalize();
}
if ( m_iQuakeWeapon )
{
DropBackpack();
}
if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING))
{
StudioFrameAdvance( );
m_iRespawnFrames++; // Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands
if ( m_iRespawnFrames < 120 ) // Animations should be no longer than this
return;
}
if (m_fSequenceFinished == 0)
{
StudioFrameAdvance();
return;
}
if (pev->deadflag == DEAD_DYING)
pev->deadflag = DEAD_DEAD;
StopAnimation();
pev->effects |= EF_NOINTERP;
pev->framerate = 0.0;
BOOL fAnyButtonDown = (pev->button & ~IN_SCORE );
// wait for all buttons released
if (pev->deadflag == DEAD_DEAD)
{
if (fAnyButtonDown)
return;
if ( g_pGameRules->FPlayerCanRespawn( this ) )
{
m_fDeadTime = gpGlobals->time;
pev->deadflag = DEAD_RESPAWNABLE;
}
return;
}
// if the player has been dead for one second longer than allowed by forcerespawn,
// forcerespawn isn't on. Send the player off to an intermission camera until they
// choose to respawn.
/* if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->time > (m_fDeadTime + 6) ) && !(m_afPhysicsFlags & PFLAG_OBSERVER) )
{
// go to dead camera.
StartDeathCam();
}*/
// wait for any button down, or mp_forcerespawn is set and the respawn time is up
if (!fAnyButtonDown
&& !( g_pGameRules->IsMultiplayer() && CVAR_GET_FLOAT("mp_forcerespawn") > 0 && (gpGlobals->time > (m_fDeadTime + 5))) )
return;
pev->button = 0;
m_iRespawnFrames = 0;
//ALERT(at_console, "Respawn\n");
respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam.
pev->nextthink = -1;
}
//=========================================================
// StartDeathCam - find an intermission spot and send the
// player off into observer mode
//=========================================================
void CBasePlayer::StartDeathCam( void )
{
edict_t *pSpot, *pNewSpot;
int iRand;
if ( pev->view_ofs == g_vecZero )
{
// don't accept subsequent attempts to StartDeathCam()
return;
}
pSpot = FIND_ENTITY_BY_CLASSNAME( NULL, "info_intermission");
if ( !FNullEnt( pSpot ) )
{
// at least one intermission spot in the world.
iRand = RANDOM_LONG( 0, 3 );
while ( iRand > 0 )
{
pNewSpot = FIND_ENTITY_BY_CLASSNAME( pSpot, "info_intermission");
if ( pNewSpot )
{
pSpot = pNewSpot;
}
iRand--;
}
CopyToBodyQue( pev );
StartObserver( pSpot->v.origin, pSpot->v.v_angle );
}
else
{
// no intermission spot. Push them up in the air, looking down at their corpse
TraceResult tr;
CopyToBodyQue( pev );
UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr );
StartObserver( tr.vecEndPos, UTIL_VecToAngles( tr.vecEndPos - pev->origin ) );
return;
}
}
void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle )
{
m_iHideHUD |= (HIDEHUD_HEALTH | HIDEHUD_WEAPONS);
m_afPhysicsFlags |= PFLAG_OBSERVER;
pev->effects = EF_NODRAW;
pev->view_ofs = g_vecZero;
pev->angles = pev->v_angle = vecViewAngle;
pev->fixangle = TRUE;
pev->solid = SOLID_NOT;
pev->takedamage = DAMAGE_NO;
pev->movetype = MOVETYPE_NONE;
UTIL_SetOrigin( pev, vecPosition );
ClearBits( m_afPhysicsFlags, PFLAG_DUCKING );
ClearBits( pev->flags, FL_DUCKING );
// pev->flags = FL_CLIENT | FL_SPECTATOR; // Should we set Spectator flag? Or is it reserver for people connecting with spectator 1?
pev->deadflag = DEAD_RESPAWNABLE;
// Tell the physics code that this player's now in observer mode
Observer_SetMode(OBS_CHASE_LOCKED);
m_flNextObserverInput = 0;
}
//
// PlayerUse - handles USE keypress
//
#define PLAYER_SEARCH_RADIUS (float)64
void CBasePlayer::PlayerUse ( void )
{
// Was use pressed or released?
if ( ! ((pev->button | m_afButtonPressed | m_afButtonReleased) & IN_USE) )
return;
// Hit Use on a train?
if ( m_afButtonPressed & IN_USE )
{
if ( m_pTank != NULL )
{
// Stop controlling the tank
// TODO: Send HUD Update
m_pTank->Use( this, this, USE_OFF, 0 );
m_pTank = NULL;
return;
}
else
{
if ( m_afPhysicsFlags & PFLAG_ONTRAIN )
{
m_afPhysicsFlags &= ~PFLAG_ONTRAIN;
m_iTrain = TRAIN_NEW|TRAIN_OFF;
return;
}
else
{ // Start controlling the train!
CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity );
if ( pTrain && !(pev->button & IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev) )
{
m_afPhysicsFlags |= PFLAG_ONTRAIN;
m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse);
m_iTrain |= TRAIN_NEW;
EMIT_SOUND( ENT(pev), CHAN_ITEM, "plats/train_use1.wav", 0.8, ATTN_NORM);
return;
}
}
}
}
CBaseEntity *pObject = NULL;
CBaseEntity *pClosest = NULL;
Vector vecLOS;
float flMaxDot = VIEW_FIELD_NARROW;
float flDot;
UTIL_MakeVectors ( pev->v_angle );// so we know which way we are facing
while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL)
{
if (pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE))
{
// !!!PERFORMANCE- should this check be done on a per case basis AFTER we've determined that
// this object is actually usable? This dot is being done for every object within PLAYER_SEARCH_RADIUS
// when player hits the use key. How many objects can be in that area, anyway? (sjb)
vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs));
// This essentially moves the origin of the target to the corner nearest the player to test to see
// if it's "hull" is in the view cone
vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 );
flDot = DotProduct (vecLOS , gpGlobals->v_forward);
if (flDot > flMaxDot )
{// only if the item is in front of the user
pClosest = pObject;
flMaxDot = flDot;
// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot );
}
// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot );
}
}
pObject = pClosest;
// Found an object
if (pObject )
{
//!!!UNDONE: traceline here to prevent USEing buttons through walls
int caps = pObject->ObjectCaps();
if ( m_afButtonPressed & IN_USE )
EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_NORM);
if ( ( (pev->button & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) ||
( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) )
{
if ( caps & FCAP_CONTINUOUS_USE )
m_afPhysicsFlags |= PFLAG_USING;
pObject->Use( this, this, USE_SET, 1 );
}
// UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away
else if ( (m_afButtonReleased & IN_USE) && (pObject->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use
{
pObject->Use( this, this, USE_SET, 0 );
}
}
else
{
if ( m_afButtonPressed & IN_USE )
EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_NORM);
}
}
void CBasePlayer::Jump()
{
Vector vecWallCheckDir;// direction we're tracing a line to find a wall when walljumping
Vector vecAdjustedVelocity;
Vector vecSpot;
TraceResult tr;
if (FBitSet(pev->flags, FL_WATERJUMP))
return;
if (pev->waterlevel >= 2)
{
return;
}
// jump velocity is sqrt( height * gravity * 2)
// If this isn't the first frame pressing the jump button, break out.
if ( !FBitSet( m_afButtonPressed, IN_JUMP ) )
return; // don't pogo stick
if ( !(pev->flags & FL_ONGROUND) || !pev->groundentity )
{
return;
}
// many features in this function use v_forward, so makevectors now.
UTIL_MakeVectors (pev->angles);
SetAnimation( PLAYER_JUMP );
if ( FBitSet(pev->flags, FL_DUCKING ) || FBitSet(m_afPhysicsFlags, PFLAG_DUCKING) )
{
if ( m_fLongJump && (pev->button & IN_DUCK) && gpGlobals->time - m_flDuckTime < 1 && pev->velocity.Length() > 50 )
{// If jump pressed within a second of duck while moving, long jump!
SetAnimation( PLAYER_SUPERJUMP );
}
}
// If you're standing on a conveyor, add it's velocity to yours (for momentum)
entvars_t *pevGround = VARS(pev->groundentity);
if ( pevGround && (pevGround->flags & FL_CONVEYOR) )
{
pev->velocity = pev->velocity + pev->basevelocity;
}
}
// This is a glorious hack to find free space when you've crouched into some solid space
// Our crouching collisions do not work correctly for some reason and this is easier
// than fixing the problem :(
void FixPlayerCrouchStuck( edict_t *pPlayer )
{
TraceResult trace;
// Move up as many as 18 pixels if the player is stuck.
for ( int i = 0; i < 18; i++ )
{
UTIL_TraceHull( pPlayer->v.origin, pPlayer->v.origin, dont_ignore_monsters, head_hull, pPlayer, &trace );
if ( trace.fStartSolid )
pPlayer->v.origin.z ++;
else
break;
}
}
void CBasePlayer::Duck( )
{
if (pev->button & IN_DUCK)
{
SetAnimation( PLAYER_WALK );
}
}
//
// ID's player as such.
//
int CBasePlayer::Classify ( void )
{
return CLASS_PLAYER;
}
void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore )
{
// Positive score always adds
if ( score < 0 )
{
if ( !bAllowNegativeScore )
{
if ( pev->frags < 0 ) // Can't go more negative
return;
if ( -score > pev->frags ) // Will this go negative?
{
score = -pev->frags; // Sum will be 0
}
}
}
pev->frags += score;
MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo );
WRITE_BYTE( ENTINDEX(edict()) );
WRITE_SHORT( pev->frags );
WRITE_SHORT( m_iDeaths );
WRITE_SHORT( pev->team );
MESSAGE_END();
}
void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore )
{
int index = entindex();
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
if ( pPlayer && i != index )
{
if ( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE )
{
pPlayer->AddPoints( score, bAllowNegativeScore );
}
}
}
}
#if 0
void CBasePlayer::CheckWeapon(void)
{
// play a weapon idle anim if it's time!
if ( gpGlobals->time > m_flTimeWeaponIdle )
{
WeaponIdle ( );
}
}
#endif
// play a footstep if it's time - this will eventually be frame-based. not time based.
#define STEP_CONCRETE 0 // default step sound
#define STEP_METAL 1 // metal floor
#define STEP_DIRT 2 // dirt, sand, rock
#define STEP_VENT 3 // ventillation duct
#define STEP_GRATE 4 // metal grating
#define STEP_TILE 5 // floor tiles
#define STEP_SLOSH 6 // shallow liquid puddle
#define STEP_WADE 7 // wading in liquid
#define STEP_LADDER 8 // climbing ladder
// Play correct step sound for material we're on or in
void CBasePlayer :: PlayStepSound(int step, float fvol)
{
return;
static int iSkipStep = 0;
if ( !g_pGameRules->PlayFootstepSounds( this, fvol ) )
return;
// irand - 0,1 for right foot, 2,3 for left foot
// used to alternate left and right foot
int irand = RANDOM_LONG(0,1) + (m_iStepLeft * 2);
m_iStepLeft = !m_iStepLeft;
switch (step)
{
default:
case STEP_CONCRETE:
switch (irand)
{
// right foot
case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM); break;
// left foot
case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM); break;
}
break;
case STEP_METAL:
switch(irand)
{
// right foot
case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM); break;
// left foot
case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM); break;
}
break;
case STEP_DIRT:
switch(irand)
{
// right foot
case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM); break;
// left foot
case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM); break;
}
break;
case STEP_VENT:
switch(irand)
{
// right foot
case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM); break;
// left foot
case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM); break;
}
break;
case STEP_GRATE:
switch(irand)
{
// right foot
case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM); break;
// left foot
case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM); break;
}
break;
case STEP_TILE:
if (!RANDOM_LONG(0,4))
irand = 4;
switch(irand)
{
// right foot
case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM); break;
// left foot
case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM); break;
case 4: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM); break;
}
break;
case STEP_SLOSH:
switch(irand)
{
// right foot
case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM); break;
// left foot
case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM); break;
}
break;
case STEP_WADE:
if ( iSkipStep == 0 )
{
iSkipStep++;
break;
}
if ( iSkipStep++ == 3 )
{
iSkipStep = 0;
}
switch (irand)
{
// right foot
case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM); break;
// left foot
case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM); break;
}
break;
case STEP_LADDER:
switch(irand)
{
// right foot
case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM); break;
case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM); break;
// left foot
case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM); break;
case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM); break;
}
break;
}
}
// Simple mapping from texture type character to step type
int MapTextureTypeStepType(char chTextureType)
{
switch (chTextureType)
{
default:
case CHAR_TEX_CONCRETE: return STEP_CONCRETE;
case CHAR_TEX_METAL: return STEP_METAL;
case CHAR_TEX_DIRT: return STEP_DIRT;
case CHAR_TEX_VENT: return STEP_VENT;
case CHAR_TEX_GRATE: return STEP_GRATE;
case CHAR_TEX_TILE: return STEP_TILE;
case CHAR_TEX_SLOSH: return STEP_SLOSH;
}
}
// Play left or right footstep based on material player is on or in
void CBasePlayer :: UpdateStepSound( void )
{
int fWalking;
float fvol;
char szbuffer[64];
const char *pTextureName;
Vector start, end;
float rgfl1[3];
float rgfl2[3];
Vector knee;
Vector feet;
Vector center;
float height;
float speed;
float velrun;
float velwalk;
float flduck;
int fLadder;
int step;
if (gpGlobals->time <= m_flTimeStepSound)
return;
if (pev->flags & FL_FROZEN)
return;
speed = pev->velocity.Length();
// determine if we are on a ladder
fLadder = IsOnLadder();
// UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!!
if (FBitSet(pev->flags, FL_DUCKING) || fLadder)
{
velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow
velrun = 80; // UNDONE: Move walking to server
flduck = 0.1;
}
else
{
velwalk = 120;
velrun = 210;
flduck = 0.0;
}
// ALERT (at_console, "vel: %f\n", vecVel.Length());
// if we're on a ladder or on the ground, and we're moving fast enough,
// play step sound. Also, if m_flTimeStepSound is zero, get the new
// sound right away - we just started moving in new level.
if ((fLadder || FBitSet (pev->flags, FL_ONGROUND)) && pev->velocity != g_vecZero
&& (speed >= velwalk || !m_flTimeStepSound))
{
SetAnimation( PLAYER_WALK );
fWalking = speed < velrun;
center = knee = feet = (pev->absmin + pev->absmax) * 0.5;
height = pev->absmax.z - pev->absmin.z;
knee.z = pev->absmin.z + height * 0.2;
feet.z = pev->absmin.z;
// find out what we're stepping in or on...
if (fLadder)
{
step = STEP_LADDER;
fvol = 0.35;
m_flTimeStepSound = gpGlobals->time + 0.35;
}
else if ( UTIL_PointContents ( knee ) == CONTENTS_WATER )
{
step = STEP_WADE;
fvol = 0.65;
m_flTimeStepSound = gpGlobals->time + 0.6;
}
else if (UTIL_PointContents ( feet ) == CONTENTS_WATER )
{
step = STEP_SLOSH;
fvol = fWalking ? 0.2 : 0.5;
m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3;
}
else
{
// find texture under player, if different from current texture,
// get material type
start = end = center; // center point of player BB
start.z = end.z = pev->absmin.z; // copy zmin
start.z += 4.0; // extend start up
end.z -= 24.0; // extend end down
start.CopyToArray(rgfl1);
end.CopyToArray(rgfl2);
pTextureName = TRACE_TEXTURE( ENT( pev->groundentity), rgfl1, rgfl2 );
if ( pTextureName )
{
// strip leading '-0' or '{' or '!'
if (*pTextureName == '-')
pTextureName += 2;
if (*pTextureName == '{' || *pTextureName == '!')
pTextureName++;
if (_strnicmp(pTextureName, m_szTextureName, CBTEXTURENAMEMAX-1))
{
// current texture is different from texture player is on...
// set current texture
strcpy(szbuffer, pTextureName);
szbuffer[CBTEXTURENAMEMAX - 1] = 0;
strcpy(m_szTextureName, szbuffer);
// ALERT ( at_aiconsole, "texture: %s\n", m_szTextureName );
// get texture type
m_chTextureType = TEXTURETYPE_Find(m_szTextureName);
}
}
step = MapTextureTypeStepType(m_chTextureType);
switch (m_chTextureType)
{
default:
case CHAR_TEX_CONCRETE:
fvol = fWalking ? 0.2 : 0.5;
m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3;
break;
case CHAR_TEX_METAL:
fvol = fWalking ? 0.2 : 0.5;
m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3;
break;
case CHAR_TEX_DIRT:
fvol = fWalking ? 0.25 : 0.55;
m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3;
break;
case CHAR_TEX_VENT:
fvol = fWalking ? 0.4 : 0.7;
m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3;
break;
case CHAR_TEX_GRATE:
fvol = fWalking ? 0.2 : 0.5;
m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3;
break;
case CHAR_TEX_TILE:
fvol = fWalking ? 0.2 : 0.5;
m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3;
break;
case CHAR_TEX_SLOSH:
fvol = fWalking ? 0.2 : 0.5;
m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3;
break;
}
}
m_flTimeStepSound += flduck; // slower step time if ducking
// play the sound
// 35% volume if ducking
if ( pev->flags & FL_DUCKING )
fvol *= 0.35;
}
}
void CBasePlayer::PowerUpThink(void)
{
int iPowerUp = 0;
bool bUpdate = FALSE;
//Quad time ran out
if ( m_iQuakeItems & IT_QUAD && m_flSuperDamageFinished < gpGlobals->time )
{
//Clear the glowing shell effect
pev->renderfx = kRenderFxNone;
//Reset quad timer
m_flSuperDamageFinished = 0.0;
m_bPlayedQuadSound = FALSE;
//Remove the powerup
m_iQuakeItems &= ~IT_QUAD;
//We have other powerups, choose the one with more time remaining
if ( m_iQuakeItems & IT_INVISIBILITY || m_iQuakeItems & IT_INVULNERABILITY )
{
if ( m_iQuakeItems & IT_INVULNERABILITY )
{
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 255, 128, 0 ); // RGB
pev->renderamt = 100; // Shell size
iPowerUp = 2;
}
if ( m_iQuakeItems & IT_INVISIBILITY )
{
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 128, 128, 128 ); // RGB
pev->renderamt = 5; // Shell size
}
}
bUpdate = TRUE;
}
if ( m_iQuakeItems & IT_QUAD && m_flSuperDamageFinished <= gpGlobals->time + 3 && !m_bPlayedQuadSound )
{
ClientPrint(pev, HUD_PRINTNOTIFY, "#Quad_Damage_Off" );
EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/damage2.wav", 1, ATTN_NORM );
m_bPlayedQuadSound = TRUE;
}
//Env Suit time ran out
if ( m_iQuakeItems & IT_SUIT && m_flRadsuitFinished < gpGlobals->time )
{
m_flRadsuitFinished = 0.0;
m_bPlayedEnvSound = FALSE;
//Remove the powerup
m_iQuakeItems &= ~IT_SUIT;
//We have other powerups, choose the one with more time remaining
if ( m_iQuakeItems & IT_QUAD || m_iQuakeItems & IT_INVULNERABILITY )
{
if ( m_iQuakeItems & IT_INVULNERABILITY && m_iQuakeItems & IT_QUAD )
{
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 255, 125, 255 ); // RGB
pev->renderamt = 100; // Shell size
}
else if ( m_flSuperDamageFinished > m_flInvincibleFinished )
{
pev->renderfx = kRenderFxNone;
pev->rendermode = kRenderNormal;
pev->renderamt = 255;
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 128, 128, 255 ); // RGB
pev->renderamt = 100; // Shell size
}
else
{
pev->renderfx = kRenderFxNone;
pev->rendermode = kRenderNormal;
pev->renderamt = 255;
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 255, 128, 0 ); // RGB
pev->renderamt = 100; // Shell size
}
}
else //Clear the invisi screen effect
{
pev->renderfx = kRenderFxNone;
pev->rendermode = kRenderNormal;
pev->renderamt = 255;
}
}
if ( m_iQuakeItems & IT_SUIT && m_flRadsuitFinished <= gpGlobals->time + 3 && !m_bPlayedEnvSound )
{
ClientPrint(pev, HUD_PRINTNOTIFY, "#BioSuit_Off" );
EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/suit2.wav", 1, ATTN_NORM );
m_bPlayedEnvSound = TRUE;
}
//Invisibility time ran out
if ( m_iQuakeItems & IT_INVISIBILITY && m_flInvisibleFinished < gpGlobals->time )
{
//Reset the invi timer
m_flInvisibleFinished = 0.0;
m_bPlayedProtectSound = FALSE;
//Remove the powerup
m_iQuakeItems &= ~IT_INVISIBILITY;
pev->renderfx = kRenderFxNone;
pev->rendermode = kRenderNormal;
pev->renderamt = 255;
//We have other powerups, choose the one with more time remaining
if ( m_iQuakeItems & IT_QUAD || m_iQuakeItems & IT_INVULNERABILITY )
{
if ( m_iQuakeItems & IT_QUAD && m_iQuakeItems & IT_INVULNERABILITY )
{
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 255, 125, 255 ); // RGB
pev->renderamt = 100; // Shell size
}
else if ( m_flSuperDamageFinished > m_flInvincibleFinished )
{
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 128, 128, 255 ); // RGB
pev->renderamt = 100; // Shell size
}
else
{
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 255, 128, 0 ); // RGB
pev->renderamt = 100; // Shell size
}
}
else //Clear the invisi screen effect
{
pev->renderfx = kRenderFxNone;
pev->rendermode = kRenderNormal;
pev->renderamt = 255;
}
//Forces our view model to re-appear
W_SetCurrentAmmo();
}
if ( m_iQuakeItems & IT_INVISIBILITY && m_flInvisibleFinished <= gpGlobals->time + 3 && !m_bPlayedInvSound )
{
ClientPrint(pev, HUD_PRINTNOTIFY, "#Ring_Shadows_Off" );
EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/inv2.wav", 1, ATTN_NORM );
m_bPlayedInvSound = TRUE;
}
//666 time ran out
if ( m_iQuakeItems & IT_INVULNERABILITY && m_flInvincibleFinished < gpGlobals->time )
{
//Clear the glowing shell effect
pev->renderfx = kRenderFxNone;
//Reset 666 timer
m_flInvincibleFinished = 0.0;
m_bPlayedProtectSound = FALSE;
//Remove the powerup
m_iQuakeItems &= ~IT_INVULNERABILITY;
//We have other powerups, choose the one with more time remaining
if ( m_iQuakeItems & IT_QUAD || m_iQuakeItems & IT_INVISIBILITY )
{
if ( m_flSuperDamageFinished > m_flInvisibleFinished )
{
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 128, 128, 255 ); // RGB
pev->renderamt = 100; // Shell size
iPowerUp = 1;
}
else
{
pev->renderfx = kRenderFxGlowShell;
pev->rendercolor = Vector( 128, 128, 128 ); // RGB
pev->renderamt = 5; // Shell size
}
}
bUpdate = TRUE;
}
if ( m_iQuakeItems & IT_INVULNERABILITY && m_flInvincibleFinished <= gpGlobals->time + 3 && !m_bPlayedProtectSound )
{
ClientPrint(pev, HUD_PRINTNOTIFY, "#Protection_Off" );
EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/protect2.wav", 1, ATTN_NORM );
m_bPlayedProtectSound = TRUE;
}
if ( bUpdate )
{
W_SetCurrentAmmo();
PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE,
edict(), g_usPowerUp, 0, (float *)&g_vecZero, (float *)&g_vecZero,
(float)iPowerUp, 0.0, entindex(), pev->team, 1, 0 );
}
}
#define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing
#define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible
#define CLIMB_SPEED_DEC 15 // climbing deceleration rate
#define CLIMB_PUNCH_X -7 // how far to 'punch' client X axis when climbing
#define CLIMB_PUNCH_Z 7 // how far to 'punch' client Z axis when climbing
void CBasePlayer::PreThink(void)
{
int buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame
// Debounced button codes for pressed/released
// UNDONE: Do we need auto-repeat?
m_afButtonPressed = buttonsChanged & pev->button; // The changed ones still down are "pressed"
m_afButtonReleased = buttonsChanged & (~pev->button); // The ones not down are "released"
g_pGameRules->PlayerThink( this );
if ( g_fGameOver )
return; // intermission or finale
UTIL_MakeVectors(pev->v_angle); // is this still used?
ItemPreFrame( );
WaterMove();
if ( g_pGameRules && g_pGameRules->FAllowFlashlight() )
m_iHideHUD &= ~HIDEHUD_FLASHLIGHT;
else
m_iHideHUD |= HIDEHUD_FLASHLIGHT;
// JOHN: checks if new client data (for HUD and view control) needs to be sent to the client
UpdateClientData();
CheckTimeBasedDamage();
CheckSuitUpdate();
// Observer Button Handling
if ( pev->iuser1 != 0 )
{
Observer_HandleButtons();
return;
}
//Run Powerup think (removes powerups over time, etc)
PowerUpThink();
// Dead think only if they're not an observer
if (pev->deadflag >= DEAD_DYING)
{
PlayerDeathThink();
return;
}
// So the correct flags get sent to client asap.
//
if ( m_afPhysicsFlags & PFLAG_ONTRAIN )
pev->flags |= FL_ONTRAIN;
else
pev->flags &= ~FL_ONTRAIN;
// Train speed control
if ( m_afPhysicsFlags & PFLAG_ONTRAIN )
{
CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity );
float vel;
if ( !pTrain )
{
TraceResult trainTrace;
// Maybe this is on the other side of a level transition
UTIL_TraceLine( pev->origin, pev->origin + Vector(0,0,-38), ignore_monsters, ENT(pev), &trainTrace );
// HACKHACK - Just look for the func_tracktrain classname
if ( trainTrace.flFraction != 1.0 && trainTrace.pHit )
pTrain = CBaseEntity::Instance( trainTrace.pHit );
if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev) )
{
//ALERT( at_error, "In train mode with no train!\n" );
m_afPhysicsFlags &= ~PFLAG_ONTRAIN;
m_iTrain = TRAIN_NEW|TRAIN_OFF;
return;
}
}
else if ( !FBitSet( pev->flags, FL_ONGROUND ) || FBitSet( pTrain->pev->spawnflags, SF_TRACKTRAIN_NOCONTROL ) || (pev->button & (IN_MOVELEFT|IN_MOVERIGHT) ) )
{
// Turn off the train if you jump, strafe, or the train controls go dead
m_afPhysicsFlags &= ~PFLAG_ONTRAIN;
m_iTrain = TRAIN_NEW|TRAIN_OFF;
return;
}
pev->velocity = g_vecZero;
vel = 0;
if ( m_afButtonPressed & IN_FORWARD )
{
vel = 1;
pTrain->Use( this, this, USE_SET, (float)vel );
}
else if ( m_afButtonPressed & IN_BACK )
{
vel = -1;
pTrain->Use( this, this, USE_SET, (float)vel );
}
if (vel)
{
m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse);
m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW;
}
} else if (m_iTrain & TRAIN_ACTIVE)
m_iTrain = TRAIN_NEW; // turn off train
if (pev->button & IN_JUMP)
{
// If on a ladder, jump off the ladder
// else Jump
Jump();
}
// QUAKECLASSIC
// Duck removed
// play a footstep if it's time - this will eventually be frame-based. not time based.
UpdateStepSound();
if ( !FBitSet ( pev->flags, FL_ONGROUND ) )
{
m_flFallVelocity = -pev->velocity.z;
}
// StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating?
// Clear out ladder pointer
m_hEnemy = NULL;
if ( m_afPhysicsFlags & PFLAG_ONBARNACLE )
{
pev->velocity = g_vecZero;
}
}
/* Time based Damage works as follows:
1) There are several types of timebased damage:
#define DMG_PARALYZE (1 << 14) // slows affected creature down
#define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad
#define DMG_POISON (1 << 16) // blood poisioning
#define DMG_RADIATION (1 << 17) // radiation exposure
#define DMG_DROWNRECOVER (1 << 18) // drown recovery
#define DMG_ACID (1 << 19) // toxic chemicals or acid burns
#define DMG_SLOWBURN (1 << 20) // in an oven
#define DMG_SLOWFREEZE (1 << 21) // in a subzero freezer
2) A new hit inflicting tbd restarts the tbd counter - each monster has an 8bit counter,
per damage type. The counter is decremented every second, so the maximum time
an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius
of a damaging effect like fire, nervegas, radiation will continually reset the counter to max.
3) Every second that a tbd counter is running, the player takes damage. The damage
is determined by the type of tdb.
Paralyze - 1/2 movement rate, 30 second duration.
Nervegas - 5 points per second, 16 second duration = 80 points max dose.
Poison - 2 points per second, 25 second duration = 50 points max dose.
Radiation - 1 point per second, 50 second duration = 50 points max dose.
Drown - 5 points per second, 2 second duration.
Acid/Chemical - 5 points per second, 10 second duration = 50 points max.
Burn - 10 points per second, 2 second duration.
Freeze - 3 points per second, 10 second duration = 30 points max.
4) Certain actions or countermeasures counteract the damaging effects of tbds:
Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body
- recharged by suit recharger
Air In Lungs - drowning damage is done to air in lungs first, then to body
- recharged by poking head out of water
- 10 seconds if swiming fast
Air In SCUBA - drowning damage is done to air in tanks first, then to body
- 2 minutes in tanks. Need new tank once empty.
Radiation Syringe - Each syringe full provides protection vs one radiation dosage
Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison).
Health kit - Immediate stop to acid/chemical, fire or freeze damage.
Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage.
*/
// If player is taking time based damage, continue doing damage to player -
// this simulates the effect of being poisoned, gassed, dosed with radiation etc -
// anything that continues to do damage even after the initial contact stops.
// Update all time based damage counters, and shut off any that are done.
// The m_bitsDamageType bit MUST be set if any damage is to be taken.
// This routine will detect the initial on value of the m_bitsDamageType
// and init the appropriate counter. Only processes damage every second.
//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage
//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval
//#define NERVEGAS_DURATION 16
//#define NERVEGAS_DAMAGE 5.0
//#define POISON_DURATION 25
//#define POISON_DAMAGE 2.0
//#define RADIATION_DURATION 50
//#define RADIATION_DAMAGE 1.0
//#define ACID_DURATION 10
//#define ACID_DAMAGE 5.0
//#define SLOWBURN_DURATION 2
//#define SLOWBURN_DAMAGE 1.0
//#define SLOWFREEZE_DURATION 1.0
//#define SLOWFREEZE_DAMAGE 3.0
/* */
void CBasePlayer::CheckTimeBasedDamage()
{
int i;
BYTE bDuration = 0;
static float gtbdPrev = 0.0;
if (!(m_bitsDamageType & DMG_TIMEBASED))
return;
// only check for time based damage approx. every 2 seconds
if (abs(gpGlobals->time - m_tbdPrev) < 2.0)
return;
m_tbdPrev = gpGlobals->time;
for (i = 0; i < CDMG_TIMEBASED; i++)
{
// make sure bit is set for damage type
if (m_bitsDamageType & (DMG_PARALYZE << i))
{
switch (i)
{
case itbd_Paralyze:
// UNDONE - flag movement as half-speed
bDuration = PARALYZE_DURATION;
break;
case itbd_NerveGas:
// TakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC);
bDuration = NERVEGAS_DURATION;
break;
case itbd_Poison:
TakeDamage(pev, pev, POISON_DAMAGE, DMG_GENERIC);
bDuration = POISON_DURATION;
break;
case itbd_Radiation:
// TakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC);
bDuration = RADIATION_DURATION;
break;
case itbd_DrownRecover:
// NOTE: this hack is actually used to RESTORE health
// after the player has been drowning and finally takes a breath
if (m_idrowndmg > m_idrownrestored)
{
int idif = min(m_idrowndmg - m_idrownrestored, 10);
TakeHealth(idif, DMG_GENERIC);
m_idrownrestored += idif;
}
bDuration = 4; // get up to 5*10 = 50 points back
break;
case itbd_Acid:
// TakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC);
bDuration = ACID_DURATION;
break;
case itbd_SlowBurn:
// TakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC);
bDuration = SLOWBURN_DURATION;
break;
case itbd_SlowFreeze:
// TakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC);
bDuration = SLOWFREEZE_DURATION;
break;
default:
bDuration = 0;
}
if (m_rgbTimeBasedDamage[i])
{
// use up an antitoxin on poison or nervegas after a few seconds of damage
if (((i == itbd_NerveGas) && (m_rgbTimeBasedDamage[i] < NERVEGAS_DURATION)) ||
((i == itbd_Poison) && (m_rgbTimeBasedDamage[i] < POISON_DURATION)))
{
if (m_rgItems[ITEM_ANTIDOTE])
{
m_rgbTimeBasedDamage[i] = 0;
m_rgItems[ITEM_ANTIDOTE]--;
SetSuitUpdate("!HEV_HEAL4", FALSE, SUIT_REPEAT_OK);
}
}
// decrement damage duration, detect when done.
if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0)
{
m_rgbTimeBasedDamage[i] = 0;
// if we're done, clear damage bits
m_bitsDamageType &= ~(DMG_PARALYZE << i);
}
}
else
// first time taking this damage type - init damage duration
m_rgbTimeBasedDamage[i] = bDuration;
}
}
}
/*
THE POWER SUIT
The Suit provides 3 main functions: Protection, Notification and Augmentation.
Some functions are automatic, some require power.
The player gets the suit shortly after getting off the train in C1A0 and it stays
with him for the entire game.
Protection
Heat/Cold
When the player enters a hot/cold area, the heating/cooling indicator on the suit
will come on and the battery will drain while the player stays in the area.
After the battery is dead, the player starts to take damage.
This feature is built into the suit and is automatically engaged.
Radiation Syringe
This will cause the player to be immune from the effects of radiation for N seconds. Single use item.
Anti-Toxin Syringe
This will cure the player from being poisoned. Single use item.
Health
Small (1st aid kits, food, etc.)
Large (boxes on walls)
Armor
The armor works using energy to create a protective field that deflects a
percentage of damage projectile and explosive attacks. After the armor has been deployed,
it will attempt to recharge itself to full capacity with the energy reserves from the battery.
It takes the armor N seconds to fully charge.
Notification (via the HUD)
x Health
x Ammo
x Automatic Health Care
Notifies the player when automatic healing has been engaged.
x Geiger counter
Classic Geiger counter sound and status bar at top of HUD
alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal.
x Poison
Armor
Displays the current level of armor.
Augmentation
Reanimation (w/adrenaline)
Causes the player to come back to life after he has been dead for 3 seconds.
Will not work if player was gibbed. Single use.
Long Jump
Used by hitting the ??? key(s). Caused the player to further than normal.
SCUBA
Used automatically after picked up and after player enters the water.
Works for N seconds. Single use.
Things powered by the battery
Armor
Uses N watts for every M units of damage.
Heat/Cool
Uses N watts for every second in hot/cold area.
Long Jump
Uses N watts for every jump.
Alien Cloak
Uses N watts for each use. Each use lasts M seconds.
Alien Shield
Augments armor. Reduces Armor drain by one half
*/
// if in range of radiation source, ping geiger counter
#define GEIGERDELAY 0.25
void CBasePlayer :: UpdateGeigerCounter( void )
{
BYTE range;
// delay per update ie: don't flood net with these msgs
if (gpGlobals->time < m_flgeigerDelay)
return;
m_flgeigerDelay = gpGlobals->time + GEIGERDELAY;
// send range to radition source to client
range = (BYTE) (m_flgeigerRange / 4);
if (range != m_igeigerRangePrev)
{
m_igeigerRangePrev = range;
MESSAGE_BEGIN( MSG_ONE, gmsgGeigerRange, NULL, pev );
WRITE_BYTE( range );
MESSAGE_END();
}
// reset counter and semaphore
if (!RANDOM_LONG(0,3))
m_flgeigerRange = 1000;
}
/*
================
CheckSuitUpdate
Play suit update if it's time
================
*/
#define SUITUPDATETIME 3.5
#define SUITFIRSTUPDATETIME 0.1
void CBasePlayer::CheckSuitUpdate()
{
int i;
int isentence = 0;
int isearch = m_iSuitPlayNext;
// Ignore suit updates if no suit
if ( !(pev->weapons & (1<<IT_AXE)) )
return;
// if in range of radiation source, ping geiger counter
UpdateGeigerCounter();
if ( g_pGameRules->IsMultiplayer() )
{
// don't bother updating HEV voice in multiplayer.
return;
}
if ( gpGlobals->time >= m_flSuitUpdate && m_flSuitUpdate > 0)
{
// play a sentence off of the end of the queue
for (i = 0; i < CSUITPLAYLIST; i++)
{
if (isentence = m_rgSuitPlayList[isearch])
break;
if (++isearch == CSUITPLAYLIST)
isearch = 0;
}
if (isentence)
{
m_rgSuitPlayList[isearch] = 0;
if (isentence > 0)
{
// play sentence number
char sentence[CBSENTENCENAME_MAX+1];
strcpy(sentence, "!");
strcat(sentence, gszallsentencenames[isentence]);
EMIT_SOUND_SUIT(ENT(pev), sentence);
}
else
{
// play sentence group
EMIT_GROUPID_SUIT(ENT(pev), -isentence);
}
m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME;
}
else
// queue is empty, don't check
m_flSuitUpdate = 0;
}
}
// add sentence to suit playlist queue. if fgroup is true, then
// name is a sentence group (HEV_AA), otherwise name is a specific
// sentence name ie: !HEV_AA0. If iNoRepeat is specified in
// seconds, then we won't repeat playback of this word or sentence
// for at least that number of seconds.
void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime)
{
int i;
int isentence;
int iempty = -1;
// Ignore suit updates if no suit
if ( !(pev->weapons & (1<<IT_AXE)) )
return;
if ( g_pGameRules->IsMultiplayer() )
{
// due to static channel design, etc. We don't play HEV sounds in multiplayer right now.
return;
}
// if name == NULL, then clear out the queue
if (!name)
{
for (i = 0; i < CSUITPLAYLIST; i++)
m_rgSuitPlayList[i] = 0;
return;
}
// get sentence or group number
if (!fgroup)
{
isentence = SENTENCEG_Lookup(name, NULL);
if (isentence < 0)
return;
}
else
// mark group number as negative
isentence = -SENTENCEG_GetIndex(name);
// check norepeat list - this list lets us cancel
// the playback of words or sentences that have already
// been played within a certain time.
for (i = 0; i < CSUITNOREPEAT; i++)
{
if (isentence == m_rgiSuitNoRepeat[i])
{
// this sentence or group is already in
// the norepeat list
if (m_rgflSuitNoRepeatTime[i] < gpGlobals->time)
{
// norepeat time has expired, clear it out
m_rgiSuitNoRepeat[i] = 0;
m_rgflSuitNoRepeatTime[i] = 0.0;
iempty = i;
break;
}
else
{
// don't play, still marked as norepeat
return;
}
}
// keep track of empty slot
if (!m_rgiSuitNoRepeat[i])
iempty = i;
}
// sentence is not in norepeat list, save if norepeat time was given
if (iNoRepeatTime)
{
if (iempty < 0)
iempty = RANDOM_LONG(0, CSUITNOREPEAT-1); // pick random slot to take over
m_rgiSuitNoRepeat[iempty] = isentence;
m_rgflSuitNoRepeatTime[iempty] = iNoRepeatTime + gpGlobals->time;
}
// find empty spot in queue, or overwrite last spot
m_rgSuitPlayList[m_iSuitPlayNext++] = isentence;
if (m_iSuitPlayNext == CSUITPLAYLIST)
m_iSuitPlayNext = 0;
if (m_flSuitUpdate <= gpGlobals->time)
{
if (m_flSuitUpdate == 0)
// play queue is empty, don't delay too long before playback
m_flSuitUpdate = gpGlobals->time + SUITFIRSTUPDATETIME;
else
m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME;
}
}
/*
================
CheckPowerups
Check for turning off powerups
GLOBALS ASSUMED SET: g_ulModelIndexPlayer
================
*/
static void
CheckPowerups(entvars_t *pev)
{
if (pev->health <= 0)
return;
pev->modelindex = g_ulModelIndexPlayer; // don't use eyes
}
void CBasePlayer::PostThink()
{
if ( g_fGameOver )
goto pt_end; // intermission or finale
if (!IsAlive())
goto pt_end;
// Handle Tank controlling
if ( m_pTank != NULL )
{ // if they've moved too far from the gun, or selected a weapon, unuse the gun
if ( m_pTank->OnControls( pev ) && !pev->weaponmodel )
{
m_pTank->Use( this, this, USE_SET, 2 ); // try fire the gun
}
else
{ // they've moved off the platform
m_pTank->Use( this, this, USE_OFF, 0 );
m_pTank = NULL;
}
}
// do weapon stuff
ItemPostFrame( );
// check to see if player landed hard enough to make a sound
// falling farther than half of the maximum safe distance, but not as far a max safe distance will
// play a bootscrape sound, and no damage will be inflicted. Fallling a distance shorter than half
// of maximum safe distance will make no sound. Falling farther than max safe distance will play a
// fallpain sound, and damage will be inflicted based on how far the player fell
if ( (FBitSet(pev->flags, FL_ONGROUND)) && (pev->health > 0) && m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD )
{
float fvol = 0.5;
// ALERT ( at_console, "%f\n", m_flFallVelocity );
if (pev->watertype == CONTENT_WATER)
{
// Did he hit the world or a non-moving entity?
// BUG - this happens all the time in water, especially when
// BUG - water has current force
// if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 )
// EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM);
}
else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED )
{// after this point, we start doing damage
float flFallDamage = g_pGameRules->FlPlayerFallDamage( this );
if ( flFallDamage > pev->health )
{//splat
// note: play on item channel because we play footstep landing on body channel
EMIT_SOUND(ENT(pev), CHAN_ITEM, "common/bodysplat.wav", 1, ATTN_NORM);
}
if ( flFallDamage > 0 )
{
TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL );
pev->punchangle.x = 0;
}
fvol = 1.0;
}
else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 )
{
// EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_jumpland2.wav", 1, ATTN_NORM);
fvol = 0.85;
}
else if ( m_flFallVelocity < PLAYER_MIN_BOUNCE_SPEED )
{
fvol = 0;
}
if ( fvol > 0.0 )
{
// get current texture under player right away
m_flTimeStepSound = 0;
UpdateStepSound();
}
if ( IsAlive() )
{
SetAnimation( PLAYER_WALK );
}
}
if (FBitSet(pev->flags, FL_ONGROUND))
{
//if (m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer())
// TODO Make noise
m_flFallVelocity = 0;
}
// select the proper animation for the player character
if ( IsAlive() )
{
if (!pev->velocity.x && !pev->velocity.y)
SetAnimation( PLAYER_IDLE );
else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND)))
SetAnimation( PLAYER_WALK );
else if (pev->waterlevel > 1)
SetAnimation( PLAYER_WALK );
}
StudioFrameAdvance( );
CheckPowerups(pev);
pt_end:
// Decay timers on weapons
// go through all of the weapons and make a list of the ones to pack
for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ )
{
if ( m_rgpPlayerItems[ i ] )
{
CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ];
while ( pPlayerItem )
{
CBasePlayerWeapon *gun;
gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr();
if ( gun && gun->UseDecrement() )
{
gun->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack - gpGlobals->frametime, -1.0 );
gun->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack - gpGlobals->frametime, -0.001 );
if ( gun->m_flTimeWeaponIdle != 1000 )
{
gun->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle - gpGlobals->frametime, -0.001 );
}
}
pPlayerItem = pPlayerItem->m_pNext;
}
}
}
m_flNextAttack -= gpGlobals->frametime;
if ( m_flNextAttack < -0.001 )
m_flNextAttack = -0.001;
// Track button info so we can detect 'pressed' and 'released' buttons next frame
m_afButtonLast = pev->button;
}
BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot )
{
CBaseEntity *ent = NULL;
if ( !pSpot->IsTriggered( pPlayer ) )
{
return FALSE;
}
while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 96 )) != NULL )
{
// if ent is a client, don't spawn on 'em
if ( ent->pev->flags & FL_CLIENT )
return FALSE;
}
return TRUE;
}
DLL_GLOBAL CBaseEntity *g_pLastSpawn;
inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); }
/*
============
EntSelectSpawnPoint
Returns the entity to spawn at
USES AND SETS GLOBAL g_pLastSpawn
============
*/
edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer )
{
CBaseEntity *pSpot;
edict_t *player;
player = pPlayer->edict();
// choose a info_player_deathmatch point
if (g_pGameRules->IsCoOp())
{
pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_coop");
if ( !FNullEnt(pSpot) )
goto ReturnSpot;
pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_start");
if ( !FNullEnt(pSpot) )
goto ReturnSpot;
}
else if ( g_pGameRules->IsDeathmatch() )
{
pSpot = g_pLastSpawn;
// Randomize the start spot
for ( int i = RANDOM_LONG(1,5); i > 0; i-- )
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" );
if ( FNullEnt( pSpot ) ) // skip over the null point
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" );
CBaseEntity *pFirstSpot = pSpot;
do
{
if ( pSpot )
{
// check if pSpot is valid
if ( IsSpawnPointValid( pPlayer, pSpot ) )
{
if ( pSpot->pev->origin == Vector( 0, 0, 0 ) )
{
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" );
continue;
}
// if so, go to pSpot
goto ReturnSpot;
}
}
// increment pSpot
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" );
} while ( pSpot != pFirstSpot ); // loop if we're not back to the start
// we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there
if ( !FNullEnt( pSpot ) )
{
CBaseEntity *ent = NULL;
while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL )
{
// if ent is a client, kill em (unless they are ourselves)
if ( ent->IsPlayer() && !(ent->edict() == player) )
ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC );
}
goto ReturnSpot;
}
}
// If startspot is set, (re)spawn there.
if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot)))
{
pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start");
if ( !FNullEnt(pSpot) )
goto ReturnSpot;
}
else
{
pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) );
if ( !FNullEnt(pSpot) )
goto ReturnSpot;
}
ReturnSpot:
if ( FNullEnt( pSpot ) )
{
ALERT(at_error, "PutClientInServer: no info_player_start on level");
return INDEXENT(0);
}
g_pLastSpawn = pSpot;
return pSpot->edict();
}
void CBasePlayer::Spawn( void )
{
pev->classname = MAKE_STRING("player");
pev->health = 100;
pev->armorvalue = 0;
pev->takedamage = DAMAGE_AIM;
pev->solid = SOLID_SLIDEBOX;
pev->movetype = MOVETYPE_WALK;
pev->max_health = pev->health;
pev->flags &= FL_PROXY; // keep proxy flag sey by engine
pev->flags |= FL_CLIENT;
pev->air_finished = gpGlobals->time + 12;
pev->dmg = 2; // initial water damage
pev->effects = 0;
pev->deadflag = DEAD_NO;
pev->dmg_take = 0;
pev->dmg_save = 0;
pev->friction = 1.0;
pev->gravity = 1.0;
m_bitsHUDDamage = -1;
m_bitsDamageType = 0;
m_afPhysicsFlags = 0;
m_fLongJump = FALSE;// no longjump module.
g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" );
g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" );
m_flNextDecalTime = 0;// let this player decal as soon as he spawns.
m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations
// are recieved by all clients
m_flTimeStepSound = 0;
m_iStepLeft = 0;
m_flFieldOfView = 0.5;// some monsters use this to determine whether or not the player is looking at them.
m_bloodColor = BLOOD_COLOR_RED;
m_flNextAttack = UTIL_WeaponTimeBase();
StartSneaking();
m_iFlashBattery = 99;
m_flFlashLightTime = 1; // force first message
// dont let uninitialized value here hurt the player
m_flFallVelocity = 0;
//g_pGameRules->SetDefaultPlayerTeam( this );
g_pGameRules->GetPlayerSpawnSpot( this );
SET_MODEL(ENT(pev), "models/player.mdl");
g_ulModelIndexPlayer = pev->modelindex;
pev->sequence = LookupActivity( ACT_IDLE );
UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX);
pev->view_ofs = VEC_VIEW;
Precache();
m_HackedGunPos = Vector( 0, 32, 0 );
m_fNoPlayerSound = FALSE;// normal sound behavior.
m_pLastItem = NULL;
m_fInitHUD = TRUE;
m_iClientHideHUD = -1; // force this to be recalculated
m_fWeapon = FALSE;
m_pClientActiveItem = NULL;
m_iClientBattery = -1;
m_flLightningTime = 0.0;
InitStatusBar();
m_iQuakeItems = 0;
// reset all ammo values to 0
for ( int i = 0; i < MAX_AMMO_SLOTS; i++ )
{
m_rgAmmo[i] = 0;
m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side)
}
m_lastx = m_lasty = 0;
if ( !m_bHadFirstSpawn && g_bHaveMOTD )
{
pev->effects |= EF_NODRAW;
pev->solid = SOLID_NOT;
pev->takedamage = DAMAGE_NO;
pev->movetype = MOVETYPE_NONE;
g_engfuncs.pfnSetClientMaxspeed( ENT( pev ), 1 );
m_iHideHUD |= HIDEHUD_WEAPONS | HIDEHUD_FLASHLIGHT | HIDEHUD_HEALTH;
}
else
{
g_engfuncs.pfnSetClientMaxspeed( ENT( pev ), PLAYER_MAX_SPEED );
m_iHideHUD &= ~( HIDEHUD_WEAPONS | HIDEHUD_HEALTH );
pev->effects &= ~EF_NODRAW;
g_pGameRules->PlayerSpawn( this );
PLAYBACK_EVENT_FULL ( FEV_GLOBAL, edict(), g_sTeleport, 0.0, (float *)&pev->origin, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0);
m_fKnownItem = false;
}
}
void CBasePlayer :: Precache( void )
{
// in the event that the player JUST spawned, and the level node graph
// was loaded, fix all of the node graph pointers before the game starts.
// !!!BUGBUG - now that we have multiplayer, this needs to be moved!
if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet )
{
if ( !WorldGraph.FSetGraphPointers() )
{
ALERT ( at_console, "**Graph pointers were not set!\n");
}
else
{
ALERT ( at_console, "**Graph Pointers Set!\n" );
}
}
// SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific)
// because they need to precache before any clients have connected
m_usShotgunSingle = PRECACHE_EVENT( 1, "events/shotgun1.sc" );
m_usShotgunDouble = PRECACHE_EVENT( 1, "events/shotgun2.sc" );
m_usAxe = PRECACHE_EVENT( 1, "events/axe.sc" );
m_usAxeSwing = PRECACHE_EVENT( 1, "events/axeswing.sc" );
m_usRocket = PRECACHE_EVENT( 1, "events/rocket.sc" );
m_usGrenade = PRECACHE_EVENT( 1, "events/grenade.sc" );
m_usLightning = PRECACHE_EVENT( 1, "events/lightning.sc" );
m_usSpike = PRECACHE_EVENT( 1, "events/spike.sc" );
m_usSuperSpike = PRECACHE_EVENT( 1, "events/superspike.sc" );
// init geiger counter vars during spawn and each time
// we cross a level transition
m_flgeigerRange = 1000;
m_igeigerRangePrev = 1000;
m_bitsDamageType = 0;
m_bitsHUDDamage = -1;
m_iClientBattery = -1;
m_iTrain = TRAIN_NEW;
// Make sure any necessary user messages have been registered
LinkUserMessages();
m_iUpdateTime = 5; // won't update for 1/2 a second
if ( gInitHUD )
m_fInitHUD = TRUE;
}
int CBasePlayer::Save( CSave &save )
{
if ( !CBaseMonster::Save(save) )
return 0;
return save.WriteFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) );
}
//
// Marks everything as new so the player will resend this to the hud.
//
void CBasePlayer::RenewItems(void)
{
}
int CBasePlayer::Restore( CRestore &restore )
{
if ( !CBaseMonster::Restore(restore) )
return 0;
int status = restore.ReadFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) );
SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData;
// landmark isn't present.
if ( !pSaveData->fUseLandmark )
{
ALERT( at_console, "No Landmark:%s\n", pSaveData->szLandmarkName );
// default to normal spawn
edict_t* pentSpawnSpot = EntSelectSpawnPoint( this );
pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1);
pev->angles = VARS(pentSpawnSpot)->angles;
}
pev->v_angle.z = 0; // Clear out roll
pev->angles = pev->v_angle;
pev->fixangle = TRUE; // turn this way immediately
// Copied from spawn() for now
m_bloodColor = BLOOD_COLOR_RED;
g_ulModelIndexPlayer = pev->modelindex;
/* if ( FBitSet(pev->flags, FL_DUCKING) )
{
// Use the crouch HACK
// FixPlayerCrouchStuck( edict() );
UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX);
}
else
{
UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX);
}*/
RenewItems();
return status;
}
void CBasePlayer::SelectNextItem( int iItem )
{
CBasePlayerItem *pItem;
pItem = m_rgpPlayerItems[ iItem ];
if (!pItem)
return;
if (pItem == m_pActiveItem)
{
// select the next one in the chain
pItem = m_pActiveItem->m_pNext;
if (! pItem)
{
return;
}
CBasePlayerItem *pLast;
pLast = pItem;
while (pLast->m_pNext)
pLast = pLast->m_pNext;
// relink chain
pLast->m_pNext = m_pActiveItem;
m_pActiveItem->m_pNext = NULL;
m_rgpPlayerItems[ iItem ] = pItem;
}
ResetAutoaim( );
// FIX, this needs to queue them up and delay
if (m_pActiveItem)
{
m_pActiveItem->Holster( );
}
m_pActiveItem = pItem;
if (m_pActiveItem)
{
m_pActiveItem->Deploy( );
m_pActiveItem->UpdateItemInfo( );
}
}
void CBasePlayer::SelectItem(const char *pstr)
{
if (!pstr)
return;
CBasePlayerItem *pItem = NULL;
for (int i = 0; i < MAX_ITEM_TYPES; i++)
{
if (m_rgpPlayerItems[i])
{
pItem = m_rgpPlayerItems[i];
while (pItem)
{
if (FClassnameIs(pItem->pev, pstr))
break;
pItem = pItem->m_pNext;
}
}
if (pItem)
break;
}
if (!pItem)
return;
if (pItem == m_pActiveItem)
return;
ResetAutoaim( );
// FIX, this needs to queue them up and delay
if (m_pActiveItem)
m_pActiveItem->Holster( );
m_pLastItem = m_pActiveItem;
m_pActiveItem = pItem;
if (m_pActiveItem)
{
m_pActiveItem->Deploy( );
m_pActiveItem->UpdateItemInfo( );
}
}
void CBasePlayer::SelectLastItem(void)
{
if (!m_pLastItem)
{
return;
}
if ( m_pActiveItem && !m_pActiveItem->CanHolster() )
{
return;
}
ResetAutoaim( );
// FIX, this needs to queue them up and delay
if (m_pActiveItem)
m_pActiveItem->Holster( );
CBasePlayerItem *pTemp = m_pActiveItem;
m_pActiveItem = m_pLastItem;
m_pLastItem = pTemp;
m_pActiveItem->Deploy( );
m_pActiveItem->UpdateItemInfo( );
}
//==============================================
// HasWeapons - do I have any weapons at all?
//==============================================
BOOL CBasePlayer::HasWeapons( void )
{
int i;
for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ )
{
if ( m_rgpPlayerItems[ i ] )
{
return TRUE;
}
}
return FALSE;
}
void CBasePlayer::SelectPrevItem( int iItem )
{
}
const char *CBasePlayer::TeamID( void )
{
if ( pev == NULL ) // Not fully connected yet
return "";
// return their team name
return m_szTeamName;
}
//==============================================
// !!!UNDONE:ultra temporary SprayCan entity to apply
// decal frame at a time. For PreAlpha CD
//==============================================
class CSprayCan : public CBaseEntity
{
public:
void Spawn ( entvars_t *pevOwner );
void Think( void );
virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; }
};
void CSprayCan::Spawn ( entvars_t *pevOwner )
{
pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 );
pev->angles = pevOwner->v_angle;
pev->owner = ENT(pevOwner);
pev->frame = 0;
pev->nextthink = gpGlobals->time + 0.1;
EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/sprayer.wav", 1, ATTN_NORM);
}
void CSprayCan::Think( void )
{
TraceResult tr;
int playernum;
int nFrames;
CBasePlayer *pPlayer;
pPlayer = (CBasePlayer *)GET_PRIVATE(pev->owner);
if (pPlayer)
nFrames = pPlayer->GetCustomDecalFrames();
else
nFrames = -1;
playernum = ENTINDEX(pev->owner);
// ALERT(at_console, "Spray by player %i, %i of %i\n", playernum, (int)(pev->frame + 1), nFrames);
UTIL_MakeVectors(pev->angles);
UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr);
// No customization present.
if (nFrames == -1)
{
UTIL_DecalTrace( &tr, DECAL_LAMBDA6 );
UTIL_Remove( this );
}
else
{
UTIL_PlayerDecalTrace( &tr, playernum, pev->frame, TRUE );
// Just painted last custom frame.
if ( pev->frame++ >= (nFrames - 1))
UTIL_Remove( this );
}
pev->nextthink = gpGlobals->time + 0.1;
}
class CBloodSplat : public CBaseEntity
{
public:
void Spawn ( entvars_t *pevOwner );
void Spray ( void );
};
void CBloodSplat::Spawn ( entvars_t *pevOwner )
{
pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 );
pev->angles = pevOwner->v_angle;
pev->owner = ENT(pevOwner);
SetThink ( &CBloodSplat::Spray );
pev->nextthink = gpGlobals->time + 0.1;
}
void CBloodSplat::Spray ( void )
{
TraceResult tr;
if ( g_Language != LANGUAGE_GERMAN )
{
UTIL_MakeVectors(pev->angles);
UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr);
UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED );
}
SetThink ( &CBloodSplat::SUB_Remove );
pev->nextthink = gpGlobals->time + 0.1;
}
//==============================================
void CBasePlayer::GiveNamedItem( const char *pszName )
{
edict_t *pent;
int istr = MAKE_STRING(pszName);
pent = CREATE_NAMED_ENTITY(istr);
if ( FNullEnt( pent ) )
{
ALERT ( at_console, "NULL Ent in GiveNamedItem!\n" );
return;
}
VARS( pent )->origin = pev->origin;
pent->v.spawnflags |= SF_NORESPAWN;
DispatchSpawn( pent );
DispatchTouch( pent, ENT( pev ) );
}
CBaseEntity *FindEntityForward( CBaseEntity *pMe )
{
TraceResult tr;
UTIL_MakeVectors(pMe->pev->v_angle);
UTIL_TraceLine(pMe->pev->origin + pMe->pev->view_ofs,pMe->pev->origin + pMe->pev->view_ofs + gpGlobals->v_forward * 8192,dont_ignore_monsters, pMe->edict(), &tr );
if ( tr.flFraction != 1.0 && !FNullEnt( tr.pHit) )
{
CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit );
return pHit;
}
return NULL;
}
BOOL CBasePlayer :: FlashlightIsOn( void )
{
return FBitSet(pev->effects, EF_DIMLIGHT);
}
void CBasePlayer :: FlashlightTurnOn( void )
{
if ( !g_pGameRules->FAllowFlashlight() )
{
return;
}
if ( (pev->weapons & (1<<IT_AXE)) )
{
EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_ON, 1.0, ATTN_NORM, 0, PITCH_NORM );
SetBits(pev->effects, EF_DIMLIGHT);
MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev );
WRITE_BYTE(1);
WRITE_BYTE(m_iFlashBattery);
MESSAGE_END();
m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time;
}
}
void CBasePlayer :: FlashlightTurnOff( void )
{
EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM );
ClearBits(pev->effects, EF_DIMLIGHT);
MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev );
WRITE_BYTE(0);
WRITE_BYTE(m_iFlashBattery);
MESSAGE_END();
m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time;
}
/*
===============
ForceClientDllUpdate
When recording a demo, we need to have the server tell us the entire client state
so that the client side .dll can behave correctly.
Reset stuff so that the state is transmitted.
===============
*/
void CBasePlayer :: ForceClientDllUpdate( void )
{
m_iClientHealth = -1;
m_iClientBattery = -1;
m_iClientQuakeItems = -1;
m_iClientQuakeWeapon = -1;
m_iTrain |= TRAIN_NEW; // Force new train message.
m_fWeapon = FALSE; // Force weapon send
m_fKnownItem = FALSE; // Force weaponinit messages.
m_fInitHUD = TRUE; // Force HUD gmsgResetHUD message
// Now force all the necessary messages
// to be sent.
UpdateClientData();
}
/*
============
ImpulseCommands
============
*/
extern float g_flWeaponCheat;
void CBasePlayer::ImpulseCommands( )
{
TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs
// Handle use events
PlayerUse();
int iImpulse = (int)pev->impulse;
// QUAKECLASSIC
// Handle weapon switches first
/* if (iImpulse >= 1 && iImpulse <= 8)
{
W_ChangeWeapon( iImpulse );
}
else
{*/
switch (iImpulse)
{
// QUAKECLASSIC
case 10:
W_CycleWeaponCommand();
break;
case 12:
W_CycleWeaponReverseCommand();
break;
case 99:
{
int iOn;
if (!gmsgLogo)
{
iOn = 1;
gmsgLogo = REG_USER_MSG("Logo", 1);
}
else
{
iOn = 0;
}
ASSERT( gmsgLogo > 0 );
// send "health" update message
MESSAGE_BEGIN( MSG_ONE, gmsgLogo, NULL, pev );
WRITE_BYTE(iOn);
MESSAGE_END();
if(!iOn)
gmsgLogo = 0;
break;
}
/* case 100:
// temporary flashlight for level designers
if ( FlashlightIsOn() )
{
FlashlightTurnOff();
}
else
{
FlashlightTurnOn();
}
break;*/
case 201:// paint decal
if ( gpGlobals->time < m_flNextDecalTime )
{
// too early!
break;
}
UTIL_MakeVectors(pev->v_angle);
UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr);
if ( tr.flFraction != 1.0 )
{// line hit something, so paint a decal
m_flNextDecalTime = gpGlobals->time + CVAR_GET_FLOAT("decalfrequency");
CSprayCan *pCan = GetClassPtr((CSprayCan *)NULL);
pCan->Spawn( pev );
}
break;
default:
// check all of the cheat impulse commands now
CheatImpulseCommands( iImpulse );
break;
}
//}
pev->impulse = 0;
}
//=========================================================
//=========================================================
void CBasePlayer::CheatImpulseCommands( int iImpulse )
{
#if !defined( HLDEMO_BUILD )
if ( g_flWeaponCheat == 0.0 )
{
return;
}
CBaseEntity *pEntity;
TraceResult tr;
switch ( iImpulse )
{
case 76:
{
if (!giPrecacheGrunt)
{
giPrecacheGrunt = 1;
ALERT(at_console, "You must now restart to use Grunt-o-matic.\n");
}
else
{
UTIL_MakeVectors( Vector( 0, pev->v_angle.y, 0 ) );
Create("monster_human_grunt", pev->origin + gpGlobals->v_forward * 128, pev->angles);
}
break;
}
case 101:
gEvilImpulse101 = TRUE;
m_iAmmoNails = 200;
m_iAmmoShells = 100;
m_iAmmoRockets = 100;
m_iAmmoCells = 100;
m_iQuakeItems |= IT_NAILGUN | IT_SUPER_NAILGUN | IT_SUPER_SHOTGUN | IT_ROCKET_LAUNCHER | IT_GRENADE_LAUNCHER | IT_LIGHTNING;
CheckAmmo();
W_SetCurrentAmmo();
gEvilImpulse101 = FALSE;
break;
case 102:
// Gibbage!!!
CGib::SpawnRandomGibs( pev, 1, 1 );
break;
case 103:
// What the hell are you doing?
pEntity = FindEntityForward( this );
if ( pEntity )
{
CBaseMonster *pMonster = pEntity->MyMonsterPointer();
if ( pMonster )
pMonster->ReportAIState();
}
break;
case 104:
// Dump all of the global state varaibles (and global entity names)
gGlobalState.DumpGlobals();
break;
case 105:// player makes no sound for monsters to hear.
{
if ( m_fNoPlayerSound )
{
ALERT ( at_console, "Player is audible\n" );
m_fNoPlayerSound = FALSE;
}
else
{
ALERT ( at_console, "Player is silent\n" );
m_fNoPlayerSound = TRUE;
}
break;
}
case 106:
// Give me the classname and targetname of this entity.
pEntity = FindEntityForward( this );
if ( pEntity )
{
ALERT ( at_console, "Classname: %s", STRING( pEntity->pev->classname ) );
if ( !FStringNull ( pEntity->pev->targetname ) )
{
ALERT ( at_console, " - Targetname: %s\n", STRING( pEntity->pev->targetname ) );
}
else
{
ALERT ( at_console, " - TargetName: No Targetname\n" );
}
ALERT ( at_console, "Model: %s\n", STRING( pEntity->pev->model ) );
if ( pEntity->pev->globalname )
ALERT ( at_console, "Globalname: %s\n", STRING( pEntity->pev->globalname ) );
}
break;
case 107:
{
TraceResult tr;
edict_t *pWorld = g_engfuncs.pfnPEntityOfEntIndex( 0 );
Vector start = pev->origin + pev->view_ofs;
Vector end = start + gpGlobals->v_forward * 1024;
UTIL_TraceLine( start, end, ignore_monsters, edict(), &tr );
if ( tr.pHit )
pWorld = tr.pHit;
const char *pTextureName = TRACE_TEXTURE( pWorld, start, end );
if ( pTextureName )
ALERT( at_console, "Texture: %s\n", pTextureName );
}
break;
case 195:// show shortest paths for entire level to nearest node
{
Create("node_viewer_fly", pev->origin, pev->angles);
}
break;
case 196:// show shortest paths for entire level to nearest node
{
Create("node_viewer_large", pev->origin, pev->angles);
}
break;
case 197:// show shortest paths for entire level to nearest node
{
Create("node_viewer_human", pev->origin, pev->angles);
}
break;
case 199:// show nearest node and all connections
{
ALERT ( at_console, "%d\n", WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) );
WorldGraph.ShowNodeConnections ( WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) );
}
break;
case 202:// Random blood splatter
UTIL_MakeVectors(pev->v_angle);
UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr);
if ( tr.flFraction != 1.0 )
{// line hit something, so paint a decal
CBloodSplat *pBlood = GetClassPtr((CBloodSplat *)NULL);
pBlood->Spawn( pev );
}
break;
case 203:// remove creature.
pEntity = FindEntityForward( this );
if ( pEntity )
{
if ( pEntity->pev->takedamage )
pEntity->SetThink(&CBaseEntity::SUB_Remove);
}
break;
}
#endif // HLDEMO_BUILD
}
//
// Add a weapon to the player (Item == Weapon == Selectable Object)
//
int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem )
{
CBasePlayerItem *pInsert;
pInsert = m_rgpPlayerItems[pItem->iItemSlot()];
while (pInsert)
{
if (FClassnameIs( pInsert->pev, STRING( pItem->pev->classname) ))
{
if (pItem->AddDuplicate( pInsert ))
{
g_pGameRules->PlayerGotWeapon ( this, pItem );
pItem->CheckRespawn();
// ugly hack to update clip w/o an update clip message
pInsert->UpdateItemInfo( );
if (m_pActiveItem)
m_pActiveItem->UpdateItemInfo( );
pItem->Kill( );
}
else if (gEvilImpulse101)
{
// FIXME: remove anyway for deathmatch testing
pItem->Kill( );
}
return FALSE;
}
pInsert = pInsert->m_pNext;
}
if (pItem->AddToPlayer( this ))
{
g_pGameRules->PlayerGotWeapon ( this, pItem );
pItem->CheckRespawn();
pItem->m_pNext = m_rgpPlayerItems[pItem->iItemSlot()];
m_rgpPlayerItems[pItem->iItemSlot()] = pItem;
// should we switch to this item?
if ( g_pGameRules->FShouldSwitchWeapon( this, pItem ) )
{
SwitchWeapon( pItem );
}
return TRUE;
}
else if (gEvilImpulse101)
{
// FIXME: remove anyway for deathmatch testing
pItem->Kill( );
}
return FALSE;
}
int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem )
{
if (m_pActiveItem == pItem)
{
ResetAutoaim( );
pItem->Holster( );
pItem->pev->nextthink = 0;// crowbar may be trying to swing again, etc.
pItem->SetThink( NULL );
m_pActiveItem = NULL;
pev->viewmodel = 0;
pev->weaponmodel = 0;
}
else if ( m_pLastItem == pItem )
m_pLastItem = NULL;
CBasePlayerItem *pPrev = m_rgpPlayerItems[pItem->iItemSlot()];
if (pPrev == pItem)
{
m_rgpPlayerItems[pItem->iItemSlot()] = pItem->m_pNext;
return TRUE;
}
else
{
while (pPrev && pPrev->m_pNext != pItem)
{
pPrev = pPrev->m_pNext;
}
if (pPrev)
{
pPrev->m_pNext = pItem->m_pNext;
return TRUE;
}
}
return FALSE;
}
//
// Returns the unique ID for the ammo, or -1 if error
//
int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax )
{
if ( !szName )
{
// no ammo.
return -1;
}
if ( !g_pGameRules->CanHaveAmmo( this, szName, iMax ) )
{
// game rules say I can't have any more of this ammo type.
return -1;
}
int i = 0;
i = GetAmmoIndex( szName );
if ( i < 0 || i >= MAX_AMMO_SLOTS )
return -1;
int iAdd = min( iCount, iMax - m_rgAmmo[i] );
if ( iAdd < 1 )
return i;
m_rgAmmo[ i ] += iAdd;
if ( gmsgAmmoPickup ) // make sure the ammo messages have been linked first
{
// Send the message that ammo has been picked up
MESSAGE_BEGIN( MSG_ONE, gmsgAmmoPickup, NULL, pev );
WRITE_BYTE( GetAmmoIndex(szName) ); // ammo ID
WRITE_BYTE( iAdd ); // amount
MESSAGE_END();
}
return i;
}
/*
============
ItemPreFrame
Called every frame by the player PreThink
============
*/
void CBasePlayer::ItemPreFrame()
{
if ( m_flNextAttack > 0 )
{
return;
}
if (!m_pActiveItem)
return;
m_pActiveItem->ItemPreFrame( );
}
/*
============
ItemPostFrame
Called every frame by the player PostThink
============
*/
void CBasePlayer::ItemPostFrame()
{
static int fInSelect = FALSE;
// check if the player is using a tank
if ( m_pTank != NULL )
return;
// HACKHACK: To make the axe fire 0.3 sec after fire is pressed
// See if the axe should fire now
if (m_flAxeFire && m_flAxeFire <= gpGlobals->time)
{
m_flAxeFire = 0;
W_FireAxe();
}
if ( m_flNextAttack > 0 )
{
return;
}
ImpulseCommands();
if (!m_pActiveItem)
return;
m_pActiveItem->ItemPostFrame( );
}
int CBasePlayer::AmmoInventory( int iAmmoIndex )
{
if (iAmmoIndex == -1)
{
return -1;
}
return m_rgAmmo[ iAmmoIndex ];
}
int CBasePlayer::GetAmmoIndex(const char *psz)
{
int i;
if (!psz)
return -1;
for (i = 1; i < MAX_AMMO_SLOTS; i++)
{
if ( !CBasePlayerItem::AmmoInfoArray[i].pszName )
continue;
if (stricmp( psz, CBasePlayerItem::AmmoInfoArray[i].pszName ) == 0)
return i;
}
return -1;
}
// Called from UpdateClientData
// makes sure the client has all the necessary ammo info, if values have changed
void CBasePlayer::SendAmmoUpdate(void)
{
for (int i=0; i < MAX_AMMO_SLOTS;i++)
{
if (m_rgAmmo[i] != m_rgAmmoLast[i])
{
m_rgAmmoLast[i] = m_rgAmmo[i];
ASSERT( m_rgAmmo[i] >= 0 );
ASSERT( m_rgAmmo[i] < 255 );
// send "Ammo" update message
MESSAGE_BEGIN( MSG_ONE, gmsgAmmoX, NULL, pev );
WRITE_BYTE( i );
WRITE_BYTE( max( min( m_rgAmmo[i], 254 ), 0 ) ); // clamp the value to one byte
MESSAGE_END();
}
}
}
/*
=========================================================
UpdateClientData
resends any changed player HUD info to the client.
Called every frame by PlayerPreThink
Also called at start of demo recording and playback by
ForceClientDllUpdate to ensure the demo gets messages
reflecting all of the HUD state info.
=========================================================
*/
void CBasePlayer :: UpdateClientData( void )
{
if (m_fInitHUD)
{
m_fInitHUD = FALSE;
gInitHUD = FALSE;
MESSAGE_BEGIN( MSG_ONE, gmsgResetHUD, NULL, pev );
WRITE_BYTE( 0 );
MESSAGE_END();
if ( !m_fGameHUDInitialized )
{
MESSAGE_BEGIN( MSG_ONE, gmsgInitHUD, NULL, pev );
WRITE_BYTE ( g_iTeleNum );
for ( int i = 0; i < g_iTeleNum; i++ )
{
WRITE_COORD( g_vecTeleMins[ i ].x );
WRITE_COORD( g_vecTeleMins[ i ].y );
WRITE_COORD( g_vecTeleMins[ i ].z );
WRITE_COORD( g_vecTeleMaxs[ i ].x );
WRITE_COORD( g_vecTeleMaxs[ i ].y );
WRITE_COORD( g_vecTeleMaxs[ i ].z );
}
CBaseEntity *pEntity = NULL;
pEntity = UTIL_FindEntityByClassname( pEntity, "env_fog" );
if ( pEntity )
{
ALERT( at_console, "Map has fog!\n" );
CClientFog *pFog = (CClientFog *)pEntity;
//Send as bytes?.
WRITE_SHORT ( pFog->pev->rendercolor.x );
WRITE_SHORT ( pFog->pev->rendercolor.y );
WRITE_SHORT ( pFog->pev->rendercolor.z );
WRITE_SHORT ( pFog->m_iStartDist );
WRITE_SHORT ( pFog->m_iEndDist );
}
else
ALERT( at_console, "Map doesn't have any fog!\n" );
MESSAGE_END();
g_pGameRules->InitHUD( this );
m_fGameHUDInitialized = TRUE;
if ( g_pGameRules->IsMultiplayer() )
{
FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 );
}
}
FireTargets( "game_playerspawn", this, this, USE_TOGGLE, 0 );
}
if ( m_iHideHUD != m_iClientHideHUD )
{
MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, pev );
WRITE_BYTE( m_iHideHUD );
MESSAGE_END();
m_iClientHideHUD = m_iHideHUD;
}
if ( m_iFOV != m_iClientFOV )
{
MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev );
WRITE_BYTE( m_iFOV );
MESSAGE_END();
// cache FOV change at end of function, so weapon updates can see that FOV has changed
}
// HACKHACK -- send the message to display the game title
if (gDisplayTitle)
{
MESSAGE_BEGIN( MSG_ONE, gmsgShowGameTitle, NULL, pev );
WRITE_BYTE( 0 );
MESSAGE_END();
gDisplayTitle = 0;
}
if (pev->health != m_iClientHealth)
{
#define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) )
int iHealth = clamp( pev->health, 0, 255 ); // make sure that no negative health values are sent
if ( pev->health > 0.0f && pev->health <= 1.0f )
iHealth = 1;
// send "health" update message
MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev );
WRITE_BYTE( iHealth );
MESSAGE_END();
m_iClientHealth = pev->health;
}
// QUAKECLASSIC
// Send down the state of the QuakeItems
if ( m_iQuakeItems != m_iClientQuakeItems )
{
// send "items" update message
MESSAGE_BEGIN( MSG_ONE, gmsgQItems, NULL, pev );
WRITE_LONG( m_iQuakeItems );
MESSAGE_END();
m_iClientQuakeItems = m_iQuakeItems;
}
if (pev->armorvalue != m_iClientBattery)
{
m_iClientBattery = pev->armorvalue;
ASSERT( gmsgBattery > 0 );
// send "health" update message
MESSAGE_BEGIN( MSG_ONE, gmsgBattery, NULL, pev );
WRITE_SHORT( (int)pev->armorvalue);
MESSAGE_END();
}
if (pev->dmg_take || pev->dmg_save || m_bitsHUDDamage != m_bitsDamageType)
{
// Comes from inside me if not set
Vector damageOrigin = pev->origin;
// send "damage" message
// causes screen to flash, and pain compass to show direction of damage
edict_t *other = pev->dmg_inflictor;
if ( other && UTIL_IsValidEntity(other) )
{
CBaseEntity *pEntity = CBaseEntity::Instance(other);
if ( pEntity )
damageOrigin = pEntity->Center();
}
// only send down damage type that have hud art
int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD;
MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pev );
WRITE_BYTE( pev->dmg_save );
WRITE_BYTE( pev->dmg_take );
WRITE_LONG( visibleDamageBits );
WRITE_COORD( damageOrigin.x );
WRITE_COORD( damageOrigin.y );
WRITE_COORD( damageOrigin.z );
MESSAGE_END();
pev->dmg_take = 0;
pev->dmg_save = 0;
m_bitsHUDDamage = m_bitsDamageType;
// Clear off non-time-based damage indicators
m_bitsDamageType &= DMG_TIMEBASED;
}
// Update Status Bar
if ( m_flNextSBarUpdateTime < gpGlobals->time )
{
UpdateStatusBar();
m_flNextSBarUpdateTime = gpGlobals->time + 0.2;
}
if (m_iTrain & TRAIN_NEW)
{
ASSERT( gmsgTrain > 0 );
// send "health" update message
MESSAGE_BEGIN( MSG_ONE, gmsgTrain, NULL, pev );
WRITE_BYTE(m_iTrain & 0xF);
MESSAGE_END();
m_iTrain &= ~TRAIN_NEW;
}
int iShellIndex = GetAmmoIndex("shells");
int iNailIndex = GetAmmoIndex("nails");
int iRocketIndex = GetAmmoIndex("rockets");
int iCellIndex = GetAmmoIndex("cells");
//
// New Weapon?
//
if (!m_fKnownItem)
{
m_fKnownItem = TRUE;
// WeaponInit Message
// byte = # of weapons
//
// for each weapon:
// byte name str length (not including null)
// bytes... name
// byte Ammo Type
// byte Ammo2 Type
// byte bucket
// byte bucket pos
// byte flags
// ???? Icons
// Send ALL the weapon info now
// QUAKECLASSIC
// Tell the client about the Quake weapons
for (int i = 1; i < 10; i++)
{
const char *pszName;
int iAmmoIndex;
int iMaxAmmo = 0;
int iCurrentAmmo = 0;
int iBit = 0;
switch( i )
{
case 1: iBit = IT_AXE; break;
case 2: iBit = IT_SHOTGUN; break;
case 3: iBit = IT_SUPER_SHOTGUN; break;
case 4: iBit = IT_NAILGUN; break;
case 5: iBit = IT_SUPER_NAILGUN; break;
case 6: iBit = IT_GRENADE_LAUNCHER; break;
case 7: iBit = IT_ROCKET_LAUNCHER; break;
case 8: iBit = IT_LIGHTNING; break;
case 9: iBit = IT_EXTRA_WEAPON; break;
default:
pszName = "Empty";
}
if ( ! ( m_iQuakeItems & iBit ) )
continue;
switch( i )
{
case 1: pszName = "weapon_axe"; iAmmoIndex = -1; iBit |= IT_AXE; break;
case 2: pszName = "weapon_shotgun"; iAmmoIndex = iShellIndex; iBit |= IT_SHOTGUN; iCurrentAmmo = m_iAmmoShells ; iMaxAmmo = 100; break;
case 3: pszName = "weapon_doubleshotgun"; iAmmoIndex = iShellIndex; iBit |= IT_SUPER_SHOTGUN; iCurrentAmmo = m_iAmmoShells ; iMaxAmmo = 100; break;
case 4: pszName = "weapon_nailgun"; iAmmoIndex = iNailIndex; iBit |= IT_NAILGUN; iCurrentAmmo = m_iAmmoNails; iMaxAmmo = 200; break;
case 5: pszName = "weapon_supernail"; iAmmoIndex = iNailIndex; iBit |= IT_SUPER_NAILGUN; iCurrentAmmo = m_iAmmoNails; iMaxAmmo = 200; break;
case 6: pszName = "weapon_grenadel"; iAmmoIndex = iRocketIndex; iBit |= IT_GRENADE_LAUNCHER; iCurrentAmmo = m_iAmmoRockets; iMaxAmmo = 100; break;
case 7: pszName = "weapon_rocketl"; iAmmoIndex = iRocketIndex; iBit |= IT_ROCKET_LAUNCHER; iCurrentAmmo = m_iAmmoRockets; iMaxAmmo = 100; break;
case 8: pszName = "weapon_lightning"; iAmmoIndex = iCellIndex; iBit |= IT_LIGHTNING; iCurrentAmmo = m_iAmmoCells; iMaxAmmo = 100; break;
case 9: pszName = "weapon_grapple"; iAmmoIndex = -1; iBit |= IT_EXTRA_WEAPON; break;
default:
pszName = "Empty";
}
MESSAGE_BEGIN( MSG_ONE, gmsgWeaponList, NULL, pev );
WRITE_STRING(pszName); // string weapon name
WRITE_BYTE(iAmmoIndex); // byte Ammo Type
WRITE_BYTE( iMaxAmmo ); // byte Max Ammo 1
WRITE_BYTE( -1 ); // byte Ammo2 Type
WRITE_BYTE( iCurrentAmmo ); // byte Max Ammo 2
WRITE_BYTE( i ); // byte bucket
WRITE_BYTE( 0 ); // byte bucket pos
WRITE_LONG( iBit ); // byte id (bit index into pev->weapons)
if ( i == 1 || i == 9 )
WRITE_BYTE( 1 ); // We can select this one on empty
else
WRITE_BYTE( 0 ); // Can't select it when empty.
MESSAGE_END();
}
}
// QUAKECLASSIC
// HACKHACK: Make the HL ammo types equal the Quake ammo
m_rgAmmo[ iShellIndex ] = m_iAmmoShells;
m_rgAmmo[ iNailIndex ] = m_iAmmoNails;
m_rgAmmo[ iRocketIndex ] = m_iAmmoRockets;
m_rgAmmo[ iCellIndex ] = m_iAmmoCells;
SendAmmoUpdate();
// Update all the items
for ( int i = 0; i < MAX_ITEM_TYPES; i++ )
{
if ( m_rgpPlayerItems[i] ) // each item updates it's successors
m_rgpPlayerItems[i]->UpdateClientData( this );
}
// Cache and client weapon change
m_pClientActiveItem = m_pActiveItem;
m_iClientFOV = m_iFOV;
// QUAKECLASSIC
m_iClientQuakeWeapon = m_iQuakeWeapon;
}
//=========================================================
// FBecomeProne - Overridden for the player to set the proper
// physics flags when a barnacle grabs player.
//=========================================================
BOOL CBasePlayer :: FBecomeProne ( void )
{
m_afPhysicsFlags |= PFLAG_ONBARNACLE;
return TRUE;
}
//=========================================================
// BarnacleVictimBitten - bad name for a function that is called
// by Barnacle victims when the barnacle pulls their head
// into its mouth. For the player, just die.
//=========================================================
void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle )
{
TakeDamage ( pevBarnacle, pevBarnacle, pev->health + pev->armorvalue, DMG_SLASH | DMG_ALWAYSGIB );
}
//=========================================================
// BarnacleVictimReleased - overridden for player who has
// physics flags concerns.
//=========================================================
void CBasePlayer :: BarnacleVictimReleased ( void )
{
m_afPhysicsFlags &= ~PFLAG_ONBARNACLE;
}
//=========================================================
// Illumination
// return player light level plus virtual muzzle flash
//=========================================================
int CBasePlayer :: Illumination( void )
{
int iIllum = CBaseEntity::Illumination( );
iIllum += m_iWeaponFlash;
if (iIllum > 255)
return 255;
return iIllum;
}
void CBasePlayer :: EnableControl(BOOL fControl)
{
if (!fControl)
pev->flags |= FL_FROZEN;
else
pev->flags &= ~FL_FROZEN;
}
#define DOT_1DEGREE 0.9998476951564
#define DOT_2DEGREE 0.9993908270191
#define DOT_3DEGREE 0.9986295347546
#define DOT_4DEGREE 0.9975640502598
#define DOT_5DEGREE 0.9961946980917
#define DOT_6DEGREE 0.9945218953683
#define DOT_7DEGREE 0.9925461516413
#define DOT_8DEGREE 0.9902680687416
#define DOT_9DEGREE 0.9876883405951
#define DOT_10DEGREE 0.9848077530122
#define DOT_15DEGREE 0.9659258262891
#define DOT_20DEGREE 0.9396926207859
#define DOT_25DEGREE 0.9063077870367
//=========================================================
// Autoaim
// set crosshair position to point to enemey
//=========================================================
Vector CBasePlayer :: GetAutoaimVector( float flDelta )
{
if (g_iSkillLevel == SKILL_HARD)
{
UTIL_MakeVectors( pev->v_angle + pev->punchangle );
return gpGlobals->v_forward;
}
Vector vecSrc = GetGunPosition( );
float flDist = 8192;
// always use non-sticky autoaim
// UNDONE: use sever variable to chose!
if (1 || g_iSkillLevel == SKILL_MEDIUM)
{
m_vecAutoAim = Vector( 0, 0, 0 );
// flDelta *= 0.5;
}
BOOL m_fOldTargeting = m_fOnTarget;
Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta );
// update ontarget if changed
if ( !g_pGameRules->AllowAutoTargetCrosshair() )
m_fOnTarget = 0;
else if (m_fOldTargeting != m_fOnTarget)
{
m_pActiveItem->UpdateItemInfo( );
}
if (angles.x > 180)
angles.x -= 360;
if (angles.x < -180)
angles.x += 360;
if (angles.y > 180)
angles.y -= 360;
if (angles.y < -180)
angles.y += 360;
if (angles.x > 25)
angles.x = 25;
if (angles.x < -25)
angles.x = -25;
if (angles.y > 12)
angles.y = 12;
if (angles.y < -12)
angles.y = -12;
// always use non-sticky autoaim
// UNDONE: use sever variable to chose!
if (0 || g_iSkillLevel == SKILL_EASY)
{
m_vecAutoAim = m_vecAutoAim * 0.67 + angles * 0.33;
}
else
{
m_vecAutoAim = angles * 0.9;
}
// m_vecAutoAim = m_vecAutoAim * 0.99;
// Don't send across network if sv_aim is 0
if ( CVAR_GET_FLOAT( "sv_aim" ) != 0 )
{
if ( m_vecAutoAim.x != m_lastx ||
m_vecAutoAim.y != m_lasty )
{
SET_CROSSHAIRANGLE( edict(), -m_vecAutoAim.x, m_vecAutoAim.y );
m_lastx = m_vecAutoAim.x;
m_lasty = m_vecAutoAim.y;
}
}
// ALERT( at_console, "%f %f\n", angles.x, angles.y );
UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim );
return gpGlobals->v_forward;
}
Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta )
{
edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 );
CBaseEntity *pEntity;
float bestdot;
Vector bestdir;
edict_t *bestent;
TraceResult tr;
if ( CVAR_GET_FLOAT("sv_aim") == 0 )
{
m_fOnTarget = FALSE;
return g_vecZero;
}
UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim );
// try all possible entities
bestdir = gpGlobals->v_forward;
bestdot = flDelta; // +- 10 degrees
bestent = NULL;
m_fOnTarget = FALSE;
UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, edict(), &tr );
if ( tr.pHit && tr.pHit->v.takedamage != DAMAGE_NO)
{
// don't look through water
if (!((pev->waterlevel != 3 && tr.pHit->v.waterlevel == 3)
|| (pev->waterlevel == 3 && tr.pHit->v.waterlevel == 0)))
{
if (tr.pHit->v.takedamage == DAMAGE_AIM)
m_fOnTarget = TRUE;
return m_vecAutoAim;
}
}
for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ )
{
Vector center;
Vector dir;
float dot;
if ( pEdict->free ) // Not in use
continue;
if (pEdict->v.takedamage != DAMAGE_AIM)
continue;
if (pEdict == edict())
continue;
// if (pev->team > 0 && pEdict->v.team == pev->team)
// continue; // don't aim at teammate
if ( !g_pGameRules->ShouldAutoAim( this, pEdict ) )
continue;
pEntity = Instance( pEdict );
if (pEntity == NULL)
continue;
if (!pEntity->IsAlive())
continue;
// don't look through water
if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3)
|| (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0))
continue;
center = pEntity->BodyTarget( vecSrc );
dir = (center - vecSrc).Normalize( );
// make sure it's in front of the player
if (DotProduct (dir, gpGlobals->v_forward ) < 0)
continue;
dot = fabs( DotProduct (dir, gpGlobals->v_right ) )
+ fabs( DotProduct (dir, gpGlobals->v_up ) ) * 0.5;
// tweek for distance
dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist);
if (dot > bestdot)
continue; // to far to turn
UTIL_TraceLine( vecSrc, center, dont_ignore_monsters, edict(), &tr );
if (tr.flFraction != 1.0 && tr.pHit != pEdict)
{
// ALERT( at_console, "hit %s, can't see %s\n", STRING( tr.pHit->v.classname ), STRING( pEdict->v.classname ) );
continue;
}
// don't shoot at friends
if (IRelationship( pEntity ) < 0)
{
if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch())
// ALERT( at_console, "friend\n");
continue;
}
// can shoot at this one
bestdot = dot;
bestent = pEdict;
bestdir = dir;
}
if (bestent)
{
bestdir = UTIL_VecToAngles (bestdir);
bestdir.x = -bestdir.x;
bestdir = bestdir - pev->v_angle - pev->punchangle;
if (bestent->v.takedamage == DAMAGE_AIM)
m_fOnTarget = TRUE;
return bestdir;
}
return Vector( 0, 0, 0 );
}
void CBasePlayer :: ResetAutoaim( )
{
if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0)
{
m_vecAutoAim = Vector( 0, 0, 0 );
SET_CROSSHAIRANGLE( edict(), 0, 0 );
}
m_fOnTarget = FALSE;
}
/*
=============
SetCustomDecalFrames
UNDONE: Determine real frame limit, 8 is a placeholder.
Note: -1 means no custom frames present.
=============
*/
void CBasePlayer :: SetCustomDecalFrames( int nFrames )
{
if (nFrames > 0 &&
nFrames < 8)
m_nCustomSprayFrames = nFrames;
else
m_nCustomSprayFrames = -1;
}
/*
=============
GetCustomDecalFrames
Returns the # of custom frames this player's custom clan logo contains.
=============
*/
int CBasePlayer :: GetCustomDecalFrames( void )
{
return m_nCustomSprayFrames;
}
//=========================================================
// DropPlayerItem - drop the named item, or if no name,
// the active item.
//=========================================================
void CBasePlayer::DropPlayerItem ( char *pszItemName )
{
if ( !g_pGameRules->IsMultiplayer() || (CVAR_GET_FLOAT("mp_weaponstay") > 0) )
{
// no dropping in single player.
return;
}
if ( !strlen( pszItemName ) )
{
// if this string has no length, the client didn't type a name!
// assume player wants to drop the active item.
// make the string null to make future operations in this function easier
pszItemName = NULL;
}
CBasePlayerItem *pWeapon;
int i;
for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ )
{
pWeapon = m_rgpPlayerItems[ i ];
while ( pWeapon )
{
if ( pszItemName )
{
// try to match by name.
if ( !strcmp( pszItemName, STRING( pWeapon->pev->classname ) ) )
{
// match!
break;
}
}
else
{
// trying to drop active item
if ( pWeapon == m_pActiveItem )
{
// active item!
break;
}
}
pWeapon = pWeapon->m_pNext;
}
// if we land here with a valid pWeapon pointer, that's because we found the
// item we want to drop and hit a BREAK; pWeapon is the item.
if ( pWeapon )
{
g_pGameRules->GetNextBestWeapon( this, pWeapon );
UTIL_MakeVectors ( pev->angles );
pev->weapons &= ~(1<<pWeapon->m_iId);// take item off hud
CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin + gpGlobals->v_forward * 10, pev->angles, edict() );
pWeaponBox->pev->angles.x = 0;
pWeaponBox->pev->angles.z = 0;
pWeaponBox->PackWeapon( pWeapon );
pWeaponBox->pev->velocity = gpGlobals->v_forward * 300 + gpGlobals->v_forward * 100;
// drop half of the ammo for this weapon.
int iAmmoIndex;
iAmmoIndex = GetAmmoIndex ( pWeapon->pszAmmo1() ); // ???
if ( iAmmoIndex != -1 )
{
// this weapon weapon uses ammo, so pack an appropriate amount.
if ( pWeapon->iFlags() & ITEM_FLAG_EXHAUSTIBLE )
{
// pack up all the ammo, this weapon is its own ammo type
pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] );
m_rgAmmo[ iAmmoIndex ] = 0;
}
else
{
// pack half of the ammo
pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] / 2 );
m_rgAmmo[ iAmmoIndex ] /= 2;
}
}
return;// we're done, so stop searching with the FOR loop.
}
}
}
//=========================================================
// HasPlayerItem Does the player already have this item?
//=========================================================
BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem )
{
CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()];
while (pItem)
{
if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) ))
{
return TRUE;
}
pItem = pItem->m_pNext;
}
return FALSE;
}
//=========================================================
// HasNamedPlayerItem Does the player already have this item?
//=========================================================
BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName )
{
CBasePlayerItem *pItem;
int i;
for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ )
{
pItem = m_rgpPlayerItems[ i ];
while (pItem)
{
if ( !strcmp( pszItemName, STRING( pItem->pev->classname ) ) )
{
return TRUE;
}
pItem = pItem->m_pNext;
}
}
return FALSE;
}
//=========================================================
//
//=========================================================
BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon )
{
if ( !pWeapon->CanDeploy() )
{
return FALSE;
}
ResetAutoaim( );
if (m_pActiveItem)
{
m_pActiveItem->Holster( );
}
m_pActiveItem = pWeapon;
pWeapon->Deploy( );
return TRUE;
}
//=========================================================
// Dead HEV suit prop
//=========================================================
class CDeadHEV : public CBaseMonster
{
public:
void Spawn( void );
int Classify ( void ) { return CLASS_HUMAN_MILITARY; }
void KeyValue( KeyValueData *pkvd );
int m_iPose;// which sequence to display -- temporary, don't need to save
static char *m_szPoses[4];
};
char *CDeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" };
void CDeadHEV::KeyValue( KeyValueData *pkvd )
{
if (FStrEq(pkvd->szKeyName, "pose"))
{
m_iPose = atoi(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else
CBaseMonster::KeyValue( pkvd );
}
LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CDeadHEV );
//=========================================================
// ********** DeadHEV SPAWN **********
//=========================================================
void CDeadHEV :: Spawn( void )
{
PRECACHE_MODEL("models/player.mdl");
SET_MODEL(ENT(pev), "models/player.mdl");
pev->effects = 0;
pev->yaw_speed = 8;
pev->sequence = 0;
pev->body = 1;
m_bloodColor = BLOOD_COLOR_RED;
pev->sequence = LookupSequence( m_szPoses[m_iPose] );
if (pev->sequence == -1)
{
ALERT ( at_console, "Dead hevsuit with bad pose\n" );
pev->sequence = 0;
pev->effects = EF_BRIGHTFIELD;
}
// Corpses have less health
pev->health = 8;
MonsterInitDead();
}
class CStripWeapons : public CPointEntity
{
public:
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
private:
};
LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons );
void CStripWeapons :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
CBasePlayer *pPlayer = NULL;
if ( pActivator && pActivator->IsPlayer() )
{
pPlayer = (CBasePlayer *)pActivator;
}
else if ( !g_pGameRules->IsDeathmatch() )
{
pPlayer = (CBasePlayer *)CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) );
}
if ( pPlayer )
pPlayer->RemoveAllItems( FALSE );
}
class CRevertSaved : public CPointEntity
{
public:
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void EXPORT MessageThink( void );
void EXPORT LoadThink( void );
void KeyValue( KeyValueData *pkvd );
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
inline float Duration( void ) { return pev->dmg_take; }
inline float HoldTime( void ) { return pev->dmg_save; }
inline float MessageTime( void ) { return m_messageTime; }
inline float LoadTime( void ) { return m_loadTime; }
inline void SetDuration( float duration ) { pev->dmg_take = duration; }
inline void SetHoldTime( float hold ) { pev->dmg_save = hold; }
inline void SetMessageTime( float time ) { m_messageTime = time; }
inline void SetLoadTime( float time ) { m_loadTime = time; }
private:
float m_messageTime;
float m_loadTime;
};
LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved );
TYPEDESCRIPTION CRevertSaved::m_SaveData[] =
{
DEFINE_FIELD( CRevertSaved, m_messageTime, FIELD_FLOAT ), // These are not actual times, but durations, so save as floats
DEFINE_FIELD( CRevertSaved, m_loadTime, FIELD_FLOAT ),
};
IMPLEMENT_SAVERESTORE( CRevertSaved, CPointEntity );
void CRevertSaved :: KeyValue( KeyValueData *pkvd )
{
if (FStrEq(pkvd->szKeyName, "duration"))
{
SetDuration( atof(pkvd->szValue) );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "holdtime"))
{
SetHoldTime( atof(pkvd->szValue) );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "messagetime"))
{
SetMessageTime( atof(pkvd->szValue) );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "loadtime"))
{
SetLoadTime( atof(pkvd->szValue) );
pkvd->fHandled = TRUE;
}
else
CPointEntity::KeyValue( pkvd );
}
void CRevertSaved :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), pev->renderamt, FFADE_OUT );
pev->nextthink = gpGlobals->time + MessageTime();
SetThink( &CRevertSaved::MessageThink );
}
void CRevertSaved :: MessageThink( void )
{
UTIL_ShowMessageAll( STRING(pev->message) );
float nextThink = LoadTime() - MessageTime();
if ( nextThink > 0 )
{
pev->nextthink = gpGlobals->time + nextThink;
SetThink( &CRevertSaved::LoadThink );
}
else
LoadThink();
}
void CRevertSaved :: LoadThink( void )
{
if ( !gpGlobals->deathmatch )
{
SERVER_COMMAND("reload\n");
}
}
//=========================================================
// Multiplayer intermission spots.
//=========================================================
class CInfoIntermission:public CPointEntity
{
void Spawn( void );
void Think( void );
};
void CInfoIntermission::Spawn( void )
{
UTIL_SetOrigin( pev, pev->origin );
pev->solid = SOLID_NOT;
pev->effects = EF_NODRAW;
pev->v_angle = g_vecZero;
pev->nextthink = gpGlobals->time + 2;// let targets spawn!
}
void CInfoIntermission::Think ( void )
{
edict_t *pTarget;
// find my target
pTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) );
if ( !FNullEnt(pTarget) )
{
pev->v_angle = UTIL_VecToAngles( (pTarget->v.origin - pev->origin).Normalize() );
pev->v_angle.x = -pev->v_angle.x;
}
}
LINK_ENTITY_TO_CLASS( info_intermission, CInfoIntermission );