diff --git a/TheWastes/TheWastes.dsw b/TheWastes/TheWastes.dsw new file mode 100644 index 0000000..74cc487 --- /dev/null +++ b/TheWastes/TheWastes.dsw @@ -0,0 +1,44 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "cl_dll"="..\cl_dll\cl_dll.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "hl"="..\dlls\hl.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name cl_dll + End Project Dependency +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/cl_dll/ParseBsp.h b/cl_dll/ParseBsp.h new file mode 100644 index 0000000..f5af959 --- /dev/null +++ b/cl_dll/ParseBsp.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#ifndef __PARSEBSP_H_ +#define __PARSEBSP_H_ + +class CParseBsp +{ +public: + CParseBsp(); + + int CheckMap(); +private: + char *ParseEntity( char *pBuf, int &error, CBspEntity *pEnt ); + void LumpPass( char *pBuf, char *pszClassname, CBspEntity *pEnt ); + char *LoadEntityLump( char *pszFilename ); + void ParseBsp( char *pszClassname, CBspEntity *pEnt ); + + char m_szCurrentLevel[256]; +}; + +extern CParseBsp g_ParseBsp; + +#endif \ No newline at end of file diff --git a/cl_dll/ParseBspEnt.cpp b/cl_dll/ParseBspEnt.cpp new file mode 100644 index 0000000..5e0ddb9 --- /dev/null +++ b/cl_dll/ParseBspEnt.cpp @@ -0,0 +1,234 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "extdll.h" +#include "entity_state.h" +#include "pm_defs.h" +#include "pm_movevars.h" +#include "hud_iface.h" +#include "com_model.h" +#include "event_api.h" +#include "com_weapons.h" +#include "event_flags.h" +#include "dmc_bspfile.h" +#include "cl_util.h" +#include "ParseBspEnt.h" +#include "ParticleBase.h" + +#include +using namespace std; + +CBspWorldspawn g_Worldspawn; +extern vector g_EnvFogList; + +// +// CBspEntity +// +CBspEntity::CBspEntity() +{ + ResetVars(); +} + +void CBspEntity::ResetVars() +{ + memset(szClassname,0,sizeof(szClassname)); + memset(flOrigin,0,sizeof(flOrigin)); +} + +void CBspEntity::SetKeyValue( const char *pszKey, const char *pszValue ) +{ + float x,y,z; + + if( stricmp( pszKey, "classname" ) == 0 ) + { + strcpy( szClassname, pszValue ); + } + else if( stricmp( pszKey, "origin" ) == 0 ) + { + if( sscanf( pszValue, "%f %f %f", &x, &y, &z ) == 3 ) + { + flOrigin[0] = x; + flOrigin[1] = y; + flOrigin[2] = z; + } + } +} + +// +// CBspWorldspawn +// +void CBspWorldspawn::AddEntity() +{ + g_Worldspawn.flFogcolor_b = this->flFogcolor_b; + g_Worldspawn.flFogcolor_end = this->flFogcolor_end; + g_Worldspawn.flFogcolor_g = this->flFogcolor_g; + g_Worldspawn.flFogcolor_r = this->flFogcolor_r; + g_Worldspawn.flFogcolor_start = this->flFogcolor_start; + memcpy( g_Worldspawn.flOrigin, this->flOrigin, sizeof( flOrigin ) ); + g_Worldspawn.iEnableFog = this->iEnableFog; + strcpy( g_Worldspawn.szClassname, this->szClassname ); +} + +void CBspWorldspawn::ResetVars() +{ + CBspEntity::ResetVars(); + + iEnableFog = 0; + + flFogcolor_r = 0.0f; + flFogcolor_g = 0.0f; + flFogcolor_b = 0.0f; + flFogcolor_start = 0.0f; + flFogcolor_end = 0.0f; +} + +void CBspWorldspawn::SetKeyValue( const char *pszKey, const char *pszValue ) +{ + int i; + float x; + + if(stricmp(pszKey,"enablefog") == 0) + { + if(sscanf(pszValue,"%i",&i) == 1) + iEnableFog = i; + } + else if(stricmp(pszKey,"fogcolor_r") == 0) + { + if(sscanf(pszValue,"%f",&x) == 1) + flFogcolor_r = x; + } + else if(stricmp(pszKey,"fogcolor_g") == 0) + { + if(sscanf(pszValue,"%f",&x) == 1) + flFogcolor_g = x; + } + else if(stricmp(pszKey,"fogcolor_b") == 0) + { + if(sscanf(pszValue,"%f",&x) == 1) + flFogcolor_b = x; + } + else if(stricmp(pszKey,"fogcolor_start") == 0) + { + if(sscanf(pszValue,"%f",&x) == 1) + flFogcolor_start = x; + } + else if(stricmp(pszKey,"fogcolor_end") == 0) + { + if(sscanf(pszValue,"%f",&x) == 1) + flFogcolor_end = x; + } + else + CBspEntity::SetKeyValue( pszKey, pszValue ); +} + +// +// CBspEnvFog +// +void CBspEnvFog::AddEntity() +{ + g_EnvFogList.push_back( *this ); +} + +void CBspEnvFog::ResetVars() +{ + CBspEntity::ResetVars(); + + iRadius = 0; + iFogState = 0; + + flFogcolor_r = 0.0f; + flFogcolor_g = 0.0f; + flFogcolor_b = 0.0f; + flFog_start = 0.0f; + flFog_end = 0.0f; +} + +void CBspEnvFog::SetKeyValue( const char *pszKey, const char *pszValue ) +{ + int i; + float x; + + if( stricmp( pszKey, "radius" ) == 0 ) + { + if( sscanf( pszValue, "%i", &i ) == 1 ) + iRadius = i; + } + else if( stricmp( pszKey, "fogstate" ) == 0 ) + { + if( sscanf( pszValue, "%i", &i ) == 1 ) + iFogState = i; + } + else if( stricmp( pszKey, "fogcolor_r" ) == 0 ) + { + if( sscanf( pszValue, "%f", &x ) == 1 ) + flFogcolor_r = x; + } + else if( stricmp( pszKey, "fogcolor_g" ) == 0 ) + { + if( sscanf( pszValue, "%f", &x ) == 1 ) + flFogcolor_g = x; + } + else if( stricmp( pszKey, "fogcolor_b" ) == 0 ) + { + if( sscanf( pszValue, "%f", &x ) == 1 ) + flFogcolor_b = x; + } + else if( stricmp( pszKey, "fog_start" ) == 0 ) + { + if( sscanf( pszValue, "%f", &x ) == 1 ) + flFog_start = x; + } + else if( stricmp( pszKey, "fog_end" ) == 0 ) + { + if( sscanf( pszValue, "%f", &x ) == 1 ) + flFog_end = x; + } + else + CBspEntity::SetKeyValue( pszKey, pszValue ); +} + +// +// CBspEnvParticleSystem +// +void CBspEnvParticleSystem::AddEntity() +{ + if( iClientSide ) + { + g_ParticleSystemManager.AddParticleSystem( szParticleSystem, flOrigin ); + } +} + +void CBspEnvParticleSystem::ResetVars() +{ + CBspEntity::ResetVars(); + + iClientSide = 0; + memset( szParticleSystem, 0, sizeof( szParticleSystem ) ); +} + +void CBspEnvParticleSystem::SetKeyValue( const char *pszKey, const char *pszValue ) +{ + int i; + + if( stricmp( pszKey, "client_side" ) == 0 ) + { + if( sscanf( pszValue, "%i", &i ) == 1 ) + iClientSide = i; + } + else if( stricmp( pszKey, "particle_system" ) == 0 ) + { + strcpy( szParticleSystem, pszValue ); + } + else + CBspEntity::SetKeyValue( pszKey, pszValue ); +} \ No newline at end of file diff --git a/cl_dll/ParseBspEnt.h b/cl_dll/ParseBspEnt.h new file mode 100644 index 0000000..9c36ec5 --- /dev/null +++ b/cl_dll/ParseBspEnt.h @@ -0,0 +1,76 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#if !defined( __PARSEBSPENT_H_ ) +#define __PARSEBSPENT_H_ + +class CBspEntity +{ +public: + CBspEntity(); + + virtual void AddEntity() = 0; + virtual void ResetVars(); + virtual void SetKeyValue( const char *pszKey, const char *pszValue ); + + char szClassname[32]; + float flOrigin[3]; +}; + +class CBspWorldspawn : public CBspEntity +{ +public: + void AddEntity(); + void ResetVars(); + void SetKeyValue( const char *pszKey, const char *pszValue ); + + // Fog info + int iEnableFog; + + float flFogcolor_r; + float flFogcolor_g; + float flFogcolor_b; + float flFogcolor_start; + float flFogcolor_end; +}; + +class CBspEnvFog : public CBspEntity +{ +public: + void AddEntity(); + void ResetVars(); + void SetKeyValue( const char *pszKey, const char *pszValue ); + + int iRadius; + int iFogState; + float flFogcolor_r; + float flFogcolor_g; + float flFogcolor_b; + float flFog_start; + float flFog_end; +}; + +class CBspEnvParticleSystem : public CBspEntity +{ +public: + void AddEntity(); + void ResetVars(); + void SetKeyValue( const char *pszKey, const char *pszValue ); + + int iClientSide; + char szParticleSystem[64]; +}; + +extern CBspWorldspawn g_Worldspawn; + +#endif \ No newline at end of file diff --git a/cl_dll/ParticleBase.cpp b/cl_dll/ParticleBase.cpp new file mode 100644 index 0000000..99d669e --- /dev/null +++ b/cl_dll/ParticleBase.cpp @@ -0,0 +1,525 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include + +#include "hud.h" +#include "wrect.h" +#include "cl_util.h" +#include "tri.h" +#include "pm_defs.h" +#include "event_api.h" +#include "ParticleBase.h" + +CParticleSystemManager g_ParticleSystemManager; + +void HUD_GetLastOrg( float *org ); + +//================================================== +// CSpriteEmitter +//================================================== +CSpriteEmitter::CSpriteEmitter() +{ + memset( m_szName, 0, sizeof( m_szName ) ); + memset( m_Origin, 0, sizeof( m_Origin ) ); + memset( m_Acceleration, 0, sizeof( m_Acceleration ) ); + memset( m_StartOriginOffset, 0, sizeof( m_StartOriginOffset ) ); + memset( m_StartOriginRange, 0, sizeof( m_StartOriginRange ) ); + memset( m_StartVelocity, 0, sizeof( m_StartVelocity ) ); + memset( m_MinVelocity, 0, sizeof( m_MinVelocity ) ); + memset( m_MaxVelocity, 0, sizeof( m_MaxVelocity ) ); + memset( m_flParticleLifetime, 0, sizeof( m_flParticleLifetime ) ); + memset( m_flStartScaleRange, 0, sizeof( m_flStartScaleRange ) ); + memset( m_flGrowScaleRange, 0, sizeof( m_flGrowScaleRange ) ); + + m_iNumSprites = 0; + memset( m_iSpriteHandles, 0, sizeof( m_iSpriteHandles ) ); + + m_iMaxParticles = 1; + m_flParticlesPerSecond = 0.0f; + m_iRenderMode = PRM_Normal; + m_iCoordSystem = PCS_Standard; + m_flInactiveTime = 0.0f; + + m_flLastParticleTime = 0.0f; + m_pParticles = 0; + m_iNumParticles = 0; + m_flInactiveStart = -1.0f; +} + +CSpriteEmitter::CSpriteEmitter( const CSpriteEmitter &Cpy ) +{ + strcpy( m_szName, Cpy.m_szName ); + + VectorCopy( Cpy.m_Origin, m_Origin ); + VectorCopy( Cpy.m_Acceleration, m_Acceleration ); + VectorCopy( Cpy.m_StartOriginOffset, m_StartOriginOffset ); + VectorCopy( Cpy.m_StartOriginRange[0], m_StartOriginRange[0] ); + VectorCopy( Cpy.m_StartOriginRange[1], m_StartOriginRange[1] ); + VectorCopy( Cpy.m_StartVelocity[0], m_StartVelocity[0] ); + VectorCopy( Cpy.m_StartVelocity[1], m_StartVelocity[1] ); + VectorCopy( Cpy.m_MinVelocity, m_MinVelocity ); + VectorCopy( Cpy.m_MaxVelocity, m_MaxVelocity ); + + memcpy( m_flParticleLifetime, Cpy.m_flParticleLifetime, sizeof( m_flParticleLifetime ) ); + memcpy( m_flStartScaleRange, Cpy.m_flStartScaleRange, sizeof( m_flStartScaleRange ) ); + memcpy( m_flGrowScaleRange, Cpy.m_flGrowScaleRange, sizeof( m_flGrowScaleRange ) ); + + m_iNumSprites = Cpy.m_iNumSprites; + memcpy( m_iSpriteHandles, Cpy.m_iSpriteHandles, sizeof( m_iSpriteHandles ) ); + + m_iMaxParticles = Cpy.m_iMaxParticles; + m_flParticlesPerSecond = Cpy.m_flParticlesPerSecond; + m_iRenderMode = Cpy.m_iRenderMode; + m_iCoordSystem = Cpy.m_iCoordSystem; + m_flInactiveTime = Cpy.m_flInactiveTime; + + // These are unique to each emitter + m_flLastParticleTime = 0.0f; + m_pParticles = 0; + m_iNumParticles = 0; + m_flInactiveStart = -1.0f; +} + +CSpriteEmitter::~CSpriteEmitter() +{ + ClearList( m_pParticles ); +} + +void CSpriteEmitter::Render() +{ + if( m_flInactiveTime > 0.0f ) + { + psvec3_t origin, angles, forward, right, up; + int idx = gEngfuncs.GetLocalPlayer()->index; + pmtrace_t tr; + + // See if we shouldn't render the emitter + gEngfuncs.GetViewAngles((float*)&angles); + HUD_GetLastOrg((float*)&origin); + AngleVectors(angles,forward,right,up); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, false ); + + gEngfuncs.pEventAPI->EV_PushPMStates(); + gEngfuncs.pEventAPI->EV_SetSolidPlayers( idx - 1 ); + gEngfuncs.pEventAPI->EV_SetTraceHull( 1 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( origin, m_Origin, PM_WORLD_ONLY|PM_GLASS_IGNORE, -1, &tr ); + gEngfuncs.pEventAPI->EV_PopPMStates(); + + if( tr.fraction != 1.0f ) + { + if( m_flInactiveStart == -1.0f ) + m_flInactiveStart = gHUD.m_flTime; + else if( gHUD.m_flTime >= m_flInactiveStart + m_flInactiveTime ) + return; + } + else + m_flInactiveStart = -1.0f; + } + + CParticle *pCurPcl = m_pParticles; + while( pCurPcl ) + { + const struct model_s *spritemodel; + psvec3_t angles,forward,right,up; + psvec3_t origin; + + spritemodel = gEngfuncs.GetSpritePointer( m_iSpriteHandles[ pCurPcl->m_iModelIndex ] ); + gEngfuncs.GetViewAngles( angles ); + AngleVectors( angles, forward, right, up ); + + if( m_iCoordSystem == PCS_Relative ) + { + VectorAdd( m_Origin, pCurPcl->m_Origin, origin ); + } + else + { + VectorCopy( pCurPcl->m_Origin, origin ); + } + + R_Billboard( spritemodel, 0, m_iRenderMode, + 1.0f, 1.0f, 1.0f, 1.0f, + origin, up, right, pCurPcl->m_flScale ); + + pCurPcl = pCurPcl->m_pNext; + } + + if( gHUD.m_flTime - m_flLastParticleTime >= 1/m_flParticlesPerSecond ) + { + m_flLastParticleTime = gHUD.m_flTime; + AddParticle(); + } +} + +void CSpriteEmitter::Update( double flFrametime ) +{ + // Dont update if we dont have to +// if( m_flInactiveStart != -1.0f && gHUD.m_flTime >= m_flInactiveStart + m_flInactiveTime ) +// return; + + CParticle *pCurPcl = m_pParticles; + while( pCurPcl ) + { + VectorMA( pCurPcl->m_Origin, flFrametime, pCurPcl->m_Velocity, pCurPcl->m_Origin ); + VectorAdd( pCurPcl->m_Velocity, m_Acceleration, pCurPcl->m_Velocity ); + + for( int i = 0;i < 3;i++ ) + { + if( m_MinVelocity[i] != 0 && pCurPcl->m_Velocity[i] < m_MinVelocity[i]) + pCurPcl->m_Velocity[i] = m_MinVelocity[i]; + else if( m_MaxVelocity[i] != 0 && pCurPcl->m_Velocity[i] > m_MaxVelocity[i] ) + pCurPcl->m_Velocity[i] = m_MaxVelocity[i]; + } + + pCurPcl->m_flScale += flFrametime * pCurPcl->m_flGrowScale; + pCurPcl = pCurPcl->m_pNext; + } + + // See if we should delete any particles + // TODO: Clean this shit up will ya ? :) + pCurPcl = m_pParticles; + while( pCurPcl != NULL ) + { + if( pCurPcl->m_flLifetime - gHUD.m_flTime <= 0.0f ) + { + CParticle *pPrevPcl = m_pParticles; + + if( pPrevPcl == pCurPcl ) + { + m_pParticles = pCurPcl->m_pNext; + delete pCurPcl; + pCurPcl = m_pParticles; + } + else + { + while( pPrevPcl->m_pNext != NULL ) + { + if( pPrevPcl->m_pNext == pCurPcl ) + { + pPrevPcl->m_pNext = pCurPcl->m_pNext; + delete pCurPcl; + m_iNumParticles--; + break; + } + + pPrevPcl = pPrevPcl->m_pNext; + } + + pCurPcl = pPrevPcl->m_pNext; + } + } + else + pCurPcl = pCurPcl->m_pNext; + } +} + +void CSpriteEmitter::AddParticle() +{ + // Delete oldest particle if we are at max + if( m_iNumParticles >= m_iMaxParticles && m_pParticles ) + { + CParticle *pDel = m_pParticles; + m_pParticles = m_pParticles->m_pNext; + delete pDel; + } + + CParticle *pNewPcl = new CParticle; + + // Origin + psvec3_t RangedOrigin; + RangedOrigin[0] = gEngfuncs.pfnRandomFloat( m_StartOriginRange[0][0], m_StartOriginRange[1][0] ); + RangedOrigin[1] = gEngfuncs.pfnRandomFloat( m_StartOriginRange[0][1], m_StartOriginRange[1][1] ); + RangedOrigin[2] = gEngfuncs.pfnRandomFloat( m_StartOriginRange[0][2], m_StartOriginRange[1][2] ); + + if( m_iCoordSystem == PCS_Relative ) + { + VectorCopy( RangedOrigin, pNewPcl->m_Origin ); + } + else + { + VectorAdd( m_Origin, RangedOrigin, pNewPcl->m_Origin ); + } + + VectorAdd( pNewPcl->m_Origin, m_StartOriginOffset, pNewPcl->m_Origin ); + + // Velocity + VectorSet( pNewPcl->m_Velocity, + gEngfuncs.pfnRandomFloat( m_StartVelocity[0][0], m_StartVelocity[1][0] ), + gEngfuncs.pfnRandomFloat( m_StartVelocity[0][1], m_StartVelocity[1][1] ), + gEngfuncs.pfnRandomFloat( m_StartVelocity[0][2], m_StartVelocity[1][2] ) ); + + pNewPcl->m_flLifetime = gHUD.m_flTime + gEngfuncs.pfnRandomFloat( m_flParticleLifetime[0], m_flParticleLifetime[1] ); + pNewPcl->m_flScale = gEngfuncs.pfnRandomFloat( m_flStartScaleRange[0], m_flStartScaleRange[1] ); + pNewPcl->m_flGrowScale = gEngfuncs.pfnRandomFloat( m_flGrowScaleRange[0], m_flGrowScaleRange[1] ); + + pNewPcl->m_iModelIndex = gEngfuncs.pfnRandomLong( 0, m_iNumSprites-1 ); + + if( m_pParticles ) + m_pParticles->AddParticle( pNewPcl ); + else + m_pParticles = pNewPcl; + + m_iNumParticles++; +} + +void CSpriteEmitter::ClearList( CParticle *pList ) +{ + CParticle *pCurPcl = pList; + while( pCurPcl ) + { + CParticle *pNextPcl = pCurPcl->m_pNext; + delete pCurPcl; + pCurPcl = pNextPcl; + } +} + +//================================================== +// CParticleSystem +//================================================== +CParticleSystem::CParticleSystem() +{ + memset( m_szName, 0, sizeof( m_szName ) ); + memset( m_Origin, 0, sizeof( m_Origin ) ); +} + +CParticleSystem::CParticleSystem( const CParticleSystem &Cpy ) +{ + strcpy( m_szName, Cpy.m_szName ); + VectorCopy( Cpy.m_Origin, m_Origin ); +} + +CParticleSystem::~CParticleSystem() +{ +} + +void CParticleSystem::Render( int Transparent ) +{ + if( m_Emitters.size() ) + { + list::iterator iter; + + for( iter = m_Emitters.begin(); iter != m_Emitters.end(); iter++ ) + { + if( Transparent && iter->IsTransparent() ) + iter->Render(); + else if( !Transparent && !iter->IsTransparent() ) + iter->Render(); + } + } +} + +void CParticleSystem::Update( double flFrametime ) +{ + if( m_Emitters.size() ) + { + list::iterator iter; + + for( iter = m_Emitters.begin(); iter != m_Emitters.end(); iter++ ) + { + VectorCopy( m_Origin, iter->m_Origin ); + iter->Update( flFrametime ); + } + } +} + +//================================================== +// CParticleSystemManager +//================================================== +CParticleSystemManager::CParticleSystemManager() +{ + memset( m_szCurrentLevel, 0, sizeof( m_szCurrentLevel ) ); +} + +CParticleSystemManager::~CParticleSystemManager() +{ +} + +void CParticleSystemManager::RenderNormal() +{ + if( m_ActiveSystems.size() ) + { + list::iterator iter; + + for( iter = m_ActiveSystems.begin(); iter != m_ActiveSystems.end(); iter++ ) + iter->Render( 0 ); + } +} + +void CParticleSystemManager::RenderTransparent() +{ + if( m_ActiveSystems.size() ) + { + list::iterator iter; + + for( iter = m_ActiveSystems.begin(); iter != m_ActiveSystems.end(); iter++ ) + iter->Render( 1 ); + } +} + +void CParticleSystemManager::Update( double flFrametime ) +{ + if( m_ActiveSystems.size() ) + { + list::iterator iter; + + for( iter = m_ActiveSystems.begin(); iter != m_ActiveSystems.end(); iter++ ) + iter->Update( flFrametime ); + } +} + +void CParticleSystemManager::AddParticleSystem( char *pszName, psvec3_t Origin ) +{ + gEngfuncs.Con_Printf( "Adding particle system \"%s\" at %f %f %f\n", pszName, Origin[0], + Origin[1], Origin[2] ); + + const CParticleSystem *pNewPclSystem = GetParticleSystem( pszName ); + if( pNewPclSystem != NULL ) + { + m_ActiveSystems.push_back( *pNewPclSystem ); + return; + } + + gEngfuncs.Con_Printf( "Particle system \"%s\" not found!\n", pszName ); +} + +const CParticleSystem *CParticleSystemManager::GetParticleSystem( char *pszName ) +{ + if( m_ReferenceSystems.size() ) + { + list::iterator iter; + + for( iter = m_ReferenceSystems.begin(); iter != m_ReferenceSystems.end(); iter++ ) + { + if( stricmp( iter->m_szName, pszName ) == 0 ) + return &*iter; + } + } + + return NULL; +} + +const CSpriteEmitter *CParticleSystemManager::GetEmitter( char *pszName ) +{ + if( m_ReferenceEmitters.size() ) + { + list::iterator iter; + + for( iter = m_ReferenceEmitters.begin(); iter != m_ReferenceEmitters.end(); iter++ ) + { + if( stricmp( iter->m_szName, pszName ) == 0 ) + return &*iter; + } + } + + return NULL; +} + +void CParticleSystemManager::CheckMap() +{ + // Maps dont match, time to reinitialize emitters! + if( stricmp( m_szCurrentLevel, gEngfuncs.pfnGetLevelName() ) != 0 ) + { + strcpy( m_szCurrentLevel, gEngfuncs.pfnGetLevelName() ); + + m_ActiveSystems.clear(); + m_ReferenceEmitters.clear(); + m_ReferenceSystems.clear(); + + // Test values + #if 1 + CSpriteEmitter newEmitter; + + strcpy( newEmitter.m_szName, "test.sewer_ventsmoke_emitter" ); + VectorSet( newEmitter.m_StartVelocity[0], 50, 0, -50.0f ); + VectorSet( newEmitter.m_StartVelocity[1], 100, 0, -100.0f ); + newEmitter.m_flParticlesPerSecond = 10.0f; + newEmitter.m_flParticleLifetime[0] = 5.0f; + newEmitter.m_flParticleLifetime[1] = 6.0f; + + VectorSet( newEmitter.m_StartOriginOffset, 0, 0, 0 ); + VectorSet( newEmitter.m_StartOriginRange[0], -10, -10, -10 ); + VectorSet( newEmitter.m_StartOriginRange[1], 10, 10, 10 ); + + newEmitter.m_flStartScaleRange[0] = 10.0f; + newEmitter.m_flStartScaleRange[1] = 15.0f; + newEmitter.m_flGrowScaleRange[0] = 1.0f; + newEmitter.m_flGrowScaleRange[1] = 2.0f; + + newEmitter.m_iRenderMode = PRM_TransAdd; + newEmitter.m_iCoordSystem = PCS_Standard; + + newEmitter.m_iMaxParticles = 256; + + newEmitter.m_iNumSprites = 1; + newEmitter.m_iSpriteHandles[0] = gEngfuncs.pfnSPR_Load( "sprites/circle.spr" ); + + newEmitter.m_flInactiveTime = 0.5f; + + m_ReferenceEmitters.push_back( newEmitter ); + + CParticleSystem newSystem; + strcpy( newSystem.m_szName, "test.sewer_ventsmoke" ); + newSystem.m_Emitters.push_back( newEmitter ); + m_ReferenceSystems.push_back( newSystem ); + + // test.sewer_lightsmoke + // test.sewer_ventsmoke + +/* CSpriteEmitter newEmitter; + + strcpy( newEmitter.m_szName, "test.testemitter" ); + VectorSet( newEmitter.m_Origin, 0, 0, 50.0f ); + newEmitter.m_Origin[2] -= 25.0f; + VectorSet( newEmitter.m_StartVelocity[0], 0, 0, -500.0f ); + VectorSet( newEmitter.m_StartVelocity[1], 0, 0, -750.0f ); + newEmitter.m_flParticlesPerSecond = 100.0f; + newEmitter.m_flParticleLifetime[0] = 4.0f; + newEmitter.m_flParticleLifetime[1] = 5.0f; + + VectorSet( newEmitter.m_StartOriginOffset, 0, 0, 480 ); + VectorSet( newEmitter.m_StartOriginRange[0], -500.0f, -500.0f, 0 ); + VectorSet( newEmitter.m_StartOriginRange[1], 500.0f, 500.0f, 0 ); + + VectorSet( newEmitter.m_MinVelocity, 0, 0, 0 ); + VectorSet( newEmitter.m_MaxVelocity, 0, 0, 0 ); + + newEmitter.m_flStartScaleRange[0] = 10.0; + newEmitter.m_flStartScaleRange[1] = 15.0; + newEmitter.m_flGrowScaleRange[0] = 0.0; + newEmitter.m_flGrowScaleRange[1] = 0.0; + + newEmitter.m_iRenderMode = PRM_TransAdd; + newEmitter.m_iCoordSystem = PCS_Standard; + + newEmitter.m_iMaxParticles = 1024; + + newEmitter.m_iNumSprites = 1; + newEmitter.m_iSpriteHandles[0] = gEngfuncs.pfnSPR_Load( "sprites/circle.spr" ); + + newEmitter.m_flInactiveTime = 1.0f; + + m_ReferenceEmitters.push_back( newEmitter ); + + CParticleSystem newSystem; + + strcpy( newSystem.m_szName, "test.testsystem" ); + VectorCopy( newEmitter.m_Origin, newSystem.m_Origin ); + +// newSystem.m_pEmitters.push_back( new CSpriteEmitter( g_ParticleSystemManager.GetEmitter( "test.testemitter" ) ) ); + + m_ActiveSystems.push_back( newSystem ); + m_ReferenceSystems.push_back( newSystem );*/ + #endif + } +} \ No newline at end of file diff --git a/cl_dll/ParticleBase.h b/cl_dll/ParticleBase.h new file mode 100644 index 0000000..7939bbf --- /dev/null +++ b/cl_dll/ParticleBase.h @@ -0,0 +1,158 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#if !defined( __PARTICLEBASE_H_ ) +#define __PARTICLEBASE_H_ + +#include +using namespace std; + +// +// Data types +// + +// Identical to vec3_t only it wont convert to Vector +typedef vec_t psvec3_t[3]; + +enum PclRenderMode_e { + PRM_Normal = kRenderNormal, + PRM_TransColor = kRenderTransColor, + PRM_TransTexture = kRenderTransTexture, + PRM_TransGlow = kRenderGlow, + PRM_TransAlpha = kRenderTransAlpha, + PRM_TransAdd = kRenderTransAdd, +}; + +enum PclCoordSystem_e { + PCS_Standard, // Particles spawn based off emitter location then use world coordinates + PCS_Relative, // Particles spawn based off emitter location, and stays relative to emitter location +}; + +// singly-linked particle +struct CParticle +{ + CParticle() : + m_flLifetime( 0 ), m_iModelIndex( 0 ), m_pNext( 0 ) + { + memset( m_Origin, 0, sizeof( m_Origin ) ); + memset( m_Velocity, 0, sizeof( m_Velocity ) ); + } + void AddParticle( CParticle *pPcl ) + { + if( m_pNext ) + m_pNext->AddParticle( pPcl ); + else + { + m_pNext = pPcl; + } + } + + psvec3_t m_Origin; + psvec3_t m_Velocity; + float m_flLifetime; + float m_flScale; + float m_flGrowScale; + + int m_iModelIndex; + CParticle *m_pNext; +}; + +// +// CSpriteEmitter +// +class CSpriteEmitter +{ +public: + CSpriteEmitter(); + CSpriteEmitter( const CSpriteEmitter &Cpy ); + ~CSpriteEmitter(); + + bool IsTransparent(){ return ( m_iRenderMode != PRM_Normal ) ? true : false; } + void Render(); + void Update( double flFrametime ); + + char m_szName[64]; // Emitter name + int m_iNumSprites; // How many sprites does this emitter use? + int m_iSpriteHandles[10]; // Handles to sprites to utilize + psvec3_t m_Origin; // Location of this emitter + psvec3_t m_Acceleration; // Acceleration to apply to each particle + psvec3_t m_StartOriginOffset; // Offset to apply to each newly created particle + psvec3_t m_StartOriginRange[2]; // Location min/max range to apply to each particle randomly + psvec3_t m_StartVelocity[2]; // Starting velocity range for each particle + psvec3_t m_MinVelocity; // Min particle velocity + psvec3_t m_MaxVelocity; // Max particle velocity + int m_iMaxParticles; // Maximum number of particles. + float m_flParticlesPerSecond; // Particles to generate each second + float m_flParticleLifetime[2]; // Particle lifetime range + float m_flStartScaleRange[2]; // Particle starting scale range + float m_flGrowScaleRange[2]; // Particle grow rate, rate per sec + int m_iRenderMode; // Rendering mode of the particles + int m_iCoordSystem; // Coordinate system for particles + float m_flInactiveTime; // If the particle emitter can't be seen for this time, it wont be rendered. +private: + void AddParticle(); + void ClearList( CParticle *pList ); + + CParticle *m_pParticles; + float m_flLastParticleTime; + float m_flNextPpsChange; + int m_iNumParticles; + float m_flInactiveStart; +}; + +class CParticleSystem +{ +public: + CParticleSystem(); + CParticleSystem( const CParticleSystem &Cpy ); + ~CParticleSystem(); + + void Render( int Transparent ); + void Update( double flFrametime ); + + char m_szName[64]; // Particle system name + psvec3_t m_Origin; // Particle system origin + + list m_Emitters; +}; + +// Stores definitions of all particle systems read from disk +// Also maintains currently active particle systems +class CParticleSystemManager +{ +public: + CParticleSystemManager(); + ~CParticleSystemManager(); + + void RenderNormal(); + void RenderTransparent(); + void Update( double flFrametime ); + + void AddParticleSystem( char *pszName, psvec3_t Origin ); + + const CParticleSystem *GetParticleSystem( char *pszName ); + const CSpriteEmitter *GetEmitter( char *pszName ); + + void CheckMap(); +private: + list m_ActiveSystems; // Currently active particle systems + + list m_ReferenceEmitters; // Emitters loaded from disk + list m_ReferenceSystems; // Particle Systems loaded from disk + + char m_szCurrentLevel[1024]; +}; + +extern CParticleSystemManager g_ParticleSystemManager; + +#endif \ No newline at end of file diff --git a/cl_dll/StudioModelRenderer.cpp b/cl_dll/StudioModelRenderer.cpp index 30d2dd9..9a0ace1 100644 --- a/cl_dll/StudioModelRenderer.cpp +++ b/cl_dll/StudioModelRenderer.cpp @@ -29,6 +29,15 @@ #include "StudioModelRenderer.h" #include "GameStudioModelRenderer.h" +//#include "entity_types.h" +//#include "usercmd.h" +//#include "eventscripts.h" +//#include "ev_hldm.h" +//#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" +//#include "in_defs.h" + // Global engine <-> studio model rendering code interface engine_studio_api_t IEngineStudio; @@ -1314,6 +1323,8 @@ void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) m_pPlayerInfo->gaitframe += pseqdesc->numframes; } +texture_t red_texture; + /* ==================== StudioDrawPlayer @@ -1503,6 +1514,8 @@ void CStudioModelRenderer::StudioCalcAttachments( void ) } } +extern int ev_thermal; + /* ==================== StudioRenderModel @@ -1537,6 +1550,12 @@ void CStudioModelRenderer::StudioRenderModel( void ) } else { + // no texture / translucent render + if(ev_thermal) + { + IEngineStudio.SetForceFaceFlags( STUDIO_NF_CHROME ); + } + StudioRenderFinal( ); } } diff --git a/cl_dll/ammo.cpp b/cl_dll/ammo.cpp index 58cd12b..0132924 100644 --- a/cl_dll/ammo.cpp +++ b/cl_dll/ammo.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1999, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -26,7 +26,10 @@ #include #include "ammohistory.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" + +int HUD_CanChangeWeapon(int iId); WEAPON *gpActiveSel; // NULL means off, 1 means just the menu bar, otherwise // this points to the active weapon menu item @@ -64,7 +67,7 @@ int WeaponsResource :: HasAmmo( WEAPON *p ) if ( p->iMax1 == -1 ) return TRUE; - return (p->iAmmoType == -1) || p->iClip > 0 || CountAmmo(p->iAmmoType) + return (p->iAmmoType == -1) || p->iClip > 0 || CountAmmo(p->iAmmoType) || p->iClip2 > 0 || CountAmmo(p->iAmmo2Type) || ( p->iFlags & WEAPON_FLAGS_SELECTONEMPTY ); } @@ -86,6 +89,7 @@ void WeaponsResource :: LoadWeaponSprites( WEAPON *pWeapon ) memset( &pWeapon->rcActive, 0, sizeof(wrect_t) ); memset( &pWeapon->rcInactive, 0, sizeof(wrect_t) ); memset( &pWeapon->rcAmmo, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmobullet, 0, sizeof(wrect_t) ); memset( &pWeapon->rcAmmo2, 0, sizeof(wrect_t) ); pWeapon->hInactive = 0; pWeapon->hActive = 0; @@ -179,6 +183,28 @@ void WeaponsResource :: LoadWeaponSprites( WEAPON *pWeapon ) } else pWeapon->hAmmo = 0; + p = GetSpriteList(pList, "ammobullet", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmobullet = SPR_Load(sz); + pWeapon->rcAmmobullet = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmobullet2 = 0; + p = GetSpriteList(pList, "ammobullet2", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmobullet2 = SPR_Load(sz); + pWeapon->rcAmmobullet2 = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmobullet2 = 0; p = GetSpriteList(pList, "ammo2", iRes, i); if (p) @@ -201,7 +227,7 @@ WEAPON *WeaponsResource :: GetFirstPos( int iSlot ) for (int i = 0; i < MAX_WEAPON_POSITIONS; i++) { - if ( rgSlots[iSlot][i] && HasAmmo( rgSlots[iSlot][i] ) ) + if ( rgSlots[iSlot][i] /*&& HasAmmo( rgSlots[iSlot][i] )*/ ) { pret = rgSlots[iSlot][i]; break; @@ -219,7 +245,7 @@ WEAPON* WeaponsResource :: GetNextActivePos( int iSlot, int iSlotPos ) WEAPON *p = gWR.rgSlots[ iSlot ][ iSlotPos+1 ]; - if ( !p || !gWR.HasAmmo(p) ) + if ( !p /*|| !gWR.HasAmmo(p)*/ ) return GetNextActivePos( iSlot, iSlotPos + 1 ); return p; @@ -309,7 +335,6 @@ void CHudAmmo::Reset(void) gHR.Reset(); // VidInit(); - } int CHudAmmo::VidInit(void) @@ -374,17 +399,20 @@ void CHudAmmo::Think(void) // has the player selected one? if (gHUD.m_iKeyBits & IN_ATTACK) { - if (gpActiveSel != (WEAPON *)1) + // if(HUD_CanChangeWeapon(gpActiveSel->iId)) { - ServerCmd(gpActiveSel->szName); - g_weaponselect = gpActiveSel->iId; + if (gpActiveSel != (WEAPON *)1) + { + ServerCmd(gpActiveSel->szName); + g_weaponselect = gpActiveSel->iId; + } + + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + gHUD.m_iKeyBits &= ~IN_ATTACK; + + PlaySound("common/wpn_select.wav", 1); } - - gpLastSel = gpActiveSel; - gpActiveSel = NULL; - gHUD.m_iKeyBits &= ~IN_ATTACK; - - PlaySound("common/wpn_select.wav", 1); } } @@ -429,12 +457,12 @@ void WeaponsResource :: SelectSlot( int iSlot, int fAdvance, int iDirection ) if ( gHUD.m_fPlayerDead || gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) return; - if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) +/* if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) return; if ( ! ( gHUD.m_iWeaponBits & ~(1<<(WEAPON_SUIT)) )) return; - +*/ WEAPON *p = NULL; bool fastSwitch = CVAR_GET_FLOAT( "hud_fastswitch" ) != 0; @@ -568,6 +596,7 @@ int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) int iState = READ_BYTE(); int iId = READ_CHAR(); int iClip = READ_CHAR(); + int iClip2 = READ_CHAR(); // detect if we're also on target if ( iState > 1 ) @@ -599,13 +628,17 @@ int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) pWeapon->iClip = abs(iClip); else pWeapon->iClip = iClip; - + + if ( iClip2 < -1 ) + pWeapon->iClip2 = abs(iClip2); + else + pWeapon->iClip2 = iClip2; if ( iState == 0 ) // we're not the current weapon, so update no more return 1; m_pWeapon = pWeapon; - +/* if ( !(gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) { if ( gHUD.m_iFOV >= 90 ) @@ -623,7 +656,7 @@ int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) SetCrosshair(m_pWeapon->hZoomedCrosshair, m_pWeapon->rcZoomedCrosshair, 255, 255, 255); } } - +*/ m_fFade = 200.0f; //!!! m_iFlags |= HUD_ACTIVE; @@ -656,6 +689,7 @@ int CHudAmmo::MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf ) Weapon.iId = READ_CHAR(); Weapon.iFlags = READ_BYTE(); Weapon.iClip = 0; + Weapon.iClip2 = 0; gWR.AddWeapon( &Weapon ); @@ -764,7 +798,7 @@ void CHudAmmo::UserCmd_NextWeapon(void) { WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); - if ( wsp && gWR.HasAmmo(wsp) ) + if ( wsp/* && gWR.HasAmmo(wsp)*/ ) { gpActiveSel = wsp; return; @@ -805,7 +839,7 @@ void CHudAmmo::UserCmd_PrevWeapon(void) { WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); - if ( wsp && gWR.HasAmmo(wsp) ) + if ( wsp /*&& gWR.HasAmmo(wsp)*/ ) { gpActiveSel = wsp; return; @@ -832,12 +866,18 @@ int CHudAmmo::Draw(float flTime) int a, x, y, r, g, b; int AmmoWidth; - if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) - return 1; - if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) return 1; + // If you want to reenable autoaim crosshair, feel free to try. :P + if (m_pWeapon && gHUD.m_Health.m_iHealth) + { + if ( gHUD.m_iFOV >= 90 ) // normal crosshairs + SetCrosshair(m_pWeapon->hCrosshair, m_pWeapon->rcCrosshair, 255, 255, 255); + else // zoomed crosshairs + SetCrosshair(m_pWeapon->hZoomedCrosshair, m_pWeapon->rcZoomedCrosshair, 255, 255, 255); + } + // Draw Weapon Menu DrawWList(flTime); @@ -856,7 +896,6 @@ int CHudAmmo::Draw(float flTime) if ((pw->iAmmoType < 0) && (pw->iAmmo2Type < 0)) return 0; - int iFlags = DHN_DRAWZERO; // draw 0 values AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; @@ -866,7 +905,7 @@ int CHudAmmo::Draw(float flTime) if (m_fFade > 0) m_fFade -= (gHUD.m_flTimeDelta * 20); - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); ScaleColors(r, g, b, a ); @@ -883,8 +922,8 @@ int CHudAmmo::Draw(float flTime) // room for the number and the '|' and the current ammo x = ScreenWidth - (8 * AmmoWidth) - iIconWidth; - x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, pw->iClip, r, g, b); - + //x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, pw->iClip, r, g, b); + wrect_t rc; rc.top = 0; rc.left = 0; @@ -895,18 +934,40 @@ int CHudAmmo::Draw(float flTime) x += AmmoWidth/2; - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); // draw the | bar - FillRGBA(x, y, iBarWidth, gHUD.m_iFontHeight, r, g, b, a); - - x += iBarWidth + AmmoWidth/2;; + //FillRGBA(x, y, iBarWidth, gHUD.m_iFontHeight, r, g, b, a); + m_iAmmocount = 0; // GL Seems to need this ScaleColors(r, g, b, a ); x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); - - + + SPR_Set(m_pWeapon->hAmmobullet, r, g, b); + x = ScreenWidth - (gHUD.GetSpriteRect(m_pWeapon->hAmmobullet).right - gHUD.GetSpriteRect(m_pWeapon->hAmmobullet).left); + + if( m_iAmmocount <= pw->iClip ) + { + while(m_iAmmocount < pw->iClip ) + { + int iOffset = (m_pWeapon->rcAmmobullet.bottom - m_pWeapon->rcAmmobullet.top); + SPR_DrawHoles(0, x - 21, y, &m_pWeapon->rcAmmobullet); + y-= iOffset; + m_iAmmocount++; + } + } +/* else if(m_iAmmocount > gWR.CountAmmo(pw->iAmmoType) ) + { + while(m_iAmmocount > gWR.CountAmmo(pw->iAmmoType) ) + { + iOffset = (m_pWeapon->rcAmmobullet.bottom - m_pWeapon->rcAmmobullet.top); + SPR_DrawHoles(1, x, y, &m_pWeapon->rcAmmobullet); + y+= iOffset; + m_iAmmocount--; + } + } + */ } else { @@ -916,11 +977,14 @@ int CHudAmmo::Draw(float flTime) } // Draw the ammo Icon - int iOffset = (m_pWeapon->rcAmmo.bottom - m_pWeapon->rcAmmo.top)/8; - SPR_Set(m_pWeapon->hAmmo, r, g, b); - SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo); + //int iOffset = (m_pWeapon->rcAmmo.bottom - m_pWeapon->rcAmmo.top)/8; +// SPR_Set(m_pWeapon->hAmmo, r, g, b); +// SPR_DrawAdditive(0, x, y -= 12, &m_pWeapon->rcAmmo); + + } + /* // Does weapon have seconday ammo? if (pw->iAmmo2Type > 0) { @@ -939,6 +1003,71 @@ int CHudAmmo::Draw(float flTime) SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo2); } } + */ + // ADS - 2nd clip + // Does weapon have any secondary ammo at all? + if (m_pWeapon->iAmmo2Type > 0 && (gWR.CountAmmo(pw->iAmmo2Type) || pw->iClip2 >= 0)) + { + int iIconWidth = m_pWeapon->rcAmmo2.right - m_pWeapon->rcAmmo2.left; + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight/2; + y -= gHUD.m_iFontHeight + gHUD.m_iFontHeight/4; + + + if (pw->iClip2 >= 0) + { + // room for the number and the '|' and the current ammo + x = ScreenWidth - (8 * AmmoWidth) - iIconWidth; + // x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, pw->iClip2, r, g, b); + + wrect_t rc; + rc.top = 0; + rc.left = 0; + rc.right = AmmoWidth; + rc.bottom = 100; + + int iBarWidth = AmmoWidth/10; + + x += AmmoWidth/2; + + UnpackRGB(r,g,b, RGB_WHITE); + + // draw the | bar + // FillRGBA(x, y, iBarWidth, gHUD.m_iFontHeight, r, g, b, a); + + x += iBarWidth + AmmoWidth/2;; + + // GL Seems to need this + ScaleColors(r, g, b, a ); + x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmo2Type), r, g, b); + m_iAmmocount = 0; + + SPR_Set(m_pWeapon->hAmmobullet2, r, g, b); + x = ScreenWidth - ((gHUD.GetSpriteRect(m_pWeapon->hAmmobullet2).right - gHUD.GetSpriteRect(m_pWeapon->hAmmobullet2).left)+(gHUD.GetSpriteRect(m_pWeapon->hAmmobullet2).right - gHUD.GetSpriteRect(m_pWeapon->hAmmobullet2).left)); + if( m_iAmmocount < pw->iClip2 ) + { + while(m_iAmmocount < pw->iClip2 ) + { + int iOffset = (m_pWeapon->rcAmmobullet2.bottom - m_pWeapon->rcAmmobullet2.top); + SPR_DrawHoles(0, x-21, y, &m_pWeapon->rcAmmobullet2); + y-= iOffset; + m_iAmmocount++; + } + } + } + else + { + // SPR_Draw a bullets only line + x = ScreenWidth - 4 * AmmoWidth - iIconWidth; + x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmo2Type), r, g, b); + } + + // Draw the ammo Icon +// int iOffset = (m_pWeapon->rcAmmo2.bottom - m_pWeapon->rcAmmo2.top)/8; +// SPR_Set(m_pWeapon->hAmmo2, r, g, b); +// SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo2); + } + // + return 1; } @@ -968,7 +1097,7 @@ int DrawBar(int x, int y, int width, int height, float f) width -= w; } - UnpackRGB(r, g, b, RGB_YELLOWISH); + UnpackRGB(r, g, b, RGB_WHITE); FillRGBA(x, y, width, height, r, g, b, 128); @@ -1044,7 +1173,7 @@ int CHudAmmo::DrawWList(float flTime) { int iWidth; - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); if ( iActiveSlot == i ) a = 255; @@ -1066,7 +1195,7 @@ int CHudAmmo::DrawWList(float flTime) else iWidth = giBucketWidth; - SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_bucket0 + i)); + SPR_DrawHoles(0, x, y, &gHUD.GetSpriteRect(m_HUD_bucket0 + i)); x += iWidth + 5; } @@ -1096,14 +1225,21 @@ int CHudAmmo::DrawWList(float flTime) if ( !p || !p->iId ) continue; - UnpackRGB( r,g,b, RGB_YELLOWISH ); + UnpackRGB( r,g,b, RGB_WHITE ); // if active, then we must have ammo. + // LIES -> Gage :) if ( gpActiveSel == p ) { + if ( !gWR.HasAmmo(p) ) + { + UnpackRGB(r,g,b, RGB_REDISH); + ScaleColors(r, g, b, 128); + } + SPR_Set(p->hActive, r, g, b ); - SPR_DrawAdditive(0, x, y, &p->rcActive); + SPR_DrawHoles(0, x, y, &p->rcActive); SPR_Set(gHUD.GetSprite(m_HUD_selection), r, g, b ); SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_selection)); @@ -1111,7 +1247,6 @@ int CHudAmmo::DrawWList(float flTime) else { // Draw Weapon if Red if no ammo - if ( gWR.HasAmmo(p) ) ScaleColors(r, g, b, 192); else @@ -1138,7 +1273,7 @@ int CHudAmmo::DrawWList(float flTime) { // Draw Row of weapons. - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); for ( int iPos = 0; iPos < MAX_WEAPON_POSITIONS; iPos++ ) { @@ -1149,7 +1284,7 @@ int CHudAmmo::DrawWList(float flTime) if ( gWR.HasAmmo(p) ) { - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); a = 128; } else diff --git a/cl_dll/ammo.h b/cl_dll/ammo.h index ad2b556..59de70e 100644 --- a/cl_dll/ammo.h +++ b/cl_dll/ammo.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1999, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -35,7 +35,7 @@ struct WEAPON int iFlags; int iId; int iClip; - + int iClip2; int iCount; // # of itesm in plist HSPRITE hActive; @@ -44,6 +44,10 @@ struct WEAPON wrect_t rcInactive; HSPRITE hAmmo; wrect_t rcAmmo; + HSPRITE hAmmobullet; + wrect_t rcAmmobullet; + HSPRITE hAmmobullet2; + wrect_t rcAmmobullet2; HSPRITE hAmmo2; wrect_t rcAmmo2; HSPRITE hCrosshair; diff --git a/cl_dll/ammo_secondary.cpp b/cl_dll/ammo_secondary.cpp index df8bb7f..e06b29c 100644 --- a/cl_dll/ammo_secondary.cpp +++ b/cl_dll/ammo_secondary.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1999, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -60,7 +60,7 @@ int CHudAmmoSecondary :: Draw(float flTime) // draw secondary ammo icons above normal ammo readout int a, x, y, r, g, b, AmmoWidth; - UnpackRGB( r, g, b, RGB_YELLOWISH ); + UnpackRGB( r, g, b, RGB_WHITE ); a = (int) max( MIN_ALPHA, m_fFade ); if (m_fFade > 0) m_fFade -= (gHUD.m_flTimeDelta * 20); // slowly lower alpha to fade out icons diff --git a/cl_dll/ammohistory.cpp b/cl_dll/ammohistory.cpp index 11db04a..5f5aead 100644 --- a/cl_dll/ammohistory.cpp +++ b/cl_dll/ammohistory.cpp @@ -124,7 +124,7 @@ int HistoryResource :: DrawAmmoHistory( float flTime ) HSPRITE *spr = gWR.GetAmmoPicFromWeapon( rgAmmoHistory[i].iId, rcPic ); int r, g, b; - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; ScaleColors(r, g, b, min(scale, 255) ); @@ -148,7 +148,7 @@ int HistoryResource :: DrawAmmoHistory( float flTime ) return 1; // we don't know about the weapon yet, so don't draw anything int r, g, b; - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); if ( !gWR.HasAmmo( weap ) ) UnpackRGB(r,g,b, RGB_REDISH); // if the weapon doesn't have ammo, display it as red @@ -170,7 +170,7 @@ int HistoryResource :: DrawAmmoHistory( float flTime ) wrect_t rect = gHUD.GetSpriteRect( rgAmmoHistory[i].iId ); - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; ScaleColors(r, g, b, min(scale, 255) ); diff --git a/cl_dll/ammohistory.h b/cl_dll/ammohistory.h index 5b52c3e..4c75f59 100644 --- a/cl_dll/ammohistory.h +++ b/cl_dll/ammohistory.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1999, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -17,7 +17,7 @@ // // this is the max number of items in each bucket -#define MAX_WEAPON_POSITIONS MAX_WEAPON_SLOTS +#define MAX_WEAPON_POSITIONS 8//MAX_WEAPON_SLOTS class WeaponsResource { @@ -28,7 +28,6 @@ private: // counts of weapons * ammo WEAPON* rgSlots[MAX_WEAPON_SLOTS+1][MAX_WEAPON_POSITIONS+1]; // The slots currently in use by weapons. The value is a pointer to the weapon; if it's NULL, no weapon is there int riAmmo[MAX_AMMO_TYPES]; // count of each ammo type - public: void Init( void ) { diff --git a/cl_dll/cdll_int.cpp b/cl_dll/cdll_int.cpp index ffd1dc1..928ee61 100644 --- a/cl_dll/cdll_int.cpp +++ b/cl_dll/cdll_int.cpp @@ -32,13 +32,27 @@ extern "C" #include "hud_servers.h" #include "vgui_int.h" #include "interface.h" +#include "ParseBspEnt.h" +#include "ParseBsp.h" +#include "twm.h" +#include "twmmanager.h" #define DLLEXPORT __declspec( dllexport ) - cl_enginefunc_t gEngfuncs; CHud gHUD; -TeamFortressViewport *gViewPort = NULL; +TheWastesViewport *gViewPort = NULL; + +extern int ev_thermal; +extern int g_iClientLasersEnabled[32]; + +cvar_t *cl_shellcase_lifetime = NULL; +cvar_t *cl_shellcase_quality = NULL; +cvar_t *cl_enablefog = NULL; +cvar_t *cl_efx_frequency = NULL; + +// Msg definitions +int __MsgFunc_LaserInfo(const char *pszName, int iSize, void *pbuf ); void InitInput (void); void EV_HookEvents( void ); @@ -66,7 +80,7 @@ int DLLEXPORT HUD_ConnectionlessPacket( struct netadr_s *net_from, const char * int DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); void DLLEXPORT HUD_Frame( double time ); void DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); -void DLLEXPORT HUD_DirectorEvent(unsigned char command, unsigned int firstObject, unsigned int secondObject, unsigned int flags); +void DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ); } /* @@ -150,10 +164,15 @@ int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ) EV_HookEvents(); + // Client side cvars + cl_shellcase_lifetime = gEngfuncs.pfnRegisterVariable("cl_shellcase_lifetime","5.0",FCVAR_ARCHIVE); // How long shellcases stay on ground + cl_shellcase_quality = gEngfuncs.pfnRegisterVariable("cl_shellcase_quality","0",FCVAR_ARCHIVE); // Hi/Lo Quality casings + cl_enablefog = gEngfuncs.pfnRegisterVariable("cl_enablefog","0",FCVAR_ARCHIVE); // Fog + cl_efx_frequency = gEngfuncs.pfnRegisterVariable("cl_efx_frequency","0.5",FCVAR_ARCHIVE); // How often special efx should be enabled + return 1; } - /* ========================== HUD_VidInit @@ -163,13 +182,24 @@ and whenever the vid_mode is changed so the HUD can reinitialize itself. ========================== */ - int DLLEXPORT HUD_VidInit( void ) { gHUD.VidInit(); VGui_Startup(); + ev_thermal = 0; // Make sure thermal is off + + // No clients have lasersights enabled + memset(g_iClientLasersEnabled,0,sizeof(int)*32); + + // Reset .TWM data + memset(g_MuzzleflashModels,0,sizeof(g_MuzzleflashModels)); + g_iNumMuzzleflashModels = 0; + + // Precache .TWM's + g_TwmManager.BeginPrecache(); + return 1; } @@ -182,16 +212,19 @@ to a server. Reinitializes all the hud variables. ========================== */ - int DLLEXPORT HUD_Init( void ) { InitInput(); gHUD.Init(); Scheme_Init(); + + // Not strictly HUD based + // messages, so stick them here ? + HOOK_MESSAGE(LaserInfo); + return 1; } - /* ========================== HUD_Redraw @@ -208,7 +241,6 @@ int DLLEXPORT HUD_Redraw( float time, int intermission ) return 1; } - /* ========================== HUD_UpdateClientData @@ -274,15 +306,14 @@ void DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking) /* ========================== -HUD_DirectorEvent +HUD_DirectorMessage Called when a director event message was received ========================== */ - -void DLLEXPORT HUD_DirectorEvent(unsigned char command, unsigned int firstObject, unsigned int secondObject, unsigned int flags) +void DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ) { - gHUD.m_Spectator.DirectorEvent(command, firstObject, secondObject, flags); + gHUD.m_Spectator.DirectorMessage( iSize, pbuf ); } diff --git a/cl_dll/cl_dll.dsp b/cl_dll/cl_dll.dsp index 1c76e99..3deb683 100644 --- a/cl_dll/cl_dll.dsp +++ b/cl_dll/cl_dll.dsp @@ -38,8 +38,8 @@ RSC=rc.exe # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 -# PROP Output_Dir ".\Release" -# PROP Intermediate_Dir ".\Release" +# PROP Output_Dir "..\obj\ReleaseClient" +# PROP Intermediate_Dir "..\obj\ReleaseClient" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c @@ -53,7 +53,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib /nologo /subsystem:windows /dll /map /machine:I386 /out:".\Release\client.dll" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib /nologo /subsystem:windows /dll /map /machine:I386 /out:"../../cl_dlls/client.dll" !ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" @@ -64,8 +64,8 @@ LINK32=link.exe # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 1 -# PROP Output_Dir ".\Debug" -# PROP Intermediate_Dir ".\Debug" +# PROP Output_Dir "..\obj\DebugClient" +# PROP Intermediate_Dir "..\obj\DebugClient" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c @@ -79,7 +79,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 -# ADD LINK32 oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /out:".\Debug\client.dll" +# ADD LINK32 oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /out:"../../cl_dlls/client.dll" !ENDIF @@ -95,51 +95,23 @@ LINK32=link.exe # PROP Default_Filter "*.CPP" # Begin Source File -SOURCE=..\dlls\crossbow.cpp +SOURCE=.\ev_thewastes.cpp # End Source File # Begin Source File -SOURCE=..\dlls\crowbar.cpp +SOURCE=.\thewastes\hl_baseentity.cpp # End Source File # Begin Source File -SOURCE=..\dlls\egon.cpp +SOURCE=.\thewastes\hl_events.cpp # End Source File # Begin Source File -SOURCE=.\ev_hldm.cpp +SOURCE=.\thewastes\hl_objects.cpp # End Source File # Begin Source File -SOURCE=..\dlls\gauss.cpp -# End Source File -# Begin Source File - -SOURCE=..\dlls\handgrenade.cpp -# End Source File -# Begin Source File - -SOURCE=.\hl\hl_baseentity.cpp -# End Source File -# Begin Source File - -SOURCE=.\hl\hl_events.cpp -# End Source File -# Begin Source File - -SOURCE=.\hl\hl_objects.cpp -# End Source File -# Begin Source File - -SOURCE=.\hl\hl_weapons.cpp -# End Source File -# Begin Source File - -SOURCE=..\dlls\wpn_shared\hl_wpn_glock.cpp -# End Source File -# Begin Source File - -SOURCE=..\dlls\hornetgun.cpp +SOURCE=.\thewastes\hl_weapons.cpp # End Source File # Begin Source File @@ -147,31 +119,31 @@ SOURCE=..\common\interface.cpp # End Source File # Begin Source File -SOURCE=..\dlls\mp5.cpp +SOURCE=..\dlls\thewastes.cpp # End Source File # Begin Source File -SOURCE=..\dlls\python.cpp +SOURCE=..\dlls\wpn_shared\tw_akimbos.cpp # End Source File # Begin Source File -SOURCE=..\dlls\rpg.cpp +SOURCE=..\dlls\wpn_shared\tw_automatics.cpp # End Source File # Begin Source File -SOURCE=..\dlls\satchel.cpp +SOURCE=..\dlls\wpn_shared\tw_explosives.cpp # End Source File # Begin Source File -SOURCE=..\dlls\shotgun.cpp +SOURCE=..\dlls\wpn_shared\tw_melee.cpp # End Source File # Begin Source File -SOURCE=..\dlls\squeakgrenade.cpp +SOURCE=..\dlls\wpn_shared\tw_shotguns.cpp # End Source File # Begin Source File -SOURCE=..\dlls\tripmine.cpp +SOURCE=..\dlls\wpn_shared\tw_sidearms.cpp # End Source File # Begin Source File @@ -208,10 +180,6 @@ SOURCE=.\ammohistory.cpp # End Source File # Begin Source File -SOURCE=.\battery.cpp -# End Source File -# Begin Source File - SOURCE=.\cdll_int.cpp # End Source File # Begin Source File @@ -232,6 +200,10 @@ SOURCE=.\entity.cpp # End Source File # Begin Source File +SOURCE=.\env_fog.cpp +# End Source File +# Begin Source File + SOURCE=.\ev_common.cpp # End Source File # Begin Source File @@ -305,11 +277,19 @@ SOURCE=.\overview.cpp # End Source File # Begin Source File +SOURCE=.\parsebsp.cpp +# End Source File +# Begin Source File + +SOURCE=.\ParseBspEnt.cpp +# End Source File +# Begin Source File + SOURCE=.\parsemsg.cpp # End Source File # Begin Source File -SOURCE=.\parsemsg.h +SOURCE=.\ParticleBase.cpp # End Source File # Begin Source File @@ -341,6 +321,10 @@ SOURCE=.\studio_util.cpp # End Source File # Begin Source File +SOURCE=.\studioevent.cpp +# End Source File +# Begin Source File + SOURCE=.\StudioModelRenderer.cpp # End Source File # Begin Source File @@ -349,6 +333,10 @@ SOURCE=.\text_message.cpp # End Source File # Begin Source File +SOURCE=.\thewastes_hud.cpp +# End Source File +# Begin Source File + SOURCE=.\train.cpp # End Source File # Begin Source File @@ -357,6 +345,10 @@ SOURCE=.\tri.cpp # End Source File # Begin Source File +SOURCE=.\twm.cpp +# End Source File +# Begin Source File + SOURCE=.\util.cpp # End Source File # Begin Source File @@ -365,10 +357,6 @@ SOURCE=..\game_shared\vgui_checkbutton2.cpp # End Source File # Begin Source File -SOURCE=.\vgui_ClassMenu.cpp -# End Source File -# Begin Source File - SOURCE=.\vgui_ConsolePanel.cpp # End Source File # Begin Source File @@ -393,6 +381,10 @@ SOURCE=.\vgui_int.cpp # End Source File # Begin Source File +SOURCE=.\vgui_ItemSelection.cpp +# End Source File +# Begin Source File + SOURCE=..\game_shared\vgui_listbox.cpp # End Source File # Begin Source File @@ -417,11 +409,11 @@ SOURCE=.\vgui_ServerBrowser.cpp # End Source File # Begin Source File -SOURCE=.\vgui_TeamFortressViewport.cpp +SOURCE=.\vgui_teammenu.cpp # End Source File # Begin Source File -SOURCE=.\vgui_teammenu.cpp +SOURCE=.\vgui_TheWastesViewport.cpp # End Source File # Begin Source File @@ -449,6 +441,14 @@ SOURCE=.\cl_dll.h # End Source File # Begin Source File +SOURCE=..\common\cl_entity.h +# End Source File +# Begin Source File + +SOURCE=.\cl_util.h +# End Source File +# Begin Source File + SOURCE=.\com_weapons.h # End Source File # Begin Source File @@ -457,7 +457,15 @@ SOURCE=.\demo.h # End Source File # Begin Source File -SOURCE=.\ev_hldm.h +SOURCE=..\common\entity_state.h +# End Source File +# Begin Source File + +SOURCE=.\ev_thewastes.h +# End Source File +# Begin Source File + +SOURCE=..\common\event_args.h # End Source File # Begin Source File @@ -497,10 +505,6 @@ SOURCE=.\in_defs.h # End Source File # Begin Source File -SOURCE=..\common\itrackeruser.h -# End Source File -# Begin Source File - SOURCE=.\kbutton.h # End Source File # Begin Source File @@ -509,6 +513,22 @@ SOURCE=.\overview.h # End Source File # Begin Source File +SOURCE=.\parsebsp.h +# End Source File +# Begin Source File + +SOURCE=.\ParseBspEnt.h +# End Source File +# Begin Source File + +SOURCE=.\parsemsg.h +# End Source File +# Begin Source File + +SOURCE=.\ParticleBase.h +# End Source File +# Begin Source File + SOURCE=..\pm_shared\pm_debug.h # End Source File # Begin Source File @@ -533,6 +553,22 @@ SOURCE=..\pm_shared\pm_shared.h # End Source File # Begin Source File +SOURCE=..\common\pmtrace.h +# End Source File +# Begin Source File + +SOURCE=..\common\r_efx.h +# End Source File +# Begin Source File + +SOURCE=.\r_studioint.h +# End Source File +# Begin Source File + +SOURCE=..\common\ref_params.h +# End Source File +# Begin Source File + SOURCE=.\studio_util.h # End Source File # Begin Source File @@ -541,7 +577,35 @@ SOURCE=.\StudioModelRenderer.h # End Source File # Begin Source File -SOURCE=.\util.h +SOURCE=.\tf_defs.h +# End Source File +# Begin Source File + +SOURCE=.\tri.h +# End Source File +# Begin Source File + +SOURCE=..\common\triangleapi.h +# End Source File +# Begin Source File + +SOURCE=..\common\tw_common.h +# End Source File +# Begin Source File + +SOURCE=.\tw_vgui.h +# End Source File +# Begin Source File + +SOURCE=..\common\twm.h +# End Source File +# Begin Source File + +SOURCE=.\twmmanager.h +# End Source File +# Begin Source File + +SOURCE=..\dlls\util.h # End Source File # Begin Source File @@ -573,7 +637,7 @@ SOURCE=.\vgui_ServerBrowser.h # End Source File # Begin Source File -SOURCE=.\vgui_TeamFortressViewport.h +SOURCE=.\vgui_TheWastesViewport.h # End Source File # Begin Source File diff --git a/cl_dll/cl_util.h b/cl_dll/cl_util.h index c35b31c..ed92d3f 100644 --- a/cl_dll/cl_util.h +++ b/cl_dll/cl_util.h @@ -114,10 +114,7 @@ inline void ConsolePrint( const char *string ) gEngfuncs.pfnConsolePrint( string ); } -inline void CenterPrint( const char *string ) -{ - gEngfuncs.pfnCenterPrint( string ); -} +void CenterPrint(const char*); // returns the players name of entity no. #define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) @@ -136,6 +133,7 @@ void ScaleColors( int &r, int &g, int &b, int a ); #define VectorSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];} #define VectorAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];} #define VectorCopy(a,b) {(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];} +#define VectorSet(v,x,y,z) {(v)[0]=(x);(v)[1]=(y);(v)[2]=(z);} inline void VectorClear(float *a) { a[0]=0.0;a[1]=0.0;a[2]=0.0;} float Length(const float *v); void VectorMA (const float *veca, float scale, const float *vecb, float *vecc); @@ -158,3 +156,6 @@ inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ } HSPRITE LoadSprite(const char *pszName); + +// This code is used for jerking around a persons view +#define RECOIL_VIEW(amountx,amounty) { vec3_t ViewAngle; gEngfuncs.GetViewAngles((float*)ViewAngle); ViewAngle.x -= amountx; ViewAngle.y -= amounty; gEngfuncs.SetViewAngles((float*)ViewAngle); } diff --git a/cl_dll/death.cpp b/cl_dll/death.cpp index a3012cc..df1ffb9 100644 --- a/cl_dll/death.cpp +++ b/cl_dll/death.cpp @@ -22,17 +22,20 @@ #include #include -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" DECLARE_MESSAGE( m_DeathNotice, DeathMsg ); struct DeathNoticeItem { char szKiller[MAX_PLAYER_NAME_LENGTH*2]; char szVictim[MAX_PLAYER_NAME_LENGTH*2]; + char szDeathMessage[128]; int iId; // the index number of the associated sprite int iSuicide; int iTeamKill; int iNonPlayerKill; + int iSpecialDeath; float flDisplayTime; float *KillerColor; float *VictimColor; @@ -41,7 +44,7 @@ struct DeathNoticeItem { #define MAX_DEATHNOTICES 4 static int DEATHNOTICE_DISPLAY_TIME = 6; -#define DEATHNOTICE_TOP 20 +#define DEATHNOTICE_TOP ScreenHeight - 72 DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; @@ -83,7 +86,9 @@ void CHudDeathNotice :: InitHUDData( void ) int CHudDeathNotice :: VidInit( void ) { - m_HUD_d_skull = gHUD.GetSpriteIndex( "d_skull" ); + m_HUD_d_skull = gHUD.GetSpriteIndex( "d_skull" ); + m_HUD_d_headshot = gHUD.GetSpriteIndex( "d_headshot" ); + m_HUD_d_bleeding = gHUD.GetSpriteIndex( "d_bleed" ); return 1; } @@ -91,8 +96,19 @@ int CHudDeathNotice :: VidInit( void ) int CHudDeathNotice :: Draw( float flTime ) { int x, y, r, g, b; + int text_y; + int deathcount = 0; - for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + // How many death messages are there. + + for( int i = 0;i < MAX_DEATHNOTICES; i++) + { + if(rgDeathNoticeList[i].iId == 0) + break; + deathcount++; + } + + for ( i = 0; i < MAX_DEATHNOTICES; i++ ) { if ( rgDeathNoticeList[i].iId == 0 ) break; // we've gone through them all @@ -110,11 +126,41 @@ int CHudDeathNotice :: Draw( float flTime ) // Only draw if the viewport will let me if ( gViewPort && gViewPort->AllowedToPrintText() ) { - // Draw the death notice - y = DEATHNOTICE_TOP + (20 * i); //!!! + // Get the initial offset. + int offset = (20 * deathcount ); - int id = (rgDeathNoticeList[i].iId == -1) ? m_HUD_d_skull : rgDeathNoticeList[i].iId; - x = ScreenWidth - ConsoleStringLen(rgDeathNoticeList[i].szVictim) - (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + // Draw the death notice + y = DEATHNOTICE_TOP + (20 * i) - offset; //!!! + + int id,special_id = 0; + + switch(rgDeathNoticeList[i].iSpecialDeath) + { + default: + case 0: + break; + case 1: + special_id = m_HUD_d_headshot; + break; + case 2: + special_id = m_HUD_d_bleeding; + break; + } + + id = (rgDeathNoticeList[i].iId == -1) ? m_HUD_d_skull : rgDeathNoticeList[i].iId; + + int id_length; + + if(rgDeathNoticeList[i].iSpecialDeath == 2 && !rgDeathNoticeList[i].iSuicide) + id_length = 0; + else + id_length = (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + int special_id_length = (rgDeathNoticeList[i].iSpecialDeath && !rgDeathNoticeList[i].iSuicide) ? (gHUD.GetSpriteRect(special_id).right - gHUD.GetSpriteRect(special_id).left) : 0; + + // Put in center + x = (ScreenWidth/2) - + (ConsoleStringLen(rgDeathNoticeList[i].szVictim) - id_length - special_id_length)/2; if ( !rgDeathNoticeList[i].iSuicide ) { @@ -126,17 +172,29 @@ int CHudDeathNotice :: Draw( float flTime ) x = 5 + DrawConsoleString( x, y, rgDeathNoticeList[i].szKiller ); } - r = 255; g = 80; b = 0; +// r = 255; g = 80; b = 0; + r = g = b = 255; // WHITE - Gage if ( rgDeathNoticeList[i].iTeamKill ) { r = 10; g = 240; b = 10; // display it in sickly green } - // Draw death weapon - SPR_Set( gHUD.GetSprite(id), r, g, b ); - SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(id) ); + // Draw death weapon if needed + if(!(special_id_length && rgDeathNoticeList[i].iSuicide) && id_length) + { + SPR_Set( gHUD.GetSprite(id), r, g, b ); + SPR_DrawHoles( 0, x, y, &gHUD.GetSpriteRect(id) ); - x += (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + x += (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + } + + if(special_id_length) + { + SPR_Set(gHUD.GetSprite(special_id),r,g,b); + SPR_DrawHoles(0,x,y,&gHUD.GetSpriteRect(special_id)); + + x += (gHUD.GetSpriteRect(special_id).right - gHUD.GetSpriteRect(special_id).left); + } // Draw victims name (if it was a player that was killed) if (rgDeathNoticeList[i].iNonPlayerKill == FALSE) @@ -145,6 +203,13 @@ int CHudDeathNotice :: Draw( float flTime ) gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].VictimColor[0], rgDeathNoticeList[i].VictimColor[1], rgDeathNoticeList[i].VictimColor[2] ); x = DrawConsoleString( x, y, rgDeathNoticeList[i].szVictim ); } + + int text_w,text_h; + GetConsoleStringSize(rgDeathNoticeList[i].szDeathMessage,&text_w,&text_h); + + text_y = text_h*i; + + DrawConsoleString(ScreenWidth-text_w,text_y,rgDeathNoticeList[i].szDeathMessage); } } @@ -158,8 +223,9 @@ int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *p BEGIN_READ( pbuf, iSize ); - int killer = READ_BYTE(); - int victim = READ_BYTE(); + int killer = READ_BYTE(); + int victim = READ_BYTE(); + int special = READ_BYTE(); char killedwith[32]; strcpy( killedwith, "d_" ); @@ -184,6 +250,9 @@ int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *p if (gViewPort) gViewPort->GetAllPlayersInfo(); + // Set special death + rgDeathNoticeList[i].iSpecialDeath = special; + // Get the Killer's name char *killer_name = g_PlayerInfoList[ killer ].name; if ( !killer_name ) @@ -232,6 +301,14 @@ int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *p rgDeathNoticeList[i].iTeamKill = TRUE; } + char *szDeathMessage = READ_STRING(); + + // fill deathmessage string + if(killer == victim || killer == 0) + sprintf(rgDeathNoticeList[i].szDeathMessage,"%s commited suicide",victim_name); + else + sprintf(rgDeathNoticeList[i].szDeathMessage,szDeathMessage,killer_name,victim_name); + // Find the sprite in the list int spr = gHUD.GetSpriteIndex( killedwith ); @@ -249,47 +326,9 @@ int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *p } else { - // record the death notice in the console - if ( rgDeathNoticeList[i].iSuicide ) - { - ConsolePrint( rgDeathNoticeList[i].szVictim ); - - if ( !strcmp( killedwith, "d_world" ) ) - { - ConsolePrint( " died" ); - } - else - { - ConsolePrint( " killed self" ); - } - } - else if ( rgDeathNoticeList[i].iTeamKill ) - { - ConsolePrint( rgDeathNoticeList[i].szKiller ); - ConsolePrint( " killed his teammate " ); - ConsolePrint( rgDeathNoticeList[i].szVictim ); - } - else - { - ConsolePrint( rgDeathNoticeList[i].szKiller ); - ConsolePrint( " killed " ); - ConsolePrint( rgDeathNoticeList[i].szVictim ); - } - - if ( killedwith && *killedwith && (*killedwith > 13 ) && strcmp( killedwith, "d_world" ) && !rgDeathNoticeList[i].iTeamKill ) - { - ConsolePrint( " with " ); - - // replace the code names with the 'real' names - if ( !strcmp( killedwith+2, "egon" ) ) - strcpy( killedwith, "d_gluon gun" ); - if ( !strcmp( killedwith+2, "gauss" ) ) - strcpy( killedwith, "d_tau cannon" ); - - ConsolePrint( killedwith+2 ); // skip over the "d_" part - } - - ConsolePrint( "\n" ); + // record new death notices + ConsolePrint(rgDeathNoticeList[i].szDeathMessage); + ConsolePrint("\n"); } return 1; diff --git a/cl_dll/dmc_bspfile.h b/cl_dll/dmc_bspfile.h new file mode 100644 index 0000000..bc5b530 --- /dev/null +++ b/cl_dll/dmc_bspfile.h @@ -0,0 +1,48 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( DMC_BSPFILE_H ) +#define DMC_BSPFILE_H +#ifdef _WIN32 +#pragma once +#endif + +// MINI-version of BSPFILE.H to support DeathMatch Classic's entity lump extraction stuff. + +#define BSPVERSION 30 + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 + +#define HEADER_LUMPS 15 + +typedef struct +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + + +#endif // DMC_BSPFILE_H \ No newline at end of file diff --git a/cl_dll/entity.cpp b/cl_dll/entity.cpp index 2b0eef0..33270b3 100644 --- a/cl_dll/entity.cpp +++ b/cl_dll/entity.cpp @@ -17,22 +17,24 @@ #include "r_efx.h" #include "event_api.h" #include "pm_defs.h" -#include "pmtrace.h" - +#include "pmtrace.h" +#include "in_defs.h" #define DLLEXPORT __declspec( dllexport ) void Game_AddObjects( void ); +void Game_UpdateObjects(double frametime); extern vec3_t v_origin; int g_iAlive = 1; +double g_iFrametime = 0; + extern "C" { int DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ); void DLLEXPORT HUD_CreateEntities( void ); - void DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); void DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ); void DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ); void DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ); @@ -159,6 +161,10 @@ void DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct g_iUser2 = src->iuser2; g_iUser3 = src->iuser3; + // if the movetype is NOCLIP, we are in observer mode. + if(src->movetype == MOVETYPE_NOCLIP) + { + } } } @@ -210,15 +216,11 @@ void DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct pcd->iuser1 = g_iUser1; // observer mode pcd->iuser2 = g_iUser2; // first target pcd->iuser3 = g_iUser3; // second target - } // Fire prevention pcd->iuser4 = ppcd->iuser4; - pcd->fuser2 = ppcd->fuser2; - pcd->fuser3 = ppcd->fuser3; - VectorCopy( ppcd->vuser1, pcd->vuser1 ); VectorCopy( ppcd->vuser2, pcd->vuser2 ); VectorCopy( ppcd->vuser3, pcd->vuser3 ); @@ -228,82 +230,26 @@ void DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct } /* -//#define TEST_IT -#if defined( TEST_IT ) - -cl_entity_t mymodel[9]; - -void MoveModel( void ) -{ - cl_entity_t *player; - int i, j; - int modelindex; - struct model_s *mod; - - // Load it up with some bogus data - player = gEngfuncs.GetLocalPlayer(); - if ( !player ) - return; - - mod = gEngfuncs.CL_LoadModel( "models/sentry3.mdl", &modelindex ); - for ( i = 0; i < 3; i++ ) - { - for ( j = 0; j < 3; j++ ) - { - // Don't draw over ourself... - if ( ( i == 1 ) && ( j == 1 ) ) - continue; - - mymodel[ i * 3 + j ] = *player; - - mymodel[ i * 3 + j ].player = 0; - - mymodel[ i * 3 + j ].model = mod; - mymodel[ i * 3 + j ].curstate.modelindex = modelindex; - - // Move it out a bit - mymodel[ i * 3 + j ].origin[0] = player->origin[0] + 50 * ( 1 - i ); - mymodel[ i * 3 + j ].origin[1] = player->origin[1] + 50 * ( 1 - j ); - - gEngfuncs.CL_CreateVisibleEntity( ET_NORMAL, &mymodel[i*3+j] ); - } - } - -} - -#endif - -//#define TRACE_TEST -#if defined( TRACE_TEST ) - -extern int hitent; - -cl_entity_t hit; - -void TraceModel( void ) -{ - cl_entity_t *ent; - - if ( hitent <= 0 ) - return; - - // Load it up with some bogus data - ent = gEngfuncs.GetEntityByIndex( hitent ); - if ( !ent ) - return; - - hit = *ent; - //hit.curstate.rendermode = kRenderTransTexture; - //hit.curstate.renderfx = kRenderFxGlowShell; - //hit.curstate.renderamt = 100; - - hit.origin[2] += 40; - - gEngfuncs.CL_CreateVisibleEntity( ET_NORMAL, &hit ); -} - -#endif +========================= +HUD_CreateEntities + +Gives us a chance to add additional entities to the render this frame +========================= */ +void DLLEXPORT HUD_CreateEntities( void ) +{ + // e.g., create a persistent cl_entity_t somewhere. + // Load an appropriate model into it ( gEngfuncs.CL_LoadModel ) + // Call gEngfuncs.CL_CreateVisibleEntity to add it to the visedicts list + + // Add in any game specific objects + Game_AddObjects(); + + // Update any game specific objects + Game_UpdateObjects(g_iFrametime); + + GetClientVoiceMgr()->CreateEntities(); +} /* void ParticleCallback( struct particle_s *particle, float frametime ) @@ -431,152 +377,6 @@ void TempEnts( void ) } */ -#if defined( BEAM_TEST ) -// Note can't index beam[ 0 ] in Beam callback, so don't use that index -// Room for 1 beam ( 0 can't be used ) -static cl_entity_t beams[ 2 ]; - -void BeamEndModel( void ) -{ - cl_entity_t *player, *model; - int modelindex; - struct model_s *mod; - - // Load it up with some bogus data - player = gEngfuncs.GetLocalPlayer(); - if ( !player ) - return; - - mod = gEngfuncs.CL_LoadModel( "models/sentry3.mdl", &modelindex ); - if ( !mod ) - return; - - // Slot 1 - model = &beams[ 1 ]; - - *model = *player; - model->player = 0; - model->model = mod; - model->curstate.modelindex = modelindex; - - // Move it out a bit - model->origin[0] = player->origin[0] - 100; - model->origin[1] = player->origin[1]; - - model->attachment[0] = model->origin; - model->attachment[1] = model->origin; - model->attachment[2] = model->origin; - model->attachment[3] = model->origin; - - gEngfuncs.CL_CreateVisibleEntity( ET_NORMAL, model ); -} - -void Beams( void ) -{ - static float lasttime; - float curtime; - struct model_s *mod; - int index; - - BeamEndModel(); - - curtime = gEngfuncs.GetClientTime(); - float end[ 3 ]; - - if ( ( curtime - lasttime ) < 10.0 ) - return; - - mod = gEngfuncs.CL_LoadModel( "sprites/laserbeam.spr", &index ); - if ( !mod ) - return; - - lasttime = curtime; - - end [ 0 ] = v_origin.x + 100; - end [ 1 ] = v_origin.y + 100; - end [ 2 ] = v_origin.z; - - BEAM *p1; - p1 = gEngfuncs.pEfxAPI->R_BeamEntPoint( -1, end, index, - 10.0, 2.0, 0.3, 1.0, 5.0, 0.0, 1.0, 1.0, 1.0, 1.0 ); -} -#endif - -/* -========================= -HUD_CreateEntities - -Gives us a chance to add additional entities to the render this frame -========================= -*/ -void DLLEXPORT HUD_CreateEntities( void ) -{ - // e.g., create a persistent cl_entity_t somewhere. - // Load an appropriate model into it ( gEngfuncs.CL_LoadModel ) - // Call gEngfuncs.CL_CreateVisibleEntity to add it to the visedicts list -/* -#if defined( TEST_IT ) - MoveModel(); -#endif - -#if defined( TRACE_TEST ) - TraceModel(); -#endif -*/ -/* - Particles(); -*/ -/* - TempEnts(); -*/ - -#if defined( BEAM_TEST ) - Beams(); -#endif - - - // Add in any game specific objects - Game_AddObjects(); - - GetClientVoiceMgr()->CreateEntities(); -} - -/* -========================= -HUD_StudioEvent - -The entity's studio model description indicated an event was -fired during this frame, handle the event by it's tag ( e.g., muzzleflash, sound ) -========================= -*/ -void DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ) -{ - switch( event->event ) - { - case 5001: - gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[0], atoi( event->options) ); - break; - case 5011: - gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[1], atoi( event->options) ); - break; - case 5021: - gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[2], atoi( event->options) ); - break; - case 5031: - gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[3], atoi( event->options) ); - break; - case 5002: - gEngfuncs.pEfxAPI->R_SparkEffect( (float *)&entity->attachment[0], atoi( event->options), -100, 100 ); - break; - // Client side sound - case 5004: - gEngfuncs.pfnPlaySoundByNameAtLocation( (char *)event->options, 1.0, (float *)&entity->attachment[0] ); - break; - default: - break; - } -} - /* ================= CL_UpdateTEnts @@ -598,6 +398,8 @@ void DLLEXPORT HUD_TempEntUpdate ( TEMPENTITY *pTemp, *pnext, *pprev; float freq, gravity, gravitySlow, life, fastFreq; + g_iFrametime = frametime; + // Nothing to simulate if ( !*ppTempEntActive ) return; @@ -729,7 +531,6 @@ void DLLEXPORT HUD_TempEntUpdate ( pTemp->entity.origin[1] += pTemp->entity.baseline.origin[1] * frametime + 4 * sin( client_time * 30 + (int)pTemp ); pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; } - else { for ( i = 0; i < 3; i++ ) @@ -912,9 +713,13 @@ void DLLEXPORT HUD_TempEntUpdate ( } if ( pTemp->flags & FTENT_GRAVITY ) + { pTemp->entity.baseline.origin[2] += gravity; + } else if ( pTemp->flags & FTENT_SLOWGRAVITY ) + { pTemp->entity.baseline.origin[2] += gravitySlow; + } if ( pTemp->flags & FTENT_CLIENTCUSTOM ) { @@ -958,19 +763,13 @@ Indices must start at 1, not zero. */ cl_entity_t DLLEXPORT *HUD_GetUserEntity( int index ) { -#if defined( BEAM_TEST ) - // None by default, you would return a valic pointer if you create a client side - // beam and attach it to a client side entity. - if ( index > 0 && index <= 1 ) - { - return &beams[ index ]; - } + // Do we need this? - Gage +#if 0 + if(index > 0 && index <= 1) + return &laser_beams[index]; else - { return NULL; - } #else return NULL; #endif } - diff --git a/cl_dll/env_fog.cpp b/cl_dll/env_fog.cpp new file mode 100644 index 0000000..6d33309 --- /dev/null +++ b/cl_dll/env_fog.cpp @@ -0,0 +1,119 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// env_fog.cpp -> env_fog's are client side entities useful in manipulating +// the fog code in HL for more useful purposes +// +#include "hud.h" +#include "cl_util.h" + +#include "ParseBspEnt.h" +#include "ParseBsp.h" +#include "tri.h" + +#include +using namespace std; + +#define VectorLength(a) sqrt((double) ((double)((a)[0] * (a)[0]) + (double)( (a)[1] * (a)[1]) + (double)( (a)[2] * (a)[2])) ) + +enum bsp_fogstate_e { + FOGSTATE_OFF = 0, + FOGSTATE_ON, + FOGSTATE_MAPDEFAULT, +}; + +vector g_EnvFogList; +CBspEnvFog *g_pActiveEnvFog = NULL; + +extern cvar_t *cl_enablefog; + +/* +============================== +EnvFog_RenderFog + +Return 0 to use worldspawn fog +============================== +*/ +int EnvFog_RenderFog(CBspEnvFog *pActive) +{ + if(pActive == NULL) + return 0; + + if(pActive->iFogState == FOGSTATE_MAPDEFAULT) + return 0; + + R_SetFog(pActive->flFogcolor_r, + pActive->flFogcolor_g, + pActive->flFogcolor_b, + pActive->flFog_start, + pActive->flFog_end, + (cl_enablefog->value && pActive->iFogState == FOGSTATE_ON) ? 1 : 0); + + return 1; +} + +/* +============================== +EnvFog_SetFog + +============================== +*/ +int EnvFog_SetFog() +{ + // Find the nearest valid env_fog + for( int i = 0;i < g_EnvFogList.size();i++ ) + { + CBspEnvFog *pCurNode = &g_EnvFogList[i]; + + vec3_t location = pCurNode->flOrigin; + int radius = pCurNode->iRadius; + double dist = VectorLength(location - gEngfuncs.GetLocalPlayer()->origin); + + // The player is inside this env_fog, + // So compare against the current active + // env_fog to fight for priority + if(radius && dist <= radius) + { + if(g_pActiveEnvFog == NULL) + { + g_pActiveEnvFog = pCurNode; + return EnvFog_RenderFog(g_pActiveEnvFog); + } + else + { + vec3_t activeloc = g_pActiveEnvFog->flOrigin; + double activedist = VectorLength(activeloc - gEngfuncs.GetLocalPlayer()->origin); + + // See if this env_fog is better than the current + // active fog entity. + if(activedist <= radius) + { + if(dist < activedist) + { + g_pActiveEnvFog = pCurNode; + return EnvFog_RenderFog(g_pActiveEnvFog); + } + } + else + { + g_pActiveEnvFog = pCurNode; + return EnvFog_RenderFog(g_pActiveEnvFog); + } + } + } + } + + return EnvFog_RenderFog(g_pActiveEnvFog); +} \ No newline at end of file diff --git a/cl_dll/ev_common.cpp b/cl_dll/ev_common.cpp index 723d296..606bb17 100644 --- a/cl_dll/ev_common.cpp +++ b/cl_dll/ev_common.cpp @@ -132,7 +132,8 @@ void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, i vec3_t endpos; VectorClear( endpos ); endpos[1] = rotation; - gEngfuncs.pEfxAPI->R_TempModel( origin, velocity, endpos, 2.5, model, soundtype ); + + gEngfuncs.pEfxAPI->R_TempModel( origin, velocity, endpos, CVAR_GET_FLOAT("cl_shellcase_lifetime"), model, soundtype ); } /* @@ -142,7 +143,7 @@ EV_GetDefaultShellInfo Determine where to eject shells from ================= */ -void EV_GetDefaultShellInfo( event_args_t *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ) +void EV_GetDefaultShellInfo( int iWeaponType, event_args_t *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ) { int i; vec3_t view_ofs; @@ -155,7 +156,7 @@ void EV_GetDefaultShellInfo( event_args_t *args, float *origin, float *velocity, VectorClear( view_ofs ); view_ofs[2] = DEFAULT_VIEWHEIGHT; - if ( EV_IsPlayer( idx ) ) + if(EV_IsPlayer(idx)) { if ( EV_IsLocal( idx ) ) { @@ -167,13 +168,38 @@ void EV_GetDefaultShellInfo( event_args_t *args, float *origin, float *velocity, } } - fR = gEngfuncs.pfnRandomFloat( 50, 70 ); - fU = gEngfuncs.pfnRandomFloat( 100, 150 ); + switch(iWeaponType) + { + case 2: + // Automatics dont have an "upward" ejection motion + fU = gEngfuncs.pfnRandomFloat( 5, 15 ); + fR = gEngfuncs.pfnRandomFloat( 120, 140 ); + break; + case 1: + default: + fR = gEngfuncs.pfnRandomFloat( 50, 70 ); + fU = gEngfuncs.pfnRandomFloat( 100, 150 ); + break; + } for ( i = 0; i < 3; i++ ) { ShellVelocity[i] = velocity[i] + right[i] * fR + up[i] * fU + forward[i] * 25; - ShellOrigin[i] = origin[i] + view_ofs[i] + up[i] * upScale + forward[i] * forwardScale + right[i] * rightScale; + + switch(iWeaponType) + { + case 1: + // Aiming pistols if needed + if(!args->bparam2) + ShellOrigin[i] = origin[i] + view_ofs[i] + up[i] * upScale + forward[i] * forwardScale + right[i] * rightScale; + else + ShellOrigin[i] = origin[i] + view_ofs[i] + up[i] * upScale + forward[i] * forwardScale; + break; + default: + // Everything else + ShellOrigin[i] = origin[i] + view_ofs[i] + up[i] * upScale + forward[i] * forwardScale + right[i] * rightScale; + break; + } } } diff --git a/cl_dll/ev_thewastes.cpp b/cl_dll/ev_thewastes.cpp new file mode 100644 index 0000000..6338ab4 --- /dev/null +++ b/cl_dll/ev_thewastes.cpp @@ -0,0 +1,2252 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "entity_types.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_materials.h" + +#include "eventscripts.h" +#include "ev_thewastes.h" + +#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" +#include "in_defs.h" +#include "tw_common.h" + +#include + +static int tracerCount[ 32 ]; + +#define DISABLE_PENETRATION 0 // Disables client side wall penetration + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +extern "C" char PM_FindTextureType( char *name ); + +void V_PunchAxis( int axis, float punch ); +void VectorAngles( const float *forward, float *angles ); + +extern cvar_t *cl_lw; + +// Used for gun recoil +extern cl_enginefunc_t gEngfuncs; + +// Thermal vision on the client +extern int ev_thermal; + +extern "C" +{ + // Events + void EV_BerettaShoot (struct event_args_s *args); + void EV_ColtShoot (struct event_args_s *args); + void EV_DeagleShoot (struct event_args_s *args); + void EV_RugerShoot (struct event_args_s *args); + void EV_HandcannonShoot(struct event_args_s *args); + void EV_SawedOffShoot (struct event_args_s *args); + void EV_MossbergShoot (struct event_args_s *args); + void EV_WinchesterShoot(struct event_args_s *args); + void EV_Smg9Shoot (struct event_args_s *args); + void EV_FnFalShoot (struct event_args_s *args); + void EV_TommyGunShoot (struct event_args_s *args); + void EV_JackHammerShoot(struct event_args_s *args); + void EV_G11Shoot (struct event_args_s *args); + void EV_BoltRifleShoot (struct event_args_s *args); + void EV_StenShoot (struct event_args_s *args); + void EV_MolotovCocktail(struct event_args_s *args); + void EV_FragGrenade (struct event_args_s *args); + void EV_Pipebomb (struct event_args_s *args); + void EV_CombatKnife (struct event_args_s *args); + void EV_BaseballBat (struct event_args_s *args); + void EV_SledgeHammer (struct event_args_s *args); + void EV_Katana (struct event_args_s *args); + void EV_Spear (struct event_args_s *args); + void EV_CattleProd (struct event_args_s *args); + void EV_AkimboBerettas (struct event_args_s *args); + void EV_AkimboColts (struct event_args_s *args); + void EV_AkimboDeagles (struct event_args_s *args); + void EV_AkimboSawedOffs(struct event_args_s *args); +} + +#define VECTOR_CONE_1DEGREES Vector( 0.00873, 0.00873, 0.00873 ) +#define VECTOR_CONE_2DEGREES Vector( 0.01745, 0.01745, 0.01745 ) +#define VECTOR_CONE_3DEGREES Vector( 0.02618, 0.02618, 0.02618 ) +#define VECTOR_CONE_4DEGREES Vector( 0.03490, 0.03490, 0.03490 ) +#define VECTOR_CONE_5DEGREES Vector( 0.04362, 0.04362, 0.04362 ) +#define VECTOR_CONE_6DEGREES Vector( 0.05234, 0.05234, 0.05234 ) +#define VECTOR_CONE_7DEGREES Vector( 0.06105, 0.06105, 0.06105 ) +#define VECTOR_CONE_8DEGREES Vector( 0.06976, 0.06976, 0.06976 ) +#define VECTOR_CONE_9DEGREES Vector( 0.07846, 0.07846, 0.07846 ) +#define VECTOR_CONE_10DEGREES Vector( 0.08716, 0.08716, 0.08716 ) +#define VECTOR_CONE_15DEGREES Vector( 0.13053, 0.13053, 0.13053 ) +#define VECTOR_CONE_20DEGREES Vector( 0.17365, 0.17365, 0.17365 ) + +// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the +// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. +// returns volume of strike instrument (crowbar) to play +void VectorAngles2( const float *forward, float *angles ) +{ + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = sqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} + +int EV_HLDM_SpawnSmoke(int iBulletType) +{ + // we use cl_efx_frequency to decide + // if we want to spawn a tent + switch(iBulletType) + { + case BULLET_9MMP: + case BULLET_10MM: + case BULLET_50AE: + case BULLET_454CASULL: + case BULLET_SLUG: + case BULLET_556MM: + if(gEngfuncs.pfnRandomFloat(0,1) > CVAR_GET_FLOAT("cl_efx_frequency")*2) + return 0; + case BULLET_9MM: + case BULLET_45ACP: + case BULLET_762MM: + case BULLET_47MM: + if(gEngfuncs.pfnRandomFloat(0,1) > CVAR_GET_FLOAT("cl_efx_frequency")) + return 0; + case BULLET_20GAUGE: + case BULLET_12GAUGE: + case BULLET_10GAUGE: + if(gEngfuncs.pfnRandomFloat(0,1) > CVAR_GET_FLOAT("cl_efx_frequency")/2) + return 0; + } + + return 1; +} + +TEMPENTITY *EV_HLDM_GunshotSmoke(pmtrace_t *pTrace,float *vecSrc,int iBulletType,color24 Color) +{ + int iSpriteIdx; + vec3_t vecDir; + float scale; + + // Game told us not to make smoke + if(!EV_HLDM_SpawnSmoke(iBulletType)) + return NULL; + + iSpriteIdx = gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/smoke1.spr"); + + // TODO: Find a way to prevent bullets from going + // Into the wall if you hit the wall directly? + VectorSubtract(pTrace->endpos,vecSrc,vecDir); + VectorNormalize(vecDir); // Set actual sprite speed below + + switch(iBulletType) + { + // small caliber + case BULLET_9MMP: + case BULLET_10MM: + case BULLET_9MM: + VectorScale(vecDir,2,vecDir); + scale = 0.25f; + break; + // medium caliber + case BULLET_50AE: + case BULLET_454CASULL: + case BULLET_45ACP: + VectorScale(vecDir,3,vecDir); + scale = 0.5f; + break; + // large caliber + case BULLET_SLUG: + case BULLET_556MM: + case BULLET_762MM: + case BULLET_47MM: + VectorScale(vecDir,4,vecDir); + scale = 1.0f; + break; + // buckshot + case BULLET_20GAUGE: + case BULLET_12GAUGE: + case BULLET_10GAUGE: + VectorScale(vecDir,1,vecDir); + scale = 1.25f; + break; + // insane values so we know we need + // to assign a bullet type to the list! + default: + VectorScale(vecDir,-5,vecDir); + scale = 20; + break; + } + + // create the puff + TEMPENTITY *pTent = gEngfuncs.pEfxAPI->R_TempSprite(pTrace->endpos + pTrace->plane.normal*4, + vecDir, + scale, + iSpriteIdx, + kRenderTransAlpha, + 0, + 25, + 5, + FTENT_SPRANIMATE); + + // state settings + if(pTent != NULL) + { + pTent->entity.curstate.rendercolor = Color; + pTent->entity.curstate.framerate = 10; + pTent->entity.curstate.renderamt = 128; + + return pTent; + } + + return NULL; +} + +void EV_HLDM_SetColor(color24 &color,byte r,byte g,byte b) +{ + color.r = r; + color.g = g; + color.b = b; +} + +void EV_HLDM_GunshotLight( float *origin, float radius, byte r, byte g, byte b, float lifetime ) +{ + // TODO: IMPLEMENT AT LATER STAGE +/* dlight_t *dl; + + dl = gEngfuncs.pEfxAPI->CL_AllocDlight( 0 ); + if( dl == NULL ) + return; + + VectorCopy( origin, dl->origin ); + dl->radius = radius; + dl->color.r = r; + dl->color.g = g; + dl->color.b = b; + + dl->die = gHUD.m_flTime + lifetime; + lifetime -= 0.05f; + dl->decay = 1/(lifetime/dl->radius);*/ +} + +void EV_HLDM_GunshotEfx( pmtrace_t *pTrace, int iBulletType, float *vecSrc, float *vecEnd) +{ + int iRand = gEngfuncs.pfnRandomLong(0,0x7FFF); + int bRicochet = (iRand < (0x7fff/2)); + int bGenericDecal = 0; + physent_t *pe; + static char decalname[32]; + + // we create texture based smoke instead of just cute little sparks + color24 SmokeColor; + const char *szTextureName = gEngfuncs.pEventAPI->EV_TraceTexture(pTrace->ent,vecSrc,vecEnd); // get texture info + char cTextureType = 0; + int iRenderSmoke = 1; // Do we want to show a smokepuff ? + int bShowLight = 1; + static unsigned int lightcount; + + EV_HLDM_SetColor(SmokeColor,255,255,255); // default color + + if( szTextureName != NULL ) + cTextureType = PM_FindTextureType((char*)szTextureName); + + switch(cTextureType) + { + case CHAR_TEX_COMPUTER: + case CHAR_TEX_GLASS: + if(bRicochet) + { + switch(iRand % 3) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_glass1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_glass2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_glass3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + iRenderSmoke = 0; // No smoke puff + bShowLight = 0; + bGenericDecal = 1; + break; + case CHAR_TEX_WOOD: + if(bRicochet) + { + switch(iRand % 5) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_wood1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_wood2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_wood3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_wood4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_wood5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + strcpy(decalname, "{holewood"); + EV_HLDM_SetColor(SmokeColor,187,141,43); + break; + case CHAR_TEX_CONCRETE: + if(bRicochet) + { + switch(iRand % 3) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_con1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_con2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_con3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + strcpy(decalname, "{holeconcrete"); + break; + case CHAR_TEX_RUST: + if(bRicochet) + { + switch(iRand % 2) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_metalwall1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_metalwall2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + strcpy(decalname, "{holerust"); + break; + case CHAR_TEX_VENT: + case CHAR_TEX_METAL: + if(bRicochet) + { + switch(iRand % 2) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_metalwall1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_metalwall2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + strcpy(decalname, "{holemetal"); + break; + case CHAR_TEX_GRATE: + case CHAR_TEX_BARREL: + if(bRicochet) + { + switch(iRand % 4) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_barrel1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_barrel2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_barrel3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_barrel4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + sprintf(decalname, "{dent%i", gEngfuncs.pfnRandomLong( 0, 5 ) + 1 ); + break; + case CHAR_TEX_TIN: + if(bRicochet) + { + switch(iRand % 3) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_tinroof1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_tinroof2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_tinroof3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + sprintf(decalname, "{dent%i", gEngfuncs.pfnRandomLong( 0, 5 ) + 1 ); + break; + case CHAR_TEX_TILE: + if(bRicochet) + { + switch(iRand % 2) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_drywall1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_drywall2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + strcpy(decalname, "{holetile"); + break; + case CHAR_TEX_DRYWALL: + if(bRicochet) + { + switch(iRand % 2) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_drywall1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric_drywall2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + strcpy(decalname, "{holedry"); + EV_HLDM_SetColor(SmokeColor,255,255,200); + break; + case CHAR_TEX_SAND: + strcpy(decalname, "{holesand"); + EV_HLDM_SetColor(SmokeColor,255,255,192); + break; + case CHAR_TEX_FLESH: + EV_HLDM_SetColor(SmokeColor,200,25,25); + bGenericDecal = 1; + break; + case CHAR_TEX_DIRT: + EV_HLDM_SetColor(SmokeColor,115,53,25); + bGenericDecal = 1; + break; + case CHAR_TEX_SNOW: + EV_HLDM_SetColor(SmokeColor,255,255,255); + bGenericDecal = 1; + break; + case CHAR_TEX_SLOSH: + iRenderSmoke = 0; + default: + if(bRicochet) + { + switch( iRand % 5) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "debris/ric5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + bGenericDecal = 1; + break; + } + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + // Override some object decals + if ( pe->classnumber == 1 ) + sprintf( decalname, "{break%i", gEngfuncs.pfnRandomLong( 0, 2 ) + 1 ); + else if ( pe->rendermode != kRenderNormal ) + { + sprintf( decalname, "{bproof1" ); + } + else if(iBulletType >= BULLET_20GAUGE && iBulletType <= BULLET_12GAUGE) + { + // Buckshot + sprintf(decalname,"{bigshot%i",gEngfuncs.pfnRandomLong( 0, 4 )+ 1 ); + bShowLight = 0; + } + else if(bGenericDecal) + sprintf( decalname, "{shot%i", gEngfuncs.pfnRandomLong( 0, 7 ) + 1 ); + + if( bShowLight && CVAR_GET_FLOAT( "cl_efx_frequency" ) > 0 ) + { + // Only do every 3 bullets + if( lightcount++ % 3 == 1 ) + EV_HLDM_GunshotLight( pTrace->endpos, gEngfuncs.pfnRandomFloat( 30.0f, 40.0f ), 255, 255, 255, 0.75f ); + } + + if(iRenderSmoke) + EV_HLDM_GunshotSmoke(pTrace,vecSrc,iBulletType,SmokeColor); + + // Only decal brush models such as the world etc. + if ( decalname && decalname[0] && pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) ) + { + if ( CVAR_GET_FLOAT( "r_decals" ) ) + { + gEngfuncs.pEfxAPI->R_DecalShoot( + gEngfuncs.pEfxAPI->Draw_DecalIndex( gEngfuncs.pEfxAPI->Draw_DecalIndexFromName( decalname ) ), + gEngfuncs.pEventAPI->EV_IndexFromTrace( pTrace ), 0, pTrace->endpos, 0 ); + } + } +} + +int EV_HLDM_CheckTracer( int idx, float *vecSrc, float *end, float *forward, float *right, int iBulletType, int iTracerFreq, int *tracerCount ) +{ + int tracer = 0; + int i; + qboolean player = idx >= 1 && idx <= gEngfuncs.GetMaxClients() ? true : false; + + if ( iTracerFreq != 0 && ( (*tracerCount)++ % iTracerFreq) == 0 ) + { + vec3_t vecTracerSrc; + + if ( player ) + { + vec3_t offset( 0, 0, -4 ); + + // adjust tracer position for player + for ( i = 0; i < 3; i++ ) + { + vecTracerSrc[ i ] = vecSrc[ i ] + offset[ i ] + right[ i ] * 2 + forward[ i ] * 16; + } + } + else + { + VectorCopy( vecSrc, vecTracerSrc ); + } + + if ( iTracerFreq != 1 ) // guns that always trace also always decal + tracer = 1; + +// switch( iBulletType ) +// { +// default: + EV_CreateTracer( vecTracerSrc, end ); +// break; +// } + } + + return tracer; +} + +void EV_HLDM_BulletDecal(int iBulletType,pmtrace_t *pTr,float *vecSrc,float *vecEnd) +{ + // do damage, paint decals + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTr->ent ); + + if ( pe && pe->solid == SOLID_BSP ) + { + // smoke and decal + EV_HLDM_GunshotEfx( pTr, iBulletType, vecSrc, vecEnd ); + } +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int cShots, float *vecSrc, float *vecDirShooting, float flDistance, int iBulletType, int iTracerFreq, int *tracerCount, float flSpreadX, float flSpreadY,int penetration_type ) +{ + int i; + pmtrace_t tr; + int tracer; + int iShot; + +#if !(DISABLE_PENETRATION) + // penetration values + int wall_thickness = 0; + float falloff_rate = 0; + int wall_limit = MAX_WALLCOUNT; + + switch(penetration_type) + { + case P_BERETTA: + wall_thickness = MAX_THICKNESS_BERETTA; + falloff_rate = FALLOFF_BERETTA; + break; + case P_COLT: + wall_thickness = MAX_THICKNESS_COLT; + falloff_rate = FALLOFF_COLT; + break; + case P_RUGER: + wall_thickness = MAX_THICKNESS_RUGER; + falloff_rate = FALLOFF_RUGER; + break; + case P_DEAGLE: + wall_thickness = MAX_THICKNESS_DEAGLE; + falloff_rate = FALLOFF_DEAGLE; + break; + case P_HANDCANNON: + wall_thickness = MAX_THICKNESS_HANDCANNON; + falloff_rate = FALLOFF_HANDCANNON; + break; + case P_WINCHESTER: + wall_thickness = MAX_THICKNESS_WINCHESTER; + falloff_rate = FALLOFF_WINCHESTER; + break; + case P_SMG9: + wall_thickness = MAX_THICKNESS_SMG9; + falloff_rate = FALLOFF_SMG9; + break; + case P_FNFAL: + wall_thickness = MAX_THICKNESS_FNFAL; + falloff_rate = FALLOFF_FNFAL; + break; + case P_TOMMYGUN: + wall_thickness = MAX_THICKNESS_TOMMYGUN; + falloff_rate = FALLOFF_TOMMYGUN; + break; + case P_G11: + wall_thickness = MAX_THICKNESS_G11; + falloff_rate = FALLOFF_G11; + break; + case P_BOLTRIFLE: + wall_thickness = MAX_THICKNESS_BOLTRIFLE; + falloff_rate = FALLOFF_BOLTRIFLE; + break; + case P_STEN: + wall_thickness = MAX_THICKNESS_STEN; + falloff_rate = FALLOFF_STEN; + break; + } +#endif + + +#if !(DISABLE_PENETRATION) + do + { +#endif + for ( iShot = 1; iShot <= cShots; iShot++ ) + { + vec3_t vecDir, vecEnd; + + float x, y, z; + //We randomize for the Shotguns + if(iBulletType >= BULLET_20GAUGE && iBulletType <= BULLET_12GAUGE) + { + do { + x = gEngfuncs.pfnRandomFloat(-0.5,0.5) + gEngfuncs.pfnRandomFloat(-0.5,0.5); + y = gEngfuncs.pfnRandomFloat(-0.5,0.5) + gEngfuncs.pfnRandomFloat(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + + for ( i = 0 ; i < 3; i++ ) + { + vecDir[i] = vecDirShooting[i] + x * flSpreadX * right[ i ] + y * flSpreadY * up [ i ]; + vecEnd[i] = vecSrc[ i ] + flDistance * vecDir[ i ]; + } + }//But other guns already have their spread randomized in the synched spread. + else + { + + for ( i = 0 ; i < 3; i++ ) + { + vecDir[i] = vecDirShooting[i] + flSpreadX * right[ i ] + flSpreadY * up [ i ]; + vecEnd[i] = vecSrc[ i ] + flDistance * vecDir[ i ]; + } + } + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull(2); + gEngfuncs.pEventAPI->EV_PlayerTrace(vecSrc,vecEnd,PM_STUDIO_BOX,-1,&tr); + + tracer = EV_HLDM_CheckTracer( idx, vecSrc, tr.endpos, forward, right, iBulletType, iTracerFreq, tracerCount ); + + if( tr.fraction != 1.0 ) + { + EV_HLDM_BulletDecal(iBulletType,&tr,vecSrc,(float*)&vecEnd); + +#if !(DISABLE_PENETRATION) + // If we want an exit hole, show it. + if(wall_thickness) + { + // wall thickness + vec3_t wall_begin = tr.endpos; + vec3_t wall_end; + pmtrace_t new_tr; + + gEngfuncs.pEventAPI->EV_PlayerTrace(tr.endpos+vecDir*wall_thickness,vecEnd,PM_STUDIO_BOX,-1,&new_tr); + + // We cannot continue + if(new_tr.allsolid) + wall_thickness = 0; + else + { + // Get exit hole + gEngfuncs.pEventAPI->EV_PlayerTrace(new_tr.endpos,tr.endpos,PM_STUDIO_BOX,-1,&tr); + + if(!tr.allsolid) + { + // get length of wall, and detract from overall bullet effectiveness + // only subtract for hard targets, not players. + if(tr.ent != NULL && !EV_IsPlayer(tr.ent)) + { + VectorSubtract(tr.endpos,wall_begin,wall_end); + wall_thickness -= (int)(Length(wall_end) * falloff_rate); + } + + if(wall_thickness <= 0) + wall_thickness = 0; + else + EV_HLDM_BulletDecal(iBulletType,&tr,vecSrc,(float*)&vecEnd); + + // Trace ahead to get next wall. + vecSrc = tr.endpos; + } + else + wall_thickness = 0; // cant go any further. + } + } +#endif + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + } +#if !(DISABLE_PENETRATION) + }while(wall_thickness && wall_limit--); +#endif +} + +// globals used by most Pistol events +// Some Ruger specific code is here too. +int EV_PistolShoot( event_args_t *args , char *fire_sound,int iBulletType, char *loshell, char *hishell) +{ + int idx = args->entindex; + int empty = args->bparam1; + int aimmode = args->bparam2; + + vec3_t up, right, forward; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + AngleVectors( angles, forward, right, up ); + vec3_t vecSrc, vecAiming; + + if ( EV_IsLocal( idx ) ) + { + EV_MuzzleFlash(); + + if(iBulletType == BULLET_454CASULL) + { + if(!aimmode) + gEngfuncs.pEventAPI->EV_WeaponAnimation( RUGER_SHOOT, 2 ); + else + gEngfuncs.pEventAPI->EV_WeaponAnimation( RUGER_AIM_SHOOT, 2 ); + } + else + { + if(!aimmode) + gEngfuncs.pEventAPI->EV_WeaponAnimation( empty ? SPISTOL_SHOOT_LAST : SPISTOL_SHOOT, 2 ); + else + gEngfuncs.pEventAPI->EV_WeaponAnimation( empty ? SPISTOL_AIM_SHOOT_LAST : SPISTOL_AIM_SHOOT , 2 ); + } + + V_PunchAxis( 0, -2.0 ); + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON,fire_sound, gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + int penetration_type; + + switch(iBulletType) + { + case BULLET_9MMP: + penetration_type = P_BERETTA; + break; + case BULLET_10MM: + penetration_type = P_COLT; + break; + case BULLET_454CASULL: + penetration_type = P_RUGER; + break; + case BULLET_50AE: + penetration_type = P_DEAGLE; + break; + default: + penetration_type = 0; + break; + } + + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + + if( iBulletType != BULLET_454CASULL ) + { + if(!CVAR_GET_FLOAT("cl_shellcase_quality")) + shell = gEngfuncs.pEventAPI->EV_FindModelIndex( loshell ); + else + shell = gEngfuncs.pEventAPI->EV_FindModelIndex( hishell ); + + EV_GetDefaultShellInfo( 0, args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 10, -4, 5 ); + + EV_EjectBrass( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHELL ); + } + + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, iBulletType, (ev_thermal) ? 1 : 0, (ev_thermal) ? &tracerCount[idx-1] : 0, args->fparam1, args->fparam2, penetration_type ); + + return 1; +} + +void EV_PistolWhip(event_args_t *args) +{ + int idx = args->entindex; + int clip_empty = args->iparam1; + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + VectorCopy( args->origin, origin ); + + //Play Swing sound + switch(gEngfuncs.pfnRandomLong(0,2)) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "weapons/whip.wav", 1, ATTN_NORM, 0, PITCH_NORM); break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "weapons/whip3.wav", 1, ATTN_NORM, 0, PITCH_NORM); break; + case 2: + default: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "weapons/whip2.wav", 1, ATTN_NORM, 0, PITCH_NORM); break; + } + + if ( EV_IsLocal( idx ) ) + { + switch( gEngfuncs.pfnRandomLong(0,1) ) + { + case 0: + gEngfuncs.pEventAPI->EV_WeaponAnimation ( clip_empty ? SPISTOL_WHIP1_EMPTY : SPISTOL_WHIP1, 1 ); break; + case 1: + gEngfuncs.pEventAPI->EV_WeaponAnimation ( clip_empty ? SPISTOL_WHIP2_EMPTY : SPISTOL_WHIP2, 1 ); break; + } + } +} + +/************* + Beretta +*************/ +void EV_BerettaShoot(event_args_t *args) +{ + if(args->iparam2) + EV_PistolShoot(args,"weapons/beretta_fire.wav",BULLET_9MMP,"models/shells/shell_9x18mm_lo.mdl","models/shells/shell_9x18mm_hi.mdl"); + else + EV_PistolWhip(args); +} + +/************* + Enforcer +*************/ +void EV_ColtShoot(event_args_t *args) +{ + if(args->iparam2) + EV_PistolShoot(args,"weapons/colt_fire.wav",BULLET_10MM,"models/shells/shell_10mm_lo.mdl","models/shells/shell_10mm_hi.mdl"); + else + EV_PistolWhip(args); +} + +/************* + Desert Eagle +*************/ +void EV_DeagleShoot(event_args_t *args) +{ + if(args->iparam2) + { + EV_PistolShoot(args,"weapons/deagle_fire.wav",BULLET_50AE,"models/shells/shell_50AE_lo.mdl","models/shells/shell_50AE_hi.mdl"); + + // Pop up the view angle - (dont pop up if aiming) + if(EV_IsPlayer(args->entindex) && EV_IsLocal(args->entindex) && !args->bparam2) + RECOIL_VIEW(5.5,0); + } + else + EV_PistolWhip(args); +} + +/************* + Ruger +*************/ +void EV_RugerShoot(event_args_t *args) +{ + if(EV_PistolShoot(args,"weapons/ruger_fire.wav",BULLET_454CASULL,"models/shells/shell_454_lo.mdl","models/shells/shell_454_hi.mdl")) + { + // Not as extreme as Desert Eagle, but still there :D + if(EV_IsPlayer(args->entindex) && EV_IsLocal(args->entindex) && !args->bparam2 && !args->iparam2) + RECOIL_VIEW(5,0); + } +} + +/************* + 5.56mm Handcannon +*************/ +void EV_HandcannonShoot(event_args_t *args) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + int empty; + + vec3_t vecSrc, vecAiming; + vec3_t up, right, forward; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + empty = args->bparam1; + AngleVectors( angles, forward, right, up ); + + // Play sound only + if(args->iparam1) + { + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_AUTO,args->bparam2 ? "weapons/handcannon_laser_on.wav" : "weapons/handcannon_laser_off.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + return; + } + + if ( EV_IsLocal( idx ) ) + { + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( empty ? HC_SHOOT_LAST : HC_SHOOT, 2 ); + + if(!args->bparam2) + { + RECOIL_VIEW(13.5,0); + } + else + // Huge kickback noticed if your using a laser + V_PunchAxis( 0, -10.0 ); + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON,"weapons/handcannon_fire.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_556MM, (ev_thermal) ? 1 : 0, (ev_thermal) ? &tracerCount[idx-1] : 0, args->fparam1, args->fparam2,P_HANDCANNON ); +} + +/************* + Sawed Off +*************/ +void EV_SawedOffShoot(event_args_t *args) +{ + int idx = args->entindex; + int barrels = args->iparam1; + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t up, right, forward; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + AngleVectors( angles, forward, right, up ); + + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + + if( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( (barrels == 2) ? SAWED_SHOOT_L : SAWED_SHOOT_R, 2 ); + + if(barrels == 1) + V_PunchAxis( 0, -5.0 ); + else + V_PunchAxis( 0, -11.5 ); + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, (barrels == 1) ? "weapons/sawedoff_fire1.wav" : "weapons/sawedoff_fire2.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + int shotCount = (barrels == 1) ? 14 : 26; + + // Remember to change the randomization code at the bottom :) + EV_HLDM_FireBullets( idx, forward, right, up, shotCount, vecSrc, vecAiming, 2048, BULLET_20GAUGE, (ev_thermal) ? 1 : 0, (ev_thermal) ? &tracerCount[idx-1] : 0, args->fparam1, args->fparam2,0 ); +} + +/************* + Mossberg +*************/ +void EV_MossbergBlast(event_args_t *args) +{ + int idx = args->entindex; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t up, right, forward; + + VectorCopy ( args->origin, origin ); + VectorCopy ( args->angles, angles ); + VectorCopy ( args->velocity, velocity ); + AngleVectors( angles, forward, right, up ); + + // Shoot gun as normal + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + + if(EV_IsLocal(idx)) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( args->bparam2 ? MOSSBERG_SHOOT : MOSSBERG_SHOOT2, 2 ); + V_PunchAxis( 0, -4.5 ); + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, args->bparam2 ? "weapons/mossberg_fire1.wav" : "weapons/mossberg_fire2.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + int shotCount = args->iparam1; + + // Remember to change the randomization code at the bottom :) + EV_HLDM_FireBullets( idx, forward, right, up, shotCount, vecSrc, vecAiming, 2048, BULLET_12GAUGE, (ev_thermal) ? 1 : 0, (ev_thermal) ? &tracerCount[idx-1] : 0, args->fparam1, args->fparam2,0 ); +} + +void EV_MossbergWhip(event_args_t *args) +{ + int idx = args->entindex; + int clip_empty = args->iparam1; + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + VectorCopy( args->origin, origin ); + + //Play Swing sound + switch(gEngfuncs.pfnRandomLong(0,2)) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "weapons/whip.wav", 1, ATTN_NORM, 0, PITCH_NORM); break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "weapons/whip3.wav", 1, ATTN_NORM, 0, PITCH_NORM); break; + case 2: + default: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "weapons/whip2.wav", 1, ATTN_NORM, 0, PITCH_NORM); break; + } + + if ( EV_IsLocal( idx ) ) + gEngfuncs.pEventAPI->EV_WeaponAnimation ( MOSSBERG_WHIP, 1 ); +} + +void EV_MossbergShoot(event_args_t *args) +{ + if(args->iparam2) + EV_MossbergBlast(args); + else + EV_MossbergWhip(args); +} + +/************* + Winchester +*************/ +void EV_WinchesterShoot(event_args_t *args) +{ + int idx = args->entindex; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t up, right, forward; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + AngleVectors( angles, forward, right, up ); + + int animation = args->iparam1; + + vec3_t vecSrc, vecAiming; + + if(EV_IsLocal(idx)) + { + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( animation ? WINNY_SHOOT : WINNY_SHOOT2, 2 ); + V_PunchAxis( 0, -2.0 ); + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON,animation ? "weapons/winchester_fire1.wav" : "weapons/winchester_fire2.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_SLUG, ev_thermal ? 1 : 0, ev_thermal ? &tracerCount[idx-1] : 0, args->fparam1, args->fparam2, P_WINCHESTER); +} + +/************* + Smg9 +*************/ +void EV_Smg9Shoot(struct event_args_s *args) +{ + float dirx = args->fparam1; + float diry = args->fparam2; + float accuracy = args->angles[0]; + float recoil; + int idx = args->entindex; + int empty = args->iparam2; + int secondary = args->iparam1; + int lowrecoil = args->bparam1; + int burstpredict = args->bparam2; + + if(secondary) + { + if(lowrecoil) + recoil = gEngfuncs.pfnRandomFloat(1.4,2.4); + else + recoil = gEngfuncs.pfnRandomFloat(3.4,4.4); + } + else + { + if(lowrecoil) + recoil = gEngfuncs.pfnRandomFloat(0.4,1.4); + else + recoil = gEngfuncs.pfnRandomFloat(1.4,2.0); + } + + // muck up bullet locations a bit in burst fire + if(burstpredict) + { + dirx *= gEngfuncs.pfnRandomFloat(-accuracy,accuracy); + diry *= gEngfuncs.pfnRandomFloat(accuracy/4,accuracy); + } + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t vecSrc; + vec3_t up, right, forward; + + VectorCopy(args->origin,origin); + VectorCopy(args->angles,angles); + VectorCopy(args->velocity,velocity); + AngleVectors(angles,forward,right,up); + + // Shell ejection + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + + if(!CVAR_GET_FLOAT("cl_shellcase_quality")) + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shells/shell_9x22mm_lo.mdl"); + else + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shells/shell_9x22mm_hi.mdl"); + + if(!secondary) + EV_GetDefaultShellInfo( 2, args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 10, -4, 5 ); + else + // Eject upwards as normal + EV_GetDefaultShellInfo( 0, args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 10, -4, 0 ); + + EV_EjectBrass(ShellOrigin,ShellVelocity,angles[ YAW ],shell,TE_BOUNCE_SHELL); + + if(EV_IsLocal(idx)) + { + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation(secondary ? SMG9_SECONDARY_SHOOT : SMG9_SHOOT,2); + + RECOIL_VIEW(recoil,0); + } + + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,(gEngfuncs.pfnRandomFloat(0,1) == 0) ? "weapons/smg9_fire1.wav" : "weapons/smg9_fire2.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + EV_HLDM_FireBullets(idx,forward,right,up,1,vecSrc,forward,8192,BULLET_9MM,ev_thermal ? 1 : 0,ev_thermal ? &tracerCount[idx-1] : 0,dirx,diry,P_SMG9); +} + +/************* + FN FAL +*************/ +void EV_FnFalShoot(event_args_t *args) +{ + int idx = args->entindex; + int aimfire = args->bparam2; + int lowrecoil = args->bparam1; + int semiauto = args->iparam2; + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t vecSrc; + vec3_t up, right, forward; + + VectorCopy(args->origin,origin); + VectorCopy(args->angles,angles); + VectorCopy(args->velocity,velocity); + AngleVectors(angles,forward,right,up); + + // Shell ejection + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + + if(!CVAR_GET_FLOAT("cl_shellcase_quality")) + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shells/shell_762mm_lo.mdl"); + else + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shells/shell_762mm_hi.mdl"); + + if(!aimfire) + EV_GetDefaultShellInfo( 2, args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 10, -4, 5 ); + else + EV_GetDefaultShellInfo( 2, args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 10, -4, 0 ); + + EV_EjectBrass(ShellOrigin,ShellVelocity,angles[ YAW ],shell,TE_BOUNCE_SHELL); + + if(EV_IsLocal(idx)) + { + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation(aimfire ? FNFAL_AIM_SHOOT : FNFAL_SHOOT,2); + + if(aimfire) + { + if( semiauto ) + V_PunchAxis(0,-3.4); + else + { + if( lowrecoil ) + { + RECOIL_VIEW( gEngfuncs.pfnRandomFloat( 0.50, 1.50 ), 0 ); + } + else + { + RECOIL_VIEW( gEngfuncs.pfnRandomFloat( 1.25, 2.25 ), 0 ); + } + } + } + else + { + if(lowrecoil) + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(1.25,2.25),0); + } + else + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(2.75,4.5),0); + } + } + } + + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,(gEngfuncs.pfnRandomLong(0,1) == 0) ? "weapons/fnfal_fire1.wav" : "weapons/fnfal_fire2.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + EV_HLDM_FireBullets(idx,forward,right,up,1,vecSrc,forward,8192,BULLET_762MM,ev_thermal ? 1 : 2,&tracerCount[idx-1],args->fparam1,args->fparam2,P_FNFAL); +} + +/************* + Tommy Gun +*************/ +void EV_TommyGunShoot(event_args_t *args) +{ + int idx = args->entindex; + int lessrecoil = args->bparam1; + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t vecSrc; + vec3_t up, right, forward; + + VectorCopy(args->origin,origin); + VectorCopy(args->angles,angles); + VectorCopy(args->velocity,velocity); + AngleVectors(angles,forward,right,up); + + // Shell ejection + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + + if(!CVAR_GET_FLOAT("cl_shellcase_quality")) + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shells/shell_45cal_lo.mdl"); + else + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shells/shell_45cal_hi.mdl"); + + EV_GetDefaultShellInfo( 2, args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 10, -4, 5 ); + EV_EjectBrass(ShellOrigin,ShellVelocity,angles[ YAW ],shell,TE_BOUNCE_SHELL); + + if(EV_IsLocal(idx)) + { + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation(TOMMYGUN_SHOOT,2); + + if(lessrecoil) + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(1.25,2.0),0); + } + else + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(2.0,3.5),0); + } + } + + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,(gEngfuncs.pfnRandomLong(0,1) == 0) ? "weapons/tommygun_fire1.wav" : "weapons/tommygun_fire2.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + EV_HLDM_FireBullets(idx,forward,right,up,1,vecSrc,forward,8192,BULLET_45ACP,ev_thermal ? 1 : 3,&tracerCount[idx-1],args->fparam1,args->fparam2,P_TOMMYGUN); +} + +/************* + Jackhammer +*************/ +void EV_JackHammerShoot(event_args_t *args) +{ + int idx = args->entindex; + int ducking = args->iparam2; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t up, right, forward; + + VectorCopy ( args->origin, origin ); + VectorCopy ( args->angles, angles ); + VectorCopy ( args->velocity, velocity ); + AngleVectors( angles, forward, right, up ); + + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + + if(EV_IsLocal(idx)) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( JACKHAMMER_SHOOT, 2 ); + + if(ducking) + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(3.0,4.0),0); + } + else + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(5.0,6.5),0); + } + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, gEngfuncs.pfnRandomLong(0,1) ? "weapons/jackhammer_fire1.wav" : "weapons/jackhammer_fire2.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + int shotCount = args->iparam1; + + // Remember to change the randomization code at the bottom :) + EV_HLDM_FireBullets( idx, forward, right, up, shotCount, vecSrc, vecAiming, 2048, BULLET_10GAUGE, ev_thermal ? 1 : 0, ev_thermal ? &tracerCount[idx-1] : 0, args->fparam1, args->fparam2,0 ); +} + +/************* + H&K G11 +*************/ +void EV_G11Shoot(event_args_t *args) +{ + int idx = args->entindex; + int burstfire = args->iparam1; + float bursthop = args->fparam2; + int burstcount = args->iparam2; // How many bullets to fire + int zoom_sound = args->bparam1; // just do a cute zoom sound ? + int lessrecoil = args->bparam2; + + // We do this as an event so other people can hear it + if(zoom_sound) + { + gEngfuncs.pEventAPI->EV_PlaySound(idx,args->origin,CHAN_AUTO,args->bparam2 ? "weapons/g11_zoom2.wav" : "weapons/g11_zoom1.wav", gEngfuncs.pfnRandomFloat(0.7,0.8), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + return; + } + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t vecSrc; + vec3_t up, right, forward; + + VectorCopy(args->origin,origin); + VectorCopy(args->angles,angles); + VectorCopy(args->velocity,velocity); + AngleVectors(angles,forward,right,up); + + if(EV_IsLocal(idx)) + { + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation(burstfire ? G11_BURST : G11_SHOOT,2); + + if(burstfire) + { + if(lessrecoil) + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(2.0,3.0),0); + } + else + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(3.0,4.5),0); + } + } + else + { + if(lessrecoil) + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(0.5,1.0),0); + } + else + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(1.0,1.5),0); + } + } + } + + if(burstfire) + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON, "weapons/g11_burst.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + else + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,(gEngfuncs.pfnRandomLong(0,1) == 0) ? "weapons/g11_ffire1.wav" : "weapons/g11_ffire2.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + + if(burstfire) + { + float spreadX = args->fparam1; + float spreadY = args->fparam2; + + for(int i = 0;i < burstcount;i++) + { + spreadX += gEngfuncs.pfnRandomFloat(-0.015,0.015); + + EV_HLDM_FireBullets(idx,forward,right,up,1,vecSrc,forward,8192,BULLET_47MM,ev_thermal ? 1 : 3,&tracerCount[idx-1],spreadX,spreadY,P_G11); + + spreadY += bursthop; + spreadX = args->fparam1; + } + } + else + EV_HLDM_FireBullets(idx,forward,right,up,1,vecSrc,forward,8192,BULLET_47MM,ev_thermal ? 1 : 10,&tracerCount[idx-1],args->fparam1,args->fparam2,P_G11); +} + +/************* + Bolt Rifle +*************/ +void EV_BoltRifleShoot(event_args_t *args) +{ + int idx = args->entindex; + int whip_only = args->bparam1; + int empty = args->iparam1; + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t vecSrc; + vec3_t up, right, forward; + + VectorCopy(args->origin,origin); + VectorCopy(args->angles,angles); + VectorCopy(args->velocity,velocity); + AngleVectors(angles,forward,right,up); + + // BoltRifle whip + if(whip_only) + { + //Play Swing sound + switch(gEngfuncs.pfnRandomLong(0,2)) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "weapons/whip.wav", 1, ATTN_NORM, 0, PITCH_NORM); break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "weapons/whip3.wav", 1, ATTN_NORM, 0, PITCH_NORM); break; + case 2: + default: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "weapons/whip2.wav", 1, ATTN_NORM, 0, PITCH_NORM); break; + } + + if ( EV_IsLocal( idx ) ) + gEngfuncs.pEventAPI->EV_WeaponAnimation ( BOLTRIFLE_STRIKE, 1 ); + + return; + } + + if(EV_IsLocal(idx)) + { + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation(empty ? BOLTRIFLE_SHOOT_LAST : BOLTRIFLE_SHOOT,2); + + RECOIL_VIEW(5.0,gEngfuncs.pfnRandomFloat(-1.5,1.5)); + } + + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,"weapons/boltrifle_fire1.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + EV_HLDM_FireBullets(idx,forward,right,up,1,vecSrc,forward,8192,BULLET_762MM,ev_thermal ? 1 : 0,ev_thermal ? &tracerCount[idx-1] : 0,args->fparam1,args->fparam2,P_BOLTRIFLE); +} + +/************* + Sten +*************/ +void EV_StenShoot(event_args_t *args) +{ + int idx = args->entindex; + int silenced = args->bparam1; + int overheat = args->bparam2; + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t vecSrc; + vec3_t up, right, forward; + + VectorCopy(args->origin,origin); + VectorCopy(args->angles,angles); + VectorCopy(args->velocity,velocity); + AngleVectors(angles,forward,right,up); + + if(overheat) + { + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,"weapons/sten_overheat.wav",1.0,ATTN_NORM,0,98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + return; + } + + // Shell ejection + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + + if(!CVAR_GET_FLOAT("cl_shellcase_quality")) + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shells/shell_9x22mm_lo.mdl"); + else + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shells/shell_9x22mm_hi.mdl"); + + EV_GetDefaultShellInfo( 2, args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 10, -4, 5 ); + EV_EjectBrass(ShellOrigin,ShellVelocity,angles[ YAW ],shell,TE_BOUNCE_SHELL); + + if(EV_IsLocal(idx)) + { + if(!silenced) + EV_MuzzleFlash(); + + gEngfuncs.pEventAPI->EV_WeaponAnimation(silenced ? STEN_SHOOT_SILENCED : STEN_SHOOT,2); + + if(!silenced) + { + RECOIL_VIEW(gEngfuncs.pfnRandomFloat(1.4,2.4),0); + } + else + { + V_PunchAxis( 0, -1.0 ); + } + } + + if(silenced) + { + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,(gEngfuncs.pfnRandomFloat(0,1) == 0) ? "weapons/sten_sfire1.wav" : "weapons/sten_sfire2.wav",gEngfuncs.pfnRandomFloat(0.6, 0.75),ATTN_NORM,0,98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,(gEngfuncs.pfnRandomFloat(0,1) == 0) ? "weapons/sten_nfire1.wav" : "weapons/sten_nfire2.wav",gEngfuncs.pfnRandomFloat(0.6, 0.75),ATTN_NORM,0,98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + } + + EV_GetGunPosition( args, vecSrc, origin ); + EV_HLDM_FireBullets(idx,forward,right,up,1,vecSrc,forward,8192,BULLET_9MMP,ev_thermal ? 1 : 0,ev_thermal ? &tracerCount[idx-1] : 0,args->fparam1,args->fparam2,P_STEN); +} + +/************* + Molotov Cocktail +*************/ +void EV_MolotovCocktail(event_args_t *args) +{ + int flameidx = gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/fire.spr"); + int idx = args->entindex; + int i; + float lifetime = args->fparam1; + float radius = args->fparam2; + vec3_t origin,origin_down,normal,flame_origin,dl_normal; + dlight_t *dl; + pmtrace_t tr; + physent_t *pe; + TEMPENTITY *pExplosion; + + VectorCopy(args->origin,origin); + VectorCopy(args->angles,normal); + + // If we're underwater, dont bother running the event + if(gEngfuncs.PM_PointContents(origin,NULL) == CONTENTS_WATER) + return; + + VectorScale(normal,-1024,origin_down); + VectorAdd(origin,origin_down,origin_down); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction(false,true); + gEngfuncs.pEventAPI->EV_PushPMStates(); + gEngfuncs.pEventAPI->EV_SetSolidPlayers(idx - 1); + gEngfuncs.pEventAPI->EV_SetTraceHull(2); + gEngfuncs.pEventAPI->EV_PlayerTrace(origin,origin_down,PM_WORLD_ONLY,-1,&tr); + + pe = gEngfuncs.pEventAPI->EV_GetPhysent(tr.ent); + + // Add scorch mark + if(pe && (pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP)) + if(CVAR_GET_FLOAT("r_decals")) + { + gEngfuncs.pEfxAPI->R_DecalShoot( + gEngfuncs.pEfxAPI->Draw_DecalIndex(gEngfuncs.pEfxAPI->Draw_DecalIndexFromName("{scorch1")), + gEngfuncs.pEventAPI->EV_IndexFromTrace(&tr),0,tr.endpos,0); + } + + pExplosion = gEngfuncs.pEfxAPI->R_TempSprite(origin + normal * 8, + Vector(0,0,8), + 4, + gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/explode1.spr"), + kRenderTransAdd, + 0, + 25, + 2, + FTENT_SPRANIMATE); + + // Create dynamiclight + dl = gEngfuncs.pEfxAPI->CL_AllocDlight(0); + if(dl != NULL) + { + VectorCopy(args->origin,dl->origin); + + // Pull out of the wall so the light shows up + VectorScale(normal,6,dl_normal); + VectorAdd(dl_normal,dl->origin,dl->origin); + + dl->radius = radius; + + // Color + dl->color.r = 255; + dl->color.g = 220; + dl->color.b = 0; + + dl->die = gHUD.m_flTime + lifetime; + dl->decay = 1/(lifetime/dl->radius); // Make it so it completely decays + } + + switch(gEngfuncs.pfnRandomLong(0,2)) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_AUTO,"debris/glass1.wav",1.0,ATTN_NORM,0,98 + gEngfuncs.pfnRandomLong(0,3)); break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_AUTO,"debris/glass2.wav",1.0,ATTN_NORM,0,98 + gEngfuncs.pfnRandomLong(0,3)); break; + case 2: + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_AUTO,"debris/glass3.wav",1.0,ATTN_NORM,0,98 + gEngfuncs.pfnRandomLong(0,3)); break; + } + + // Main flame + VectorCopy(origin,flame_origin); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + // Cinders + for(i = 0;i <= CVAR_GET_FLOAT("cl_efx_frequency");i++) + { + vec3_t cinder_origin = flame_origin + normal*10; + + // randomize cinder_origin a bit + cinder_origin[0] += gEngfuncs.pfnRandomFloat(-9.0f,9.0f); + cinder_origin[1] += gEngfuncs.pfnRandomFloat(-9.0f,9.0f); + cinder_origin[2] += gEngfuncs.pfnRandomFloat(-9.0f,9.0f); + TEMPENTITY *pTent = gEngfuncs.pEfxAPI->CL_TempEntAllocNoModel(cinder_origin); + + if(pTent != NULL) + { + pTent->flags |= FTENT_NOMODEL|FTENT_GRAVITY|FTENT_COLLIDEWORLD|FTENT_COLLIDEKILL|FTENT_PERSIST|FTENT_CLIENTCUSTOM; + pTent->die += lifetime; + } + } +} + +/************* + Frag Grenade +*************/ + +// copied from util.cpp to here +float EV_WaterLevel(float *position, float minz, float maxz ) +{ + vec3_t midUp = position; + midUp.z = minz; + + if (gEngfuncs.PM_PointContents(midUp,NULL) != CONTENTS_WATER) + return minz; + + midUp.z = maxz; + if (gEngfuncs.PM_PointContents(midUp,NULL) == CONTENTS_WATER) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if (gEngfuncs.PM_PointContents(midUp,NULL) == CONTENTS_WATER) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + +void EV_FragGrenade_ShrapnelCallback(struct tempent_s *ent,float frametime,float currenttime) +{ + if(gHUD.m_flTime >= ent->entity.baseline.fuser1) + { + ent->entity.baseline.fuser1 = gHUD.m_flTime + 0.25f; + + // Emit a smoke tent + TEMPENTITY *pTent = gEngfuncs.pEfxAPI->R_TempSprite(ent->entity.origin, + Vector(0,0,5), + 1, + gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/smoke1.spr"), + kRenderTransAlpha, + 0, + 25, + 5, + FTENT_SPRANIMATE); + + // state settings + if(pTent != NULL) + { + pTent->entity.curstate.framerate = 30; + pTent->entity.curstate.renderamt = 128; + } + } +} + +void EV_FragGrenade(event_args_t *args) +{ + int idx = args->entindex; + int inwater = args->iparam1; + vec3_t origin,normal; + vec3_t origin_down; + vec3_t sh_forward,sh_right,sh_up,sh_velocity; + pmtrace_t tr; + physent_t *pe; + TEMPENTITY *pExplosion; + TEMPENTITY *pShrapnel; + + VectorCopy(args->origin,origin); + VectorCopy(args->angles,normal); + + VectorScale(normal,-32,origin_down); + VectorAdd(origin,origin_down,origin_down); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction(false,true); + gEngfuncs.pEventAPI->EV_PushPMStates(); + gEngfuncs.pEventAPI->EV_SetSolidPlayers(idx - 1); + + gEngfuncs.pEventAPI->EV_SetTraceHull(2); + gEngfuncs.pEventAPI->EV_PlayerTrace(origin + normal*16,origin_down,PM_WORLD_ONLY,-1,&tr); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + pe = gEngfuncs.pEventAPI->EV_GetPhysent(tr.ent); + + // Add scorch mark + if(!inwater && pe && (pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP)) + if(CVAR_GET_FLOAT("r_decals")) + { + gEngfuncs.pEfxAPI->R_DecalShoot( + gEngfuncs.pEfxAPI->Draw_DecalIndex(gEngfuncs.pEfxAPI->Draw_DecalIndexFromName("{scorch1")), + gEngfuncs.pEventAPI->EV_IndexFromTrace(&tr),0,tr.endpos,0); + } + + // create the explosion sprite + if(!inwater) + { + pExplosion = gEngfuncs.pEfxAPI->R_TempSprite(origin + normal * 8, + Vector(0,0,8), + 4, + gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/explode1.spr"), + kRenderTransAdd, + 0, + 25, + 2, + FTENT_SPRANIMATE); + } + else + { + vec3_t mins = origin - Vector(64,64,64); + vec3_t maxs = origin + Vector(64,64,64); + vec3_t mid = (mins + maxs) * 0.5f; + + float flHeight = EV_WaterLevel(mid,mid.z,mid.z + 1024); + + flHeight = flHeight - mins.z; + + // Water bubbles! + gEngfuncs.pEfxAPI->R_Bubbles(mins, + maxs, + flHeight, + gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/bubble.spr"), + CVAR_GET_FLOAT("cl_efx_frequency") * 75, + 8); + } + + // Explosion + switch(gEngfuncs.pfnRandomLong(0,2)) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound(idx, origin, CHAN_AUTO, "weapons/explode3.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0,3)); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound(idx, origin, CHAN_AUTO, "weapons/explode4.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0,3)); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound(idx, origin, CHAN_AUTO, "weapons/explode5.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0,3)); break; + } + + // Debris sound + if(!inwater) + { + if(gEngfuncs.pfnRandomLong(0,1)) + { + switch(gEngfuncs.pfnRandomLong(0,2)) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound(idx, origin, CHAN_AUTO, "weapons/debris1.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0,3)); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound(idx, origin, CHAN_AUTO, "weapons/debris2.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0,3)); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound(idx, origin, CHAN_AUTO, "weapons/debris3.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0,3)); break; + } + } + } + + // Shrapnel ! + if(!inwater) + { + AngleVectors(normal,sh_forward,sh_right,sh_up); + + for(int i = 0;i < CVAR_GET_FLOAT("cl_efx_frequency") * 4;i++) + { + // Randomize velocity + sh_velocity = sh_forward * gEngfuncs.pfnRandomFloat(-800.0f,800.0f) + sh_right * gEngfuncs.pfnRandomFloat(-800.0f,800.0f) + sh_up * gEngfuncs.pfnRandomFloat(200.0f,400.0f); + + pShrapnel = gEngfuncs.pEfxAPI->R_TempModel(origin + normal * 10, + sh_velocity, + normal, + 5, + gEngfuncs.pEventAPI->EV_FindModelIndex("models/shrapnel.mdl"), + 0); + + if(pShrapnel != NULL) + { + pShrapnel->flags |= FTENT_CLIENTCUSTOM; + pShrapnel->callback = EV_FragGrenade_ShrapnelCallback; + + // Next smoke emit + pShrapnel->entity.baseline.fuser1 = gHUD.m_flTime + 0.25f; + } + } + } +} + +/************* + Pipebomb +*************/ +void EV_Pipebomb(event_args_t *args) +{ + int idx = args->entindex; + int inwater = args->iparam1; + vec3_t origin,normal; + vec3_t origin_down; + pmtrace_t tr; + physent_t *pe; + TEMPENTITY *pExplosion; + + VectorCopy(args->origin,origin); + VectorCopy(args->angles,normal); + + VectorScale(normal,-32,origin_down); + VectorAdd(origin,origin_down,origin_down); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction(false,true); + gEngfuncs.pEventAPI->EV_PushPMStates(); + gEngfuncs.pEventAPI->EV_SetSolidPlayers(idx - 1); + + gEngfuncs.pEventAPI->EV_SetTraceHull(2); + gEngfuncs.pEventAPI->EV_PlayerTrace(origin + normal*16,origin_down,PM_WORLD_ONLY,-1,&tr); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + pe = gEngfuncs.pEventAPI->EV_GetPhysent(tr.ent); + + // Add scorch mark + if(!inwater && pe && (pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP)) + if(CVAR_GET_FLOAT("r_decals")) + { + gEngfuncs.pEfxAPI->R_DecalShoot( + gEngfuncs.pEfxAPI->Draw_DecalIndex(gEngfuncs.pEfxAPI->Draw_DecalIndexFromName("{scorch1")), + gEngfuncs.pEventAPI->EV_IndexFromTrace(&tr),0,tr.endpos,0); + } + + // Explosion + switch(gEngfuncs.pfnRandomLong(0,2)) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound(idx, origin, CHAN_AUTO, "weapons/explode3.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0,3)); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound(idx, origin, CHAN_AUTO, "weapons/explode4.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0,3)); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound(idx, origin, CHAN_AUTO, "weapons/explode5.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0,3)); break; + } + + // create the explosion sprite + if(!inwater) + { + pExplosion = gEngfuncs.pEfxAPI->R_TempSprite(origin + normal * 8, + Vector(0,0,8), + 4.75f, + gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/explode1.spr"), + kRenderTransAdd, + 0, + 25, + 2, + FTENT_SPRANIMATE); + } + else + { + vec3_t mins = origin - Vector(64,64,64); + vec3_t maxs = origin + Vector(64,64,64); + vec3_t mid = (mins + maxs) * 0.5f; + + float flHeight = EV_WaterLevel(mid,mid.z,mid.z + 1024); + + flHeight = flHeight - mins.z; + + // Water bubbles! + gEngfuncs.pEfxAPI->R_Bubbles(mins, + maxs, + flHeight, + gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/bubble.spr"), + CVAR_GET_FLOAT("cl_efx_frequency") * 75, + 8); + } +} + +/************* + Combat Knife +*************/ +void EV_CombatKnife(event_args_t *args) +{ + int idx,primary_attack; + vec3_t origin; + vec3_t vecSrc,vecDest; + + idx = args->entindex; + primary_attack = args->iparam1; + + VectorCopy( args->origin, origin ); + + //Play Swing sound + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,"weapons/combatknife_slash1.wav",1,ATTN_NORM,0,PITCH_NORM); + + if(EV_IsLocal(idx)) + { + if(primary_attack) + gEngfuncs.pEventAPI->EV_WeaponAnimation(COMBATKNIFE_ATTACK1MISS,1); + else + { + switch(gEngfuncs.pfnRandomLong(0,1)) + { + case 0: + gEngfuncs.pEventAPI->EV_WeaponAnimation(COMBATKNIFE_ATTACK2,1); break; + case 1: + gEngfuncs.pEventAPI->EV_WeaponAnimation(COMBATKNIFE_ATTACK3,1); break; + } + } + } +} + +/************* + Baseball Bat +*************/ +void EV_BaseballBat(event_args_t *args) +{ + int idx; + vec3_t origin; + vec3_t vecSrc,vecDest; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + //Play Swing sound + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,"weapons/baseballbat_slash1.wav",1,ATTN_NORM,0,PITCH_NORM); + + if(EV_IsLocal(idx)) + { + switch(gEngfuncs.pfnRandomLong(0,2)) + { + case 0: gEngfuncs.pEventAPI->EV_WeaponAnimation(BAT_ATTACK1,1); break; + case 1: gEngfuncs.pEventAPI->EV_WeaponAnimation(BAT_ATTACK2,1); break; + case 2: gEngfuncs.pEventAPI->EV_WeaponAnimation(BAT_ATTACK3,1); break; + } + } +} + +/************* + Sledge Hammer +*************/ +void EV_SledgeHammer(event_args_t *args) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + //Play Swing sound + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,"weapons/sledgehammer_slash1.wav",1,ATTN_NORM,0,PITCH_NORM); + + if(EV_IsLocal(idx)) + { + switch(gEngfuncs.pfnRandomLong(0,1)) + { + case 0: gEngfuncs.pEventAPI->EV_WeaponAnimation(SLEDGE_ATTACK1,1); break; + case 1: gEngfuncs.pEventAPI->EV_WeaponAnimation(SLEDGE_ATTACK2,1); break; + } + } +} + +/************* + Katana +*************/ +void EV_Katana(event_args_t *args) +{ + int idx,primary_attack; + vec3_t origin; + + idx = args->entindex; + primary_attack = args->iparam1; + + VectorCopy( args->origin, origin ); + + //Play Swing sound + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,"weapons/katana_slash1.wav",1,ATTN_NORM,0,PITCH_NORM); + + if(EV_IsLocal(idx)) + { + if(primary_attack) + { + gEngfuncs.pEventAPI->EV_WeaponAnimation(KATANA_ST1_ATTACK1,1); + } + else + { + switch(gEngfuncs.pfnRandomLong(0,1)) + { + case 0: gEngfuncs.pEventAPI->EV_WeaponAnimation(KATANA_ST2_ATTACK1,1); break; + case 1: gEngfuncs.pEventAPI->EV_WeaponAnimation(KATANA_ST2_ATTACK2,1); break; + } + } + } +} + +/************* + Spear +*************/ +void EV_Spear(event_args_t *args) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + //Play Swing sound + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,"weapons/spear_slash1.wav",1,ATTN_NORM,0,PITCH_NORM); + + if(EV_IsLocal(idx)) + { + switch(gEngfuncs.pfnRandomLong(0,1)) + { + case 0: gEngfuncs.pEventAPI->EV_WeaponAnimation(SPEAR_ATTACK1,1); break; + case 1: gEngfuncs.pEventAPI->EV_WeaponAnimation(SPEAR_ATTACK2,1); break; + } + } +} + +extern cvar_t *sensitivity; + +/************* + Cattle Prod +*************/ +void EV_CattleProd(event_args_t *args) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + //Play Swing sound + gEngfuncs.pEventAPI->EV_PlaySound(idx,origin,CHAN_WEAPON,"weapons/prod_hit.wav",1,ATTN_NORM,0,PITCH_NORM); + + if(EV_IsLocal(idx)) + { + switch(gEngfuncs.pfnRandomLong(0,1)) + { + case 0: gEngfuncs.pEventAPI->EV_WeaponAnimation(PROD_ATTACK1,1); break; + case 1: gEngfuncs.pEventAPI->EV_WeaponAnimation(PROD_ATTACK2,1); break; + } + } +} + +/************* + Akimbo Weapons +*************/ +void EV_AkimboWeapon(event_args_t *args, char *sound, char *loshell, char *hishell, int bullettype, int ptype ) +{ + int idx; + vec3_t origin, angles, up, right, forward, velocity; + vec3_t vecSrc, vecAiming; + int curhand; + int ammo_left; + + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + AngleVectors( angles, forward, right, up ); + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + idx = args->entindex; + curhand = args->iparam1; + ammo_left = args->iparam2; + + // Play firing sound + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, sound, 1, ATTN_NORM, 0, PITCH_NORM ); + + // Shell ejection + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + + if(!CVAR_GET_FLOAT("cl_shellcase_quality")) + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ( loshell ); + else + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ( hishell ); + + if( curhand == AH_RIGHT ) + EV_GetDefaultShellInfo( 0, args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 10, -4, 5 ); + else + EV_GetDefaultShellInfo( 0, args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 10, -4, -5 ); + + EV_EjectBrass( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHELL ); + + if( EV_IsLocal( idx ) ) + { + EV_MuzzleFlash(); + + if( curhand == AH_LEFT ) + { + if( ammo_left <= 1 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( AKIMBO_LSHOOTLAST, 1 ); + else + gEngfuncs.pEventAPI->EV_WeaponAnimation( AKIMBO_LSHOOT, 1 ); + } + else + { + if( ammo_left <= 1 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( AKIMBO_RSHOOTLAST_LEMPTY, 1 ); + else + gEngfuncs.pEventAPI->EV_WeaponAnimation( AKIMBO_RSHOOT, 1 ); + } + } + + // TODO: change penetration type and bullet type to match different guns! + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, bullettype, (ev_thermal) ? 1 : 0, (ev_thermal) ? &tracerCount[idx-1] : 0, args->fparam1, args->fparam2, ptype ); +} + +void EV_AkimboBerettas( event_args_t *args ) +{ + int idx = args->entindex; + + EV_AkimboWeapon( args, + "weapons/beretta_fire.wav", + "models/shells/shell_9x18mm_lo.mdl", + "models/shells/shell_9x18mm_hi.mdl", + BULLET_9MMP, + P_BERETTA ); + + if( EV_IsLocal( idx ) ) + V_PunchAxis( 0, -2.0 ); +} + +void EV_AkimboColts( event_args_t *args ) +{ + int idx = args->entindex; + + EV_AkimboWeapon( args, + "weapons/colt_fire.wav", + "models/shells/shell_10mm_lo.mdl", + "models/shells/shell_10mm_hi.mdl", + BULLET_10MM, + P_COLT ); + + if( EV_IsLocal( idx ) ) + V_PunchAxis( 0, -2.0 ); +} + +void EV_AkimboDeagles( event_args_t *args ) +{ + int idx = args->entindex; + + EV_AkimboWeapon( args, + "weapons/deagle_fire.wav", + "models/shells/shell_50AE_lo.mdl", + "models/shells/shell_50AE_hi.mdl", + BULLET_50AE, + P_DEAGLE ); + + if( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, -2.0 ); + + RECOIL_VIEW(5.5,0); + } +} + +void EV_AkimboSawedOffs( event_args_t *args ) +{ + int idx = args->entindex; + int curhand = args->iparam1; + int bothshoot = args->bparam1; // Both guns were fired :O + + vec3_t origin; + vec3_t angles; + vec3_t velocity; + vec3_t up, right, forward; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + AngleVectors( angles, forward, right, up ); + + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + + if( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + + if( bothshoot ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( AKIMBOSS_BSHOOT, 2 ); + else + gEngfuncs.pEventAPI->EV_WeaponAnimation( (curhand == AH_LEFT) ? AKIMBOSS_LSHOOT : AKIMBOSS_RSHOOT, 2 ); + + if( bothshoot ) + V_PunchAxis( 0, -11.5 ); + else + V_PunchAxis( 0, -5.0 ); + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, ( !bothshoot ) ? "weapons/sawedoff_fire1.wav" : "weapons/sawedoff_fire2.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + int shotCount = (bothshoot) ? 26 : 14; + + // Remember to change the randomization code at the bottom :) + EV_HLDM_FireBullets( idx, forward, right, up, shotCount, vecSrc, vecAiming, 2048, BULLET_20GAUGE, (ev_thermal) ? 1 : 0, (ev_thermal) ? &tracerCount[idx-1] : 0, args->fparam1, args->fparam2, 0 ); +} diff --git a/cl_dll/ev_thewastes.h b/cl_dll/ev_thewastes.h new file mode 100644 index 0000000..40b5981 --- /dev/null +++ b/cl_dll/ev_thewastes.h @@ -0,0 +1,39 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( EV_HLDMH ) +#define EV_HLDMH + +// bullet types +typedef enum +{ + BULLET_NONE = 0, + + // Wasteland Bullet Types + BULLET_9MMP, + BULLET_10MM, + BULLET_50AE, + BULLET_454CASULL, + BULLET_556MM, + BULLET_20GAUGE, + BULLET_10GAUGE, + BULLET_12GAUGE, + BULLET_SLUG, + BULLET_9MM, + BULLET_762MM, + BULLET_45ACP, + BULLET_47MM, +} Bullet; + +void EV_HLDM_GunshotDecalTrace( pmtrace_t *pTrace, char *decalName ); +void EV_HLDM_DecalGunshot( pmtrace_t *pTrace, int iBulletType ); +int EV_HLDM_CheckTracer( int idx, float *vecSrc, float *end, float *forward, float *right, int iBulletType, int iTracerFreq, int *tracerCount ); +void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int cShots, float *vecSrc, float *vecDirShooting, float flDistance, int iBulletType, int iTracerFreq, int *tracerCount, float flSpreadX, float flSpreadY ); +//BLONDE - Bulletsmoke is for weapon effects and texture specific bulletholes +float EV_HLDM_BulletSmoke ( int idx, pmtrace_t *ptr, float *vecSrc, float *vecEnd, int iBulletType, int glowy ); +//&BLONDE +#endif // EV_HLDMH \ No newline at end of file diff --git a/cl_dll/eventscripts.h b/cl_dll/eventscripts.h index 9653628..d8e4f5e 100644 --- a/cl_dll/eventscripts.h +++ b/cl_dll/eventscripts.h @@ -48,20 +48,18 @@ #define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) //TF ADDITIONS -#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn +/*#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn #define DMG_RADIUS_MAX (1 << 25) // Radius damage with this flag doesn't decrease over distance #define DMG_RADIUS_QUAKE (1 << 26) // Radius damage is done like Quake. 1/2 damage at 1/2 radius. #define DMG_IGNOREARMOR (1 << 27) // Damage ignores target's armor #define DMG_AIMED (1 << 28) // Does Hit location damage #define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls - -#define DMG_CALTROP (1<<30) -#define DMG_HALLUC (1<<31) +*/ // Some of these are HL/TFC specific? void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ); void EV_GetGunPosition( struct event_args_s *args, float *pos, float *origin ); -void EV_GetDefaultShellInfo( struct event_args_s *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ); +void EV_GetDefaultShellInfo( int iWeaponType,struct event_args_s *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ); qboolean EV_IsLocal( int idx ); qboolean EV_IsPlayer( int idx ); void EV_CreateTracer( float *start, float *end ); diff --git a/cl_dll/flashlight.cpp b/cl_dll/flashlight.cpp index 2f08d5d..bf935ba 100644 --- a/cl_dll/flashlight.cpp +++ b/cl_dll/flashlight.cpp @@ -25,8 +25,6 @@ #include #include - - DECLARE_MESSAGE(m_Flash, FlashBat) DECLARE_MESSAGE(m_Flash, Flashlight) @@ -102,9 +100,6 @@ int CHudFlashlight::Draw(float flTime) int r, g, b, x, y, a; wrect_t rc; - if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) - return 1; - if (m_fOn) a = 225; else @@ -113,7 +108,7 @@ int CHudFlashlight::Draw(float flTime) if (m_flBat < 0.20) UnpackRGB(r,g,b, RGB_REDISH); else - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); ScaleColors(r, g, b, a); diff --git a/cl_dll/health.cpp b/cl_dll/health.cpp index 890044a..1f0cc58 100644 --- a/cl_dll/health.cpp +++ b/cl_dll/health.cpp @@ -27,7 +27,6 @@ #include "parsemsg.h" #include - DECLARE_MESSAGE(m_Health, Health ) DECLARE_MESSAGE(m_Health, Damage ) @@ -46,19 +45,18 @@ int giDmgFlags[NUM_DMG_TYPES] = DMG_NERVEGAS, DMG_RADIATION, DMG_SHOCK, - DMG_CALTROP, DMG_TRANQ, DMG_CONCUSS, - DMG_HALLUC }; int CHudHealth::Init(void) { HOOK_MESSAGE(Health); HOOK_MESSAGE(Damage); - m_iHealth = 100; - m_fFade = 0; - m_iFlags = 0; + m_iFlags = HUD_ACTIVE; + + m_iHealth = m_iOldHealth = 100; + m_bitsDamage = 0; m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; giDmgHeight = 0; @@ -66,7 +64,6 @@ int CHudHealth::Init(void) memset(m_dmg, 0, sizeof(DAMAGE_IMAGE) * NUM_DMG_TYPES); - gHUD.AddHudElem(this); return 1; } @@ -76,7 +73,6 @@ void CHudHealth::Reset( void ) // make sure the pain compass is cleared when the player respawns m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; - // force all the flashing damage icons to expire m_bitsDamage = 0; for ( int i = 0; i < NUM_DMG_TYPES; i++ ) @@ -87,35 +83,29 @@ void CHudHealth::Reset( void ) int CHudHealth::VidInit(void) { - m_hSprite = 0; + m_hDamageSprite = 0; m_HUD_dmg_bio = gHUD.GetSpriteIndex( "dmg_bio" ) + 1; - m_HUD_cross = gHUD.GetSpriteIndex( "cross" ); - giDmgHeight = gHUD.GetSpriteRect(m_HUD_dmg_bio).right - gHUD.GetSpriteRect(m_HUD_dmg_bio).left; - giDmgWidth = gHUD.GetSpriteRect(m_HUD_dmg_bio).bottom - gHUD.GetSpriteRect(m_HUD_dmg_bio).top; + m_HUD_healthbar = gHUD.GetSpriteIndex("healthbar"); + m_HUD_healthbg = gHUD.GetSpriteIndex("healthbg"); + + giDmgHeight = gHUD.GetSpriteRect(m_HUD_dmg_bio).right - gHUD.GetSpriteRect(m_HUD_dmg_bio).left; + giDmgWidth = gHUD.GetSpriteRect(m_HUD_dmg_bio).bottom - gHUD.GetSpriteRect(m_HUD_dmg_bio).top; + return 1; } int CHudHealth:: MsgFunc_Health(const char *pszName, int iSize, void *pbuf ) { - // TODO: update local health data BEGIN_READ( pbuf, iSize ); int x = READ_BYTE(); - m_iFlags |= HUD_ACTIVE; - - // Only update the fade if we've changed health - if (x != m_iHealth) - { - m_fFade = FADE_TIME; - m_iHealth = x; - } + m_iHealth = x; return 1; } - int CHudHealth:: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) { BEGIN_READ( pbuf, iSize ); @@ -133,12 +123,13 @@ int CHudHealth:: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) // Actually took damage? if ( damageTaken > 0 || armor > 0 ) + { CalcDamageDirection(vecFrom); + } return 1; } - // Returns back a color from the // Green <-> Yellow <-> Red ramp void CHudHealth::GetPainColor( int &r, int &g, int &b ) @@ -156,7 +147,7 @@ void CHudHealth::GetPainColor( int &r, int &g, int &b ) #else if (m_iHealth > 25) { - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); } else { @@ -169,66 +160,83 @@ void CHudHealth::GetPainColor( int &r, int &g, int &b ) int CHudHealth::Draw(float flTime) { - int r, g, b; - int a = 0, x, y; - int HealthWidth; - - if ( (gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH) || gEngfuncs.IsSpectateOnly() ) + if(gHUD.m_iHideHUDDisplay & (HIDEHUD_ALL|HIDEHUD_HEALTH)) return 1; - if ( !m_hSprite ) - m_hSprite = LoadSprite(PAIN_NAME); - - // Has health changed? Flash the health # - if (m_fFade) + int x,y,r,g,b,a; + wrect_t *prc; + + // Draw background + a = 255; + + UnpackRGB(r,g,b,RGB_WHITE); + ScaleColors(r,g,b,a); + + prc = &gHUD.GetSpriteRect(m_HUD_healthbg); + + x = (ScreenWidth / 2) - (prc->right - prc->left) / 2; + y = ScreenHeight - (prc->bottom - prc->top); + + SPR_Set(gHUD.GetSprite(m_HUD_healthbg),r,g,b); + SPR_DrawHoles(0,x,y,prc); + + if(gHUD.m_PlayerState.ActiveState() == PS_BLEEDING && gHUD.m_Health.m_iHealth > 0 && gHUD.m_Health.m_iHealth < 100) + UnpackRGB(r,g,b,RGB_REDISH); + else + UnpackRGB(r,g,b,RGB_GREENISH); + + ScaleColors(r,g,b,a); + + // Center of health bar + x += (prc->right - prc->left) / 2; + y += (prc->bottom - prc->top) / 2; + + prc = &gHUD.GetSpriteRect(m_HUD_healthbar); + + // Get new coordinates + x -= (prc->right - prc->left) / 2; + y -= (prc->bottom - prc->top) / 2; + + int iHealthWidth = (prc->right - prc->left) * m_iHealth / 100; + + // Draw old health + if(m_iOldHealth > m_iHealth) { - m_fFade -= (gHUD.m_flTimeDelta * 20); - if (m_fFade <= 0) - { - a = MIN_ALPHA; - m_fFade = 0; - } + int or,og,ob,oa; + int iScissorX,iScissorWidth; - // Fade the health number back to dim + oa = 225; - a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + UnpackRGB(or,og,ob,RGB_YELLOWISH); + ScaleColors(or,og,ob,oa); + iScissorX = x + iHealthWidth; + iScissorWidth = ((prc->right-prc->left) * m_iOldHealth / 100) - iHealthWidth; + + SPR_EnableScissor(iScissorX,0,iScissorWidth,ScreenHeight); + + SPR_Set(gHUD.GetSprite(m_HUD_healthbar),or,og,ob); + SPR_DrawAdditive(0,x,y,prc); + + SPR_DisableScissor(); + + // Reduce old health value + m_iOldHealth -= gHUD.m_flTimeDelta * 1.5; } else - a = MIN_ALPHA; + m_iOldHealth = m_iHealth; - // If health is getting low, make it bright red - if (m_iHealth <= 15) - a = 255; - - GetPainColor( r, g, b ); - ScaleColors(r, g, b, a ); + // Draw current health + SPR_EnableScissor(x,0,iHealthWidth,ScreenHeight); - // Only draw health if we have the suit. - if (gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT))) - { - HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; - int CrossWidth = gHUD.GetSpriteRect(m_HUD_cross).right - gHUD.GetSpriteRect(m_HUD_cross).left; + SPR_Set(gHUD.GetSprite(m_HUD_healthbar),r,g,b); + SPR_DrawAdditive(0,x,y,prc); - y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; - x = CrossWidth /2; - - SPR_Set(gHUD.GetSprite(m_HUD_cross), r, g, b); - SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_cross)); - - x = CrossWidth + HealthWidth / 2; - - x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iHealth, r, g, b); - - x += HealthWidth/2; - - int iHeight = gHUD.m_iFontHeight; - int iWidth = HealthWidth/10; - FillRGBA(x, y, iWidth, iHeight, 255, 160, 0, a); - } + SPR_DisableScissor(); DrawDamage(flTime); - return DrawPain(flTime); +// return DrawPain(flTime); + return 1; } void CHudHealth::CalcDamageDirection(vec3_t vecFrom) @@ -243,11 +251,9 @@ void CHudHealth::CalcDamageDirection(vec3_t vecFrom) return; } - memcpy(vecOrigin, gHUD.m_vecOrigin, sizeof(vec3_t)); memcpy(vecAngles, gHUD.m_vecAngles, sizeof(vec3_t)); - VectorSubtract (vecFrom, vecOrigin, vecFrom); float flDistToTarget = vecFrom.Length(); @@ -309,10 +315,10 @@ int CHudHealth::DrawPain(float flTime) GetPainColor(r,g,b); shade = a * max( m_fAttackFront, 0.5 ); ScaleColors(r, g, b, shade); - SPR_Set(m_hSprite, r, g, b ); + SPR_Set(m_hDamageSprite, r, g, b ); - x = ScreenWidth/2 - SPR_Width(m_hSprite, 0)/2; - y = ScreenHeight/2 - SPR_Height(m_hSprite,0) * 3; + x = ScreenWidth/2 - SPR_Width(m_hDamageSprite, 0)/2; + y = ScreenHeight/2 - SPR_Height(m_hDamageSprite,0) * 3; SPR_DrawAdditive(0, x, y, NULL); m_fAttackFront = max( 0, m_fAttackFront - fFade ); } else @@ -323,10 +329,10 @@ int CHudHealth::DrawPain(float flTime) GetPainColor(r,g,b); shade = a * max( m_fAttackRight, 0.5 ); ScaleColors(r, g, b, shade); - SPR_Set(m_hSprite, r, g, b ); + SPR_Set(m_hDamageSprite, r, g, b ); - x = ScreenWidth/2 + SPR_Width(m_hSprite, 1) * 2; - y = ScreenHeight/2 - SPR_Height(m_hSprite,1)/2; + x = ScreenWidth/2 + SPR_Width(m_hDamageSprite, 1) * 2; + y = ScreenHeight/2 - SPR_Height(m_hDamageSprite,1)/2; SPR_DrawAdditive(1, x, y, NULL); m_fAttackRight = max( 0, m_fAttackRight - fFade ); } else @@ -337,10 +343,10 @@ int CHudHealth::DrawPain(float flTime) GetPainColor(r,g,b); shade = a * max( m_fAttackRear, 0.5 ); ScaleColors(r, g, b, shade); - SPR_Set(m_hSprite, r, g, b ); + SPR_Set(m_hDamageSprite, r, g, b ); - x = ScreenWidth/2 - SPR_Width(m_hSprite, 2)/2; - y = ScreenHeight/2 + SPR_Height(m_hSprite,2) * 2; + x = ScreenWidth/2 - SPR_Width(m_hDamageSprite, 2)/2; + y = ScreenHeight/2 + SPR_Height(m_hDamageSprite,2) * 2; SPR_DrawAdditive(2, x, y, NULL); m_fAttackRear = max( 0, m_fAttackRear - fFade ); } else @@ -351,10 +357,10 @@ int CHudHealth::DrawPain(float flTime) GetPainColor(r,g,b); shade = a * max( m_fAttackLeft, 0.5 ); ScaleColors(r, g, b, shade); - SPR_Set(m_hSprite, r, g, b ); + SPR_Set(m_hDamageSprite, r, g, b ); - x = ScreenWidth/2 - SPR_Width(m_hSprite, 3) * 3; - y = ScreenHeight/2 - SPR_Height(m_hSprite,3)/2; + x = ScreenWidth/2 - SPR_Width(m_hDamageSprite, 3) * 3; + y = ScreenHeight/2 - SPR_Height(m_hDamageSprite,3)/2; SPR_DrawAdditive(3, x, y, NULL); m_fAttackLeft = max( 0, m_fAttackLeft - fFade ); @@ -372,7 +378,7 @@ int CHudHealth::DrawDamage(float flTime) if (!m_bitsDamage) return 1; - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); a = (int)( fabs(sin(flTime*2)) * 256.0); @@ -389,7 +395,6 @@ int CHudHealth::DrawDamage(float flTime) } } - // check for bits that should be expired for ( i = 0; i < NUM_DMG_TYPES; i++ ) { @@ -424,7 +429,6 @@ int CHudHealth::DrawDamage(float flTime) return 1; } - void CHudHealth::UpdateTiles(float flTime, long bitsDamage) { DAMAGE_IMAGE *pdmg; @@ -448,7 +452,7 @@ void CHudHealth::UpdateTiles(float flTime, long bitsDamage) if (bitsOn & giDmgFlags[i]) { // put this one at the bottom - pdmg->x = giDmgWidth/8; + pdmg->x = giDmgWidth/8 + ScreenWidth/4; pdmg->y = ScreenHeight - giDmgHeight * 2; pdmg->fExpire=flTime + DMG_IMAGE_LIFE; diff --git a/cl_dll/health.h b/cl_dll/health.h index fc4d521..86b905d 100644 --- a/cl_dll/health.h +++ b/cl_dll/health.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1999, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -46,7 +46,6 @@ #define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death #define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. - // time-based damage //mask off TF-specific stuff too #define DMG_TIMEBASED (~(0xff003fff)) // mask for time-based damage @@ -73,21 +72,15 @@ #define DMG_AIMED (1 << 28) // Does Hit location damage #define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls -#define DMG_CALTROP (1<<30) -#define DMG_HALLUC (1<<31) - // TF Healing Additions for TakeHealth #define DMG_IGNORE_MAXHEALTH DMG_IGNITE // TF Redefines since we never use the originals #define DMG_NAIL DMG_SLASH #define DMG_NOT_SELF DMG_FREEZE - #define DMG_TRANQ DMG_MORTAR #define DMG_CONCUSS DMG_SONIC - - typedef struct { float fExpire; @@ -105,21 +98,27 @@ public: virtual int VidInit( void ); virtual int Draw(float fTime); virtual void Reset( void ); + int MsgFunc_Health(const char *pszName, int iSize, void *pbuf); int MsgFunc_Damage(const char *pszName, int iSize, void *pbuf); - int m_iHealth; - int m_HUD_dmg_bio; - int m_HUD_cross; - float m_fAttackFront, m_fAttackRear, m_fAttackLeft, m_fAttackRight; - void GetPainColor( int &r, int &g, int &b ); - float m_fFade; + // Made public so other parts of the HUD can read it + int m_iHealth; private: - HSPRITE m_hSprite; - HSPRITE m_hDamage; + int m_HUD_dmg_bio; + int m_HUD_healthbar; + int m_HUD_healthbg; DAMAGE_IMAGE m_dmg[NUM_DMG_TYPES]; int m_bitsDamage; + + HSPRITE m_hDamageSprite; + float m_fAttackFront, m_fAttackRear, m_fAttackLeft, m_fAttackRight; + + int m_iOldHealth; + + // Support functions + void GetPainColor( int &r, int &g, int &b ); int DrawPain(float fTime); int DrawDamage(float fTime); void CalcDamageDirection(vec3_t vecFrom); diff --git a/cl_dll/hud.cpp b/cl_dll/hud.cpp index 5401dfb..9c806e8 100644 --- a/cl_dll/hud.cpp +++ b/cl_dll/hud.cpp @@ -25,13 +25,16 @@ #include "parsemsg.h" #include "hud_servers.h" #include "vgui_int.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" #include "demo.h" #include "demo_api.h" +#include "tw_vgui.h" #include "vgui_scorepanel.h" +#include "tw_common.h" - +extern int g_iClientLasersEnabled[32]; class CHLVoiceStatusHelper : public IVoiceStatusHelper { @@ -85,12 +88,6 @@ cvar_t *cl_lw = NULL; void ShutdownInput (void); -//DECLARE_MESSAGE(m_Logo, Logo) -int __MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) -{ - return gHUD.MsgFunc_Logo(pszName, iSize, pbuf ); -} - //DECLARE_MESSAGE(m_Logo, Logo) int __MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf) { @@ -118,6 +115,18 @@ int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf ); } +int __MsgFunc_LaserInfo(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + int setting = READ_BYTE(); + + g_iClientLasersEnabled[index] = setting; + + return 1; +} + // TFFree Command Menu void __CmdFunc_OpenCommandMenu(void) { @@ -127,15 +136,6 @@ void __CmdFunc_OpenCommandMenu(void) } } -// TFC "special" command -void __CmdFunc_InputPlayerSpecial(void) -{ - if ( gViewPort ) - { - gViewPort->InputPlayerSpecial(); - } -} - void __CmdFunc_CloseCommandMenu(void) { if ( gViewPort ) @@ -152,6 +152,14 @@ void __CmdFunc_ForceCloseCommandMenu( void ) } } +void __CmdFunc_OpenItemSelectionMenu( void ) +{ + if( gViewPort ) + { + gViewPort->ShowVGUIMenu(MENU_ITEMSELECTION); + } +} + void __CmdFunc_ToggleServerBrowser( void ) { if ( gViewPort ) @@ -175,20 +183,6 @@ int __MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf) return 0; } -int __MsgFunc_Feign(const char *pszName, int iSize, void *pbuf) -{ - if (gViewPort) - return gViewPort->MsgFunc_Feign( pszName, iSize, pbuf ); - return 0; -} - -int __MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf) -{ - if (gViewPort) - return gViewPort->MsgFunc_Detpack( pszName, iSize, pbuf ); - return 0; -} - int __MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf) { if (gViewPort) @@ -203,6 +197,13 @@ int __MsgFunc_MOTD(const char *pszName, int iSize, void *pbuf) return 0; } +int __MsgFunc_Briefing(const char *pszName,int iSize,void *pbuf) +{ + if(gViewPort) + return gViewPort->MsgFunc_Briefing(pszName,iSize,pbuf); + return 0; +} + int __MsgFunc_BuildSt(const char *pszName, int iSize, void *pbuf) { if (gViewPort) @@ -258,11 +259,17 @@ int __MsgFunc_AllowSpec(const char *pszName, int iSize, void *pbuf) return gViewPort->MsgFunc_AllowSpec( pszName, iSize, pbuf ); return 0; } + +int __MsgFunc_LmsStart( const char *pszName, int iSize, void *pbuf ) +{ + if( gViewPort ) + return gViewPort->MsgFunc_LmsStart( pszName, iSize, pbuf ); + return 0; +} // This is called every time the DLL is loaded void CHud :: Init( void ) { - HOOK_MESSAGE( Logo ); HOOK_MESSAGE( ResetHUD ); HOOK_MESSAGE( GameMode ); HOOK_MESSAGE( InitHUD ); @@ -272,15 +279,14 @@ void CHud :: Init( void ) // TFFree CommandMenu HOOK_COMMAND( "+commandmenu", OpenCommandMenu ); HOOK_COMMAND( "-commandmenu", CloseCommandMenu ); + HOOK_COMMAND( "itemmenu", OpenItemSelectionMenu ); HOOK_COMMAND( "ForceCloseCommandMenu", ForceCloseCommandMenu ); - HOOK_COMMAND( "special", InputPlayerSpecial ); HOOK_COMMAND( "togglebrowser", ToggleServerBrowser ); HOOK_MESSAGE( ValClass ); HOOK_MESSAGE( TeamNames ); - HOOK_MESSAGE( Feign ); - HOOK_MESSAGE( Detpack ); HOOK_MESSAGE( MOTD ); + HOOK_MESSAGE( Briefing ); HOOK_MESSAGE( BuildSt ); HOOK_MESSAGE( RandomPC ); HOOK_MESSAGE( ServerName ); @@ -290,6 +296,7 @@ void CHud :: Init( void ) HOOK_MESSAGE( Spectator ); HOOK_MESSAGE( AllowSpec ); + HOOK_MESSAGE( LmsStart ); // VGUI Menus HOOK_MESSAGE( VGUIMenu ); @@ -297,8 +304,6 @@ void CHud :: Init( void ) CVAR_CREATE( "hud_classautokill", "1", FCVAR_ARCHIVE | FCVAR_USERINFO ); // controls whether or not to suicide immediately on TF class switch CVAR_CREATE( "hud_takesshots", "0", FCVAR_ARCHIVE ); // controls whether or not to automatically take screenshots at the end of a round - - m_iLogo = 0; m_iFOV = 0; CVAR_CREATE( "zoom_sensitivity_ratio", "1.2", 0 ); @@ -325,20 +330,23 @@ void CHud :: Init( void ) // In case we get messages before the first update -- time will be valid m_flTime = 1.0; - m_Ammo.Init(); + m_Scanlines.Init(); m_Health.Init(); m_Geiger.Init(); m_Train.Init(); - m_Battery.Init(); m_Flash.Init(); m_Message.Init(); m_StatusBar.Init(); m_DeathNotice.Init(); + m_Scope.Init(); + m_Ammo.Init(); m_AmmoSecondary.Init(); m_TextMessage.Init(); m_StatusIcons.Init(); GetClientVoiceMgr()->Init(&g_VoiceStatusHelper, (vgui::Panel**)&gViewPort); m_Spectator.Init(); + m_CrosshairInfo.Init(); + m_PlayerState.Init(); m_SayText.Init(); m_Menu.Init(); @@ -474,9 +482,9 @@ void CHud :: VidInit( void ) m_Ammo.VidInit(); m_Health.VidInit(); + m_Scanlines.VidInit(); m_Geiger.VidInit(); m_Train.VidInit(); - m_Battery.VidInit(); m_Flash.VidInit(); m_Message.VidInit(); m_StatusBar.VidInit(); @@ -485,19 +493,12 @@ void CHud :: VidInit( void ) m_Menu.VidInit(); m_AmmoSecondary.VidInit(); m_TextMessage.VidInit(); + m_Scope.VidInit(); m_StatusIcons.VidInit(); GetClientVoiceMgr()->VidInit(); m_Spectator.VidInit(); -} - -int CHud::MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) -{ - BEGIN_READ( pbuf, iSize ); - - // update Train data - m_iLogo = READ_BYTE(); - - return 1; + m_CrosshairInfo.VidInit(); + m_PlayerState.VidInit(); } float g_lastFOV = 0.0; @@ -601,7 +602,7 @@ int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) int newfov = READ_BYTE(); int def_fov = CVAR_GET_FLOAT( "default_fov" ); - //Weapon prediction already takes care of changing the fog. ( g_lastFOV ). + //Weapon prediction already takes care of changing the fov. ( g_lastFOV ). if ( cl_lw && cl_lw->value ) return 1; @@ -633,7 +634,6 @@ int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) return 1; } - void CHud::AddHudElem(CHudBase *phudelem) { HUDLIST *pdl, *ptemp; diff --git a/cl_dll/hud.h b/cl_dll/hud.h index 7194097..0ad5f35 100644 --- a/cl_dll/hud.h +++ b/cl_dll/hud.h @@ -21,9 +21,11 @@ // +#define RGB_WHITE 0x00FFFFFF //255,255,255 #define RGB_YELLOWISH 0x00FFA000 //255,160,0 -#define RGB_REDISH 0x00FF1010 //255,160,0 -#define RGB_GREENISH 0x0000A000 //0,160,0 +#define RGB_REDISH 0x00FF1010 //255,160,0 +#define RGB_GREENISH 0x0000A000 //0,160,0 +#define RGB_BLUISH 0x000080FF //0,128,255 #include "wrect.h" #include "cl_dll.h" @@ -77,7 +79,6 @@ public: virtual void Think(void) {return;} virtual void Reset(void) {return;} virtual void InitHUDData( void ) {} // called every time a server is connected to - }; struct HUDLIST { @@ -129,12 +130,15 @@ public: void _cdecl UserCmd_NextWeapon( void ); void _cdecl UserCmd_PrevWeapon( void ); + const WEAPON *GetCurWeapon(){ return m_pWeapon; } + private: float m_fFade; RGBA m_rgba; WEAPON *m_pWeapon; int m_HUD_bucket0; int m_HUD_selection; + int m_iAmmocount; }; @@ -259,6 +263,58 @@ protected: float *m_pflNameColors[MAX_STATUSBAR_LINES]; }; +class CHudScanlines : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float fTime); + void Reset( void ); +private: + int m_iScanlines; + int m_iScrolllines; + + float m_flScrollY; +}; + +class CCrosshairInfo : public CHudBase +{ +public: + int Init(); + int VidInit(); + int Draw(float fTime); + + void UpdateInfo(int index); +private: + float m_flCenterTime; + char m_szCenterName[64]; +}; + +enum enum_playerstate_e { + PS_NORMAL = 0, + PS_COMPOSURE, + PS_BLEEDING, + PS_WILLPOWER, +}; + +class CHudPlayerState : public CHudBase +{ +public: + int Init(); + int VidInit(); + int Draw(float fTime); + + int _cdecl MsgFunc_PlayerState(const char *pszName,int iSize,void *pbuf); + + int ActiveState(); +private: + int m_iActiveSprite; + + int m_HUD_bleeding; + int m_HUD_composure; + int m_HUD_willpower; +}; + // //----------------------------------------------------- // @@ -298,7 +354,7 @@ struct extra_player_info_t { short frags; short deaths; - short playerclass; + short alive; short teamnumber; char teamname[MAX_TEAM_NAME]; }; @@ -336,7 +392,9 @@ public: int MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ); private: - int m_HUD_d_skull; // sprite index of skull icon + int m_HUD_d_skull; // sprite index of skull icon + int m_HUD_d_headshot; // sprite index of headshot icon + int m_HUD_d_bleeding; // sprite index of bleeding icon }; // @@ -375,28 +433,6 @@ public: void EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ); }; -// -//----------------------------------------------------- -// -class CHudBattery: public CHudBase -{ -public: - int Init( void ); - int VidInit( void ); - int Draw(float flTime); - int MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ); - -private: - HSPRITE m_hSprite1; - HSPRITE m_hSprite2; - wrect_t *m_prc1; - wrect_t *m_prc2; - int m_iBat; - float m_fFade; - int m_iHeight; // width of the battery innards -}; - - // //----------------------------------------------------- // @@ -421,7 +457,7 @@ private: int m_iBat; int m_fOn; float m_fFade; - int m_iWidth; // width of the battery innards + int m_iWidth; // width of the innards }; // @@ -493,6 +529,22 @@ private: int m_HUD_title_half; }; +// +//----------------------------------------------------- +// +class CHudScope : public CHudBase +{ +private: + HSPRITE m_h320Scope; + HSPRITE m_h640Scope; + HSPRITE m_h1280Scope; +public: + int Init(); + int VidInit(); + void DrawScopePart(int frame,int x,int y,HSPRITE hSprite,wrect_t *rect); + int Draw(float flTime); +}; + // //----------------------------------------------------- // @@ -535,21 +587,16 @@ private: // //----------------------------------------------------- // - - - class CHud { private: HUDLIST *m_pHudList; HSPRITE m_hsprLogo; - int m_iLogo; client_sprite_t *m_pSpriteList; int m_iSpriteCount; int m_iSpriteCountAllRes; float m_flMouseSensitivity; int m_iConcussionEffect; - public: HSPRITE m_hsprCursor; @@ -561,7 +608,7 @@ public: int m_iKeyBits; int m_iHideHUDDisplay; int m_iFOV; - int m_Teamplay; + int m_Gamemode; int m_iRes; cvar_t *m_pCvarStealMouse; @@ -594,21 +641,24 @@ public: int GetSpriteIndex( const char *SpriteName ); // gets a sprite index, for use in the m_rghSprites[] array - CHudAmmo m_Ammo; - CHudHealth m_Health; - CHudGeiger m_Geiger; - CHudBattery m_Battery; - CHudTrain m_Train; - CHudFlashlight m_Flash; - CHudMessage m_Message; - CHudStatusBar m_StatusBar; - CHudDeathNotice m_DeathNotice; - CHudSayText m_SayText; - CHudMenu m_Menu; + CHudScanlines m_Scanlines; + CHudAmmo m_Ammo; + CHudHealth m_Health; + CHudGeiger m_Geiger; + CHudTrain m_Train; + CHudFlashlight m_Flash; + CHudMessage m_Message; + CHudStatusBar m_StatusBar; + CHudDeathNotice m_DeathNotice; + CHudSayText m_SayText; + CHudMenu m_Menu; CHudAmmoSecondary m_AmmoSecondary; - CHudTextMessage m_TextMessage; - CHudStatusIcons m_StatusIcons; - CHudSpectator m_Spectator; + CHudTextMessage m_TextMessage; + CHudScope m_Scope; + CHudStatusIcons m_StatusIcons; + CHudSpectator m_Spectator; + CCrosshairInfo m_CrosshairInfo; + CHudPlayerState m_PlayerState; void Init( void ); void VidInit( void ); @@ -622,7 +672,6 @@ public: // user messages int _cdecl MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ); int _cdecl MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ); - int _cdecl MsgFunc_Logo(const char *pszName, int iSize, void *pbuf); int _cdecl MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf); void _cdecl MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ); int _cdecl MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf); @@ -638,17 +687,15 @@ public: // sprite indexes int m_HUD_number_0; - void AddHudElem(CHudBase *p); float GetSensitivity(); - }; -class TeamFortressViewport; +class TheWastesViewport; extern CHud gHUD; -extern TeamFortressViewport *gViewPort; +extern TheWastesViewport *gViewPort; extern int g_iPlayerClass; extern int g_iTeamNumber; diff --git a/cl_dll/hud_msg.cpp b/cl_dll/hud_msg.cpp index ebe08ba..58a2ab6 100644 --- a/cl_dll/hud_msg.cpp +++ b/cl_dll/hud_msg.cpp @@ -23,9 +23,6 @@ #define MAX_CLIENTS 32 -extern BEAM *pBeam; -extern BEAM *pBeam2; - /// USER-DEFINED SERVER MESSAGE HANDLERS int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf ) @@ -62,16 +59,13 @@ void CHud :: MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) pList->p->InitHUDData(); pList = pList->pNext; } - - //Probably not a good place to put this. - pBeam = pBeam2 = NULL; } int CHud :: MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) { BEGIN_READ( pbuf, iSize ); - m_Teamplay = READ_BYTE(); + m_Gamemode = READ_BYTE(); return 1; } diff --git a/cl_dll/hud_redraw.cpp b/cl_dll/hud_redraw.cpp index 143817f..700e9f6 100644 --- a/cl_dll/hud_redraw.cpp +++ b/cl_dll/hud_redraw.cpp @@ -18,8 +18,10 @@ #include #include "hud.h" #include "cl_util.h" +#include "tw_common.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" #define MAX_LOGO_FRAMES 56 @@ -146,26 +148,7 @@ int CHud :: Redraw( float flTime, int intermission ) pList = pList->pNext; } - // are we in demo mode? do we need to draw the logo in the top corner? - if (m_iLogo) - { - int x, y, i; - - if (m_hsprLogo == 0) - m_hsprLogo = LoadSprite("sprites/%d_logo.spr"); - - SPR_Set(m_hsprLogo, 250, 250, 250 ); - - x = SPR_Width(m_hsprLogo, 0); - x = ScreenWidth - x; - y = SPR_Height(m_hsprLogo, 0)/2; - - // Draw the logo at 20 fps - int iFrame = (int)(flTime * 20) % MAX_LOGO_FRAMES; - i = grgLogoFrame[iFrame] - 1; - - SPR_DrawAdditive(i, x, y, NULL); - } +// m_Scope.Draw(flTime); /* if ( g_iVisibleMouse ) @@ -189,6 +172,15 @@ int CHud :: Redraw( float flTime, int intermission ) } */ +#ifdef TW_BETA + char szBanner[256]; + + sprintf(szBanner,TW_BANNER,__DATE__); + + gEngfuncs.pfnDrawSetTextColor(255,0,0); + DrawConsoleString((ScreenWidth/2)-(ConsoleStringLen(szBanner)/2),0,szBanner); +#endif + return 1; } diff --git a/cl_dll/hud_spectator.cpp b/cl_dll/hud_spectator.cpp index 0732183..1acd3d6 100644 --- a/cl_dll/hud_spectator.cpp +++ b/cl_dll/hud_spectator.cpp @@ -9,8 +9,11 @@ #include "cl_util.h" #include "cl_entity.h" #include "triangleapi.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" #include "hltv.h" +#include "tw_common.h" +#include "parsemsg.h" #include "pm_shared.h" #include "entity_types.h" @@ -242,7 +245,6 @@ int CHudSpectator::Draw(float flTime) } - // Only draw the icon names only if map mode is in Main Mode if ( m_iMainMode != MAIN_MAP_FREE ) return 1; @@ -253,7 +255,6 @@ int CHudSpectator::Draw(float flTime) // make sure we have player info gViewPort->GetAllPlayersInfo(); - // loop through all the players and draw additional infos to their sprites on the map for (int i = 0; i < MAX_PLAYERS; i++) { @@ -293,41 +294,142 @@ int CHudSpectator::Draw(float flTime) return 1; } - - -void CHudSpectator::DirectorEvent(unsigned char command, unsigned int firstObject, unsigned int secondObject, unsigned int flags) +void CHudSpectator::DirectorMessage( int iSize, void *pbuf ) { - switch ( command ) - { - case HLTV_ACTIVE : // we are connected to a proxy or listening to a multicast stream - // now we have to do some things clientside, since the proxy doesn't know our mod - g_iPlayerClass = 0; - g_iTeamNumber = 0; - iJumpSpectator = 0; - m_iMainMode = m_iInsetMode = 0; - m_iObserverTarget = m_lastPrimaryObject = m_lastSecondaryObject = 0; - m_flNextObserverInput = 0.0f; - memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); - SetModes(MAIN_CHASE_FREE, INSET_OFF); - ParseOverviewFile(); - LoadMapSprites(); - SetSpectatorStartPosition(); - break; + BEGIN_READ( pbuf, iSize ); - case HLTV_CAMERA : if ( g_iUser1 == OBS_DIRECTED ) - { - m_iObserverTarget = g_iUser2 = firstObject; - g_iUser3 = secondObject; - } + int cmd = READ_BYTE(); //read in what type of message it is - m_lastPrimaryObject = firstObject; - m_lastSecondaryObject = secondObject; - uiDirectorFlags = flags; - // gEngfuncs.Con_Printf("Director Camera: %i %i\n", firstObject, secondObject); - break; + switch ( cmd ) // director command byte + { + //sent by proxy + case DRC_CMD_START : // now we have to do some things clientside, since the proxy doesn't know our mod + { + g_iPlayerClass = 0; + g_iTeamNumber = 0; - default : gEngfuncs.Con_DPrintf("CHudSpectator::DirectorEvent: unknown director command.\n"); - } + // fake a InitHUD & ResetHUD message + gHUD.MsgFunc_InitHUD(NULL,0, NULL); + gHUD.MsgFunc_ResetHUD(NULL, 0, NULL); // put all init stuff in + } + break; + + //sent by client! + case DRC_CMD_EVENT : + { + m_lastPrimaryObject = READ_WORD(); + m_lastSecondaryObject = READ_WORD(); + // m_iObserverFlags = READ_LONG(); //TODO + + //This section below was sent by Martin, but it seems to be a CS bit of code. I commented it out, and director mode works fine. + + /* TODO + //this is where the magic happens! + //unfortunately we don't know what m_autoDirector is :/ + + if ( m_autoDirector->value ) + { + if ( (g_iUser2 != m_lastPrimaryObject) || (g_iUser3 != m_lastSecondaryObject) ) + V_ResetChaseCam(); + + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + + */ + + //I kept these two just in case + + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + + //some debug text + gEngfuncs.Con_Printf("Director Camera: %i %i\n", m_lastPrimaryObject,m_lastSecondaryObject); + } + break; + + case DRC_CMD_TIMESCALE : + { + float scale = READ_FLOAT(); // if timescale was changed by slow motion effect + gEngfuncs.Con_Printf("HLTV Timescale: %f\n", scale ); + } + break; + + case DRC_CMD_STATUS: + { + int slots = READ_LONG(); // total number of spectator slots + int numspecs = READ_LONG(); // total number of spectator + int relays = READ_WORD(); // total number of relay proxies + + // gViewPort->UpdateSpectatorPanel(); TODO //if you wanted to update a VGUI showing the number of spectators, do something here + gEngfuncs.Con_Printf("HLTV Status: %i / %i spectators, %i relays \n", numspecs, slots, relays ); + + } + break; + + case DRC_CMD_MESSAGE: + { + + //This message is from the "msg" command on the HLTV console, along with random "You are watching HLTV..." messages that I kept getting. + + //The first 4 bytes are color, and 29 and on are the string. The rest I was too lazy to figure out. + + char data[64]; + memset( data, 0, 64 ); + int i = 4; + + int color = READ_LONG(); + + + while( i < 29 ) // we dont know yet what the format of this data is. it represents duration, x and y positions on the screen + { + data[i] = READ_BYTE(); + i++; + } + + //29++ is the string + char *str = READ_STRING(); + + gEngfuncs.Con_Printf( "HLTV Message: %s \n", str ); + } + break; + + //below are some other messages that don't seem to be implemented. ( at least not by me ) + + case DRC_CMD_SOUND: + { + gEngfuncs.Con_Printf( "HLTV Sound: \n" ); + } + break; + + case DRC_CMD_FADE: + { + gEngfuncs.Con_Printf( "HLTV Fade: \n" ); + } + break; + + case DRC_CMD_SHAKE: + { + gEngfuncs.Con_Printf( "HLTV Shake!: \n" ); + } + break; + + case DRC_CMD_BANNER: + { + // gEngfuncs.Con_DPrintf("GUI: Banner %s\n",READ_STRING() ); + // name of banner tga eg gfx/temp/7454562234563475.tga + + //not yet supported + + //gViewPort->m_pSpectatorPanel->m_TopBanner->LoadImage( READ_STRING() ); + //gViewPort->UpdateSpectatorPanel(); + } + break; + + default: + gEngfuncs.Con_DPrintf("CHudSpectator::DirectorMessage: unknown command %i.\n", cmd ); + break; + } } void CHudSpectator::FindNextPlayer(bool bReverse) @@ -472,6 +574,7 @@ void CHudSpectator::HandleButtonsUp( int ButtonPressed ) if ( ButtonPressed & (IN_MOVELEFT | IN_MOVERIGHT) ) m_moveDelta = 0.0f; } + void CHudSpectator::SetModes(int iNewMainMode, int iNewInsetMode) { char string[32]; @@ -576,7 +679,6 @@ void CHudSpectator::SetModes(int iNewMainMode, int iNewInsetMode) SetModes( m_iMainMode, INSET_OFF ); } gViewPort->UpdateSpectatorMenu(); - } bool CHudSpectator::IsActivePlayer(cl_entity_t * ent) @@ -617,6 +719,8 @@ bool CHudSpectator::ParseOverviewFile( ) sprintf(filename, "overviews/%s.txt", levelname ); + strcpy( m_OverviewData.map, levelname ); + pfile = (char *)gEngfuncs.COM_LoadFile( filename, 5, NULL); if (!pfile) @@ -918,7 +1022,7 @@ void CHudSpectator::DrawOverviewEntities() z = m_OverviewData.layersHeights[0] * zScale; // get yellow/brown HUD color - UnpackRGB(ir,ig,ib, RGB_YELLOWISH); + UnpackRGB(ir,ig,ib, RGB_WHITE); r = (float)ir/255.0f; g = (float)ig/255.0f; b = (float)ib/255.0f; @@ -1181,3 +1285,51 @@ bool CHudSpectator::AddOverviewEntityToList(HSPRITE sprite, cl_entity_t *ent, do return false; // maximum overview entities reached } + +void CHudSpectator::Reset() +{ + // Reset HUD + if ( strcmp( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ) ) + { + // update level overview if level changed + ParseOverviewFile(); + LoadMapSprites(); + } + + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + + SetSpectatorStartPosition(); +} + +void CHudSpectator::InitHUDData() +{ + m_lastPrimaryObject = m_lastSecondaryObject = 0; + m_flNextObserverInput = 0.0f; + // m_lastHudMessage = 0; //? + // m_iSpectatorNumber = 0; //? + iJumpSpectator = 0; + g_iUser1 = g_iUser2 = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + + /* // this block was in martin's code - not clear what m_autoDirector is. + + if ( gEngfuncs.IsSpectateOnly() || gEngfuncs.pDemoAPI->IsPlayingback() ) + m_autoDirector->value = 1.0f; + else + m_autoDirector->value = 0.0f; + +*/ + + Reset(); + + SetModes( OBS_CHASE_FREE, INSET_OFF ); + CenterPrint(""); // Prevent stupid "Free Chase-Camera" on startup - gage + + g_iUser2 = 0; // fake not target until first camera command + + // reset HUD FOV + gHUD.m_iFOV = CVAR_GET_FLOAT("default_fov"); +} + diff --git a/cl_dll/hud_spectator.h b/cl_dll/hud_spectator.h index 1c994a9..027f3d3 100644 --- a/cl_dll/hud_spectator.h +++ b/cl_dll/hud_spectator.h @@ -43,6 +43,7 @@ typedef struct overviewInfo_s { int insetWindowY; int insetWindowHeight; int insetWindowWidth; + char map[255]; // holds the name of the map we're on } overviewInfo_t; typedef struct overviewEntity_s { @@ -72,7 +73,9 @@ public: void HandleButtonsDown(int ButtonPressed); void HandleButtonsUp(int ButtonPressed); void FindNextPlayer( bool bReverse ); - void DirectorEvent(unsigned char command, unsigned int firstObject, unsigned int secondObject, unsigned int flags); + void DirectorMessage( int iSize, void *pbuf ); + void InitHUDData( void ); + void Reset( void ); void SetSpectatorStartPosition(); int Init(); int VidInit(); diff --git a/cl_dll/in_camera.cpp b/cl_dll/in_camera.cpp index 98bd35c..d32365d 100644 --- a/cl_dll/in_camera.cpp +++ b/cl_dll/in_camera.cpp @@ -428,7 +428,7 @@ void CAM_ToThirdPerson(void) vec3_t viewangles; #if !defined( _DEBUG ) - if ( gEngfuncs.GetMaxClients() > 1 ) + if ( gEngfuncs.GetMaxClients() > 1 && !CVAR_GET_FLOAT("sv_cheats")) { // no thirdperson in multiplayer. return; diff --git a/cl_dll/input.cpp b/cl_dll/input.cpp index 79ef0fd..48d67c2 100644 --- a/cl_dll/input.cpp +++ b/cl_dll/input.cpp @@ -27,7 +27,8 @@ extern "C" #include #include -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" extern "C" @@ -77,6 +78,7 @@ cvar_t *cl_yawspeed; cvar_t *cl_pitchspeed; cvar_t *cl_anglespeedkey; cvar_t *cl_vsmoothing; + /* =============================================================================== @@ -116,11 +118,11 @@ kbutton_t in_use; kbutton_t in_jump; kbutton_t in_attack; kbutton_t in_attack2; +kbutton_t in_special; kbutton_t in_up; kbutton_t in_down; kbutton_t in_duck; kbutton_t in_reload; -kbutton_t in_alt1; kbutton_t in_score; kbutton_t in_break; kbutton_t in_graph; // Display the netgraph @@ -455,13 +457,9 @@ void IN_SpeedUp(void) {KeyUp(&in_speed);} void IN_StrafeDown(void) {KeyDown(&in_strafe);} void IN_StrafeUp(void) {KeyUp(&in_strafe);} -// needs capture by hud/vgui also -extern void __CmdFunc_InputPlayerSpecial(void); - void IN_Attack2Down(void) { KeyDown(&in_attack2); - __CmdFunc_InputPlayerSpecial(); gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK2 ); } @@ -484,8 +482,6 @@ void IN_DuckDown(void) void IN_DuckUp(void) {KeyUp(&in_duck);} void IN_ReloadDown(void) {KeyDown(&in_reload);} void IN_ReloadUp(void) {KeyUp(&in_reload);} -void IN_Alt1Down(void) {KeyDown(&in_alt1);} -void IN_Alt1Up(void) {KeyUp(&in_alt1);} void IN_GraphDown(void) {KeyDown(&in_graph);} void IN_GraphUp(void) {KeyUp(&in_graph);} @@ -501,6 +497,16 @@ void IN_AttackUp(void) in_cancel = 0; } +void IN_SpecialDown( void ) +{ + KeyDown( &in_special ); +} + +void IN_SpecialUp( void ) +{ + KeyUp( &in_special ); +} + // Special handling void IN_Cancel(void) { @@ -788,6 +794,11 @@ int CL_ButtonBits( int bResetState ) { int bits = 0; + if( in_special.state & 3 ) + { + bits |= IN_SPECIAL; + } + if ( in_attack.state & 3 ) { bits |= IN_ATTACK; @@ -853,11 +864,6 @@ int CL_ButtonBits( int bResetState ) bits |= IN_RELOAD; } - if (in_alt1.state & 3) - { - bits |= IN_ALT1; - } - if ( in_score.state & 3 ) { bits |= IN_SCORE; @@ -871,6 +877,7 @@ int CL_ButtonBits( int bResetState ) if ( bResetState ) { + in_special.state &= ~2; in_attack.state &= ~2; in_duck.state &= ~2; in_jump.state &= ~2; @@ -883,7 +890,6 @@ int CL_ButtonBits( int bResetState ) in_moveright.state &= ~2; in_attack2.state &= ~2; in_reload.state &= ~2; - in_alt1.state &= ~2; in_score.state &= ~2; } @@ -951,6 +957,8 @@ void InitInput (void) gEngfuncs.pfnAddCommand ("-attack", IN_AttackUp); gEngfuncs.pfnAddCommand ("+attack2", IN_Attack2Down); gEngfuncs.pfnAddCommand ("-attack2", IN_Attack2Up); + gEngfuncs.pfnAddCommand ("+special", IN_SpecialDown ); + gEngfuncs.pfnAddCommand ("-special", IN_SpecialUp ); gEngfuncs.pfnAddCommand ("+use", IN_UseDown); gEngfuncs.pfnAddCommand ("-use", IN_UseUp); gEngfuncs.pfnAddCommand ("+jump", IN_JumpDown); @@ -966,8 +974,6 @@ void InitInput (void) gEngfuncs.pfnAddCommand ("-duck", IN_DuckUp); gEngfuncs.pfnAddCommand ("+reload", IN_ReloadDown); gEngfuncs.pfnAddCommand ("-reload", IN_ReloadUp); - gEngfuncs.pfnAddCommand ("+alt1", IN_Alt1Down); - gEngfuncs.pfnAddCommand ("-alt1", IN_Alt1Up); gEngfuncs.pfnAddCommand ("+score", IN_ScoreDown); gEngfuncs.pfnAddCommand ("-score", IN_ScoreUp); gEngfuncs.pfnAddCommand ("+showscores", IN_ScoreDown); @@ -989,7 +995,6 @@ void InitInput (void) cl_movespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_movespeedkey", "0.3", 0 ); cl_pitchup = gEngfuncs.pfnRegisterVariable ( "cl_pitchup", "89", 0 ); cl_pitchdown = gEngfuncs.pfnRegisterVariable ( "cl_pitchdown", "89", 0 ); - cl_vsmoothing = gEngfuncs.pfnRegisterVariable ( "cl_vsmoothing", "0.05", FCVAR_ARCHIVE ); m_pitch = gEngfuncs.pfnRegisterVariable ( "m_pitch","0.022", FCVAR_ARCHIVE ); diff --git a/cl_dll/inputw32.cpp b/cl_dll/inputw32.cpp index b00bd4f..abb4f29 100644 --- a/cl_dll/inputw32.cpp +++ b/cl_dll/inputw32.cpp @@ -311,7 +311,8 @@ void IN_MouseMove ( float frametime, usercmd_t *cmd) //jjb - this disbles normal mouse control if the user is trying to // move the camera, or if the mouse cursor is visible or if we're in intermission - if ( !iMouseInUse && !g_iVisibleMouse && !gHUD.m_iIntermission ) + // GAGE -> if your dead you cant move your head dumbass :) + if ( !iMouseInUse && !g_iVisibleMouse && !gHUD.m_iIntermission) { GetCursorPos (¤t_pos); @@ -321,54 +322,57 @@ void IN_MouseMove ( float frametime, usercmd_t *cmd) mx_accum = 0; my_accum = 0; - if (m_filter->value) + if(gHUD.m_Health.m_iHealth) { - mouse_x = (mx + old_mouse_x) * 0.5; - mouse_y = (my + old_mouse_y) * 0.5; - } - else - { - mouse_x = mx; - mouse_y = my; - } - - old_mouse_x = mx; - old_mouse_y = my; - - if ( gHUD.GetSensitivity() != 0 ) - { - mouse_x *= gHUD.GetSensitivity(); - mouse_y *= gHUD.GetSensitivity(); - } - else - { - mouse_x *= sensitivity->value; - mouse_y *= sensitivity->value; - } - - // add mouse X/Y movement to cmd - if ( (in_strafe.state & 1) || (lookstrafe->value && (in_mlook.state & 1) )) - cmd->sidemove += m_side->value * mouse_x; - else - viewangles[YAW] -= m_yaw->value * mouse_x; - - if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) - { - viewangles[PITCH] += m_pitch->value * mouse_y; - if (viewangles[PITCH] > cl_pitchdown->value) - viewangles[PITCH] = cl_pitchdown->value; - if (viewangles[PITCH] < -cl_pitchup->value) - viewangles[PITCH] = -cl_pitchup->value; - } - else - { - if ((in_strafe.state & 1) && gEngfuncs.IsNoClipping() ) + if (m_filter->value) { - cmd->upmove -= m_forward->value * mouse_y; + mouse_x = (mx + old_mouse_x) * 0.5; + mouse_y = (my + old_mouse_y) * 0.5; } else { - cmd->forwardmove -= m_forward->value * mouse_y; + mouse_x = mx; + mouse_y = my; + } + + old_mouse_x = mx; + old_mouse_y = my; + + if ( gHUD.GetSensitivity() != 0 ) + { + mouse_x *= gHUD.GetSensitivity(); + mouse_y *= gHUD.GetSensitivity(); + } + else + { + mouse_x *= sensitivity->value; + mouse_y *= sensitivity->value; + } + + // add mouse X/Y movement to cmd + if ( (in_strafe.state & 1) || (lookstrafe->value && (in_mlook.state & 1) )) + cmd->sidemove += m_side->value * mouse_x; + else + viewangles[YAW] -= m_yaw->value * mouse_x; + + if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) + { + viewangles[PITCH] += m_pitch->value * mouse_y; + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + } + else + { + if ((in_strafe.state & 1) && gEngfuncs.IsNoClipping() ) + { + cmd->upmove -= m_forward->value * mouse_y; + } + else + { + cmd->forwardmove -= m_forward->value * mouse_y; + } } } diff --git a/cl_dll/menu.cpp b/cl_dll/menu.cpp index 7e5f6d6..5c45b94 100644 --- a/cl_dll/menu.cpp +++ b/cl_dll/menu.cpp @@ -24,7 +24,8 @@ #include #include -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" #define MAX_MENU_STRING 512 char g_szMenuString[MAX_MENU_STRING]; diff --git a/cl_dll/overview.cpp b/cl_dll/overview.cpp index 5d24816..d884f3d 100644 --- a/cl_dll/overview.cpp +++ b/cl_dll/overview.cpp @@ -9,7 +9,7 @@ #include "cl_util.h" #include "cl_entity.h" #include "triangleapi.h" -#include "vgui_TeamFortressViewport.h" +#include "vgui_TheWastesViewport.h" // these are included for the math functions #include "com_model.h" diff --git a/cl_dll/parsebsp.cpp b/cl_dll/parsebsp.cpp new file mode 100644 index 0000000..ff66e2d --- /dev/null +++ b/cl_dll/parsebsp.cpp @@ -0,0 +1,228 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// parsebsp.cpp -> parse bsp entity information client side +// some of this code was graciously accepted from DMC_Teleporters.cpp +// +#include "extdll.h" +#include "entity_state.h" +#include "pm_defs.h" +#include "pm_movevars.h" +#include "hud_iface.h" +#include "com_model.h" +#include "event_api.h" +#include "com_weapons.h" +#include "event_flags.h" +#include "dmc_bspfile.h" +#include "cl_util.h" +#include "ParseBspEnt.h" +#include "ParseBsp.h" + +#include +using namespace std; + +CParseBsp g_ParseBsp; + +CParseBsp::CParseBsp() +{ + memset( m_szCurrentLevel, 0, sizeof( m_szCurrentLevel ) ); +} + +int CParseBsp::CheckMap() +{ + if( stricmp( m_szCurrentLevel, gEngfuncs.pfnGetLevelName() ) != 0 ) + { + strcpy( m_szCurrentLevel, gEngfuncs.pfnGetLevelName() ); + + // Right now this is kind of a strong approach to read entities + // The entire .bsp must be read for each new entity we wish to read. + ParseBsp( "worldspawn", new CBspWorldspawn() ); + ParseBsp( "env_fog", new CBspEnvFog() ); + ParseBsp( "env_particlesystem", new CBspEnvParticleSystem() ); + + return 1; + } + + return 0; +} + +char *CParseBsp::ParseEntity( char *pBuf, int &error, CBspEntity *pEnt ) +{ + char key[256]; + char token[ 1024 ]; + int n; + + while (1) + { + // Parse key + pBuf = gEngfuncs.COM_ParseFile ( pBuf, token ); + if ( token[0] == '}' ) + break; + + // Ran out of input buffer? + if ( !pBuf ) + { + error = 1; + break; + } + + // Store off the key + strcpy ( key, token ); + + // Fix heynames with trailing spaces + n = strlen( key ); + while (n && key[n-1] == ' ') + { + key[n-1] = 0; + n--; + } + + // Parse value + pBuf = gEngfuncs.COM_ParseFile ( pBuf, token ); + + // Ran out of buffer? + if (!pBuf) + { + error = 1; + break; + } + + // Hit the end instead of a value? + if ( token[0] == '}' ) + { + error = 1; + break; + } + + if ( token[0] == '}' && token[1] == '(' ) + int k = 0; + + // Assign k/v pair + pEnt->SetKeyValue( key, token ); + } + + // Return what's left in the stream + return pBuf; +} + +char *CParseBsp::LoadEntityLump( char *pszFilename ) +{ + FILE *fp; + int i; + dheader_t header; + int size; + lump_t *curLump; + char *buffer = NULL; + + fp = fopen( pszFilename, "rb" ); + if( !fp ) + return NULL; + + // Read in the .bsp header + if ( fread(&header, sizeof(dheader_t), 1, fp) != 1 ) + { + gEngfuncs.Con_Printf("Dmc_LoadEntityLump: Could not read BSP header for map [%s].\n", pszFilename); + fclose(fp); + return NULL; + } + + // Check the version + i = header.version; + if ( i != 29 && i != 30) + { + fclose(fp); + gEngfuncs.Con_Printf("Dmc_LoadEntityLump: Map [%s] has incorrect BSP version (%i should be %i).\n", pszFilename, i, BSPVERSION); + return NULL; + } + + // Get entity lump + curLump = &header.lumps[ LUMP_ENTITIES ]; + // and entity lump size + size = curLump->filelen; + + // Jump to it + fseek( fp, curLump->fileofs, SEEK_SET ); + + // Allocate sufficient memmory + buffer = new char[ size + 1 ]; + if ( buffer == NULL ) + { + fclose(fp); + gEngfuncs.Con_Printf("Dmc_LoadEntityLump: Couldn't allocate %i bytes\n", size + 1 ); + return NULL; + } + + // Read in the entity lump + fread( buffer, size, 1, fp ); + + // Terminate the string + buffer[ size ] = '\0'; + + if ( fp ) + { + fclose(fp); + } + + return buffer; +} + +void CParseBsp::LumpPass( char *pBuf, char *pszClassname, CBspEntity *pEnt ) +{ + char szToken[ 1024 ]; + int error = 0; + + while( 1 ) + { + pBuf = gEngfuncs.COM_ParseFile( pBuf, szToken ); + if( pBuf == NULL ) + break; + + // Didn't find opening brace? + if( szToken[0] != '{' ) + { + gEngfuncs.Con_Printf( "CParseBsp::ParseBsp: found %s when expecting {\n", szToken[0] ); + break; + } + + pBuf = ParseEntity( pBuf, error, pEnt ); + if( stricmp( pszClassname, pEnt->szClassname ) == 0 ) + pEnt->AddEntity(); + + if( error ) + { + gEngfuncs.Con_Printf( "CParseBsp::ParseBsp: error parsing entities\n" ); + break; + } + } + + delete pEnt; +} + +void CParseBsp::ParseBsp( char *pszClassname, CBspEntity *pEnt ) +{ + char szFilename[256]; + char *pBuf; + + sprintf( szFilename, "%s/%s", gEngfuncs.pfnGetGameDirectory(), m_szCurrentLevel ); + + pBuf = LoadEntityLump( szFilename ); + if( pBuf != NULL ) + { + LumpPass( pBuf, pszClassname, pEnt ); + delete[] pBuf; + } +} + + diff --git a/cl_dll/saytext.cpp b/cl_dll/saytext.cpp index 15737dd..63daafb 100644 --- a/cl_dll/saytext.cpp +++ b/cl_dll/saytext.cpp @@ -25,7 +25,8 @@ #include #include -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" extern float *GetClientColor( int clientIndex ); diff --git a/cl_dll/studioevent.cpp b/cl_dll/studioevent.cpp new file mode 100644 index 0000000..719ad8a --- /dev/null +++ b/cl_dll/studioevent.cpp @@ -0,0 +1,321 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// studioevent.cpp -> Model Event code +// + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_types.h" +#include "studio_event.h" // def. of mstudioevent_t +#include "r_efx.h" +#include "event_api.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "twm.h" +#include "twmmanager.h" + +#define DLLEXPORT __declspec( dllexport ) + +extern "C" +{ +#if 0 + // THE ORIGINAL HUD_StudioEvent declaration is + void DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); + // But we want to modify the cl_entity_s* parameter. Since C doesnt have the concept of const, + // and to the best of my knowledge the HL code is still c, i Figured i'd remove it... if we + // have problems with studio events dont hesitate to point at this. + // Gage - July 02, 2002 +#else + void DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, struct cl_entity_s *entity ); +#endif +} + +void HUD_GetCurrentAmmoInfo(int &cur_ammo,int &max_ammo); + +extern int cam_thirdperson; +extern vec3_t v_origin,v_angles; + +/* +========================= +STUDIO_SmokePuff + +========================= +*/ +void STUDIO_SmokePuff(float *vecSrc,float scale,cl_entity_t *entity) +{ + // create the puff + TEMPENTITY *pTent = gEngfuncs.pEfxAPI->R_TempSprite(vecSrc, + Vector(0,0,5), + scale, + gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/smoke1.spr"), + kRenderTransAlpha, + 0, + 25, + 5, + FTENT_SPRANIMATE); + + // state settings + if(pTent != NULL) + { + pTent->clientIndex = entity->index; + + //pTent->entity.curstate.rendercolor = Color; + pTent->entity.curstate.framerate = 10; + pTent->entity.curstate.renderamt = 128; + } +} + +/* +========================= +STUDIO_EjectModel + +========================= +*/ +void STUDIO_EjectModel(int iEventId,const char *szEventOptions,struct cl_entity_s *entity,int count) +{ + char szShellname[64]; + int shell_index; + int attachment_num,velocity_type,sound_type,quality_check; + float scaleR,scaleU; + vec3_t shell_velocity; + vec3_t shell_origin; + vec3_t angles,forward,right,up; + vec3_t endpos; + + // Read options from event parameters + sscanf(szEventOptions,"%i,%i,%i,%i,%s",&attachment_num,&velocity_type,&sound_type,&quality_check,szShellname); + + // Finalize shellcase parameter (set high or low quality) + if(quality_check) + sprintf(szShellname,"%s%s",szShellname,CVAR_GET_FLOAT("cl_shellcase_quality") ? "_hi.mdl" : "_lo.mdl"); + +#if 0 + gEngfuncs.pfnCenterPrint(szShellname); +#endif + + // set up shell properties + shell_index = gEngfuncs.pEventAPI->EV_FindModelIndex(szShellname); + + if(shell_index == 0) + gEngfuncs.Con_DPrintf("STUDIO_EjectModel: invalid model %s\n",szShellname); + + // Get needed angles + VectorCopy(entity->angles,angles); + AngleVectors(angles,forward,right,up); + + for(int i = 0;i < count;i++) + { + + switch(velocity_type) + { + case 1: + // Pistols, Small arms ejection + scaleR = gEngfuncs.pfnRandomFloat( 50, 70 ); + scaleU = gEngfuncs.pfnRandomFloat( 100, 150 ); + + VectorClear(shell_origin); + break; + case 2: + // No velocity, magazine dropping + scaleR = gEngfuncs.pfnRandomFloat(0.0,0.0); + scaleU = gEngfuncs.pfnRandomFloat(0.0,0.0); + + // Randomize the location a bit, + // so events that call multiple times + // (such as ruger reload) have shellcases + // going everywhere + VectorClear(shell_origin); + + shell_origin[0] = gEngfuncs.pfnRandomFloat(-1.0f,6.0f); + shell_origin[1] = gEngfuncs.pfnRandomFloat(-1.0f,6.0f); + shell_origin[2] = gEngfuncs.pfnRandomFloat(-1.0f,6.0f); + break; + } + + for(int i = 0;i< 3;i++) + { + shell_velocity[i] = right[i] * scaleR + up[i] * scaleU + forward[i] * 25; + + // go by ent origin, not attachment + if(attachment_num == -1) + { + shell_origin[i] += entity->origin[i]; + + // Spawn a bit lower + if(i == 2) + shell_origin[i] -= 16; + } + else + shell_origin[i] += entity->attachment[attachment_num][i]; + } + + // Eject the shell + VectorClear( endpos ); + endpos[1] = angles[1]; + + // sound type, we do a + // switch in the unlikely event + // that these macros change on us + switch(sound_type) + { + case 0: sound_type = TE_BOUNCE_NULL; break; + case 1: sound_type = TE_BOUNCE_SHELL; break; + case 2: sound_type = TE_BOUNCE_SHOTSHELL; break; + } + + gEngfuncs.pEfxAPI->R_TempModel(shell_origin,shell_velocity,endpos,CVAR_GET_FLOAT("cl_shellcase_lifetime"),shell_index,sound_type); + + } +} + +void STUDIO_TwmMuzzleFlash(int iEventId,const char *szEventOptions,struct cl_entity_s *entity) +{ + CTwmModel *pTwm; + twm_clientinfo_t *clientinfo = NULL; + int attachment_num; + float fadetime; + + // sanity check + if(g_iNumMuzzleflashModels > 64) + { + gEngfuncs.Con_Printf("STUDIO_TwmMuzzleFlash: too many muzzle flashes!\n"); + return; + } + + g_iNumMuzzleflashModels++; + + // TODO: use szEventOptions for this stuff + pTwm = g_TwmManager.GetModelByName("models/muz_test.twm"); + attachment_num = 0; + fadetime = 2.5f; + + // end of array + clientinfo = &g_MuzzleflashModels[g_iNumMuzzleflashModels - 1]; + + // We go through the current muzzleflash + // array, if this entity has a + // muzzleflash for it, put in our new data + for(int i = 0;i < g_iNumMuzzleflashModels;i++) + { + twm_clientinfo_t *cur_info = &g_MuzzleflashModels[i]; + + if(cur_info->attached_ent == entity) + { + clientinfo = &g_MuzzleflashModels[i]; + g_iNumMuzzleflashModels--; // reset value + break; + } + } + + clientinfo->twm_info = &pTwm->twminfo; + clientinfo->attached_ent = entity; + clientinfo->attachment_num = attachment_num; + + clientinfo->fadetime = fadetime; + + // triAPI information + clientinfo->render_mode = kRenderTransAdd; + clientinfo->sprite_frame = 0; // TODO: make a random frame + clientinfo->brightness = gEngfuncs.pfnRandomFloat(0.8f,1.0f); + + // RGBA + clientinfo->color[0] = 1.0f; + clientinfo->color[1] = 1.0f; + clientinfo->color[2] = 1.0f; + clientinfo->color[3] = gEngfuncs.pfnRandomFloat(0.5f,1.0f); +} + +/* +========================= +HUD_StudioEvent + +The entity's studio model description indicated an event was +fired during this frame, handle the event by it's tag ( e.g., muzzleflash, sound ) +========================= +*/ +void DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, struct cl_entity_s *entity ) +{ + switch( event->event ) + { + + /******************* + * muzzle flashes * + *******************/ + case 5001: + // STUDIO_TwmMuzzleFlash(event->event,event->options,entity); + gEngfuncs.pEfxAPI->R_MuzzleFlash((float *)&entity->attachment[0], atoi( event->options) ); + break; + case 5011: + // STUDIO_TwmMuzzleFlash(event->event,event->options,entity); + gEngfuncs.pEfxAPI->R_MuzzleFlash((float *)&entity->attachment[1], atoi( event->options) ); + break; + case 5021: + // STUDIO_TwmMuzzleFlash(event->event,event->options,entity); + gEngfuncs.pEfxAPI->R_MuzzleFlash((float *)&entity->attachment[2], atoi( event->options) ); + break; + case 5031: + // STUDIO_TwmMuzzleFlash(event->event,event->options,entity); + gEngfuncs.pEfxAPI->R_MuzzleFlash((float *)&entity->attachment[3], atoi( event->options) ); + break; + + /******************* + * smoke puffs * + *******************/ + case 5041: + STUDIO_SmokePuff((float *)&entity->attachment[1],0.5f,entity); + break; + + /******************* + * sparks * + *******************/ + case 5002: + gEngfuncs.pEfxAPI->R_SparkEffect((float *)&entity->attachment[0], atoi( event->options), -100, 100 ); + break; + + /******************* + * sound * + *******************/ + case 5004: + gEngfuncs.pfnPlaySoundByNameAtLocation((char*)event->options,1.0,(float*)&entity->attachment[0]); + break; + + /******************* + * skin change * + *******************/ + case 5005: + entity->curstate.skin = atoi(event->options); + break; + + /******************* + * model ejection * + *******************/ + case 5006: + STUDIO_EjectModel(event->event,event->options,entity,1); + break; + // Ruger reload + case 5007: + STUDIO_EjectModel(event->event,event->options,entity,5 - gHUD.m_Ammo.GetCurWeapon()->iClip); + break; + // Sawed off reload + case 5008: + STUDIO_EjectModel(event->event,event->options,entity,2 - gHUD.m_Ammo.GetCurWeapon()->iClip); + break; + default: + break; + } +} \ No newline at end of file diff --git a/cl_dll/text_message.cpp b/cl_dll/text_message.cpp index cd30c25..390431d 100644 --- a/cl_dll/text_message.cpp +++ b/cl_dll/text_message.cpp @@ -26,7 +26,8 @@ #include #include "parsemsg.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" DECLARE_MESSAGE( m_TextMessage, TextMsg ); diff --git a/cl_dll/tf_defs.h b/cl_dll/tf_defs.h index 42a7963..b225ca5 100644 --- a/cl_dll/tf_defs.h +++ b/cl_dll/tf_defs.h @@ -82,7 +82,6 @@ #define IT_INVISIBILITY 524288 #define IT_INVULNERABILITY 1048576 -#define IT_SUIT 2097152 #define IT_QUAD 4194304 #define IT_HOOK 8388608 @@ -101,7 +100,6 @@ #define TFSTATE_INVINCIBLE 32 // Player has permanent Invincibility (Usually by GoalItem) #define TFSTATE_INVISIBLE 64 // Player has permanent Invisibility (Usually by GoalItem) #define TFSTATE_QUAD 128 // Player has permanent Quad Damage (Usually by GoalItem) -#define TFSTATE_RADSUIT 256 // Player has permanent Radsuit (Usually by GoalItem) #define TFSTATE_BURNING 512 // Is on fire #define TFSTATE_GRENTHROWING 1024 // is throwing a grenade #define TFSTATE_AIMING 2048 // is using the laser sight @@ -284,11 +282,6 @@ enum #define TF_SCAN_SOUND 162 // Scanner sounds on/off #define TF_SCAN_30 163 // Scan using 30 energy (2 cells) #define TF_SCAN_100 164 // Scan using 100 energy (5 cells) -#define TF_DETPACK_5 165 // Detpack set to 5 seconds -#define TF_DETPACK_20 166 // Detpack set to 20 seconds -#define TF_DETPACK_50 167 // Detpack set to 50 seconds -#define TF_DETPACK 168 // Detpack Pre-Impulse -#define TF_DETPACK_STOP 169 // Impulse to stop setting detpack #define TF_PB_DETONATE 170 // Detonate Pipebombs // Special skill @@ -309,10 +302,6 @@ enum // Select Medikit #define TF_MEDIKIT 176 -// Spy Impulses -#define TF_SPY_SPY 177 // On net, go invisible, on LAN, change skin/color -#define TF_SPY_DIE 178 // Feign Death - // Engineer Impulses #define TF_ENGINEER_BUILD 179 #define TF_ENGINEER_SANDBAG 180 @@ -357,9 +346,6 @@ enum // Yet MORE Admin Commands #define TF_ADMIN_LISTIPS 198 -// Silent Spy Feign -#define TF_SPY_SILENTDIE 199 - /*==================================================*/ /* Colors */ @@ -455,7 +441,6 @@ enum #define WEAP_INCENDIARY 16384 #define WEAP_ASSAULT_CANNON 32768 #define WEAP_LIGHTNING 65536 -#define WEAP_DETPACK 131072 #define WEAP_TRANQ 262144 #define WEAP_LASER 524288 // still room for 12 more weapons @@ -480,7 +465,6 @@ enum #define WEAPON_INCENDIARY (WEAPON_HOOK+14) #define WEAPON_ASSAULT_CANNON (WEAPON_HOOK+16) #define WEAPON_LIGHTNING (WEAPON_HOOK+17) -#define WEAPON_DETPACK (WEAPON_HOOK+18) #define WEAPON_TRANQ (WEAPON_HOOK+19) #define WEAPON_LASER (WEAPON_HOOK+20) #define WEAPON_PIPEBOMB_LAUNCHER (WEAPON_HOOK+21) @@ -512,13 +496,6 @@ enum // Spanner #define WEAP_SPANNER_REPAIR 10 -// Detpack -#define WEAP_DETPACK_DISARMTIME 3 // Time it takes to disarm a Detpack -#define WEAP_DETPACK_SETTIME 3 // Time it takes to set a Detpack -#define WEAP_DETPACK_SIZE 700 // Explosion Size -#define WEAP_DETPACK_GOAL_SIZE 1500 // Explosion Size for goal triggering -#define WEAP_DETPACK_BITS_NO 12 // Bits that detpack explodes into - // Tranquiliser Gun #define TRANQ_TIME 15 @@ -687,34 +664,6 @@ enum #define MAX_CONCUSSION_GRENS 3 #define MAX_CALTROP_CANS 3 -// Class Details for DEMOLITION MAN -#define PC_DEMOMAN_SKIN 1 -#define PC_DEMOMAN_MAXHEALTH 90 -#define PC_DEMOMAN_MAXSPEED 280 -#define PC_DEMOMAN_MAXSTRAFESPEED 280 -#define PC_DEMOMAN_MAXARMOR 120 -#define PC_DEMOMAN_INITARMOR 50 -#define PC_DEMOMAN_MAXARMORTYPE 0.6 -#define PC_DEMOMAN_INITARMORTYPE 0.6 -#define PC_DEMOMAN_ARMORCLASSES 31 // ALL -#define PC_DEMOMAN_INITARMORCLASS 0 -#define PC_DEMOMAN_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_GRENADE_LAUNCHER | WEAP_DETPACK -#define PC_DEMOMAN_MAXAMMO_SHOT 75 -#define PC_DEMOMAN_MAXAMMO_NAIL 50 -#define PC_DEMOMAN_MAXAMMO_CELL 50 -#define PC_DEMOMAN_MAXAMMO_ROCKET 50 -#define PC_DEMOMAN_MAXAMMO_DETPACK 1 -#define PC_DEMOMAN_INITAMMO_SHOT 30 -#define PC_DEMOMAN_INITAMMO_NAIL 0 -#define PC_DEMOMAN_INITAMMO_CELL 0 -#define PC_DEMOMAN_INITAMMO_ROCKET 20 -#define PC_DEMOMAN_INITAMMO_DETPACK 1 -#define PC_DEMOMAN_GRENADE_TYPE_1 GR_TYPE_NORMAL -#define PC_DEMOMAN_GRENADE_TYPE_2 GR_TYPE_MIRV -#define PC_DEMOMAN_GRENADE_INIT_1 4 -#define PC_DEMOMAN_GRENADE_INIT_2 4 -#define PC_DEMOMAN_TF_ITEMS 0 - // Class Details for COMBAT MEDIC #define PC_MEDIC_SKIN 3 #define PC_MEDIC_MAXHEALTH 90 @@ -891,7 +840,6 @@ enum // for complete descriptions. // Defines for Goal Activation types : goal_activation (in goals) #define TFGA_TOUCH 1 // Activated when touched -#define TFGA_TOUCH_DETPACK 2 // Activated when touched by a detpack explosion #define TFGA_REVERSE_AP 4 // Activated when AP details are _not_ met #define TFGA_SPANNER 8 // Activated when hit by an engineer's spanner #define TFGA_DROPTOGROUND 2048 // Drop to Ground when spawning @@ -1046,11 +994,9 @@ float already_chosen_map; #define DMSG_GREN_NAIL 9 #define DMSG_GREN_MIRV 10 #define DMSG_GREN_PIPE 11 -#define DMSG_DETPACK 12 #define DMSG_BIOWEAPON 13 #define DMSG_BIOWEAPON_ATT 14 #define DMSG_FLAME 15 -#define DMSG_DETPACK_DIS 16 #define DMSG_AXE 17 #define DMSG_SNIPERRIFLE 18 #define DMSG_AUTORIFLE 19 @@ -1107,24 +1053,11 @@ float already_chosen_map; #define MENU_DEFAULT 1 #define MENU_TEAM 2 -#define MENU_CLASS 3 #define MENU_MAPBRIEFING 4 #define MENU_INTRO 5 -#define MENU_CLASSHELP 6 -#define MENU_CLASSHELP2 7 #define MENU_REPEATHELP 8 +#define MENU_ITEMSELECTION 9 - - -#define MENU_SPY 12 -#define MENU_SPY_SKIN 13 -#define MENU_SPY_COLOR 14 -#define MENU_ENGINEER 15 -#define MENU_ENGINEER_FIX_DISPENSER 16 -#define MENU_ENGINEER_FIX_SENTRYGUN 17 -#define MENU_ENGINEER_FIX_MORTAR 18 -#define MENU_DISPENSER 19 -#define MENU_CLASS_CHANGE 20 #define MENU_TEAM_CHANGE 21 #define MENU_REFRESH_RATE 25 @@ -1142,8 +1075,6 @@ float already_chosen_map; #define TF_TIMER_REGENERATION 6 #define TF_TIMER_GRENPRIME 7 #define TF_TIMER_CELLREGENERATION 8 -#define TF_TIMER_DETPACKSET 9 -#define TF_TIMER_DETPACKDISARM 10 #define TF_TIMER_BUILD 11 #define TF_TIMER_CHECKBUILDDISTANCE 12 #define TF_TIMER_DISGUISE 13 @@ -1285,7 +1216,7 @@ extern cvar_t tfc_spam_penalty2;// incremental gag penalty (seconds) for each ti extern cvar_t tfc_spam_limit; // at this many points, gag the spammer extern cvar_t tfc_clanbattle, tfc_clanbattle_prematch, tfc_prematch, tfc_clanbattle_ceasefire, tfc_balance_teams, tfc_balance_scores; extern cvar_t tfc_clanbattle_locked, tfc_birthday, tfc_autokick_kills, tfc_fragscoring, tfc_autokick_time, tfc_adminpwd; -extern cvar_t weaponstay, footsteps, flashlight, aimcrosshair, falldamage, teamplay; +extern cvar_t weaponstay, flashlight, aimcrosshair, teamplay; /*==========================================================================*/ class CTFFlame : public CBaseMonster diff --git a/cl_dll/thewastes/hl_baseentity.cpp b/cl_dll/thewastes/hl_baseentity.cpp new file mode 100644 index 0000000..8ebbd6c --- /dev/null +++ b/cl_dll/thewastes/hl_baseentity.cpp @@ -0,0 +1,340 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ + +/* +========================== +This file contains "stubs" of class member implementations so that we can predict certain + weapons client side. From time to time you might find that you need to implement part of the + these functions. If so, cut it from here, paste it in hl_weapons.cpp or somewhere else and + add in the functionality you need. +========================== +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" + +// Globals used by game logic +const Vector g_vecZero = Vector( 0, 0, 0 ); +int gmsgWeapPickup = 0; +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) { } + +// CBaseEntity Stubs +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) { return 1; } +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) { return 1; } +CBaseEntity *CBaseEntity::GetNextTarget( void ) { return NULL; } +int CBaseEntity::Save( CSave &save ) { return 1; } +int CBaseEntity::Restore( CRestore &restore ) { return 1; } +void CBaseEntity::SetObjectCollisionBox( void ) { } +int CBaseEntity :: Intersects( CBaseEntity *pOther ) { return 0; } +void CBaseEntity :: MakeDormant( void ) { } +int CBaseEntity :: IsDormant( void ) { return 0; } +BOOL CBaseEntity :: IsInWorld( void ) { return TRUE; } +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) { return 0; } +int CBaseEntity :: DamageDecal( int bitsDamageType ) { return -1; } +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) { return NULL; } +void CBaseEntity::SUB_Remove( void ) { } + +// CBaseDelay Stubs +void CBaseDelay :: KeyValue( struct KeyValueData_s * ) { } +int CBaseDelay::Restore( class CRestore & ) { return 1; } +int CBaseDelay::Save( class CSave & ) { return 1; } + +// CBaseAnimating Stubs +int CBaseAnimating::Restore( class CRestore & ) { return 1; } +int CBaseAnimating::Save( class CSave & ) { return 1; } + +// DEBUG Stubs +edict_t *DBG_EntOfVars( const entvars_t *pev ) { return NULL; } +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage) { } + +// UTIL_* Stubs +void UTIL_PrecacheOther( const char *szClassname ) { } +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) { } +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) { } +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) { } +void UTIL_MakeVectors( const Vector &vecAngles ) { } +BOOL UTIL_IsValidEntity( edict_t *pent ) { return TRUE; } +void UTIL_SetOrigin( entvars_t *, const Vector &org ) { } +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) { return TRUE; } +void UTIL_LogPrintf(char *,...) { } +void UTIL_ClientPrintAll( int,char const *,char const *,char const *,char const *,char const *) { } +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) { } + +// CBaseToggle Stubs +int CBaseToggle::Restore( class CRestore & ) { return 1; } +int CBaseToggle::Save( class CSave & ) { return 1; } +void CBaseToggle :: KeyValue( struct KeyValueData_s * ) { } + +// CGrenade Stubs +void CGrenade::BounceSound( void ) { } +void CGrenade::Explode( Vector, Vector ) { } +void CGrenade::Explode( TraceResult *, int ) { } +void CGrenade::Killed( entvars_t *, int ) { } +void CGrenade::Spawn( void ) { } +CGrenade * CGrenade:: ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ){ return 0; } +CGrenade *CGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ){ return 0; } +void CGrenade::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ){ } + +void UTIL_Remove( CBaseEntity *pEntity ){ } +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ){ } +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ){ return 0;} + +Vector UTIL_VecToAngles( const Vector &vec ){ return 0; } +CSprite *CSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) { return 0; } +void CBeam::PointEntInit( const Vector &start, int endIndex ) { } +CBeam *CBeam::BeamCreate( const char *pSpriteName, int width ) { return NULL; } +void CSprite::Expand( float scaleSpeed, float fadeSpeed ) { } + +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) { return NULL; } +void CBaseMonster :: Eat ( float flFullDuration ) { } +BOOL CBaseMonster :: FShouldEat ( void ) { return TRUE; } +void CBaseMonster :: Listen ( void ) { } +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) { return 0.0; } +BOOL CBaseMonster :: FValidateHintType ( short sHint ) { return FALSE; } +void CBaseMonster :: Look ( int iDistance ) { } +int CBaseMonster :: ISoundMask ( void ) { return 0; } +CSound* CBaseMonster :: PBestSound ( void ) { return NULL; } +CSound* CBaseMonster :: PBestScent ( void ) { return NULL; } +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) { return 0.0; } +void CBaseMonster :: MonsterThink ( void ) { } +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { } +int CBaseMonster :: IgnoreConditions ( void ) { return 0; } +void CBaseMonster :: RouteClear ( void ) { } +void CBaseMonster :: RouteNew ( void ) { } +BOOL CBaseMonster :: FRouteClear ( void ) { return FALSE; } +BOOL CBaseMonster :: FRefreshRoute ( void ) { return 0; } +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +int ShouldSimplify( int routeType ) { return TRUE; } +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) { } +BOOL CBaseMonster :: FBecomeProne ( void ) { return TRUE; } +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) { return FALSE; } +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) { } +BOOL CBaseMonster :: FCanCheckAttacks ( void ) { return FALSE; } +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) { return 0; } +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) { } +BOOL CBaseMonster :: PopEnemy( ) { return FALSE; } +void CBaseMonster :: SetActivity ( Activity NewActivity ) { } +void CBaseMonster :: SetSequenceByName ( char *szSequence ) { } +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) { return 0; } +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) { return 0.0; } +void CBaseMonster :: AdvanceRoute ( float distance ) { } +int CBaseMonster :: RouteClassify( int iMoveFlag ) { return 0; } +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) { return FALSE; } +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) { } +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) { return FALSE; } +void CBaseMonster :: Move ( float flInterval ) { } +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) { return FALSE; } +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) { } +void CBaseMonster :: MonsterInit ( void ) { } +void CBaseMonster :: MonsterInitThink ( void ) { } +void CBaseMonster :: StartMonster ( void ) { } +void CBaseMonster :: MovementComplete( void ) { } +int CBaseMonster::TaskIsRunning( void ) { return 0; } +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) { return 0; } +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) { return NULL; } +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) { return FALSE; } +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } +float CBaseMonster::FlYawDiff ( void ) { return 0.0; } +float CBaseMonster::ChangeYaw ( int yawSpeed ) { return 0; } +float CBaseMonster::VecToYaw ( Vector vecDir ) { return 0.0; } +int CBaseAnimating :: LookupActivity ( int activity ) { return 0; } +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) { return 0; } +void CBaseMonster :: SetEyePosition ( void ) { } +int CBaseAnimating :: LookupSequence ( const char *label ) { return 0; } +void CBaseAnimating :: ResetSequenceInfo ( ) { } +BOOL CBaseAnimating :: GetSequenceFlags( ) { return FALSE; } +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) { } +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) { } +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) { return 0.0; } +void CBaseAnimating :: InitBoneControllers ( void ) { } +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) { return 0; } +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) { } +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) { } +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) { return -1; } +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) { } +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) { } +int CBaseAnimating :: GetBodygroup( int iGroup ) { return 0; } +Vector CBaseMonster :: GetGunPosition( void ) { return g_vecZero; } +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue) {} +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) { } +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) { } +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) { } +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) { return TRUE; } +int CBaseMonster :: FindHintNode ( void ) { return NO_NODE; } +void CBaseMonster::ReportAIState( void ) { } +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) { } +BOOL CBaseMonster :: FCheckAITrigger ( void ) { return FALSE; } +int CBaseMonster :: CanPlaySequence( BOOL fDisregardMonsterState, int interruptLevel ) { return FALSE; } +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) { return FALSE; } +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) { return g_vecZero; } +BOOL CBaseMonster :: FacingIdeal( void ) { return FALSE; } +BOOL CBaseMonster :: FCanActiveIdle ( void ) { return FALSE; } +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) { } +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) { } +void CBaseMonster::SentenceStop( void ) { } +void CBaseMonster::CorpseFallThink( void ) { } +void CBaseMonster :: MonsterInitDead( void ) { } +BOOL CBaseMonster :: BBoxFlat ( void ) { return TRUE; } +BOOL CBaseMonster :: GetEnemy ( void ) { return FALSE; } +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue) { } +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) { return NULL; } +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) { return FALSE; } +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } +void CBaseMonster::FadeMonster( void ) { } +void CBaseMonster :: GibMonster( void ) { } +BOOL CBaseMonster :: HasHumanGibs( void ) { return FALSE; } +BOOL CBaseMonster :: HasAlienGibs( void ) { return FALSE; } +Activity CBaseMonster :: GetDeathActivity ( void ) { return ACT_DIE_HEADSHOT; } +void CBaseMonster :: RunTask ( Task_t *pTask ) { } +void CBaseMonster :: StartTask ( Task_t *pTask ) { } +void CBaseMonster::BecomeDead( void ) {} +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib) {} +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) { return 0; } +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +int CBaseMonster::Restore( class CRestore & ) { return 1; } +int CBaseMonster::Save( class CSave & ) { return 1; } + +int TrainSpeed(int iSpeed, int iMax) { return 0; } + +CPlayerInventory::CPlayerInventory(){} +BOOL CPlayerInventory::SetInventory(int Slot,int Index){ return FALSE; } +void CPlayerInventory::SpawnInventory(CBasePlayer *pPlayer){} +void CPlayerInventory::SetRandom( BOOL b ){} +playeritemlist_t *CPlayerInventory::GetListForSlot( int Slot ){ return NULL; } + +void CBasePlayer :: DeathSound( void ) { } +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) { return 0; } +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue) {} +int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +void CBasePlayer::PackDeadPlayerItems( void ) { } +void CBasePlayer::RemoveAllItems( void ) { } +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) { } +void CBasePlayer::WaterMove() { } +BOOL CBasePlayer::IsOnLadder( void ) { return FALSE; } +void CBasePlayer::PlayerDeathThink(void) { } +void CBasePlayer::StartDeathCam( void ) { } +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) { } +void CBasePlayer::PlayerUse ( void ) { } +void CBasePlayer::Jump() { } +void CBasePlayer::Duck( ) { } +int CBasePlayer::Classify ( void ) { return 0; } +void CBasePlayer::PreThink(void) { } +void CBasePlayer::CheckTimeBasedDamage() { } +void CBasePlayer :: UpdatePlayerSound ( void ) { } +void CBasePlayer :: Precache( void ) { } +int CBasePlayer::Save( CSave &save ) { return 0; } +void CBasePlayer::RenewItems(void) { } +int CBasePlayer::Restore( CRestore &restore ) { return 0; } +void CBasePlayer::SelectNextItem( int iItem ) { } +BOOL CBasePlayer::HasWeapons( void ) { return FALSE; } +void CBasePlayer::SelectPrevItem( int iItem ) { } +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) { return NULL; } +BOOL CBasePlayer :: FlashlightIsOn( void ) { return FALSE; } +void CBasePlayer :: FlashlightTurnOn( void ) { } +void CBasePlayer :: FlashlightTurnOff( void ) { } +void CBasePlayer :: ForceClientDllUpdate( void ) { } +void CBasePlayer::ImpulseCommands( ) { } +void CBasePlayer::CheatImpulseCommands( int iImpulse ) { } +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) { return FALSE; } +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) { return FALSE; } +void CBasePlayer::ItemPreFrame() { } +void CBasePlayer::ItemPostFrame() { } +void CBasePlayer::PostThink(){ } +float CBasePlayer::flCalculateInventoryWeight(){ return 1; } +int CBasePlayer::AmmoInventory( int iAmmoIndex ) { return -1; } +int CBasePlayer::GetAmmoIndex(const char *psz) { return -1; } +void CBasePlayer::SendAmmoUpdate(void) { } +void CBasePlayer :: UpdateClientData( void ) { } +BOOL CBasePlayer :: FBecomeProne ( void ) { return TRUE; } +int CBasePlayer :: Illumination( void ) { return 0; } +void CBasePlayer :: EnableControl(BOOL fControl) { } +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) { return g_vecZero; } +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) { return g_vecZero; } +void CBasePlayer :: ResetAutoaim( ) { } +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) { } +int CBasePlayer :: GetCustomDecalFrames( void ) { return -1; } +void CBasePlayer::DropPlayerItem ( char *pszItemName ) { } +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) { return FALSE; } +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) { return FALSE; } +Vector CBasePlayer :: GetGunPosition( void ) { return g_vecZero; } +const char *CBasePlayer::TeamID( void ) { return ""; } +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) { return 0; } +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) { } +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) { } + +void ClearMultiDamage(void) { } +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) { } +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) { } +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) { } +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) { return 0; } +void DecalGunshot( TraceResult *pTrace, int iBulletType ) { } +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) { } +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) { } +int CBasePlayerItem::Restore( class CRestore & ) { return 1; } +int CBasePlayerItem::Save( class CSave & ) { return 1; } +int CBasePlayerWeapon::Restore( class CRestore & ) { return 1; } +int CBasePlayerWeapon::Save( class CSave & ) { return 1; } +void CBasePlayerItem :: SetObjectCollisionBox( void ) { } +void CBasePlayerItem :: FallInit( void ) { } +void CBasePlayerItem::FallThink ( void ) { } +void CBasePlayerItem::Materialize( void ) { } +void CBasePlayerItem::AttemptToMaterialize( void ) { } +void CBasePlayerItem :: CheckRespawn ( void ) { } +CBaseEntity* CBasePlayerItem::Respawn( void ) { return NULL; } +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) { } +void CBasePlayerItem::DestroyItem( void ) { } +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) { return TRUE; } +void CBasePlayerItem::Drop( void ) { } +void CBasePlayerItem::Kill( void ) { } +void CBasePlayerItem::Holster( int skiplocal ) { } +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) { } +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) { return 0; } +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) { return FALSE; } +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) { return 0; } +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) { return TRUE; } +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) { return TRUE; } +BOOL CBasePlayerWeapon :: IsUseable( void ) { return TRUE; } +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) { return -1; } +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) { return -1; } +void CBasePlayerAmmo::Spawn( void ) { } +CBaseEntity* CBasePlayerAmmo::Respawn( void ) { return this; } +void CBasePlayerAmmo::Materialize( void ) { } +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) { } +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) { return 0; } +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) { return 0; } +void CBasePlayerWeapon::RetireWeapon( void ) { } +void CSoundEnt::InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) {} +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ){} diff --git a/cl_dll/thewastes/hl_events.cpp b/cl_dll/thewastes/hl_events.cpp new file mode 100644 index 0000000..96adf5b --- /dev/null +++ b/cl_dll/thewastes/hl_events.cpp @@ -0,0 +1,95 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "event_api.h" + +extern "C" +{ + // Events + void EV_BerettaShoot (struct event_args_s *args); + void EV_ColtShoot (struct event_args_s *args); + void EV_DeagleShoot (struct event_args_s *args); + void EV_RugerShoot (struct event_args_s *args); + void EV_HandcannonShoot(struct event_args_s *args); + void EV_SawedOffShoot (struct event_args_s *args); + void EV_MossbergShoot (struct event_args_s *args); + void EV_WinchesterShoot(struct event_args_s *args); + void EV_Smg9Shoot (struct event_args_s *args); + void EV_FnFalShoot (struct event_args_s *args); + void EV_TommyGunShoot (struct event_args_s *args); + void EV_JackHammerShoot(struct event_args_s *args); + void EV_G11Shoot (struct event_args_s *args); + void EV_BoltRifleShoot (struct event_args_s *args); + void EV_StenShoot (struct event_args_s *args); + void EV_MolotovCocktail(struct event_args_s *args); + void EV_FragGrenade (struct event_args_s *args); + void EV_Pipebomb (struct event_args_s *args); + void EV_CombatKnife (struct event_args_s *args); + void EV_BaseballBat (struct event_args_s *args); + void EV_SledgeHammer (struct event_args_s *args); + void EV_Katana (struct event_args_s *args); + void EV_Spear (struct event_args_s *args); + void EV_CattleProd (struct event_args_s *args); + void EV_AkimboBerettas (struct event_args_s *args); + void EV_AkimboColts (struct event_args_s *args); + void EV_AkimboDeagles (struct event_args_s *args); + void EV_AkimboSawedOffs(struct event_args_s *args); +} + +/* +====================== +Game_HookEvents + +Associate script file name with callback functions. Callback's must be extern "C" so + the engine doesn't get confused about name mangling stuff. Note that the format is + always the same. Of course, a clever mod team could actually embed parameters, behavior + into the actual .sc files and create a .sc file parser and hook their functionality through + that.. i.e., a scripting system. + +That was what we were going to do, but we ran out of time...oh well. +====================== +*/ +void Game_HookEvents( void ) +{ + gEngfuncs.pfnHookEvent( "events/beretta.sc", EV_BerettaShoot ); + gEngfuncs.pfnHookEvent( "events/colt.sc", EV_ColtShoot ); + gEngfuncs.pfnHookEvent( "events/deagle.sc", EV_DeagleShoot ); + gEngfuncs.pfnHookEvent( "events/ruger.sc", EV_RugerShoot ); + gEngfuncs.pfnHookEvent( "events/handcannon.sc", EV_HandcannonShoot ); + gEngfuncs.pfnHookEvent( "events/sawedoff.sc", EV_SawedOffShoot ); + gEngfuncs.pfnHookEvent( "events/mossberg.sc", EV_MossbergShoot ); + gEngfuncs.pfnHookEvent( "events/winchester.sc", EV_WinchesterShoot ); + gEngfuncs.pfnHookEvent( "events/smg9.sc", EV_Smg9Shoot ); + gEngfuncs.pfnHookEvent( "events/fnfal.sc", EV_FnFalShoot ); + gEngfuncs.pfnHookEvent( "events/tommygun.sc", EV_TommyGunShoot ); + gEngfuncs.pfnHookEvent( "events/jackhammer.sc", EV_JackHammerShoot ); + gEngfuncs.pfnHookEvent( "events/g11.sc", EV_G11Shoot ); + gEngfuncs.pfnHookEvent( "events/boltrifle.sc", EV_BoltRifleShoot ); + gEngfuncs.pfnHookEvent( "events/sten.sc", EV_StenShoot ); + gEngfuncs.pfnHookEvent( "events/molotovcocktail.sc", EV_MolotovCocktail ); + gEngfuncs.pfnHookEvent( "events/fraggrenade.sc", EV_FragGrenade ); + gEngfuncs.pfnHookEvent( "events/pipebomb.sc", EV_Pipebomb ); + gEngfuncs.pfnHookEvent( "events/combatknife.sc", EV_CombatKnife ); + gEngfuncs.pfnHookEvent( "events/baseballbat.sc", EV_BaseballBat ); + gEngfuncs.pfnHookEvent( "events/sledgehammer.sc", EV_SledgeHammer ); + gEngfuncs.pfnHookEvent( "events/katana.sc", EV_Katana ); + gEngfuncs.pfnHookEvent( "events/spear.sc", EV_Spear ); + gEngfuncs.pfnHookEvent( "events/cattleprod.sc", EV_CattleProd ); + gEngfuncs.pfnHookEvent( "events/akimboberettas.sc", EV_AkimboBerettas ); + gEngfuncs.pfnHookEvent( "events/akimbocolts.sc", EV_AkimboColts ); + gEngfuncs.pfnHookEvent( "events/akimbodeagles.sc", EV_AkimboDeagles ); + gEngfuncs.pfnHookEvent( "events/akimbosawedoffs.sc", EV_AkimboSawedOffs ); +} diff --git a/cl_dll/thewastes/hl_objects.cpp b/cl_dll/thewastes/hl_objects.cpp new file mode 100644 index 0000000..f9b1b7b --- /dev/null +++ b/cl_dll/thewastes/hl_objects.cpp @@ -0,0 +1,151 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "../demo.h" + +#include "demo_api.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "pm_defs.h" +#include "event_api.h" +#include "entity_types.h" +#include "r_efx.h" + +#include "twm.h" +#include "../twmmanager.h" + +#include "../ParseBspEnt.h" +#include "../ParseBsp.h" +#include "../tri.h" + +#include "../ParticleBase.h" + +extern cvar_t *cl_enablefog; + +int EnvFog_SetFog(); +void HUD_GetLastOrg( float *org ); + +void UpdateBeams ( void ) +{ + vec3_t forward, vecSrc, vecEnd, origin, angles, right, up; + vec3_t view_ofs; + pmtrace_t tr; + cl_entity_t *pthisplayer = gEngfuncs.GetLocalPlayer(); + int idx = pthisplayer->index; + + // Get our exact viewangles from engine + gEngfuncs.GetViewAngles( (float *)angles ); + + // Determine our last predicted origin + HUD_GetLastOrg( (float *)&origin ); + + AngleVectors( angles, forward, right, up ); + + VectorCopy( origin, vecSrc ); + + VectorMA( vecSrc, 2048, forward, vecEnd ); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +void Game_CrosshairInfo() +{ + vec3_t angles,origin,forward,right,up,src,dest; + pmtrace_t tr; + cl_entity_t *pLocalPlayer = gEngfuncs.GetLocalPlayer(); + int idx = pLocalPlayer->index; + + // Get player's origin and angle + gEngfuncs.GetViewAngles((float*)&angles); + HUD_GetLastOrg((float*)&origin); + AngleVectors(angles,forward,right,up); + + // Set up source and dest vectors + VectorCopy(origin,src); + VectorMA(src,128,forward,dest); + + // Trace ahead to find a player + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction(false,true); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers(idx - 1); + + gEngfuncs.pEventAPI->EV_SetTraceHull(2); + gEngfuncs.pEventAPI->EV_PlayerTrace(src,dest,PM_STUDIO_BOX,-1,&tr); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + // We hit something + if(tr.fraction != 1.0f) + gHUD.m_CrosshairInfo.UpdateInfo(tr.ent); +} + +/* +===================== +Game_AddObjects + +Add game specific, client-side objects here +===================== +*/ +void Game_AddObjects( void ) +{ +} + +/* +===================== +Game_UpdateObjects + +Update info needed +===================== +*/ +void Game_UpdateObjects(double frametime) +{ + // Update particles + g_ParticleSystemManager.Update( frametime ); + + // Update crosshair + Game_CrosshairInfo(); + + // Update 3d muzzleflashes + TWM_UPDATE_ARRAY(g_MuzzleflashModels,g_iNumMuzzleflashModels,frametime); + + // Update fog + if(!EnvFog_SetFog()) + { + R_SetFog(g_Worldspawn.flFogcolor_r, + g_Worldspawn.flFogcolor_g, + g_Worldspawn.flFogcolor_b, + g_Worldspawn.flFogcolor_start, + g_Worldspawn.flFogcolor_end, + cl_enablefog->value ? g_Worldspawn.iEnableFog : 0); + } +} diff --git a/cl_dll/thewastes/hl_weapons.cpp b/cl_dll/thewastes/hl_weapons.cpp new file mode 100644 index 0000000..243a81e --- /dev/null +++ b/cl_dll/thewastes/hl_weapons.cpp @@ -0,0 +1,1112 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "thewastes.h" + +#include "usercmd.h" +#include "entity_state.h" +#include "demo_api.h" +#include "pm_defs.h" +#include "event_api.h" +#include "r_efx.h" + +#include "../ParseBspEnt.h" +#include "../ParseBsp.h" +#include "../hud_iface.h" +#include "../com_weapons.h" +#include "../demo.h" + +#include "../ParticleBase.h" + +extern "C" +{ + void PM_CheckMap(char *pszMapname); +} + +extern globalvars_t *gpGlobals; + +// Pool of client side entities/entvars_t +static entvars_t ev[ 32 ]; +static int num_ents = 0; + +// The entity we'll use to represent the local client +static CBasePlayer player; + +// Local version of game .dll global variables ( time, etc. ) +static globalvars_t Globals; + +static CWasteWeapon *g_pWpns[ 32 ]; + +vec3_t previousorigin; + +extern int ev_thermal; +extern void V_DisableFade(); +extern void V_StopSway(); + +// Wasteland weapons +CBeretta g_Beretta; +CColt g_Colt; +CDesertEagle g_Deagle; +CRuger g_Ruger; +CHandcannon g_Handcannon; +CSawedOff g_SawedOff; +CMossberg g_Mossberg; +CWinchester g_Winchester; +CSmg9 g_Smg9; +CFnFal g_FnFal; +CTommyGun g_TommyGun; +CJackHammer g_JackHammer; +CG11 g_G11; +CBoltRifle g_BoltRifle; +CSten g_Sten; +CMolotovCocktail g_MolotovCocktail; +CFragGrenade g_FragGrenade; +CPipebomb g_Pipebomb; +CCombatKnife g_CombatKnife; +CThrowingKnife g_ThrowingKnife; +CBaseballBat g_BaseballBat; +CSledgeHammer g_SledgeHammer; +CKatana g_Katana; +CSpear g_Spear; +CCattleProd g_CattleProd; +CAkimboBerettas g_AkimboBerettas; +CAkimboColts g_AkimboColts; +CAkimboDeagles g_AkimboDeagles; +CAkimboSawedOffs g_AkimboSawedOffs; + +/* +====================== +AlertMessage + +Print debug messages to console +====================== +*/ +void AlertMessage( ALERT_TYPE atype, char *szFmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, szFmt); + vsprintf (string, szFmt,argptr); + va_end (argptr); + + gEngfuncs.Con_Printf( "cl: " ); + gEngfuncs.Con_Printf( string ); +} + +//Returns if it's multiplayer. +//Mostly used by the client side weapons. +bool bIsMultiplayer ( void ) +{ + return gEngfuncs.GetMaxClients() == 1 ? 0 : 1; +} +//Just loads a v_ model. +void LoadVModel ( char *szViewModel, CBasePlayer *m_pPlayer ) +{ + gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel ); +} + +/* +===================== +HUD_PrepEntity + +Links the raw entity to an entvars_s holder. If a player is passed in as the owner, then +we set up the m_pPlayer field. +===================== +*/ +void HUD_PrepEntity( CBaseEntity *pEntity, CBasePlayer *pWeaponOwner ) +{ + memset( &ev[ num_ents ], 0, sizeof( entvars_t ) ); + pEntity->pev = &ev[ num_ents++ ]; + + pEntity->Precache(); + pEntity->Spawn(); + + if ( pWeaponOwner ) + { + ItemInfo info; + + ((CBasePlayerWeapon *)pEntity)->m_pPlayer = pWeaponOwner; + + ((CBasePlayerWeapon *)pEntity)->GetItemInfo( &info ); + + g_pWpns[ info.iId ] = (CWasteWeapon *)pEntity; + } +} + +/* +===================== +HUD_GetCurrentAmmoInfo + +used in studioevent.cpp, +return cur weapon's +max ammo and current clip count +===================== +*/ +void HUD_GetCurrentAmmoInfo(int &cur_ammo,int &max_ammo) +{ + CBasePlayerWeapon *pWeap = (CBasePlayerWeapon*)player.m_pActiveItem; + ItemInfo *p = NULL; + + if(pWeap != NULL) + { + pWeap->GetItemInfo(p); + + max_ammo = p->iMaxClip; + cur_ammo = pWeap->m_iClip; + } +} + +/* +===================== +HUD_CanChangeWeapon + +Can the client change weapons ? +===================== +*/ +int HUD_CanChangeWeapon(int iId) +{ + if(player.m_flNextAttack > UTIL_WeaponTimeBase()) + return 0; + return 1; +} + +/* +===================== +CBaseEntity :: Killed + +If weapons code "kills" an entity, just set its effects to EF_NODRAW +===================== +*/ +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->effects |= EF_NODRAW; +} + +/* +===================== +CBasePlayerWeapon :: DefaultReload +===================== +*/ +BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay, int body ) +{ + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return FALSE; + + int j = min(iClipSize - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + if (j == 0) + return FALSE; + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim, UseDecrement(), body ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: CanDeploy +===================== +*/ +BOOL CBasePlayerWeapon :: CanDeploy( void ) +{ + BOOL bHasAmmo = 0; + + if ( !pszAmmo1() ) + { + // this weapon doesn't use ammo, can always deploy. + return TRUE; + } + + if ( pszAmmo1() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); + } + if ( pszAmmo2() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); + } + if (m_iClip > 0) + { + bHasAmmo |= 1; + } + if (!bHasAmmo) + { + return FALSE; + } + + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: DefaultDeploy + +===================== +*/ +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal, int body ) +{ + if ( !CanDeploy() ) + return FALSE; + + gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel ); + + SendWeaponAnim( iAnim, skiplocal, body ); + + m_pPlayer->m_flNextAttack = 0.5; + m_flTimeWeaponIdle = 1.0; + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: PlayEmptySound + +===================== +*/ +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { +// HUD_PlaySound( "weapons/357_cock1.wav", 0.8 ); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +/* +===================== +CBasePlayerWeapon :: ResetEmptySound + +===================== +*/ +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +/* +===================== +CBasePlayerWeapon::Holster + +Put away weapon +===================== +*/ +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pPlayer->pev->viewmodel = 0; +} + +/* +===================== +CBasePlayerWeapon::SendWeaponAnim + +Animate weapon model +===================== +*/ +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal, int body ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + HUD_SendWeaponAnim( iAnim, body, 0 ); +} + +/* +===================== +CBaseEntity::FireBulletsPlayer + +Only produces random numbers to match the server ones. +===================== +*/ +Vector CBaseEntity::FireBulletsPlayer ( CBaseEntity *pPlayerItem, ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker, int shared_rand,int penetration_type ) +{ + float x, y, z; + + for ( ULONG iShot = 1; iShot <= cShots; iShot++ ) + { + if ( pevAttacker == NULL ) + { + // get circular gaussian spread + do { + x = RANDOM_FLOAT(-0.5, 0.5) + RANDOM_FLOAT(-0.5, 0.5); + y = RANDOM_FLOAT(-0.5, 0.5) + RANDOM_FLOAT(-0.5, 0.5); + z = x*x+y*y; + } while (z > 1); + } + else + { + //Use player's random seed. + // get circular gaussian spread + x = UTIL_SharedRandomFloat( shared_rand + iShot, -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 1 + iShot ) , -0.5, 0.5 ); + y = UTIL_SharedRandomFloat( shared_rand + ( 2 + iShot ), -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 3 + iShot ), -0.5, 0.5 ); + z = x * x + y * y; + } + + } + + return Vector ( x * vecSpread.x, y * vecSpread.y, 0.0 ); +} + +/* +===================== +CBasePlayerWeapon::ItemPostFrame + +Handles weapon firing, reloading, etc. +===================== +*/ +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && (m_pPlayer->m_flNextAttack <= 0.0)) + { +#if 0 // FIXME, need ammo on client to make this work right + // complete the reload. + int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; +#else + m_iClip += 10; +#endif + m_fInReload = FALSE; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && (m_flNextSecondaryAttack <= 0.0)) + { + if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) + { + m_fFireOnEmpty = TRUE; + } + + SecondaryAttack(); + m_pPlayer->pev->button &= ~IN_ATTACK2; + } + else if ((m_pPlayer->pev->button & IN_ATTACK) && (m_flNextPrimaryAttack <= 0.0)) + { + if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) ) + { + m_fFireOnEmpty = TRUE; + } + + PrimaryAttack(); + } + else if ( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + Reload(); + } + else if ( !(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2) ) ) + { + // no fire buttons down + + m_fFireOnEmpty = FALSE; + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +/* +===================== +CBasePlayer::SelectItem + + Switch weapons +===================== +*/ +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + } +} + +/* +===================== +CBasePlayer::SelectLastItem + +===================== +*/ +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); +} + +/* +===================== +CBasePlayer::Killed + +===================== +*/ +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + // Holster weapon immediately, to allow it to cleanup + if ( m_pActiveItem ) + m_pActiveItem->Holster(); +} + +/* +===================== +CBasePlayer::Spawn + +===================== +*/ +void CBasePlayer::Spawn( void ) +{ + if (m_pActiveItem) + m_pActiveItem->Deploy( ); +} + +/* +===================== +UTIL_TraceLine + +Don't actually trace, but act like the trace didn't hit anything. +===================== +*/ +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + memset( ptr, 0, sizeof( *ptr ) ); + ptr->flFraction = 1.0; +} + +/* +===================== +UTIL_ParticleBox + +For debugging, draw a box around a player made out of particles +===================== +*/ +void UTIL_ParticleBox( CBasePlayer *player, float *mins, float *maxs, float life, unsigned char r, unsigned char g, unsigned char b ) +{ + int i; + vec3_t mmin, mmax; + + for ( i = 0; i < 3; i++ ) + { + mmin[ i ] = player->pev->origin[ i ] + mins[ i ]; + mmax[ i ] = player->pev->origin[ i ] + maxs[ i ]; + } + + gEngfuncs.pEfxAPI->R_ParticleBox( (float *)&mmin, (float *)&mmax, 5.0, 0, 255, 0 ); +} + +/* +===================== +UTIL_ParticleBoxes + +For debugging, draw boxes for other collidable players +===================== +*/ +void UTIL_ParticleBoxes( void ) +{ + int idx; + physent_t *pe; + cl_entity_t *player; + vec3_t mins, maxs; + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + player = gEngfuncs.GetLocalPlayer(); + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( player->index - 1 ); + + for ( idx = 1; idx < 100; idx++ ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( idx ); + if ( !pe ) + break; + + if ( pe->info >= 1 && pe->info <= gEngfuncs.GetMaxClients() ) + { + mins = pe->origin + pe->mins; + maxs = pe->origin + pe->maxs; + + gEngfuncs.pEfxAPI->R_ParticleBox( (float *)&mins, (float *)&maxs, 0, 0, 255, 2.0 ); + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +===================== +UTIL_ParticleLine + +For debugging, draw a line made out of particles +===================== +*/ +void UTIL_ParticleLine( CBasePlayer *player, float *start, float *end, float life, unsigned char r, unsigned char g, unsigned char b ) +{ + gEngfuncs.pEfxAPI->R_ParticleLine( start, end, r, g, b, life ); +} + +/* +===================== +CBasePlayerWeapon::PrintState + +For debugging, print out state variables to log file +===================== +*/ +void CBasePlayerWeapon::PrintState( void ) +{ + COM_Log( "c:\\hl.log", "%.4f ", gpGlobals->time ); + COM_Log( "c:\\hl.log", "%.4f ", m_pPlayer->m_flNextAttack ); + COM_Log( "c:\\hl.log", "%.4f ", m_flNextPrimaryAttack ); + COM_Log( "c:\\hl.log", "%.4f ", m_flTimeWeaponIdle - gpGlobals->time); + COM_Log( "c:\\hl.log", "%i ", m_iClip ); +} + +/* +===================== +HUD_InitClientWeapons + +Set up weapons, player and functions needed to run weapons code client-side. +===================== +*/ +void HUD_InitClientWeapons( void ) +{ + static int initialized = 0; + if ( initialized ) + return; + + initialized = 1; + + // Set up pointer ( dummy object ) + gpGlobals = &Globals; + + // Fill in current time ( probably not needed ) + gpGlobals->time = gEngfuncs.GetClientTime(); + + // Fake functions + g_engfuncs.pfnPrecacheModel = stub_PrecacheModel; + g_engfuncs.pfnPrecacheSound = stub_PrecacheSound; + g_engfuncs.pfnPrecacheEvent = stub_PrecacheEvent; + g_engfuncs.pfnNameForFunction = stub_NameForFunction; + g_engfuncs.pfnSetModel = stub_SetModel; + g_engfuncs.pfnSetClientMaxspeed = HUD_SetMaxSpeed; + + // Handled locally + g_engfuncs.pfnPlaybackEvent = HUD_PlaybackEvent; + g_engfuncs.pfnAlertMessage = AlertMessage; + + // Pass through to engine + g_engfuncs.pfnPrecacheEvent = gEngfuncs.pfnPrecacheEvent; + g_engfuncs.pfnRandomFloat = gEngfuncs.pfnRandomFloat; + g_engfuncs.pfnRandomLong = gEngfuncs.pfnRandomLong; + + // Allocate a slot for the local player + HUD_PrepEntity( &player , NULL ); + + // Slots for each wasteland weapon + HUD_PrepEntity( &g_Beretta, &player ); + HUD_PrepEntity( &g_Colt, &player ); + HUD_PrepEntity( &g_Deagle, &player ); + HUD_PrepEntity( &g_Ruger, &player ); + HUD_PrepEntity( &g_Handcannon, &player ); + HUD_PrepEntity( &g_SawedOff, &player ); + HUD_PrepEntity( &g_Mossberg, &player ); + HUD_PrepEntity( &g_Winchester, &player ); + HUD_PrepEntity( &g_Smg9, &player ); + HUD_PrepEntity( &g_FnFal, &player ); + HUD_PrepEntity( &g_TommyGun, &player ); + HUD_PrepEntity( &g_JackHammer, &player ); + HUD_PrepEntity( &g_G11, &player ); + HUD_PrepEntity( &g_BoltRifle, &player ); + HUD_PrepEntity( &g_Sten, &player ); + HUD_PrepEntity( &g_MolotovCocktail, &player ); + HUD_PrepEntity( &g_FragGrenade, &player ); + HUD_PrepEntity( &g_Pipebomb, &player ); + HUD_PrepEntity( &g_CombatKnife, &player ); + HUD_PrepEntity( &g_ThrowingKnife, &player ); + HUD_PrepEntity( &g_BaseballBat, &player ); + HUD_PrepEntity( &g_SledgeHammer, &player ); + HUD_PrepEntity( &g_Katana, &player ); + HUD_PrepEntity( &g_Spear, &player ); + HUD_PrepEntity( &g_CattleProd, &player ); + HUD_PrepEntity( &g_AkimboBerettas, &player ); + HUD_PrepEntity( &g_AkimboColts, &player ); + HUD_PrepEntity( &g_AkimboDeagles, &player ); + HUD_PrepEntity( &g_AkimboSawedOffs, &player ); +} + +/* +===================== +HUD_GetLastOrg + +Retruns the last position that we stored for egon beam endpoint. +===================== +*/ +void HUD_GetLastOrg( float *org ) +{ + int i; + + // Return last origin + for ( i = 0; i < 3; i++ ) + { + org[i] = previousorigin[i]; + } +} + +/* +===================== +HUD_SetLastOrg + +Remember our exact predicted origin so we can draw the egon to the right position. +===================== +*/ +void HUD_SetLastOrg( void ) +{ + int i; + + // Offset final origin by view_offset + for ( i = 0; i < 3; i++ ) + { + previousorigin[i] = g_finalstate->playerstate.origin[i] + g_finalstate->client.view_ofs[ i ]; + } +} + +/* +===================== +HUD_WeaponsPostThink + +Run Weapon firing code on client +===================== +*/ +void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cmd, double time, unsigned int random_seed ) +{ + int i; + int buttonsChanged; + CWasteWeapon *pWeapon = NULL; + CWasteWeapon *pCurrent; + weapon_data_t nulldata, *pfrom, *pto; + static int lasthealth; + + memset( &nulldata, 0, sizeof( nulldata ) ); + + HUD_InitClientWeapons(); + + // Get current clock + gpGlobals->time = time; + + // Set our weapon + switch (from->client.m_iId) + { + case WEAPON_BERETTA: pWeapon = &g_Beretta; break; + case WEAPON_COLT: pWeapon = &g_Colt; break; + case WEAPON_DEAGLE: pWeapon = &g_Deagle; break; + case WEAPON_RUGER: pWeapon = &g_Ruger; break; + case WEAPON_HANDCANNON: pWeapon = &g_Handcannon; break; + case WEAPON_SAWEDOFF: pWeapon = &g_SawedOff; break; + case WEAPON_MOSSBERG: pWeapon = &g_Mossberg; break; + case WEAPON_WINCHESTER: pWeapon = &g_Winchester; break; + case WEAPON_SMG9: pWeapon = &g_Smg9; break; + case WEAPON_FNFAL: pWeapon = &g_FnFal; break; + case WEAPON_TOMMYGUN: pWeapon = &g_TommyGun; break; + case WEAPON_JACKHAMMER: pWeapon = &g_JackHammer; break; + case WEAPON_G11: pWeapon = &g_G11; break; + case WEAPON_BOLTRIFLE: pWeapon = &g_BoltRifle; break; + case WEAPON_STEN: pWeapon = &g_Sten; break; + case WEAPON_MOLOTOVCOCKTAIL: pWeapon = &g_MolotovCocktail; break; + case WEAPON_FRAGGRENADE: pWeapon = &g_FragGrenade; break; + case WEAPON_PIPEBOMB: pWeapon = &g_Pipebomb; break; + case WEAPON_COMBATKNIFE: pWeapon = &g_CombatKnife; break; + case WEAPON_THROWINGKNIFE: pWeapon = &g_ThrowingKnife; break; + case WEAPON_BASEBALLBAT: pWeapon = &g_BaseballBat; break; + case WEAPON_SLEDGEHAMMER: pWeapon = &g_SledgeHammer; break; + case WEAPON_KATANA: pWeapon = &g_Katana; break; + case WEAPON_SPEAR: pWeapon = &g_Spear; break; + case WEAPON_CATTLEPROD: pWeapon = &g_CattleProd; break; + case WEAPON_AKIMBOBERETTAS: pWeapon = &g_AkimboBerettas; break; + case WEAPON_AKIMBOCOLTS: pWeapon = &g_AkimboColts; break; + case WEAPON_AKIMBODEAGLES: pWeapon = &g_AkimboDeagles; break; + case WEAPON_AKIMBOSAWEDOFFS: pWeapon = &g_AkimboSawedOffs; break; + } + + // Fill in data based on selected weapon + // FIXME, make this a method in each weapon? where you pass in an entity_state_t *? +// switch ( from->client.m_iId ) +// { +// } + + // Store pointer to our destination entity_state_t so we can get our origin, etc. from it + // for setting up events on the client + g_finalstate = to; + + // If we are running events/etc. go ahead and see if we + // managed to die between last frame and this one + // If so, run the appropriate player killed or spawn function + if ( g_runfuncs ) + { + if ( to->client.health <= 0 && lasthealth > 0 ) + { + player.Killed( NULL, 0); + } + else if ( to->client.health > 0 && lasthealth <= 0 ) + { + player.Spawn(); + } + + lasthealth = to->client.health; + } + + // We are not predicting the current weapon, just bow out here. + if ( !pWeapon ) + return; + + for ( i = 0; i < 32; i++ ) + { + pCurrent = g_pWpns[ i ]; + if ( !pCurrent ) + { + continue; + } + + pfrom = &from->weapondata[ i ]; + + pCurrent->m_fInReload = pfrom->m_fInReload; + pCurrent->m_fInSpecialReload = pfrom->m_fInSpecialReload; +// pCurrent->m_flPumpTime = pfrom->m_flPumpTime; + pCurrent->m_iClip = pfrom->m_iClip; + pCurrent->m_flNextPrimaryAttack = pfrom->m_flNextPrimaryAttack; + pCurrent->m_flNextSecondaryAttack = pfrom->m_flNextSecondaryAttack; + pCurrent->m_flTimeWeaponIdle = pfrom->m_flTimeWeaponIdle; + pCurrent->pev->fuser1 = pfrom->fuser1; + pCurrent->m_flStartThrow = pfrom->fuser2; + pCurrent->m_flReleaseThrow = pfrom->fuser3; + pCurrent->m_fInAttack = pfrom->iuser2; + + pCurrent->UnpackWeapon(pfrom); + + pCurrent->m_iSecondaryAmmoType = (int)from->client.vuser3[ 2 ]; + pCurrent->m_iPrimaryAmmoType = (int)from->client.vuser4[ 0 ]; + player.m_rgAmmo[ pCurrent->m_iPrimaryAmmoType ] = (int)from->client.vuser4[ 1 ]; + player.m_rgAmmo[ pCurrent->m_iSecondaryAmmoType ] = (int)from->client.vuser4[ 2 ]; + } + + // For random weapon events, use this seed to seed random # generator + player.random_seed = random_seed; + + // Get old buttons from previous state. + player.m_afButtonLast = from->playerstate.oldbuttons; + + // Which buttsons chave changed + buttonsChanged = (player.m_afButtonLast ^ cmd->buttons); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // The changed ones still down are "pressed" + player.m_afButtonPressed = buttonsChanged & cmd->buttons; + // The ones not down are "released" + player.m_afButtonReleased = buttonsChanged & (~cmd->buttons); + + // Set player variables that weapons code might check/alter + player.pev->button = cmd->buttons; + + player.pev->velocity = from->client.velocity; + player.pev->flags = from->client.flags; + + player.pev->deadflag = from->client.deadflag; + player.pev->waterlevel = from->client.waterlevel; + player.pev->maxspeed = from->client.maxspeed; + player.pev->fov = from->client.fov; + player.pev->weaponanim = from->client.weaponanim; + player.pev->viewmodel = from->client.viewmodel; + player.m_flNextAttack = from->client.m_flNextAttack; + + //Stores all our ammo info, so the client side weapons can use them. + player.ammo_9mm = (int)from->client.vuser1[0]; + player.ammo_357 = (int)from->client.vuser1[1]; + player.ammo_argrens = (int)from->client.vuser1[2]; + player.ammo_bolts = (int)from->client.ammo_nails; //is an int anyways... + player.ammo_buckshot = (int)from->client.ammo_shells; + player.ammo_uranium = (int)from->client.ammo_cells; + player.ammo_hornets = (int)from->client.vuser2[0]; + player.ammo_rockets = (int)from->client.ammo_rockets; + + // Point to current weapon object + if ( from->client.m_iId ) + { + player.m_pActiveItem = g_pWpns[ from->client.m_iId ]; + } + + // Don't go firing anything if we have died. + // Or if we don't have a weapon model deployed + if ( ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) && !CL_IsDead() && player.pev->viewmodel ) + { + if ( player.m_flNextAttack <= 0 ) + { + pWeapon->ItemPostFrame(); + } + } + + // Assume that we are not going to switch weapons + to->client.m_iId = from->client.m_iId; + + // Now see if we issued a changeweapon command ( and we're not dead ) + if ( cmd->weaponselect && ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) ) + { + // Switched to a different weapon? + if ( from->weapondata[ cmd->weaponselect ].m_iId == cmd->weaponselect ) + { + CBasePlayerWeapon *pNew = g_pWpns[ cmd->weaponselect ]; + if ( pNew && ( pNew != pWeapon ) ) + { + // Put away old weapon + if (player.m_pActiveItem) + player.m_pActiveItem->Holster( ); + + player.m_pLastItem = player.m_pActiveItem; + player.m_pActiveItem = pNew; + + // Deploy new weapon + if (player.m_pActiveItem) + { + player.m_pActiveItem->Deploy( ); + } + + // Update weapon id so we can predict things correctly. + to->client.m_iId = cmd->weaponselect; + } + } + } + + // Copy in results of prediction code + to->client.viewmodel = player.pev->viewmodel; + to->client.fov = player.pev->fov; + to->client.weaponanim = player.pev->weaponanim; + to->client.m_flNextAttack = player.m_flNextAttack; + to->client.maxspeed = player.pev->maxspeed; + + //HL Weapons + to->client.vuser1[0] = player.ammo_9mm; + to->client.vuser1[1] = player.ammo_357; + to->client.vuser1[2] = player.ammo_argrens; + + to->client.ammo_nails = player.ammo_bolts; + to->client.ammo_shells = player.ammo_buckshot; + to->client.ammo_cells = player.ammo_uranium; + to->client.vuser2[0] = player.ammo_hornets; + to->client.ammo_rockets = player.ammo_rockets; + + // Make sure that weapon animation matches what the game .dll is telling us + // over the wire ( fixes some animation glitches ) + if ( g_runfuncs && ( HUD_GetWeaponAnim() != to->client.weaponanim ) ) + { + int body = 2; + + // Force a fixed anim down to viewmodel + HUD_SendWeaponAnim( to->client.weaponanim, body, 1 ); + } + + for ( i = 0; i < 32; i++ ) + { + pCurrent = g_pWpns[ i ]; + + pto = &to->weapondata[ i ]; + + if ( !pCurrent ) + { + memset( pto, 0, sizeof( weapon_data_t ) ); + continue; + } + + // Update weapon_data_t + pCurrent->PackWeapon(pto); + + pto->m_fInReload = pCurrent->m_fInReload; + pto->m_fInSpecialReload = pCurrent->m_fInSpecialReload; +// pto->m_flPumpTime = pCurrent->m_flPumpTime; + pto->m_iClip = pCurrent->m_iClip; + pto->m_flNextPrimaryAttack = pCurrent->m_flNextPrimaryAttack; + pto->m_flNextSecondaryAttack = pCurrent->m_flNextSecondaryAttack; + pto->m_flTimeWeaponIdle = pCurrent->m_flTimeWeaponIdle; + pto->fuser1 = pCurrent->pev->fuser1; + pto->fuser2 = pCurrent->m_flStartThrow; + pto->fuser3 = pCurrent->m_flReleaseThrow; + pto->iuser2 = pCurrent->m_fInAttack; + + // Decrement weapon counters, server does this at same time ( during post think, after doing everything else ) + pto->m_flNextReload -= cmd->msec / 1000.0; + pto->m_fNextAimBonus -= cmd->msec / 1000.0; + pto->m_flNextPrimaryAttack -= cmd->msec / 1000.0; + pto->m_flNextSecondaryAttack -= cmd->msec / 1000.0; + pto->m_flTimeWeaponIdle -= cmd->msec / 1000.0; + pto->fuser1 -= cmd->msec / 1000.0; + + to->client.vuser3[2] = pCurrent->m_iSecondaryAmmoType; + to->client.vuser4[0] = pCurrent->m_iPrimaryAmmoType; + to->client.vuser4[1] = player.m_rgAmmo[ pCurrent->m_iPrimaryAmmoType ]; + to->client.vuser4[2] = player.m_rgAmmo[ pCurrent->m_iSecondaryAmmoType ]; + + if ( pto->m_fNextAimBonus < -1.0 ) + { + pto->m_fNextAimBonus = -1.0; + } + + if ( pto->m_flNextPrimaryAttack < -1.0 ) + { + pto->m_flNextPrimaryAttack = -1.0; + } + + if ( pto->m_flNextSecondaryAttack < -0.001 ) + { + pto->m_flNextSecondaryAttack = -0.001; + } + + if ( pto->m_flTimeWeaponIdle < -0.001 ) + { + pto->m_flTimeWeaponIdle = -0.001; + } + + if ( pto->m_flNextReload < -0.001 ) + { + pto->m_flNextReload = -0.001; + } + + if ( pto->fuser1 < -0.001 ) + { + pto->fuser1 = -0.001; + } + } + + // m_flNextAttack is now part of the weapons, but is part of the player instead + to->client.m_flNextAttack -= cmd->msec / 1000.0; + if ( to->client.m_flNextAttack < -0.001 ) + { + to->client.m_flNextAttack = -0.001; + } + + to->client.fuser2 -= cmd->msec / 1000.0; + if ( to->client.fuser2 < -0.001 ) + { + to->client.fuser2 = -0.001; + } + + to->client.fuser3 -= cmd->msec / 1000.0; + if ( to->client.fuser3 < -0.001 ) + { + to->client.fuser3 = -0.001; + } + + // Make sure we arent running thermal if we arent supposed to :) + if( player.m_pActiveItem != &g_G11 ) + { + if( ev_thermal ) + { + ev_thermal = 0; + V_DisableFade(); + V_StopSway(); + } + } + + // Store off the last position from the predicted state. + HUD_SetLastOrg(); + + // Wipe it so we can't use it after this frame + g_finalstate = NULL; +} + +/* +===================== +HUD_PostRunCmd + +Client calls this during prediction, after it has moved the player and updated any info changed into to-> +time is the current client clock based on prediction +cmd is the command that caused the movement, etc +runfuncs is 1 if this is the first time we've predicted this command. If so, sounds and effects should play, otherwise, they should +be ignored +===================== +*/ +void _DLLEXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ) +{ + g_runfuncs = runfuncs; + +#if defined( CLIENT_WEAPONS ) + if ( cl_lw && cl_lw->value ) + { + HUD_WeaponsPostThink( from, to, cmd, time, random_seed ); + } + else +#endif + { + to->client.fov = g_lastFOV; + } + + // Various client systems need to check the current level + g_ParticleSystemManager.CheckMap(); + g_ParseBsp.CheckMap(); + PM_CheckMap((char*)gEngfuncs.pfnGetLevelName()); + + // All games can use FOV state + g_lastFOV = to->client.fov; +} diff --git a/cl_dll/thewastes_hud.cpp b/cl_dll/thewastes_hud.cpp new file mode 100644 index 0000000..4414fc5 --- /dev/null +++ b/cl_dll/thewastes_hud.cpp @@ -0,0 +1,393 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// thewastes_hud.cpp +// +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" +#include + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "cl_entity.h" + +#include "tw_common.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" + +#define MAX_CLIENTS 32 + +/* +=================== +CrosshairInfo +=================== +*/ +int CCrosshairInfo::Init() +{ + m_iFlags |= HUD_ACTIVE; + gHUD.AddHudElem(this); + + m_flCenterTime = 0.0f; + + strcpy(m_szCenterName,""); + + return 1; +} + +int CCrosshairInfo::VidInit() +{ + return 1; +} + +// We do this client side now ;) +int CCrosshairInfo::Draw(float fTime) +{ + // print in middle of screen + // TODO: Disabled until i can fix the code from + // flagging the wrong entities +/* if(!gHUD.m_iIntermission && gHUD.m_flTime < m_flCenterTime) + { + int x,y; + + int width,height; + GetConsoleStringSize(m_szCenterName,&width,&height); + + y = ScreenHeight * 0.55f; + x = ScreenWidth/2 - width/2; + + DrawConsoleString(x,y,m_szCenterName); + } +*/ + return 1; +} + +void CCrosshairInfo::UpdateInfo(int index) +{ + cl_entity_t *pEnt = gEngfuncs.GetEntityByIndex(index); + + gViewPort->GetAllPlayersInfo(); + + if(pEnt == NULL) + return; + + // Player checks + if(!pEnt->player || pEnt == gEngfuncs.GetLocalPlayer()) + return; + + sprintf(m_szCenterName,"%s (%i%%)",g_PlayerInfoList[pEnt->index].name,pEnt->curstate.health); + m_flCenterTime = gHUD.m_flTime + 0.25f; +} + +/* +=================== +PlayerState +=================== +*/ +DECLARE_MESSAGE(m_PlayerState,PlayerState); + +int CHudPlayerState::Init() +{ + m_iFlags |= HUD_ACTIVE; + gHUD.AddHudElem(this); + + HOOK_MESSAGE(PlayerState); + + return 1; +} + +int CHudPlayerState::VidInit() +{ + m_iActiveSprite = 0; + + m_HUD_bleeding = gHUD.GetSpriteIndex("bleeding"); + m_HUD_composure = gHUD.GetSpriteIndex("composure"); + m_HUD_willpower = gHUD.GetSpriteIndex("willpower"); + + return 1; +} + +int CHudPlayerState::Draw(float fTime) +{ + if(gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH) + return 1; + + if(gHUD.m_Health.m_iHealth >= 100 || gHUD.m_Health.m_iHealth <= 0) + return 1; + + int r,g,b,x,y; + wrect_t *prc; + int cursprite; + + switch(m_iActiveSprite) + { + case 0: return 1; + case 1: cursprite = m_HUD_composure; break; + case 2: cursprite = m_HUD_bleeding; break; + case 3: cursprite = m_HUD_willpower; break; + } + + UnpackRGB(r,g,b,RGB_WHITE); + ScaleColors(r,g,b,225); + + prc = &gHUD.GetSpriteRect(cursprite); + + x = 5; + y = ScreenHeight / 2 - (prc->bottom - prc->top) / 2; + + // Draw sprite + SPR_Set(gHUD.GetSprite(cursprite),r,g,b); + SPR_DrawHoles(0,x,y,prc); + + return 1; +} + +int CHudPlayerState::MsgFunc_PlayerState(const char *pszName,int iSize,void *pbuf) +{ + BEGIN_READ(pbuf,iSize); + + m_iActiveSprite = READ_BYTE(); + + return 1; +} + +int CHudPlayerState::ActiveState() +{ + return m_iActiveSprite; +} + +/* +=================== +Scope +=================== +*/ +int CHudScope::Init() +{ + m_iFlags |= HUD_ACTIVE; + gHUD.AddHudElem(this); + + return 1; +} + +int CHudScope::VidInit() +{ + m_h320Scope = SPR_Load("sprites/320scope.spr"); + m_h640Scope = SPR_Load("sprites/640scope.spr"); + m_h1280Scope = SPR_Load("sprites/1280scope.spr"); + + return 1; +} + +void CHudScope::DrawScopePart(int frame,int x,int y,HSPRITE hSprite,wrect_t *rect) +{ + SPR_Set(hSprite,255,255,255); + SPR_DrawHoles(frame,x,y,rect); +} + +int CHudScope::Draw(float flTime) +{ + if(gHUD.m_iHideHUDDisplay & HIDEHUD_ALL) + return 1; + + // y Yes~ + return 1; + + int x,y; + int center_x,center_y; + wrect_t rect; + + x = y = 0; + center_x = ScreenWidth / 2; + center_y = ScreenHeight / 2; + + // We have a few scope sprites to choose from + // To match the best resolution + switch(ScreenWidth) + { + case 320: + // 320x240 + rect = gHUD.GetSpriteRect(gHUD.GetSpriteIndex("320scope")); + + DrawScopePart(0,0,0,m_h320Scope,&rect); + DrawScopePart(1,160,0,m_h320Scope,&rect); + DrawScopePart(1,0,120,m_h320Scope,&rect); + DrawScopePart(1,160,120,m_h320Scope,&rect); + break; + case 640: + case 800: + // 640x480 + // 800x600 + rect = gHUD.GetSpriteRect(gHUD.GetSpriteIndex("640scope")); + + x = center_x - 160*2; + y = center_y - 240; + DrawScopePart(0,x,y,m_h640Scope,&rect); + x += 160; + DrawScopePart(1,x,y,m_h640Scope,&rect); + x += 160; + DrawScopePart(2,x,y,m_h640Scope,&rect); + x += 160; + DrawScopePart(3,x,y,m_h640Scope,&rect); + + y += 240; + x = center_x - 160*2; + DrawScopePart(4,x,y,m_h640Scope,&rect); + x += 160; + DrawScopePart(5,x,y,m_h640Scope,&rect); + x += 160; + DrawScopePart(6,x,y,m_h640Scope,&rect); + x += 160; + DrawScopePart(7,x,y,m_h640Scope,&rect); + + // Fill blank areas + { + int r,g,b,a; + + r = g = b = 128; + a = 255; + ScaleColors(r, g, b, a ); + + FillRGBA(0,(ScreenHeight-480)/2,ScreenWidth/2-160*2,480,r,g,b,a); + FillRGBA(ScreenWidth-(ScreenWidth/2-160*2),(ScreenHeight-480)/2,ScreenWidth/2-160*2,480,r,g,b,a); + FillRGBA(0,0,ScreenWidth,(ScreenHeight-480)/2,r,g,b,a); + FillRGBA(0,ScreenHeight-((ScreenHeight-480)/2),ScreenWidth,(ScreenHeight-480)/2,r,g,b,a); + } + + break; + case 1280: + case 1600: + // 1280x960 + // 1600x1200 + rect = gHUD.GetSpriteRect(gHUD.GetSpriteIndex("1280scope")); + + x = 0; + DrawScopePart(0,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(1,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(2,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(3,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(4,x,y,m_h1280Scope,&rect); + y += 240; + x = 0; + DrawScopePart(5,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(6,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(7,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(8,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(9,x,y,m_h1280Scope,&rect); + y += 240; + x = 0; + DrawScopePart(10,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(11,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(12,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(13,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(14,x,y,m_h1280Scope,&rect); + y += 240; + x = 0; + DrawScopePart(15,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(16,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(17,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(18,x,y,m_h1280Scope,&rect); + x += 256; + DrawScopePart(19,x,y,m_h1280Scope,&rect); + break; + } + + return 1; +} + +/* +=================== +Scanlines +=================== +*/ +#define SCROLL_WIDTH ScreenWidth + +#include + +extern int ev_thermal; + +int CHudScanlines::Init() +{ + m_iFlags |= HUD_ACTIVE; + gHUD.AddHudElem(this); + + m_flScrollY = 0; + return 1; +} + +int CHudScanlines::VidInit() +{ + m_iScanlines = gHUD.GetSpriteIndex("scanlines"); + m_iScrolllines = gHUD.GetSpriteIndex("scrolllines"); + + return 1; +} + +int CHudScanlines::Draw(float fTime) +{ + m_flScrollY += (150*(ScreenHeight/640)) * gHUD.m_flTimeDelta; // always increment the scroll line + + // Dont draw unless we are in thermal vision + if(!ev_thermal || (gHUD.m_iHideHUDDisplay & HIDEHUD_ALL)) + return 1; + + int r,g,b,a; + wrect_t rect; + int iScanWidth,iScanHeight; + int x,y; + + UnpackRGB(r,g,b,RGB_REDISH); + a = 128; + ScaleColors(r,g,b,a); + + rect = gHUD.GetSpriteRect(m_iScanlines); + iScanWidth = rect.right - rect.left; + iScanHeight = rect.bottom - rect.top; + + SPR_Set(gHUD.GetSprite(m_iScanlines), r, g, b); + + // Tile across screen + for(y = 0;y < ScreenHeight;y += iScanHeight) + for(x = 0;x < ScreenWidth;x += iScanWidth) + SPR_DrawAdditive(0,x,y,&rect); + + // Sync scroll + if(m_flScrollY > ScreenHeight) + m_flScrollY = -(gHUD.GetSpriteRect(m_iScrolllines).bottom-gHUD.GetSpriteRect(m_iScrolllines).top); + + SPR_Set(gHUD.GetSprite(m_iScrolllines),r,g,b); + for(x = 0;x < ScreenWidth;x += gHUD.GetSpriteRect(m_iScrolllines).right-gHUD.GetSpriteRect(m_iScrolllines).left) + SPR_DrawAdditive(0,x,m_flScrollY,&gHUD.GetSpriteRect(m_iScrolllines)); + + return 1; +} + +void CHudScanlines::Reset() +{ +} diff --git a/cl_dll/train.cpp b/cl_dll/train.cpp index 0a4d604..29eb6d4 100644 --- a/cl_dll/train.cpp +++ b/cl_dll/train.cpp @@ -54,7 +54,7 @@ int CHudTrain::Draw(float fTime) { int r, g, b, x, y; - UnpackRGB(r,g,b, RGB_YELLOWISH); + UnpackRGB(r,g,b, RGB_WHITE); SPR_Set(m_hSprite, r, g, b ); // This should show up to the right and part way up the armor number diff --git a/cl_dll/tri.cpp b/cl_dll/tri.cpp index 8729035..e22d8e9 100644 --- a/cl_dll/tri.cpp +++ b/cl_dll/tri.cpp @@ -16,6 +16,22 @@ #include "entity_state.h" #include "cl_entity.h" #include "triangleapi.h" +#include "ref_params.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "event_api.h" +#include "r_efx.h" +#include "ParseBspEnt.h" +#include "ParseBsp.h" +#include + +#include "in_defs.h" +#include "twm.h" +#include "twmmanager.h" + +#include "../common/com_model.h" + +#include "ParticleBase.h" #define DLLEXPORT __declspec( dllexport ) @@ -25,71 +41,274 @@ extern "C" void DLLEXPORT HUD_DrawTransparentTriangles( void ); }; -//#define TEST_IT -#if defined( TEST_IT ) +// This should be all the muzzleflashes we need, +// assuming everyone on the server shoots akimbo weapons at the same time and you see it :) +twm_clientinfo_t g_MuzzleflashModels[64]; +int g_iNumMuzzleflashModels; /* ================= -Draw_Triangles +R_TwmModel -Example routine. Draws a sprite offset from the player origin. +Render a TWM model with +given origin and angles ================= */ -void Draw_Triangles( void ) +void R_TwmModel(twm_clientinfo_t *twm_clientinfo,vec3_t origin,vec3_t angles) { - cl_entity_t *player; - vec3_t org; + int i,k,j; + const twm_info_t *twm_info = twm_clientinfo->twm_info; + float *color = twm_clientinfo->color; + vec3_t forward,right,up; - // Load it up with some bogus data - player = gEngfuncs.GetLocalPlayer(); - if ( !player ) - return; + gEngfuncs.pTriAPI->RenderMode(twm_clientinfo->render_mode); + gEngfuncs.pTriAPI->CullFace(TRI_NONE); // TODO: Cull something? - org = player->origin; - - org.x += 50; - org.y += 50; - - if (gHUD.m_hsprCursor == 0) + // Render model by materialgroup + for(k = 0;k < twm_info->num_materialgroups;k++) { - char sz[256]; - sprintf( sz, "sprites/cursor.spr" ); - gHUD.m_hsprCursor = SPR_Load( sz ); + twm_materialgroup_t *cur_mat = &twm_info->materialgroup_lump[k]; + + gEngfuncs.pTriAPI->SpriteTexture((struct model_s*)gEngfuncs.GetSpritePointer(cur_mat->sprite_handle),twm_clientinfo->sprite_frame); + + // Render each triangle + for(i = 0;i < cur_mat->num_triangles;i++) + { + twm_triangle_t *cur_tri = &twm_info->triangle_lump[cur_mat->tris_indices[i]]; + + gEngfuncs.pTriAPI->Begin(TRI_TRIANGLES); + gEngfuncs.pTriAPI->Color4f(color[0],color[1],color[2],color[3]); + // Add vertex information + for(j = 0;j < 3;j++) + { + vec3_t vec; + + vec[0] = twm_info->vertex_lump[cur_tri->vert_indices[j]][0]; + vec[1] = twm_info->vertex_lump[cur_tri->vert_indices[j]][1]; + vec[2] = twm_info->vertex_lump[cur_tri->vert_indices[j]][2]; + + // Add in angles + VectorMA(vec,1,angles,vec); + + // Add in origin + // VectorAdd(origin,vec,vec); + + gEngfuncs.pTriAPI->Brightness(twm_clientinfo->brightness); + gEngfuncs.pTriAPI->TexCoord2f(cur_tri->u[j],cur_tri->v[j]); + gEngfuncs.pTriAPI->Vertex3fv(vec); + } + gEngfuncs.pTriAPI->End(); + } } - - if ( !gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *)gEngfuncs.GetSpritePointer( gHUD.m_hsprCursor ), 0 )) - { - return; - } - - // Create a triangle, sigh - gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); - gEngfuncs.pTriAPI->CullFace( TRI_NONE ); - gEngfuncs.pTriAPI->Begin( TRI_QUADS ); - // Overload p->color with index into tracer palette, p->packedColor with brightness - gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); - // UNDONE: This gouraud shading causes tracers to disappear on some cards (permedia2) - gEngfuncs.pTriAPI->Brightness( 1 ); - gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); - gEngfuncs.pTriAPI->Vertex3f( org.x, org.y, org.z ); - - gEngfuncs.pTriAPI->Brightness( 1 ); - gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); - gEngfuncs.pTriAPI->Vertex3f( org.x, org.y + 50, org.z ); - - gEngfuncs.pTriAPI->Brightness( 1 ); - gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); - gEngfuncs.pTriAPI->Vertex3f( org.x + 50, org.y + 50, org.z ); - - gEngfuncs.pTriAPI->Brightness( 1 ); - gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); - gEngfuncs.pTriAPI->Vertex3f( org.x + 50, org.y, org.z ); - - gEngfuncs.pTriAPI->End(); - gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); } -#endif +/* +================= +R_Billboard + +Render a quad that always faces the camera +================= +*/ +void R_Billboard(const struct model_s *sprite,int sprite_frame,int render_mode,float r,float g,float b,float alpha,float *origin,float *v_up,float *v_right,float scale) +{ + gEngfuncs.pTriAPI->RenderMode(render_mode); + gEngfuncs.pTriAPI->CullFace(TRI_NONE); + gEngfuncs.pTriAPI->SpriteTexture((struct model_s*)sprite,sprite_frame); + + // Render the sprite + gEngfuncs.pTriAPI->Begin(TRI_QUADS); + gEngfuncs.pTriAPI->Color4f(r,g,b,alpha); + + gEngfuncs.pTriAPI->Brightness(1); + gEngfuncs.pTriAPI->TexCoord2f(0,1); + gEngfuncs.pTriAPI->Vertex3f(origin[0]+((-v_right[0]+v_up[0])*scale),origin[1]+((-v_right[1]+v_up[1])*scale),origin[2]+((-v_right[2]+v_up[2])*scale)); + + gEngfuncs.pTriAPI->Brightness(1); + gEngfuncs.pTriAPI->TexCoord2f(0,0); + gEngfuncs.pTriAPI->Vertex3f(origin[0]+((v_right[0]+v_up[0])*scale),origin[1]+((v_right[1]+v_up[1])*scale),origin[2]+((v_right[2]+v_up[2])*scale)); + + gEngfuncs.pTriAPI->Brightness(1); + gEngfuncs.pTriAPI->TexCoord2f(1,0); + gEngfuncs.pTriAPI->Vertex3f(origin[0]+((v_right[0]-v_up[0])*scale),origin[1]+((v_right[1]-v_up[1])*scale),origin[2]+((v_right[2]-v_up[2])*scale)); + + gEngfuncs.pTriAPI->Brightness(1); + gEngfuncs.pTriAPI->TexCoord2f(1,1); + gEngfuncs.pTriAPI->Vertex3f(origin[0]+((-v_right[0]-v_up[0])*scale),origin[1]+((-v_right[1]-v_up[1])*scale),origin[2]+((-v_right[2]-v_up[2])*scale)); + gEngfuncs.pTriAPI->End(); + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); +} + +static cl_entity_t laser_beams[2]; // The beams :) +int g_iClientLasersEnabled[32]; + +extern int cam_thirdperson; +extern vec3_t v_origin,v_angles; + +/* +================= +R_LaserBeam + +Draw a laser beam +Parts contributed by Stuart Crouch +================= +*/ +void R_LaserBeam( cl_entity_t *player ) +{ + float end[ 3 ], start[ 3 ]; + int beamindex; + struct model_s *laserbeam; + vec3_t forward, up, right; + vec3_t origin, vecSrc, vecDest, angles; + pmtrace_t tr; + float dotscale; + int dotindex; + struct model_s *dotmodel; + vec3_t v_forward,v_up,v_right; + float beam_r,beam_g,beam_b; + float dot_r,dot_g,dot_b; + BEAM *pBeam1; + + // Load it up with some bogus data + //player = gEngfuncs.GetLocalPlayer(); + cl_entity_t *localplayer = gEngfuncs.GetLocalPlayer(); + + if ( !localplayer ) + return; // paranioa + if ( !player ) + return; + if ( !player->player ) + return; // Player isnt a player. + + // we cancel if its intermission time and + // trying to draw the local laserbeam + if(g_pparams->intermission && !cam_thirdperson && gEngfuncs.pEventAPI->EV_IsLocal(player->index-1)) + return; + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( true, true ); + gEngfuncs.pEventAPI->EV_PushPMStates(); + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( player->index -1 ); + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + if(!cam_thirdperson && gEngfuncs.pEventAPI->EV_IsLocal(player->index - 1)) + { + // First person starting points are + // different so they line up + cl_entity_t *pView = gEngfuncs.GetViewModel(); + + VectorCopy(pView->attachment[1],vecSrc); + VectorCopy(pView->angles,angles); + + // Use Attachment 3 on the view model to get the forward axis + VectorSubtract(pView->attachment[1],pView->attachment[2],forward); + VectorNormalize(forward); + VectorMA(vecSrc,8192,forward,vecDest); + } + else + { + // Third person starting points + VectorCopy( player->origin, vecSrc ); + VectorCopy( player->angles, angles ); + angles[ PITCH ] *= -3; + vecSrc[2] += 20; + + AngleVectors (angles, forward, right, up); + + // Draw from player model + for (int i = 0; i < 3; i++ ) + vecSrc[ i ] += 20 * forward[ i ]; + + VectorMA(vecSrc,8192,forward,vecDest); + } + + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecDest, PM_GLASS_IGNORE|PM_STUDIO_BOX, -1, &tr ); + + if(!cam_thirdperson && gEngfuncs.pEventAPI->EV_IsLocal(player->index - 1)) + { + // Draw from view models attachment + cl_entity_t *pView = gEngfuncs.GetViewModel(); + + if(pView != NULL) + { + // Attachment 1 + start[0] = pView->attachment[1].x; + start[1] = pView->attachment[1].y; + start[2] = pView->attachment[1].z; + } + } + else + { + // Draw from players attachment point + start [ 0 ] = player->attachment[1].x; + start [ 1 ] = player->attachment[1].y; + start [ 2 ] = player->attachment[1].z; + } + + end [ 0 ] = tr.endpos.x; + end [ 1 ] = tr.endpos.y; + end [ 2 ] = tr.endpos.z; + + laserbeam = gEngfuncs.CL_LoadModel( "sprites/laserbeam.spr", &beamindex ); + if ( !laserbeam ) + return; + + // Easter Egg ;D + time_t Time; + time(&Time); + + // Josh's Birthday, July 9th + if(localtime(&Time)->tm_mday == 9 && localtime(&Time)->tm_mon == 6) + { + beam_r = dot_r = 0.0; + beam_g = dot_g = 1.0; + beam_b = dot_b = 0.0; + } + else + { + // normal :) + beam_r = 1.0; + beam_g = 0.0; + beam_b = 0.0; + + dot_r = 1.0; + dot_g = 1.0; + dot_b = 1.0; + } + + pBeam1 = gEngfuncs.pEfxAPI->R_BeamPoints( start, end, beamindex, + 0.0001, 0.3, 0.0, 0.14, 5.0, 0.0, 1.0, beam_r,beam_g,beam_b ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + // Now to render a halo around the light beginning point. :) + dotscale = 4; // TODO: make like a lenticular halo and resize for distance? + + AngleVectors (v_angles, v_forward, v_right, v_up); + + dotmodel = gEngfuncs.CL_LoadModel( "sprites/hand_laser_dot.spr", &dotindex ); + if(dotmodel == NULL) + gEngfuncs.Con_DPrintf("Bogus laser sprite, check R_LaserBeam"); + + R_Billboard(dotmodel,0,kRenderTransAdd,dot_r,dot_g,dot_b,1.0,start,v_up,v_right,dotscale); +} + +/* +================= +R_SetFog + +Set current fog state +================= +*/ +void R_SetFog(float r,float g,float b,float start,float end,int enable) +{ + float color[3]; + + color[0] = r; + color[1] = g; + color[2] = b; + + gEngfuncs.pTriAPI->Fog(color,start,end,enable); +} /* ================= @@ -100,12 +319,10 @@ Non-transparent triangles-- add them here */ void DLLEXPORT HUD_DrawNormalTriangles( void ) { + // Render particle effects + g_ParticleSystemManager.RenderNormal(); gHUD.m_Spectator.DrawOverview(); - -#if defined( TEST_IT ) -// Draw_Triangles(); -#endif } /* @@ -117,8 +334,31 @@ Render any triangles with transparent rendermode needs here */ void DLLEXPORT HUD_DrawTransparentTriangles( void ) { + // Render particle effects + g_ParticleSystemManager.RenderTransparent(); -#if defined( TEST_IT ) -// Draw_Triangles(); -#endif + // Render laser beams + for(int i = 0;i < gEngfuncs.GetMaxClients();i++) + { + cl_entity_t *player = gEngfuncs.GetEntityByIndex(i); + if(player != NULL && g_iClientLasersEnabled[i]) + R_LaserBeam(player); + } + + // Render 3d muzzleflashes + for(i = 0;i < g_iNumMuzzleflashModels;i++) + { + twm_clientinfo_t *clientinfo = &g_MuzzleflashModels[i]; + + // TODO: Perhaps relink + // entities at the end of the + // array here, so we dont have + // dead links + if(clientinfo->dead) + continue; + + // Render it + cl_entity_t *attached_ent = clientinfo->attached_ent; + R_TwmModel(clientinfo,attached_ent->attachment[clientinfo->attachment_num],attached_ent->angles); + } } \ No newline at end of file diff --git a/cl_dll/tri.h b/cl_dll/tri.h new file mode 100644 index 0000000..f8e36ce --- /dev/null +++ b/cl_dll/tri.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#ifndef __TRI_H_ +#define __TRI_H_ + +// tri.cpp functions +void R_Billboard(const struct model_s *sprite,int sprite_frame,int render_mode,float r,float g,float b,float alpha,float *origin,float *v_up,float *v_right,float scale); +void R_LaserBeam( cl_entity_t *player ); +void R_SetFog(float r,float g,float b,float start,float end,int enable); + +#endif \ No newline at end of file diff --git a/cl_dll/tw_vgui.h b/cl_dll/tw_vgui.h new file mode 100644 index 0000000..bb74be0 --- /dev/null +++ b/cl_dll/tw_vgui.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#ifndef __TW_VGUI_H_ +#define __TW_VGUI_H_ + +// VGUI Color Scheme info + +// Panel Scheme +#define TW_PANEL_BG_RGB 64,64,64 +#define TW_PANEL_BG_RGBA TW_PANEL_BG_RGB ,128 +#define TW_PANEL_BORDER_RGBA 255,255,255,128 + +// CommandButton Scheme +#define TW_COMMANDBUTTON_BG_RGBA TW_PANEL_BG_RGBA +#define TW_COMMANDBUTTON_BORDER_RGBA TW_PANEL_BORDER_RGBA +#define TW_COMMANDBUTTON_ACTIVE_RGBA TW_PANEL_BORDER_RGBA + +// Scorepanel Scheme +#define TW_SCOREPANEL_HIGHLIGHT_TEXT_RGBA TW_PANEL_BG_RGB ,0 +#define TW_SCOREPANEL_HIGHLIGHT_COLOR_A 64 + +#endif \ No newline at end of file diff --git a/cl_dll/twm.cpp b/cl_dll/twm.cpp new file mode 100644 index 0000000..52aa38d --- /dev/null +++ b/cl_dll/twm.cpp @@ -0,0 +1,227 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// twm.cpp -> .TWM loading code +// +#include "hud.h" +#include "cl_util.h" + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "ref_params.h" +#include "event_api.h" +#include "r_efx.h" +#include "twm.h" +#include "twmmanager.h" + +CTwmManager g_TwmManager; + +/* + CTwmModel methods +*/ +CTwmModel::CTwmModel() +{ + twminfo.num_vertices = 0; + twminfo.vertex_lump = NULL; + + twminfo.num_tris = 0; + twminfo.triangle_lump = NULL; + + twminfo.num_materialgroups = 0; + twminfo.materialgroup_lump = NULL; +} + +CTwmModel::~CTwmModel() +{ + // free all our data used + if(twminfo.num_vertices) + delete[] twminfo.vertex_lump; + + if(twminfo.num_tris) + delete[] twminfo.triangle_lump; + + if(twminfo.num_materialgroups) + { + for(int i = 0;i < twminfo.num_materialgroups;i++) + delete[] twminfo.materialgroup_lump[i].tris_indices; + + delete[] twminfo.materialgroup_lump; + } +} + +/* + CTwmManager methods +*/ +CTwmManager::CTwmManager() +{ +} + +CTwmManager::~CTwmManager() +{ + // Remove all precached models + for(int i = 0;i < vecModels.size();i++) + delete vecModels[i]; + + vecModels.clear(); +} + +void CTwmManager::ParseVertexLump(FILE *pFile,CTwmModel *pTwmModel) +{ + // Vertex count + fread((char*)&pTwmModel->twminfo.num_vertices,sizeof(short),1,pFile); + + // Allocate and store verts + pTwmModel->twminfo.vertex_lump = new twm_vert_t[pTwmModel->twminfo.num_vertices]; + fread((char*)pTwmModel->twminfo.vertex_lump,sizeof(twm_vert_t)*pTwmModel->twminfo.num_vertices,1,pFile); +} + +void CTwmManager::ParseTriangleLump(FILE *pFile,CTwmModel *pTwmModel) +{ + // Triangle count + fread((char*)&pTwmModel->twminfo.num_tris,sizeof(short),1,pFile); + + // allocate and store triangle info + pTwmModel->twminfo.triangle_lump = new twm_triangle_t[pTwmModel->twminfo.num_tris]; + + for(int i = 0;i < pTwmModel->twminfo.num_tris;i++) + { + twm_triangle_t *cur_tri = &pTwmModel->twminfo.triangle_lump[i]; + + fread((char*)cur_tri->vert_indices,sizeof(short)*3,1,pFile); + fread((char*)cur_tri->u,sizeof(float)*3,1,pFile); + fread((char*)cur_tri->v,sizeof(float)*3,1,pFile); + } +} + +void CTwmManager::ParseMaterialLump(FILE *pFile,CTwmModel *pTwmModel) +{ + // Material count + fread((char*)&pTwmModel->twminfo.num_materialgroups,sizeof(short),1,pFile); + + // allocate and store material info + pTwmModel->twminfo.materialgroup_lump = new twm_materialgroup_t[pTwmModel->twminfo.num_materialgroups]; + + for(int i = 0;i < pTwmModel->twminfo.num_materialgroups;i++) + { + twm_materialgroup_t *cur_mat = &pTwmModel->twminfo.materialgroup_lump[i]; + + fread(cur_mat->texturename,sizeof(char)*64,1,pFile); + + // Precache material + cur_mat->sprite_handle = gEngfuncs.pfnSPR_Load(cur_mat->texturename); + + // allocate triangle list + fread((char*)&cur_mat->num_triangles,sizeof(short),1,pFile); + cur_mat->tris_indices = new short[cur_mat->num_triangles]; + fread((char*)cur_mat->tris_indices,sizeof(short)*cur_mat->num_triangles,1,pFile); + } +} + +// just use C style i/o +int CTwmManager::PrecacheModel(string filename) +{ + char path[256]; + CTwmModel *TwmModel = new CTwmModel; + FILE *pFile; + + // store full path + sprintf(path,"%s/%s",gEngfuncs.pfnGetGameDirectory(),filename.c_str()); + gEngfuncs.Con_DPrintf("TWM: Loading Model %s\n",filename.c_str()); + pFile = fopen(path,"rb"); + + if(pFile == NULL) + { + gEngfuncs.Con_DPrintf("TWM ERROR: Invalid file %s\n",filename.c_str()); + return 0; + } + + // Put basic information into twm model + TwmModel->filename = filename; + + fread((char*)&TwmModel->twminfo.header_id,sizeof(int),1,pFile); + fread((char*)&TwmModel->twminfo.major_version,sizeof(short),1,pFile); + fread((char*)&TwmModel->twminfo.minor_version,sizeof(short),1,pFile); + + if(TwmModel->twminfo.header_id == TWM_ID) + { + if(TwmModel->twminfo.major_version == TWM_MAJOR_VERSION) + { + // Only warning if minor versions differ + if(TwmModel->twminfo.minor_version != TWM_MINOR_VERSION) + gEngfuncs.Con_DPrintf("TWM WARNING: Different minor version for %s, expected %i got %i\n",filename.c_str(),TWM_MINOR_VERSION,TwmModel->twminfo.minor_version); + + // Start parsing! + ParseVertexLump(pFile,TwmModel); + ParseTriangleLump(pFile,TwmModel); + ParseMaterialLump(pFile,TwmModel); + + // push onto vector for storage + vecModels.push_back(TwmModel); + + goto precache_noerror; + } + else + gEngfuncs.Con_DPrintf("TWM ERROR: Invalid version for %s, expected %i got %i\n",filename.c_str(),TWM_MAJOR_VERSION,TwmModel->twminfo.major_version); + } + else + gEngfuncs.Con_DPrintf("TWM ERROR: Invalid header for %s\n",filename.c_str()); + + fclose(pFile); + return 0; +precache_noerror: + fclose(pFile); + return 1; +} + +void CTwmManager::BeginPrecache() +{ + // Remove all precached models + for(int i = 0;i < vecModels.size();i++) + delete vecModels[i]; + + vecModels.clear(); + + // Start precaching! + GetModelByName("models/muz_test.twm"); +} + +CTwmModel *CTwmManager::GetModelByName(string filename) +{ + for(int i = 0;i < vecModels.size();i++) + if(vecModels[i]->filename == filename) + return vecModels[i]; + + // Oops! we couldnt find the model, precache and return that + if(PrecacheModel(filename)) + return vecModels[vecModels.size()-1]; + + return NULL; +} + +// Update a specific twm object +void CTwmManager::TwmUpdate(twm_clientinfo_t *clientinfo, double frametime) +{ + bool bDie = false; + + // fade out model + clientinfo->color[3] -= clientinfo->fadetime * frametime; + if(clientinfo->color[3] <= 0.0f) + bDie = true; + + // set dead var + clientinfo->dead = bDie; +} + diff --git a/cl_dll/twmmanager.h b/cl_dll/twmmanager.h new file mode 100644 index 0000000..6641d83 --- /dev/null +++ b/cl_dll/twmmanager.h @@ -0,0 +1,67 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#ifndef __TWMMANAGER_H_ +#define __TWMMANAGER_H_ + +// STL stuff +#include +#include + +using namespace std; + +class CTwmModel +{ +public: + CTwmModel(); + ~CTwmModel(); + + string filename; + twm_info_t twminfo; +}; + +class CTwmManager +{ +private: + // all our precached models + vector vecModels; + + // Helper functions for PrecacheModel + void ParseVertexLump(FILE *pFile,CTwmModel *pTwmModel); + void ParseTriangleLump(FILE *pFile,CTwmModel *pTwmModel); + void ParseMaterialLump(FILE *pFile,CTwmModel *pTwmModel); + + int PrecacheModel(string filename); +public: + CTwmManager(); + ~CTwmManager(); + + // Called when we need to load the base .twm's + void BeginPrecache(); + + // .TWM retrieval operations + CTwmModel *GetModelByName(string filename); + + // Update twm info :o + void TwmUpdate(twm_clientinfo_t *clientinfo,double frametime); +}; + +extern CTwmManager g_TwmManager; + +// 3d muzzleflashes +extern twm_clientinfo_t g_MuzzleflashModels[64]; +extern int g_iNumMuzzleflashModels; + +#define TWM_UPDATE_ARRAY(array,count,frametime) {for(int i = 0;i < count;i++)g_TwmManager.TwmUpdate(&array[i],frametime);} + +#endif \ No newline at end of file diff --git a/cl_dll/util.cpp b/cl_dll/util.cpp index d8fe4c7..25aadc6 100644 --- a/cl_dll/util.cpp +++ b/cl_dll/util.cpp @@ -131,3 +131,15 @@ HSPRITE LoadSprite(const char *pszName) return SPR_Load(sz); } +// Added by gage + +int GetLocalPlayerIndex() +{ + return gEngfuncs.GetLocalPlayer()->index; +} + +// Made this a normal function so weapons code can grab it +void CenterPrint( const char *string ) +{ + gEngfuncs.pfnCenterPrint( string ); +} \ No newline at end of file diff --git a/cl_dll/util_vector.h b/cl_dll/util_vector.h index 05da19e..5887a20 100644 --- a/cl_dll/util_vector.h +++ b/cl_dll/util_vector.h @@ -116,6 +116,6 @@ public: }; inline Vector operator*(float fl, const Vector& v) { return v * fl; } inline float DotProduct(const Vector& a, const Vector& b) { return(a.x*b.x+a.y*b.y+a.z*b.z); } -inline Vector CrossProduct(const Vector& a, const Vector& b) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); } +inline Vector CrossProduct(const Vector& a, const Vector& b) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); }\ #define vec3_t Vector diff --git a/cl_dll/vgui_CustomObjects.cpp b/cl_dll/vgui_CustomObjects.cpp index fd24b3d..85ab1d9 100644 --- a/cl_dll/vgui_CustomObjects.cpp +++ b/cl_dll/vgui_CustomObjects.cpp @@ -30,10 +30,13 @@ #include "parsemsg.h" #include "vgui_int.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" #include "vgui_ServerBrowser.h" #include "..\game_shared\vgui_LoadTGA.h" +#include "tw_vgui.h" + // Arrow filenames char *sArrowFilenames[] = { @@ -69,6 +72,14 @@ BitmapTGA *LoadTGAForRes( const char* pImageName ) sprintf(sz, "%%d_%s", pImageName); pTGA = vgui_LoadTGA(GetTGANameForRes(sz)); + if(pTGA == NULL) + { + // Instead of returning a null image (and probably crashing the VGUI) we are + // going to load the NULL texture. if the null textures dont exist this is + // useless anyways D: - JAC + pTGA = vgui_LoadTGA("gfx/vgui/null.tga"); + } + return pTGA; } @@ -194,13 +205,12 @@ void CommandButton::paintBackground() { if ( isArmed() ) { - // Orange highlight background - drawSetColor( Scheme::sc_primary2 ); + drawSetColor(TW_COMMANDBUTTON_ACTIVE_RGBA); drawFilledRect(0,0,_size[0],_size[1]); } - // Orange Border - drawSetColor( Scheme::sc_secondary1 ); + // Border + drawSetColor(TW_COMMANDBUTTON_BORDER_RGBA); drawOutlinedRect(0,0,_size[0],_size[1]); } @@ -306,6 +316,23 @@ CImageLabel::CImageLabel( const char* pImageName,int x,int y,int wide,int tall ) setImage( m_pTGA ); } +void CImageLabel::NewImage(const char *pImageName) +{ + if(m_pTGA != NULL) + { + setImage(NULL); + delete m_pTGA; + } + + m_pTGA = LoadTGAForRes(pImageName); + setImage(m_pTGA); +} + +void CImageLabel::setImageColor(Color col) +{ + m_pTGA->setColor(col); +} + //=========================================================== // Image size int CImageLabel::getImageWide( void ) @@ -389,7 +416,8 @@ void CTFSlider::paintBackground( void ) getNobPos(nobx,noby); // Border - drawSetColor( Scheme::sc_secondary1 ); + drawSetColor(TW_PANEL_BORDER_RGBA); + drawOutlinedRect( 0,0,wide,tall ); if( isVertical() ) diff --git a/cl_dll/vgui_ItemSelection.cpp b/cl_dll/vgui_ItemSelection.cpp new file mode 100644 index 0000000..ec035d6 --- /dev/null +++ b/cl_dll/vgui_ItemSelection.cpp @@ -0,0 +1,633 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "VGUI_TextImage.h" + +#include "hud.h" +#include "cl_util.h" + +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" + +#include "tw_common.h" +#include + +#define INFO_PANEL_X XRES(236) +#define INFO_PANEL_Y YRES(188) +#define INFO_PANEL_SIZE_X XRES(328) +#define INFO_PANEL_SIZE_Y YRES(220) + +#define IMAGE_PANEL_X XRES(236) +#define IMAGE_PANEL_Y YRES(34) +#define IMAGE_PANEL_SIZE_X XRES(328) +#define IMAGE_PANEL_SIZE_Y YRES(76) + +#define LIST_BUTTON_X XRES(68) +#define LIST_BUTTON_Y YRES(34) + +#define MAIN_BUTTON_X XRES(68) +#define MAIN_BUTTON_Y YRES(416) + +#define BUTTON_SIZE_X XRES(160) +//#define BUTTON_SIZE_Y YRES(30) + +// Important: +// join_game -> join this game +// setweapons -> set weapons command, 5 arguments like setweapons -1 -1 -1 -1 -1 +// setrandom -> random weaps +// unsetrandom -> non random weaps +// spectate -> spectate this game + +class CItemList; + +/* +========== +CInformationPanel +========== +*/ +class CInformationPanel : public CTransparentPanel +{ +public: + CInformationPanel( int x, int y, int wide, int tall ); + + void SetInfo( char *pszClassname ); +private: + ScrollPanel *m_pScrollPanel; + TextPanel *m_pTextPanel; + CImageLabel *m_pImageLabel; + + char m_szClassname[128]; +}; + +/* +========== +CItemSelectionPanel +========== +*/ +class CItemSelectionPanel : public CMenuPanel +{ +public: + CItemSelectionPanel(int x,int y,int wide,int tall); + ~CItemSelectionPanel(); + + void paintBackground(); + + void SetInfo(char *pszClassname); + void SetImage( char *pszClassname, int iIndex ); + void AddItem( playeritemlist_t *pList, int iSlot, int iIndex ); + void SendItems(); +private: + CImageLabel *m_pImageLabels[5]; + CItemList *m_pItemList; + CInformationPanel *m_pInformationPanel; + + int m_iWeaponIndices[5]; +}; + +/* +========== +CItemSelectionPanel_Create + +Used by external modules to spawn this item selection code +========== +*/ +CMenuPanel *CItemSelectionPanel_Create(int x,int y,int wide,int tall) +{ + return new CItemSelectionPanel(x,y,wide,tall); +} + +/* +========== +CItemListHandler_NewItemList +========== +*/ +enum e_itemcategories { + ITEM_MELEE = 0, + ITEM_SIDEARMS, + ITEM_PRIMARY, + ITEM_UNIQUE, + ITEM_EXPLOSIVES, + ITEM_BACK, +}; + +class CItemListHandler_NewItemList : public ActionSignal +{ +public: + CItemListHandler_NewItemList(int iCategory,CItemList *pItemList); + + virtual void actionPerformed(Panel *panel); +private: + int m_iCategory; + CItemList *m_pOldItemList; +}; + +/* +========== +CHandler_SendChanges +========== +*/ +class CHandler_SendChanges : public ActionSignal +{ +public: + CHandler_SendChanges(CItemSelectionPanel *pPanel) + { + m_pInfoPanel = pPanel; + } + + virtual void actionPerformed(Panel *panel) + { + m_pInfoPanel->SendItems(); + } +private: + CItemSelectionPanel *m_pInfoPanel; +}; + +/* +========== +CItemButtonHandler_SetItem +========== +*/ +class CItemButtonHandler_SetItem : public ActionSignal +{ +public: + CItemButtonHandler_SetItem( playeritemlist_t *pList, int iSlot, int iIndex, CItemSelectionPanel *pPanel) : + m_pList( pList ), m_iWeaponIndex( iIndex ), m_iWeaponSlot( iSlot ), m_pPanel( pPanel ) + { + } + + virtual void actionPerformed(Panel *panel) + { + m_pPanel->AddItem( m_pList, m_iWeaponSlot, m_iWeaponIndex ); + } +private: + CItemSelectionPanel *m_pPanel; + + playeritemlist_t *m_pList; + int m_iWeaponIndex; + int m_iWeaponSlot; +}; + +/* +========== +CommandButtonShaded +========== +*/ +class CommandButtonShaded : public CommandButton +{ +public: + CommandButtonShaded(const char *text,int x,int y,int wide,int tall,bool bNoHighlight); + + void paint(); +}; + +CommandButtonShaded::CommandButtonShaded(const char *text,int x,int y,int wide,int tall,bool bNoHighlight = 0) : CommandButton(text,x,y,wide,tall,bNoHighlight) +{ +} + +void CommandButtonShaded::paint() +{ + if(!isArmed()) + { + drawSetColor(TW_PANEL_BG_RGBA); + drawFilledRect(0,0,_size[0],_size[1]); + } + + CommandButton::paint(); +} + +/* +========== +ItemButtonShaded +========== +*/ +class ItemButtonShaded : public CommandButtonShaded +{ +public: + ItemButtonShaded(const char *classname,int x,int y,int wide,int tall,bool bNoHighlight); + + void setParent(Panel *newParent); + void paint(); +private: + char m_szClassname[128]; +}; + +ItemButtonShaded::ItemButtonShaded(const char *classname,int x,int y,int wide,int tall,bool bNoHighlight = 0) : CommandButtonShaded(classname,x,y,wide,tall,bNoHighlight) +{ + char szRealText[128]; + + strcpy(m_szClassname,classname); + + // Localize the text on the buttons + sprintf(szRealText,"#ItemList_%s",classname); + + setText(CHudTextMessage::BufferedLocaliseTextString(szRealText)); +} + +void ItemButtonShaded::setParent(Panel *newParent) +{ + CommandButtonShaded::setParent(newParent); +} + +void ItemButtonShaded::paint() +{ + CommandButtonShaded::paint(); + + if(isArmed()) + { + CItemSelectionPanel *pPanel = (CItemSelectionPanel*)getParent(); + + if(pPanel != NULL) + pPanel->SetInfo(m_szClassname); + } +} + +/* +========== +CItemList + +Base class to show vgui buttons pertaining to categories of weapons +========== +*/ +class CItemList +{ +public: + CItemList(CItemSelectionPanel *pPanel, playeritemlist_t *pList, int iSlot, int iNextSlot ) : + m_pParentPanel( pPanel ), m_pItemArray( pList ), m_iSlot( iSlot ), m_iNextSlot( iNextSlot ) + { + } + virtual ~CItemList() + { + for( int i = 0;i < m_ItemButtons.size();i++ ) + { + m_pParentPanel->removeChild( m_ItemButtons[i] ); + } + } + + void BuildButtons() + { + int iButtonPosY = LIST_BUTTON_Y; + + for( int i = 0;i < m_pItemArray->size; i++ ) + { + playeritem_t *pItem = &m_pItemArray->array[i]; + ItemButtonShaded *newButton; + + newButton = new ItemButtonShaded( pItem->weapon_classname, LIST_BUTTON_X, iButtonPosY, BUTTON_SIZE_X, BUTTON_SIZE_Y ); + newButton->setParent( m_pParentPanel ); + newButton->addActionSignal( new CItemButtonHandler_SetItem( m_pItemArray, m_iSlot, i, m_pParentPanel ) ); + newButton->addActionSignal( new CItemListHandler_NewItemList(m_iNextSlot,this) ); + + m_ItemButtons.push_back( newButton ); + iButtonPosY += BUTTON_SIZE_Y + YRES(8); + } + + CommandButtonShaded *backButton; + backButton = new CommandButtonShaded(CHudTextMessage::BufferedLocaliseTextString("#ItemList_Back"),LIST_BUTTON_X,iButtonPosY,BUTTON_SIZE_X,BUTTON_SIZE_Y); + backButton->setParent( m_pParentPanel ); + backButton->addActionSignal(new CItemListHandler_NewItemList(ITEM_BACK,this)); + m_ItemButtons.push_back( backButton ); + + m_iNumButtons = ++i; + } + + playeritemlist_t *m_pItemArray; + int m_iSlot,m_iNextSlot; + CItemSelectionPanel *m_pParentPanel; +public: + std::vector m_ItemButtons; + int m_iNumButtons; +}; + +class CMainCategories : public CItemList +{ +public: + CMainCategories(CItemSelectionPanel *pPanel, playeritemlist_t *pList ) : CItemList(pPanel, pList, -1, -1) + { + int iButtonPosY = LIST_BUTTON_Y; + + m_pMeleeButton = new CommandButtonShaded(CHudTextMessage::BufferedLocaliseTextString("#ItemList_Melee"),LIST_BUTTON_X,iButtonPosY,BUTTON_SIZE_X,BUTTON_SIZE_Y); + m_pMeleeButton->setParent(m_pParentPanel); + m_pMeleeButton->addActionSignal(new CItemListHandler_NewItemList(ITEM_MELEE,this)); + + iButtonPosY += BUTTON_SIZE_Y + YRES(8); + + m_pSidearmsButton = new CommandButtonShaded(CHudTextMessage::BufferedLocaliseTextString("#ItemList_Sidearms"),LIST_BUTTON_X,iButtonPosY,BUTTON_SIZE_X,BUTTON_SIZE_Y); + m_pSidearmsButton->setParent(m_pParentPanel); + m_pSidearmsButton->addActionSignal(new CItemListHandler_NewItemList(ITEM_SIDEARMS,this)); + + iButtonPosY += BUTTON_SIZE_Y + YRES(8); + + m_pSecondaryButton = new CommandButtonShaded(CHudTextMessage::BufferedLocaliseTextString("#ItemList_Secondary"),LIST_BUTTON_X,iButtonPosY,BUTTON_SIZE_X,BUTTON_SIZE_Y); + m_pSecondaryButton->setParent(m_pParentPanel); + m_pSecondaryButton->addActionSignal(new CItemListHandler_NewItemList(ITEM_PRIMARY,this)); + + iButtonPosY += BUTTON_SIZE_Y + YRES(8); + + m_pExplosivesButton = new CommandButtonShaded(CHudTextMessage::BufferedLocaliseTextString("#ItemList_Explosives"),LIST_BUTTON_X,iButtonPosY,BUTTON_SIZE_X,BUTTON_SIZE_Y); + m_pExplosivesButton->setParent(m_pParentPanel); + m_pExplosivesButton->addActionSignal(new CItemListHandler_NewItemList(ITEM_EXPLOSIVES,this)); + + // No uniques for now :| +/* iButtonPosY += BUTTON_SIZE_Y + YRES(8); + + m_pUniqueButton = new CommandButtonShaded(CHudTextMessage::BufferedLocaliseTextString("#ItemList_Unique"),LIST_BUTTON_X,iButtonPosY,BUTTON_SIZE_X,BUTTON_SIZE_Y); + m_pUniqueButton->setParent(m_pParentPanel); + m_pUniqueButton->addActionSignal(new CItemListHandler_NewItemList(ITEM_UNIQUE,this)); +*/ + iButtonPosY += BUTTON_SIZE_Y + YRES(8); + } + + ~CMainCategories() + { + m_pParentPanel->removeChild(m_pMeleeButton); + m_pParentPanel->removeChild(m_pSidearmsButton); + m_pParentPanel->removeChild(m_pSecondaryButton); + m_pParentPanel->removeChild(m_pExplosivesButton); +// m_pParentPanel->removeChild(m_pUniqueButton); + } +private: + CommandButtonShaded *m_pMeleeButton; + CommandButtonShaded *m_pSidearmsButton; + CommandButtonShaded *m_pSecondaryButton; + CommandButtonShaded *m_pExplosivesButton; +// CommandButtonShaded *m_pUniqueButton; +}; + +/* + CItemSelectionPanel methods +*/ +CItemSelectionPanel::CItemSelectionPanel(int x,int y,int wide,int tall) : CMenuPanel(100,false,x,y,wide,tall) +{ + memset( m_iWeaponIndices, -1, sizeof( m_iWeaponIndices ) ); + + // Start up selection images + for(int i = 0;i < 5;i++) + { + m_pImageLabels[i] = new CImageLabel("",0,0); + m_pImageLabels[i]->setParent(this); + m_pImageLabels[i]->setVisible(false); + } + + // Load the item manager + m_pItemList = new CMainCategories(this, NULL ); + + // Information Panel + m_pInformationPanel = new CInformationPanel(INFO_PANEL_X,INFO_PANEL_Y,INFO_PANEL_SIZE_X,INFO_PANEL_SIZE_Y); + m_pInformationPanel->setParent(this); + m_pInformationPanel->setVisible(true); + + int iButtonPosX = MAIN_BUTTON_X; + + CommandButtonShaded *pOkButton = new CommandButtonShaded(CHudTextMessage::BufferedLocaliseTextString("#Menu_JoinGame"), iButtonPosX, MAIN_BUTTON_Y, BUTTON_SIZE_X, BUTTON_SIZE_Y ); + pOkButton->setParent(this); +// pOkButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + pOkButton->addActionSignal(new CHandler_SendChanges(this)); + + iButtonPosX += XRES(168); + + CommandButtonShaded *pRandomButton = new CommandButtonShaded(CHudTextMessage::BufferedLocaliseTextString("#Menu_Random"), iButtonPosX,MAIN_BUTTON_Y, BUTTON_SIZE_X, BUTTON_SIZE_Y ); + pRandomButton->setParent(this); + pRandomButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + pRandomButton->addActionSignal(new CMenuHandler_StringCommand("setrandom")); + pRandomButton->addActionSignal(new CMenuHandler_StringCommand("join_game")); + + iButtonPosX += XRES(168); + + CommandButtonShaded *pSpectateButton = new CommandButtonShaded(CHudTextMessage::BufferedLocaliseTextString("#Menu_Spectate"), iButtonPosX, MAIN_BUTTON_Y, BUTTON_SIZE_X,BUTTON_SIZE_Y ); + pSpectateButton->setParent(this); + pSpectateButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + pSpectateButton->addActionSignal(new CMenuHandler_StringCommand("spectate")); +} + +CItemSelectionPanel::~CItemSelectionPanel() +{ + delete m_pItemList; + + for( int i = 0;i < 5;i++ ) + delete m_pImageLabels[i]; +} + +void CItemSelectionPanel::SetInfo(char *pszClassname) +{ + m_pInformationPanel->SetInfo(pszClassname); +} + +void CItemSelectionPanel::SetImage(char *pszClassname,int iIndex) +{ + // Show image + char szImageName[128]; + + sprintf(szImageName,"inv_%s",pszClassname); + + m_pImageLabels[iIndex]->NewImage(szImageName); + m_pImageLabels[iIndex]->setVisible(true); + m_pImageLabels[iIndex]->setImageColor(Color(255,255,255,160)); // Slightly faded + + // Set image bounds + int x, y; + int w,h; + + w = m_pImageLabels[iIndex]->getImageWide(); + h = m_pImageLabels[iIndex]->getImageTall(); + + switch( iIndex ) + { + case 0: + x = IMAGE_PANEL_X; + y = IMAGE_PANEL_Y; + break; + case 1: + x = IMAGE_PANEL_X; + y = IMAGE_PANEL_Y + h + YRES(8); + break; + case 2: + x = IMAGE_PANEL_X + w + XRES(8); + y = IMAGE_PANEL_Y; + break; + case 3: + case 4: + x = IMAGE_PANEL_X + w + XRES(8); + y = IMAGE_PANEL_Y + h + YRES(8); + break; + } + + m_pImageLabels[iIndex]->setPos( x, y ); +} + +void CItemSelectionPanel::paintBackground() +{ + drawSetColor(TW_PANEL_BORDER_RGBA); + drawOutlinedRect(0,0,_size[0],_size[1]); +} + +void CItemSelectionPanel::AddItem( playeritemlist_t *pList, int iSlot, int iIndex ) +{ + m_iWeaponIndices[iSlot] = iIndex; + + SetImage( pList->array[iIndex].weapon_classname, iSlot ); +} + +void CItemSelectionPanel::SendItems() +{ + // Must have a melee or backup weapon to spawn. + if( m_iWeaponIndices[0] == -1 ) + { + gEngfuncs.pfnCenterPrint( "Must choose a melee weapon" ); + return; + } + + gViewPort->HideTopMenu(); + + char szText[64]; + sprintf( szText, "setweapons %i %i %i %i %i", + m_iWeaponIndices[0], m_iWeaponIndices[1], m_iWeaponIndices[2], + m_iWeaponIndices[3], m_iWeaponIndices[4] ); + + gEngfuncs.pfnClientCmd( "unsetrandom" ); + gEngfuncs.pfnClientCmd( szText ); + gEngfuncs.pfnClientCmd( "join_game" ); +} + +/* + CInformationPanel methods +*/ +CInformationPanel::CInformationPanel(int x,int y,int wide,int tall) : CTransparentPanel(0,x,y,wide,tall) +{ + memset( m_szClassname, 0, sizeof( m_szClassname ) ); + + m_pImageLabel = new CImageLabel("",0,0); + m_pImageLabel->setParent(this); + m_pImageLabel->setVisible(false); + + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hMOTDText = pSchemes->getSchemeHandle( "Briefing Text" ); + + // color schemes + int r, g, b, a; + + m_pScrollPanel = new CTFScrollPanel(XRES(16),YRES(16), wide - XRES(32), tall - YRES(32)); + m_pScrollPanel->setParent(this); + + // force scrollbars on to set clientClip + m_pScrollPanel->setScrollBarAutoVisible(false, false); + m_pScrollPanel->setScrollBarVisible(true, true); + m_pScrollPanel->validate(); + + m_pTextPanel = new TextPanel("",0,0,64,64); + m_pTextPanel->setParent(m_pScrollPanel->getClient()); + + // Set font + m_pTextPanel->setFont(pSchemes->getFont(hMOTDText)); + pSchemes->getFgColor(hMOTDText,r,g,b,a); + m_pTextPanel->setFgColor(r,g,b,a); + pSchemes->getBgColor(hMOTDText,r,g,b,a); + m_pTextPanel->setBgColor(r,g,b,a); +} + +void CInformationPanel::SetInfo( char *pszClassname ) +{ + // Dont update if we are already showing info + if( strcmp( pszClassname, m_szClassname ) == 0 ) + return; + + strcpy( m_szClassname, pszClassname ); + + // Show image + char szImageName[128]; + + sprintf(szImageName,"info_%s",pszClassname); + + m_pImageLabel->NewImage(szImageName); + m_pImageLabel->setVisible(true); + m_pImageLabel->setImageColor(Color(255,255,255,160)); // Slightly faded + + // Set image bounds + int w,h; + + getSize(w,h); + + m_pImageLabel->setPos(w/2 - m_pImageLabel->getImageWide()/2,h/2 - m_pImageLabel->getImageTall()/2); + + // Set text + char szLocalizedClass[128]; + + sprintf(szLocalizedClass,"#Information_%s",pszClassname); + + m_pTextPanel->setText(CHudTextMessage::BufferedLocaliseTextString(szLocalizedClass)); + + // Get the total size of the MOTD text and resize the text panel + int iScrollSizeX, iScrollSizeY; + + // First, set the size so that the client's width is correct at least because the + // width is critical for getting the "wrapped" size right. + // You'll see a horizontal scroll bar if there is a single word that won't wrap in the + // specified width. + m_pTextPanel->getTextImage()->setSize(m_pScrollPanel->getClientClip()->getWide(), m_pScrollPanel->getClientClip()->getTall()); + m_pTextPanel->getTextImage()->getTextSizeWrapped( iScrollSizeX, iScrollSizeY ); + + // Now resize the textpanel to fit the scrolled size + m_pTextPanel->setSize( iScrollSizeX , iScrollSizeY ); + + //turn the scrollbars back into automode + m_pScrollPanel->setScrollBarAutoVisible(true, true); + m_pScrollPanel->setScrollBarVisible(false, false); + + m_pScrollPanel->validate(); +} + +/* + CItemListHandler_NewItemList methods +*/ +CItemListHandler_NewItemList::CItemListHandler_NewItemList(int iCategory,CItemList *pItemList) +{ + m_iCategory = iCategory; + m_pOldItemList = pItemList; +} + +void CItemListHandler_NewItemList::actionPerformed(Panel *panel) +{ + CItemSelectionPanel *pParent = m_pOldItemList->m_pParentPanel; + + delete m_pOldItemList; + + switch( m_iCategory ) + { + case ITEM_MELEE: + m_pOldItemList = new CItemList( pParent, &g_MeleeItems, ITEM_MELEE, ITEM_SIDEARMS ); + m_pOldItemList->BuildButtons(); + break; + case ITEM_SIDEARMS: + m_pOldItemList = new CItemList( pParent, &g_SidearmItems, ITEM_SIDEARMS, ITEM_PRIMARY ); + m_pOldItemList->BuildButtons(); + break; + case ITEM_PRIMARY: + m_pOldItemList = new CItemList( pParent, &g_PrimaryItems, ITEM_PRIMARY, ITEM_EXPLOSIVES ); + m_pOldItemList->BuildButtons(); + break; +/* case ITEM_UNIQUE: + m_pOldItemList = new CItemList( pParent, &g_UniqueItems, 3 ); + m_pOldItemList->BuildButtons(); + break;*/ + case ITEM_EXPLOSIVES: + m_pOldItemList = new CItemList( pParent, &g_OtherItems, ITEM_EXPLOSIVES, ITEM_BACK ); + m_pOldItemList->BuildButtons(); + break; + case ITEM_BACK: + m_pOldItemList = new CMainCategories( pParent, NULL ); + break; + } +} \ No newline at end of file diff --git a/cl_dll/vgui_MOTDWindow.cpp b/cl_dll/vgui_MOTDWindow.cpp index 297af6f..ff8103e 100644 --- a/cl_dll/vgui_MOTDWindow.cpp +++ b/cl_dll/vgui_MOTDWindow.cpp @@ -29,7 +29,8 @@ #include "const.h" #include "vgui_int.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" #include "vgui_ServerBrowser.h" #define MOTD_TITLE_X XRES(16) @@ -46,26 +47,25 @@ class CMessageWindowPanel : public CMenuPanel { public: - CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullScreen, int iRemoveMe, int x, int y, int wide, int tall ); - + CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullScreen, int iRemoveMe, int iTextType, int x, int y, int wide, int tall ); + void paintBackground(); private: CTransparentPanel *m_pBackgroundPanel; - }; //----------------------------------------------------------------------------- // Purpose: Creates a new CMessageWindowPanel // Output : CMenuPanel - interface to the panel //----------------------------------------------------------------------------- -CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) +CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int iTextType, int x, int y, int wide, int tall ) { - return new CMessageWindowPanel( szMOTD, szTitle, iShadeFullscreen, iRemoveMe, x, y, wide, tall ); + return new CMessageWindowPanel( szMOTD, szTitle, iShadeFullscreen, iRemoveMe, iTextType, x, y, wide, tall ); } //----------------------------------------------------------------------------- // Purpose: Constructs a message panel //----------------------------------------------------------------------------- -CMessageWindowPanel::CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) : CMenuPanel( iShadeFullscreen ? 100 : 255, iRemoveMe, x, y, wide, tall ) +CMessageWindowPanel::CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int iTextType, int x, int y, int wide, int tall ) : CMenuPanel( iShadeFullscreen ? 100 : 255, iRemoveMe, x, y, wide, tall ) { // Get the scheme used for the Titles CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); @@ -80,7 +80,7 @@ CMessageWindowPanel::CMessageWindowPanel( const char *szMOTD, const char *szTitl // Create the window m_pBackgroundPanel = new CTransparentPanel( iShadeFullscreen ? 255 : 100, MOTD_WINDOW_X, MOTD_WINDOW_Y, MOTD_WINDOW_SIZE_X, MOTD_WINDOW_SIZE_Y ); m_pBackgroundPanel->setParent( this ); - m_pBackgroundPanel->setBorder( new LineBorder( Color(255 * 0.7,170 * 0.7,0,0)) ); +// m_pBackgroundPanel->setBorder(new LineBorder(Color(TW_PANEL_BORDER_RGBA))); m_pBackgroundPanel->setVisible( true ); int iXSize,iYSize,iXPos,iYPos; @@ -142,13 +142,29 @@ CMessageWindowPanel::CMessageWindowPanel( const char *szMOTD, const char *szTitl pScrollPanel->validate(); CommandButton *pButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_OK" ), iXPos + XRES(16), iYPos + iYSize - YRES(16) - BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); - pButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); - pButton->setParent(this); + switch(iTextType) + { + case SHOW_MOTD: + pButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_MAPBRIEFING)); + break; + case SHOW_MAPBRIEFING: + pButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_ITEMSELECTION)); + break; + } + + pButton->setParent(this); +} + +void CMessageWindowPanel::paintBackground() +{ + drawSetColor(TW_PANEL_BORDER_RGBA); + drawOutlinedRect(0,0,_size[0],_size[1]); } - diff --git a/cl_dll/vgui_SchemeManager.cpp b/cl_dll/vgui_SchemeManager.cpp index ad972c2..1013900 100644 --- a/cl_dll/vgui_SchemeManager.cpp +++ b/cl_dll/vgui_SchemeManager.cpp @@ -166,7 +166,7 @@ CSchemeManager::CSchemeManager( int xRes, int yRes ) // find the closest matching scheme file to our resolution char token[1024]; - char *pFile = (char*)LoadFileByResolution( "", xRes, "_textscheme.txt" ); + char *pFile = (char*)LoadFileByResolution( "gfx/vgui/", xRes, "_textscheme.txt" ); m_xRes = xRes; char *pFileStart = pFile; diff --git a/cl_dll/vgui_ScorePanel.cpp b/cl_dll/vgui_ScorePanel.cpp index cecfee3..43c550c 100644 --- a/cl_dll/vgui_ScorePanel.cpp +++ b/cl_dll/vgui_ScorePanel.cpp @@ -24,10 +24,11 @@ #include "const.h" #include "entity_state.h" #include "cl_entity.h" -#include "vgui_TeamFortressViewport.h" -#include "vgui_ScorePanel.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" #include "..\game_shared\vgui_helpers.h" #include "..\game_shared\vgui_loadtga.h" +#include "vgui_ScorePanel.h" hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll @@ -35,7 +36,6 @@ team_info_t g_TeamInfo[MAX_TEAMS+1]; int g_IsSpectator[MAX_PLAYERS+1]; int HUD_IsGame( const char *game ); -int EV_TFC_IsAllyTeam( int iTeam1, int iTeam2 ); // Scoreboard dimensions #define SBOARD_TITLE_SIZE_Y YRES(22) @@ -57,7 +57,6 @@ SBColumnInfo g_ColumnInfo[NUM_COLUMNS] = { {NULL, 24, Label::a_east}, {NULL, 140, Label::a_east}, // name - {NULL, 56, Label::a_east}, // class {"#SCORE", 40, Label::a_east}, {"#DEATHS", 46, Label::a_east}, {"#LATENCY", 46, Label::a_east}, @@ -71,7 +70,6 @@ SBColumnInfo g_ColumnInfo[NUM_COLUMNS] = #define TEAM_SPECTATORS 2 #define TEAM_BLANK 3 - //----------------------------------------------------------------------------- // ScorePanel::HitTestPanel. //----------------------------------------------------------------------------- @@ -84,8 +82,6 @@ void ScorePanel::HitTestPanel::internalMousePressed(MouseCode code) } } - - //----------------------------------------------------------------------------- // Purpose: Create the ScoreBoard panel //----------------------------------------------------------------------------- @@ -97,7 +93,12 @@ ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) Font *tfont = pSchemes->getFont(hTitleScheme); Font *smallfont = pSchemes->getFont(hSmallScheme); - setBgColor(0, 0, 0, 96); + setBgColor(TW_PANEL_BG_RGBA); + + LineBorder *border = new LineBorder(Color(TW_PANEL_BORDER_RGBA)); + setBorder(border); + setPaintBorderEnabled(true); + m_pCurrentHighlightLabel = NULL; m_iHighlightRow = -1; @@ -108,10 +109,6 @@ ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) m_TitleLabel.setFgColor( Scheme::sc_primary1 ); m_TitleLabel.setContentAlignment( vgui::Label::a_west ); - LineBorder *border = new LineBorder(Color(60, 60, 60, 128)); - setBorder(border); - setPaintBorderEnabled(true); - int xpos = g_ColumnInfo[0].m_Width + 3; if (ScreenWidth >= 640) { @@ -262,7 +259,7 @@ void ScorePanel::Update() } // If it's not teamplay, sort all the players. Otherwise, sort the teams. - if ( !gHUD.m_Teamplay ) + if ( gHUD.m_Gamemode != 2 ) SortPlayers( 0, NULL ); else SortTeams(); @@ -628,11 +625,11 @@ void ScorePanel::FillGrid() if ( pl_info->thisplayer ) // if it is their name, draw it a different color { // Highlight this player - pLabel->setFgColor(Scheme::sc_white); + pLabel->setFgColor(TW_SCOREPANEL_HIGHLIGHT_TEXT_RGBA); pLabel->setBgColor( iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][0], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][1], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][2], - 196 ); + TW_SCOREPANEL_HIGHLIGHT_COLOR_A ); } else if ( m_iSortedRows[row] == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) { @@ -642,7 +639,7 @@ void ScorePanel::FillGrid() } // Align - if (col == COLUMN_NAME || col == COLUMN_CLASS) + if (col == COLUMN_NAME) { pLabel->setContentAlignment( vgui::Label::a_west ); } @@ -693,8 +690,6 @@ void ScorePanel::FillGrid() break; case COLUMN_VOICE: break; - case COLUMN_CLASS: - break; case COLUMN_KILLS: if ( m_iIsATeam[row] == TEAM_YES ) sprintf(sz, "%d", team_info->frags ); @@ -728,35 +723,6 @@ void ScorePanel::FillGrid() GetClientVoiceMgr()->UpdateSpeakerImage(pLabel, m_iSortedRows[row]); } break; - case COLUMN_CLASS: - // No class for other team's members (unless allied or spectator) - if ( gViewPort && EV_TFC_IsAllyTeam( g_iTeamNumber, g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ) ) - bShowClass = true; - // Don't show classes if this client hasnt picked a team yet - if ( g_iTeamNumber == 0 ) - bShowClass = false; - - if (bShowClass) - { - // Only print Civilian if this team are all civilians - bool bNoClass = false; - if ( g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass == 0 ) - { - if ( gViewPort->GetValidClasses( g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ) != -1 ) - bNoClass = true; - } - - if (bNoClass) - sprintf(sz, ""); - else - sprintf( sz, "%s", CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[ g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass ] ) ); - } - else - { - strcpy(sz, ""); - } - break; - case COLUMN_TRACKER: break; case COLUMN_KILLS: @@ -955,7 +921,7 @@ void CLabelHeader::paint() if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) { - setFgColor(255, 255, 255, 0); + setFgColor(255,255, 255, 0); } // draw text diff --git a/cl_dll/vgui_ScorePanel.h b/cl_dll/vgui_ScorePanel.h index 5bc4ab4..7c8a404 100644 --- a/cl_dll/vgui_ScorePanel.h +++ b/cl_dll/vgui_ScorePanel.h @@ -24,13 +24,12 @@ // Scoreboard cells #define COLUMN_TRACKER 0 #define COLUMN_NAME 1 -#define COLUMN_CLASS 2 -#define COLUMN_KILLS 3 -#define COLUMN_DEATHS 4 -#define COLUMN_LATENCY 5 -#define COLUMN_VOICE 6 -#define COLUMN_BLANK 7 -#define NUM_COLUMNS 8 +#define COLUMN_KILLS 2 +#define COLUMN_DEATHS 3 +#define COLUMN_LATENCY 4 +#define COLUMN_VOICE 5 +#define COLUMN_BLANK 6 +#define NUM_COLUMNS 7 #define NUM_ROWS (MAX_PLAYERS + (MAX_SCOREBOARD_TEAMS * 2)) using namespace vgui; @@ -259,6 +258,8 @@ private: vgui::CListBox m_PlayerList; CGrid m_PlayerGrids[NUM_ROWS]; // The grid with player and team info. CLabelHeader m_PlayerEntries[NUM_COLUMNS][NUM_ROWS]; // Labels for the grid entries. +// WastesBitmapTGA *m_pBackground; + Label *m_pBackgroundLabel; ScorePanel::HitTestPanel m_HitTestPanel; @@ -277,10 +278,18 @@ public: int m_iLastKilledBy; int m_fLastKillTime; - public: ScorePanel(int x,int y,int wide,int tall); + ~ScorePanel() + { +// if(m_pBackground != NULL) +// delete m_pBackground; +// if(m_pBackgroundLabel != NULL) +// delete m_pBackgroundLabel; + + Panel::~Panel(); + } void Update( void ); diff --git a/cl_dll/vgui_ServerBrowser.cpp b/cl_dll/vgui_ServerBrowser.cpp index 21d723e..ed01835 100644 --- a/cl_dll/vgui_ServerBrowser.cpp +++ b/cl_dll/vgui_ServerBrowser.cpp @@ -17,7 +17,8 @@ #include "hud_servers.h" #include "net_api.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" #include "vgui_ServerBrowser.h" using namespace vgui; diff --git a/cl_dll/vgui_TheWastesViewport.cpp b/cl_dll/vgui_TheWastesViewport.cpp new file mode 100644 index 0000000..56e13a3 --- /dev/null +++ b/cl_dll/vgui_TheWastesViewport.cpp @@ -0,0 +1,1912 @@ +//=========== (C) Copyright 1996-2001 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Client DLL VGUI Viewport +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" +#include "pm_shared.h" +#include "../engine/keydefs.h" +#include "demo.h" +#include "demo_api.h" + +#include "tw_vgui.h" +#include "vgui_int.h" +#include "vgui_TheWastesViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_ScorePanel.h" +#include "tw_common.h" + +extern int g_iVisibleMouse; +class CCommandMenu; +int g_iPlayerClass; +int g_iTeamNumber; +int g_iUser1; +int g_iUser2; +int g_iUser3; + +// Scoreboard positions +#define SBOARD_INDENT_X XRES(104) +#define SBOARD_INDENT_Y YRES(40) + +// low-res scoreboard indents +#define SBOARD_INDENT_X_512 30 +#define SBOARD_INDENT_Y_512 30 + +#define SBOARD_INDENT_X_400 0 +#define SBOARD_INDENT_Y_400 20 + +void IN_ResetMouse( void ); +extern CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int iTextType, int x, int y, int wide, int tall ); +extern CMenuPanel *CItemSelectionPanel_Create(int x,int y,int wide,int tall); + +using namespace vgui; + +// Team Colors +int iNumberOfTeamColors = 6; + +int iTeamColors[5][3] = +{ + { 255, 255, 255 }, // (default) + { 125, 165, 210 }, // Blue + { 200, 90, 70 }, // Red + { 225, 205, 45 }, // Yellow + { 145, 215, 140 }, // Green +}; + +// This maps class numbers to the Invalid Class bit. +// This is needed for backwards compatability in maps that were finished before +// all the classes were in TF. Hence the wacky sequence. +int sTFValidClassInts[] = +{ + 0, + TF_ILL_SCOUT, + TF_ILL_SNIPER, + TF_ILL_SOLDIER, + TF_ILL_DEMOMAN, + TF_ILL_MEDIC, + TF_ILL_HVYWEP, + TF_ILL_PYRO, + TF_ILL_SPY, + TF_ILL_ENGINEER, + TF_ILL_RANDOMPC, +}; + +// Get the name of TGA file, based on GameDir +char* GetVGUITGAName(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + const char *gamedir; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + + gamedir = gEngfuncs.pfnGetGameDirectory(); + sprintf(gd, "%s/gfx/vgui/%s.tga",gamedir,sz); + + return gd; +} + +//================================================================ +// COMMAND MENU +//================================================================ +void CCommandMenu::AddButton( CommandButton *pButton ) +{ + if (m_iButtons >= MAX_BUTTONS) + return; + + m_aButtons[m_iButtons] = pButton; + m_iButtons++; + pButton->setParent( this ); + pButton->setFont( Scheme::sf_primary3 ); + + // give the button a default key binding + if ( m_iButtons < 10 ) + { + pButton->setBoundKey( m_iButtons + '0' ); + } + else if ( m_iButtons == 10 ) + { + pButton->setBoundKey( '0' ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tries to find a button that has a key bound to the input, and +// presses the button if found +// Input : keyNum - the character number of the input key +// Output : Returns true if the command menu should close, false otherwise +//----------------------------------------------------------------------------- +bool CCommandMenu::KeyInput( int keyNum ) +{ + // loop through all our buttons looking for one bound to keyNum + for ( int i = 0; i < m_iButtons; i++ ) + { + if ( !m_aButtons[i]->IsNotValid() ) + { + if ( m_aButtons[i]->getBoundKey() == keyNum ) + { + // hit the button + if ( m_aButtons[i]->GetSubMenu() ) + { + // open the sub menu + gViewPort->SetCurrentCommandMenu( m_aButtons[i]->GetSubMenu() ); + return false; + } + else + { + // run the bound command + m_aButtons[i]->fireActionSignal(); + return true; + } + } + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: clears the current menus buttons of any armed (highlighted) +// state, and all their sub buttons +//----------------------------------------------------------------------------- +void CCommandMenu::ClearButtonsOfArmedState( void ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + m_aButtons[i]->setArmed( false ); + + if ( m_aButtons[i]->GetSubMenu() ) + { + m_aButtons[i]->GetSubMenu()->ClearButtonsOfArmedState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSubMenu - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *CCommandMenu::FindButtonWithSubmenu( CCommandMenu *pSubMenu ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + if ( m_aButtons[i]->GetSubMenu() == pSubMenu ) + return m_aButtons[i]; + } + + return NULL; +} + +// Recalculate the visible buttons +bool CCommandMenu::RecalculateVisibles( int iNewYPos, bool bHideAll ) +{ + int iCurrentY = 0; + int iXPos, iYPos; + bool bHasButton = false; + + if (iNewYPos) + setPos( _pos[0], iNewYPos ); + + // Cycle through all the buttons in this menu, and see which will be visible + for (int i = 0; i < m_iButtons; i++) + { + int iClass = m_aButtons[i]->GetPlayerClass(); + if ( (iClass && iClass != g_iPlayerClass ) || ( m_aButtons[i]->IsNotValid() ) || bHideAll ) + { + m_aButtons[i]->setVisible( false ); + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( _pos[1] + iCurrentY, true ); + } + } + else + { + // If it's got a submenu, force it to check visibilities + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + if ( !(m_aButtons[i]->GetSubMenu())->RecalculateVisibles( _pos[1] + iCurrentY, false ) ) + { + // The submenu had no visible buttons, so don't display this button + m_aButtons[i]->setVisible( false ); + continue; + } + } + + m_aButtons[i]->setVisible( true ); + + // Make sure it's at the right Y position + m_aButtons[i]->getPos( iXPos, iYPos ); + m_aButtons[i]->setPos( iXPos, iCurrentY ); + + iCurrentY += BUTTON_SIZE_Y - 1; + bHasButton = true; + } + } + + // Set Size + setSize( _size[0], iCurrentY + 1 ); + + return bHasButton; +} + +// Make sure all submenus can fit on the screen +void CCommandMenu::RecalculatePositions( int iYOffset ) +{ + int iNewYPos = _pos[1] + iYOffset; + int iAdjust = 0; + + // Calculate if this is going to fit onscreen, and shuffle it up if it won't + int iBottom = iNewYPos + _size[1]; + if ( iBottom > ScreenHeight ) + { + // Move in increments of button sizes + while (iAdjust < (iBottom - ScreenHeight)) + { + iAdjust += BUTTON_SIZE_Y - 1; + } + iNewYPos -= iAdjust; + + // Make sure it doesn't move off the top of the screen (the menu's too big to fit it all) + if ( iNewYPos < 0 ) + { + iAdjust -= (0 - iNewYPos); + iNewYPos = 0; + } + } + + // We need to force all menus below this one to update their positions now, because they + // might have submenus riding off buttons in this menu that have just shifted. + for (int i = 0; i < m_iButtons; i++) + m_aButtons[i]->UpdateSubMenus( iAdjust ); + + setPos( _pos[0], iNewYPos ); +} + + +// Make this menu and all menus above it in the chain visible +void CCommandMenu::MakeVisible( CCommandMenu *pChildMenu ) +{ +/* + // Push down the button leading to the child menu + for (int i = 0; i < m_iButtons; i++) + { + if ( (pChildMenu != NULL) && (m_aButtons[i]->GetSubMenu() == pChildMenu) ) + { + m_aButtons[i]->setArmed( true ); + } + else + { + m_aButtons[i]->setArmed( false ); + } + } +*/ + + setVisible(true); + if (m_pParentMenu) + m_pParentMenu->MakeVisible( this ); +} + +//================================================================ +// CreateSubMenu +CCommandMenu *TheWastesViewport::CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu ) +{ + int iXPos = 0; + int iYPos = 0; + int iWide = CMENU_SIZE_X; + int iTall = 0; + + if (pParentMenu) + { + iXPos = pParentMenu->GetXOffset() + CMENU_SIZE_X - 1; + iYPos = pParentMenu->GetYOffset() + BUTTON_SIZE_Y * (m_pCurrentCommandMenu->GetNumButtons() - 1); + } + + CCommandMenu *pMenu = new CCommandMenu(pParentMenu, iXPos, iYPos, iWide, iTall ); + pMenu->setParent(this); + pButton->AddSubMenu( pMenu ); + pButton->setFont( Scheme::sf_primary3 ); + + // Create the Submenu-open signal + InputSignal *pISignal = new CMenuHandler_PopupSubMenuInput(pButton, pMenu); + pButton->addInputSignal(pISignal); + + // Put a > to show it's a submenu + CImageLabel *pLabel = new CImageLabel( "arrow", CMENU_SIZE_X - SUBMENU_SIZE_X, SUBMENU_SIZE_Y ); + pLabel->setParent(pButton); + pLabel->addInputSignal(pISignal); + + // Reposition + pLabel->getPos( iXPos, iYPos ); + pLabel->setPos( CMENU_SIZE_X - pLabel->getImageWide(), (BUTTON_SIZE_Y - pLabel->getImageTall()) / 2 ); + + // Create the mouse off signal for the Label too + if (!pButton->m_bNoHighlight) + pLabel->addInputSignal( new CHandler_CommandButtonHighlight(pButton) ); + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Makes sure the memory allocated for TheWastesViewport is nulled out +// Input : stAllocateBlock - +// Output : void * +//----------------------------------------------------------------------------- +void *TheWastesViewport::operator new( size_t stAllocateBlock ) +{ +// void *mem = Panel::operator new( stAllocateBlock ); + void *mem = ::operator new( stAllocateBlock ); + memset( mem, 0, stAllocateBlock ); + return mem; +} + +//----------------------------------------------------------------------------- +// Purpose: InputSignal handler for the main viewport +//----------------------------------------------------------------------------- +class CViewPortInputHandler : public InputSignal +{ +public: + bool bPressed; + + CViewPortInputHandler() + { + } + + virtual void cursorMoved(int x,int y,Panel* panel) {} + virtual void cursorEntered(Panel* panel) {} + virtual void cursorExited(Panel* panel) {} + virtual void mousePressed(MouseCode code,Panel* panel) + { + if ( code != MOUSE_LEFT ) + { + // send a message to close the command menu + // this needs to be a message, since a direct call screws the timing + gEngfuncs.pfnClientCmd( "ForceCloseCommandMenu\n" ); + } + } + virtual void mouseReleased(MouseCode code,Panel* panel) + { + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {} + virtual void mouseWheeled(int delta,Panel* panel) {} + virtual void keyPressed(KeyCode code,Panel* panel) {} + virtual void keyTyped(KeyCode code,Panel* panel) {} + virtual void keyReleased(KeyCode code,Panel* panel) {} + virtual void keyFocusTicked(Panel* panel) {} +}; + + +//================================================================ +TheWastesViewport::TheWastesViewport(int x,int y,int wide,int tall) : Panel(x,y,wide,tall), m_SchemeManager(wide,tall) +{ + gViewPort = this; + m_iInitialized = false; + m_flGameTimeDelay = 0.0f; + m_pTeamMenu = NULL; + m_pScoreBoard = NULL; + m_pSpectatorMenu = NULL; + m_pCurrentMenu = NULL; + m_pCurrentCommandMenu = NULL; + m_pGameInfoMenu = NULL; + + Initialize(); + addInputSignal( new CViewPortInputHandler ); + + int r, g, b, a; + + Scheme* pScheme = App::getInstance()->getScheme(); + + // primary text color + // Get the colors + //!! two different types of scheme here, need to integrate + SchemeHandle_t hPrimaryScheme = m_SchemeManager.getSchemeHandle( "Primary Button Text" ); + { + // font + pScheme->setFont( Scheme::sf_primary1, m_SchemeManager.getFont(hPrimaryScheme) ); + + // text color + m_SchemeManager.getFgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary1, r, g, b, a ); // sc_primary1 is non-transparent orange + + // background color (transparent black) + m_SchemeManager.getBgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary3, r, g, b, a ); + + // armed foreground color + m_SchemeManager.getFgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary2, r, g, b, a ); + + // armed background color + m_SchemeManager.getBgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary2, r, g, b, a ); + + //!! need to get this color from scheme file + // used for orange borders around buttons + m_SchemeManager.getBorderColor( hPrimaryScheme, r, g, b, a ); + // pScheme->setColor(Scheme::sc_secondary1, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary1, 255*0.7, 170*0.7, 0, 0); + } + + // Change the second primary font (used in the scoreboard) + SchemeHandle_t hScoreboardScheme = m_SchemeManager.getSchemeHandle( "Scoreboard Text" ); + { + pScheme->setFont(Scheme::sf_primary2, m_SchemeManager.getFont(hScoreboardScheme) ); + } + + // Change the third primary font (used in command menu) + SchemeHandle_t hCommandMenuScheme = m_SchemeManager.getSchemeHandle( "CommandMenu Text" ); + { + pScheme->setFont(Scheme::sf_primary3, m_SchemeManager.getFont(hCommandMenuScheme) ); + } + + App::getInstance()->setScheme(pScheme); + + // VGUI MENUS + CreateTeamMenu(); + CreateScoreBoard(); + CreateCommandMenu(); + CreateServerBrowser(); + CreateSpectatorMenu(); + CreateGameInfoMenu(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime a new level is started. Viewport clears out it's data. +//----------------------------------------------------------------------------- +void TheWastesViewport::Initialize( void ) +{ + // Force each menu to Initialize + if (m_pTeamMenu) + { + m_pTeamMenu->Initialize(); + } + if (m_pScoreBoard) + { + m_pScoreBoard->Initialize(); + HideScoreBoard(); + } + if (m_pSpectatorMenu) + { + // Spectator menu doesn't need initializing + m_pSpectatorMenu->setVisible( false ); + } + if( m_pGameInfoMenu ) + { + m_pGameInfoMenu->setVisible( false ); + } + + // Make sure all menus are hidden + HideVGUIMenu(); + HideCommandMenu(); + + // Clear out some data + m_iGotAllMOTD = true; + m_iRandomPC = false; + m_flScoreBoardLastUpdated = 0; + + // reset player info + g_iPlayerClass = 0; + g_iTeamNumber = 0; + + strcpy(m_sMapName, ""); + strcpy(m_szServerName, ""); + for (int i = 0; i < 5; i++) + { + m_iValidClasses[i] = 0; + strcpy(m_sTeamNames[i], ""); + } + + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_none) ); +} + +class CException; +//----------------------------------------------------------------------------- +// Purpose: Read the Command Menu structure from the txt file and create the menu. +//----------------------------------------------------------------------------- +void TheWastesViewport::CreateCommandMenu( void ) +{ + // COMMAND MENU + // Create the root of the Command Menu + m_pCommandMenus[0] = new CCommandMenu(NULL, 0, CMENU_TOP, CMENU_SIZE_X, 300); // This will be resized once we know how many items are in it + m_pCommandMenus[0]->setParent(this); + m_pCommandMenus[0]->setVisible(false); + m_iNumMenus = 1; + m_iCurrentTeamNumber = m_iUser1 = m_iUser2 = m_iUser3 = 0; + + // Read Command Menu from the txt file + char token[1024]; + char *pfile = (char*)gEngfuncs.COM_LoadFile("commandmenu.txt", 5, NULL); + if (!pfile) + { + gEngfuncs.Con_DPrintf( "Unable to open commandmenu.txt\n"); + SetCurrentCommandMenu( NULL ); + return; + } + +try +{ + // First, read in the localisation strings + + // Now start parsing the menu structure + m_pCurrentCommandMenu = m_pCommandMenus[0]; + char szLastButtonText[32] = "file start"; + pfile = gEngfuncs.COM_ParseFile(pfile, token); + while ( ( strlen ( token ) > 0 ) && ( m_iNumMenus < MAX_MENUS ) ) + { + // Keep looping until we hit the end of this menu + while ( token[0] != '}' && ( strlen( token ) > 0 ) ) + { + char cText[32] = ""; + char cBoundKey[32] = ""; + char cCustom[32] = ""; + static const int cCommandLength = 128; + char cCommand[cCommandLength] = ""; + char szMap[MAX_MAPNAME] = ""; + int iPlayerClass = 0; + int iCustom = false; + int iTeamOnly = 0; + bool bGetExtraToken = true; + CommandButton *pButton = NULL; + + // We should never be here without a Command Menu + if (!m_pCurrentCommandMenu) + { + gEngfuncs.Con_Printf("Error in Commandmenu.txt file after '%s'.\n", szLastButtonText ); + m_iInitialized = false; + return; + } + + // token should already be the bound key, or the custom name + strncpy( cCustom, token, 32 ); + cCustom[31] = '\0'; + + // See if it's a custom button + if (!strcmp(cCustom, "CUSTOM") ) + { + iCustom = true; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + // See if it's a map + else if (!strcmp(cCustom, "MAP") ) + { + // Get the mapname + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( szMap, token, MAX_MAPNAME ); + szMap[MAX_MAPNAME-1] = '\0'; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else if ( !strncmp(cCustom, "TEAM", 4) ) // TEAM1, TEAM2, TEAM3, TEAM4 + { + // make it a team only button + iTeamOnly = atoi( cCustom + 4 ); + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + /* else + { + // See if it's a Class + for (int i = 1; i <= PC_ENGINEER; i++) + { + if ( !strcmp(token, sTFClasses[i]) ) + { + // Save it off + iPlayerClass = i; + + // Get the button text + pfile = gEngfuncs.COM_ParseFile(pfile, token); + break; + } + } + } +*/ + // Get the button bound key + strncpy( cBoundKey, token, 32 ); + cText[31] = '\0'; + + // Get the button text + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cText, token, 32 ); + cText[31] = '\0'; + + // save off the last button text we've come across (for error reporting) + strcpy( szLastButtonText, cText ); + + // Get the button command + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cCommand, token, cCommandLength ); + cCommand[cCommandLength - 1] = '\0'; + + // Custom button handling + if ( iCustom ) + { + pButton = CreateCustomButton( cText, cCommand ); + + // Get the next token to see if we're a menu + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if ( token[0] == '{' ) + { + strcpy( cCommand, token ); + } + else + { + bGetExtraToken = false; + } + } + else if ( szMap[0] != '\0' ) + { + // create a map button + pButton = new MapButton(szMap, cText,0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y); + } + else if ( iTeamOnly ) + { + // button that only shows up if the player is on team iTeamOnly + pButton = new TeamOnlyCommandButton( iTeamOnly, cText,0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + else + { + // normal button + pButton = new CommandButton( iPlayerClass, cText,0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + + // add the button into the command menu + if ( pButton ) + { + m_pCurrentCommandMenu->AddButton( pButton ); + pButton->setBoundKey( cBoundKey[0] ); + pButton->setParentMenu( m_pCurrentCommandMenu ); + + // Override font in CommandMenu + pButton->setFont( Scheme::sf_primary3 ); + } + + // Find out if it's a submenu or a button we're dealing with + if ( cCommand[0] == '{' ) + { + if ( m_iNumMenus >= MAX_MENUS ) + { + gEngfuncs.Con_Printf( "Too many menus in commandmenu.txt past '%s'\n", szLastButtonText ); + } + else + { + // Create the menu + m_pCommandMenus[m_iNumMenus] = CreateSubMenu(pButton, m_pCurrentCommandMenu); + m_pCurrentCommandMenu = m_pCommandMenus[m_iNumMenus]; + m_iNumMenus++; + } + } + else if ( !iCustom ) + { + // Create the button and attach it to the current menu + pButton->addActionSignal(new CMenuHandler_StringCommand(cCommand)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + // Get the next token + if ( bGetExtraToken ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + } + + // Move back up a menu + m_pCurrentCommandMenu = m_pCurrentCommandMenu->GetParentMenu(); + + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } +} +catch( CException *e ) +{ + e; + //e->Delete(); + e = NULL; + m_iInitialized = false; + return; +} + + SetCurrentMenu( NULL ); + SetCurrentCommandMenu( NULL ); + gEngfuncs.COM_FreeFile( pfile ); + + m_iInitialized = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pButtonText - +// *pButtonName - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *TheWastesViewport::CreateCustomButton( char *pButtonText, char *pButtonName ) +{ + CommandButton *pButton = NULL; + CCommandMenu *pMenu = NULL; + + // ChangeTeam + if ( !strcmp( pButtonName, "!CHANGETEAM" ) ) + { + // ChangeTeam Submenu + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + + // Create the submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // ChangeTeam buttons + for (int i = 0; i < 4; i++) + { + char sz[256]; + sprintf(sz, "jointeam %d", i+1); + m_pTeamButtons[i] = new TeamButton(i+1, "teamname", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[i]->addActionSignal(new CMenuHandler_StringCommandWatch( sz )); + pMenu->AddButton( m_pTeamButtons[i] ); + } + + // Auto Assign button + m_pTeamButtons[4] = new TeamButton(5, gHUD.m_TextMessage.BufferedLocaliseTextString( "#Team_AutoAssign" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[4]->addActionSignal(new CMenuHandler_StringCommand( "jointeam 5" )); + pMenu->AddButton( m_pTeamButtons[4] ); + + // Spectate button + m_pTeamButtons[5] = new SpectateButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Spectate" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + m_pTeamButtons[5]->addActionSignal(new CMenuHandler_StringCommand( "spectate" )); + pMenu->AddButton( m_pTeamButtons[5] ); + } + // Map Briefing + else if ( !strcmp( pButtonName, "!MAPBRIEFING" ) ) + { + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_MAPBRIEFING)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!SERVERINFO" ) ) + { + pButton = new ClassButton(0, pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y, false); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_INTRO)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + return pButton; +} + +void TheWastesViewport::ToggleServerBrowser() +{ + if (!m_iInitialized) + return; + + if ( !m_pServerBrowser ) + return; + + if ( m_pServerBrowser->isVisible() ) + { + m_pServerBrowser->setVisible( false ); + } + else + { + m_pServerBrowser->setVisible( true ); + } + + UpdateCursorState(); +} + +//======================================================================= +void TheWastesViewport::ShowCommandMenu() +{ + if (!m_iInitialized) + return; + + // Not visible while undefined + if (g_iPlayerClass == 0) + return; + + // is the command menu open? + if ( m_pCurrentCommandMenu ) + { + HideCommandMenu(); + return; + } + + // Not visible while in intermission + if ( gHUD.m_iIntermission ) + return; + + // Recalculate visible menus + UpdateCommandMenu(); + HideVGUIMenu(); + + SetCurrentCommandMenu( m_pCommandMenus[0] ); + m_flMenuOpenTime = gHUD.m_flTime; + UpdateCursorState(); + + // get command menu parameters + for ( int i = 2; i < gEngfuncs.Cmd_Argc(); i++ ) + { + const char *param = gEngfuncs.Cmd_Argv( i - 1 ); + if ( param ) + { + if ( m_pCurrentCommandMenu->KeyInput(param[0]) ) + { + // kill the menu open time, since the key input is final + HideCommandMenu(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles the key input of "-commandmenu" +// Input : +//----------------------------------------------------------------------------- +void TheWastesViewport::InputSignalHideCommandMenu() +{ + if (!m_iInitialized) + return; + + // if they've just tapped the command menu key, leave it open + if ( (m_flMenuOpenTime + 0.3) > gHUD.m_flTime ) + return; + + HideCommandMenu(); +} + +//----------------------------------------------------------------------------- +// Purpose: Hides the command menu +//----------------------------------------------------------------------------- +void TheWastesViewport::HideCommandMenu( void ) +{ + if (!m_iInitialized) + return; + + if ( m_pCommandMenus[0] ) + { + m_pCommandMenus[0]->ClearButtonsOfArmedState(); + } + + m_flMenuOpenTime = 0.0f; + SetCurrentCommandMenu( NULL ); + UpdateCursorState(); +} + +//----------------------------------------------------------------------------- +// Purpose: Bring up the scoreboard +//----------------------------------------------------------------------------- +void TheWastesViewport::ShowScoreBoard( void ) +{ + if (m_pScoreBoard) + { + // No Scoreboard in single-player + if ( gEngfuncs.GetMaxClients() > 1 ) + { + m_pScoreBoard->Open(); + UpdateCursorState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the scoreboard is up +//----------------------------------------------------------------------------- +bool TheWastesViewport::IsScoreBoardVisible( void ) +{ + if (m_pScoreBoard) + return m_pScoreBoard->isVisible(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the scoreboard +//----------------------------------------------------------------------------- +void TheWastesViewport::HideScoreBoard( void ) +{ + // Prevent removal of scoreboard during intermission + if ( gHUD.m_iIntermission ) + return; + + if (m_pScoreBoard) + { + m_pScoreBoard->setVisible(false); + + GetClientVoiceMgr()->StopSquelchMode(); + + UpdateCursorState(); + } +} + +// Set the submenu of the Command Menu +void TheWastesViewport::SetCurrentCommandMenu( CCommandMenu *pNewMenu ) +{ + for (int i = 0; i < m_iNumMenus; i++) + m_pCommandMenus[i]->setVisible(false); + + m_pCurrentCommandMenu = pNewMenu; + + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TheWastesViewport::UpdateCommandMenu() +{ + m_pCommandMenus[0]->RecalculateVisibles( 0, false ); + m_pCommandMenus[0]->RecalculatePositions( 0 ); +} + +void TheWastesViewport::UpdateSpectatorMenu() +{ + char helpString1[128]; + char helpString2[128]; + int mode; + + m_iUser1 = g_iUser1; + m_iUser2 = g_iUser2; + m_iUser3 = g_iUser3; + + if (!m_pSpectatorMenu) + return; + + if ( gEngfuncs.IsSpectateOnly() ) + { + mode = gHUD.m_Spectator.m_iMainMode; // spec mode is set client side + + sprintf(helpString2, "#Spec_Only_Help"); + } + else + { + // spec mode is given by server + mode = m_iUser1; + // set normal help text + sprintf(helpString2, "#Spec_Help" ); + } + + if ( mode && ( gEngfuncs.IsSpectateOnly() != 2) ) // don't draw in dev_overview mode + { + m_pSpectatorMenu->setVisible( true ); + + // check if we're locked onto a target, show the player's name + if ( (m_iUser2 > 0) && (m_iUser2 <= gEngfuncs.GetMaxClients()) ) + { + // Locked onto a target, show the player's name + + + if ( g_PlayerInfoList[ m_iUser2 ].name != NULL ) + sprintf(helpString1, "#Spec_Mode%d : %s", mode, g_PlayerInfoList[ m_iUser2 ].name ); + + else + sprintf(helpString1, "#Spec_Mode%d", mode ); + } + else + { + sprintf(helpString1, "#Spec_Mode%d", mode); + } + + if ( m_iUser1 == OBS_DIRECTED ) + { + char tempString[128]; + sprintf(tempString, "#Directed %s", helpString1); + strcpy( helpString1, tempString ); + } + + m_pSpectatorLabel->setText( CHudTextMessage::BufferedLocaliseTextString( helpString1 ) ); + m_pSpectatorHelpLabel->setText( CHudTextMessage::BufferedLocaliseTextString( helpString2 ) ); + } + else + { + m_pSpectatorMenu->setVisible( false ); + } +} + +void TheWastesViewport::UpdateGameInfoMenu() +{ + if( !m_pGameInfoMenu ) + return; + + if( gHUD.m_flTime > m_flGameTimeDelay ) + { + m_pGameInfoMenu->setVisible( false ); + return; + } + + m_pGameInfoMenu->setVisible( true ); + + // Update time + m_pGameTimeLabel->setText( "%s %1.0f", CHudTextMessage::BufferedLocaliseTextString( "#GameInfo_LMS_RoundDelay" ), + m_flGameTimeDelay - gHUD.m_flTime ); +} + +//====================================================================== +void TheWastesViewport::CreateScoreBoard( void ) +{ + int xdent = SBOARD_INDENT_X, ydent = SBOARD_INDENT_Y; + if (ScreenWidth == 512) + { + xdent = SBOARD_INDENT_X_512; + ydent = SBOARD_INDENT_Y_512; + } + else if (ScreenWidth == 400) + { + xdent = SBOARD_INDENT_X_400; + ydent = SBOARD_INDENT_Y_400; + } + + m_pScoreBoard = new ScorePanel(xdent, ydent, ScreenWidth - (xdent * 2), ScreenHeight - (ydent * 2)); + m_pScoreBoard->setParent(this); + m_pScoreBoard->setVisible(false); +} + +void TheWastesViewport::CreateServerBrowser( void ) +{ + m_pServerBrowser = new ServerBrowser( 0, 0, ScreenWidth, ScreenHeight ); + m_pServerBrowser->setParent(this); + m_pServerBrowser->setVisible(false); +} + +//====================================================================== +// Set the VGUI Menu +void TheWastesViewport::SetCurrentMenu( CMenuPanel *pMenu ) +{ + m_pCurrentMenu = pMenu; + if ( m_pCurrentMenu ) + { + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + m_pCurrentMenu->Open(); + } +} + +//================================================================ +// Text Window +CMenuPanel* TheWastesViewport::CreateTextWindow( int iTextToShow ) +{ + char sz[256]; + char *cText; + char *pfile = NULL; + static const int MAX_TITLE_LENGTH = 32; + char cTitle[MAX_TITLE_LENGTH]; + + if ( iTextToShow == SHOW_MOTD ) + { + if (!m_szServerName || !m_szServerName[0]) + strcpy( cTitle, "The Wastes" ); + else + strncpy( cTitle, m_szServerName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + cText = m_szMOTD; + } + else if ( iTextToShow == SHOW_MAPBRIEFING ) + { + // Get the current mapname, and open it's map briefing text + if (m_sMapName && m_sMapName[0]) + { + strcpy( sz, "maps/"); + strcat( sz, m_sMapName ); + strcat( sz, ".txt" ); + } + else + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return NULL; + + strcpy( sz, level ); + char *ch = strchr( sz, '.' ); + *ch = '\0'; + strcat( sz, ".txt" ); + + // pull out the map name + strcpy( m_sMapName, level ); + ch = strchr( m_sMapName, '.' ); + if ( ch ) + { + *ch = 0; + } + + ch = strchr( m_sMapName, '/' ); + if ( ch ) + { + // move the string back over the '/' + memmove( m_sMapName, ch+1, strlen(ch)+1 ); + } + } + + pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + + // Return dummy stuff + if (!pfile) + { + CMenuPanel *pMOTDPanel = CMessageWindowPanel_Create("Just another area decimated by fallout\nand radiation. Happy hunting!",gEngfuncs.pfnGetLevelName(),1,false,iTextToShow,0,0,ScreenWidth,ScreenHeight); + pMOTDPanel->setParent(this); + + return pMOTDPanel; + } + + cText = pfile; + + strncpy( cTitle, m_sMapName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + } + else if( iTextToShow == SHOW_ITEMSELECTION) + { + CMenuPanel *pRetPanel = CItemSelectionPanel_Create(0,0,ScreenWidth,ScreenHeight); + pRetPanel->setParent(this); + + return pRetPanel; + } + + // if we're in the game (ie. have selected a class), flag the menu to be only grayed in the dialog box, instead of full screen + CMenuPanel *pMOTDPanel = CMessageWindowPanel_Create( cText, cTitle, 1,false, iTextToShow, 0, 0, ScreenWidth, ScreenHeight ); + pMOTDPanel->setParent( this ); + + if ( pfile ) + gEngfuncs.COM_FreeFile( pfile ); + + return pMOTDPanel; +} + +//================================================================ +// VGUI Menus +void TheWastesViewport::ShowVGUIMenu( int iMenu ) +{ + CMenuPanel *pNewMenu = NULL; + + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + // Don't open any menus except the MOTD during intermission + // MOTD needs to be accepted because it's sent down to the client + // after map change, before intermission's turned off + if ( gHUD.m_iIntermission && iMenu != MENU_INTRO ) + return; + + // Don't create one if it's already in the list + if (m_pCurrentMenu) + { + CMenuPanel *pMenu = m_pCurrentMenu; + while (pMenu != NULL) + { + if (pMenu->GetMenuID() == iMenu) + return; + pMenu = pMenu->GetNextMenu(); + } + } + + switch ( iMenu ) + { + case MENU_TEAM: + pNewMenu = ShowTeamMenu(); + break; + + // Map Briefing removed now that it appears in the team menu + case MENU_MAPBRIEFING: + pNewMenu = CreateTextWindow( SHOW_MAPBRIEFING ); + break; + + case MENU_INTRO: + pNewMenu = CreateTextWindow( SHOW_MOTD ); + break; + + case MENU_ITEMSELECTION: + pNewMenu = CreateTextWindow( SHOW_ITEMSELECTION ); + break; + + default: + break; + } + + if (!pNewMenu) + return; + + // Close the Command Menu if it's open + HideCommandMenu(); + + pNewMenu->SetMenuID( iMenu ); + pNewMenu->SetActive( true ); + pNewMenu->setParent(this); + + // See if another menu is visible, and if so, cache this one for display once the other one's finished + if (m_pCurrentMenu) + { + m_pCurrentMenu->SetNextMenu( pNewMenu ); + } + else + { + m_pCurrentMenu = pNewMenu; + m_pCurrentMenu->Open(); + UpdateCursorState(); + } +} + +// Removes all VGUI Menu's onscreen +void TheWastesViewport::HideVGUIMenu() +{ + while (m_pCurrentMenu) + { + HideTopMenu(); + } +} + +// Remove the top VGUI menu, and bring up the next one +void TheWastesViewport::HideTopMenu() +{ + if (m_pCurrentMenu) + { + // Close the top one + m_pCurrentMenu->Close(); + + // Bring up the next one + gViewPort->SetCurrentMenu( m_pCurrentMenu->GetNextMenu() ); + } + + UpdateCursorState(); +} + +// Return TRUE if the HUD's allowed to print text messages +bool TheWastesViewport::AllowedToPrintText( void ) +{ + // Prevent text messages when fullscreen menus are up + if ( m_pCurrentMenu && g_iPlayerClass == 0 ) + { + int iId = m_pCurrentMenu->GetMenuID(); + if ( iId == MENU_TEAM || iId == MENU_INTRO ) + return FALSE; + } + + return TRUE; +} + +//====================================================================================== +// TEAM MENU +//====================================================================================== +// Bring up the Team selection Menu +CMenuPanel* TheWastesViewport::ShowTeamMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + + m_pTeamMenu->Reset(); + return m_pTeamMenu; +} + +void TheWastesViewport::CreateTeamMenu() +{ + // Create the panel + m_pTeamMenu = new CTeamMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pTeamMenu->setParent( this ); + m_pTeamMenu->setVisible( false ); +} + +void TheWastesViewport::CreateGameInfoMenu() +{ + m_pGameInfoMenu = new CTransparentPanel( 100, 0, 0, ScreenWidth, YRES(60) ); + m_pGameInfoMenu->setParent( this ); + m_pGameInfoMenu->setVisible( false ); + + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hHelpText = pSchemes->getSchemeHandle( "Primary Button Text" ); + + // color schemes + int r, g, b, a; + + // Create the title + m_pGameInfoLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#GameInfo_LMS_Title" ), 0, 0, ScreenWidth, YRES(30) ); + m_pGameInfoLabel->setParent( m_pGameInfoMenu ); + m_pGameInfoLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + m_pGameInfoLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + m_pGameInfoLabel->setBgColor( r, g, b, 255 ); + m_pGameInfoLabel->setContentAlignment( vgui::Label::a_north ); + + // Create the Help + m_pGameTimeLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#GameInfo_LMS_RoundDelay" ), 0, YRES(35), ScreenWidth, YRES(15) ); + m_pGameTimeLabel->setParent( m_pGameInfoMenu ); + m_pGameTimeLabel->setFont( pSchemes->getFont(hHelpText) ); + pSchemes->getFgColor( hHelpText, r, g, b, a ); + m_pGameTimeLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hHelpText, r, g, b, a ); + m_pGameTimeLabel->setBgColor( r, g, b, 255 ); + m_pGameTimeLabel->setContentAlignment( vgui::Label::a_north ); +} + +//====================================================================================== +// SPECTATOR MENU +//====================================================================================== +// Spectator "Menu" explaining the Spectator buttons +void TheWastesViewport::CreateSpectatorMenu() +{ + // Create the Panel + m_pSpectatorMenu = new CTransparentPanel(100, 0, ScreenHeight - YRES(60), ScreenWidth, YRES(60)); + m_pSpectatorMenu->setParent(this); + m_pSpectatorMenu->setVisible(false); + + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hHelpText = pSchemes->getSchemeHandle( "Primary Button Text" ); + + // color schemes + int r, g, b, a; + + // Create the title + m_pSpectatorLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Title" ), 0, 0, ScreenWidth, YRES(25) ); + m_pSpectatorLabel->setParent( m_pSpectatorMenu ); + m_pSpectatorLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + m_pSpectatorLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + m_pSpectatorLabel->setBgColor( r, g, b, 255 ); + m_pSpectatorLabel->setContentAlignment( vgui::Label::a_north ); + + // Create the Help + m_pSpectatorHelpLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help" ), 0, YRES(25), ScreenWidth, YRES(15) ); + m_pSpectatorHelpLabel->setParent( m_pSpectatorMenu ); + m_pSpectatorHelpLabel->setFont( pSchemes->getFont(hHelpText) ); + pSchemes->getFgColor( hHelpText, r, g, b, a ); + m_pSpectatorHelpLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hHelpText, r, g, b, a ); + m_pSpectatorHelpLabel->setBgColor( r, g, b, 255 ); + m_pSpectatorHelpLabel->setContentAlignment( vgui::Label::a_north ); + +/* Label *pLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help2" ), 0, YRES(40), ScreenWidth, YRES(20) ); + pLabel->setParent( m_pSpectatorMenu ); + pLabel->setFont( pSchemes->getFont(hHelpText) ); + pSchemes->getFgColor( hHelpText, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hHelpText, r, g, b, a ); + pLabel->setBgColor( r, g, b, 255 ); + pLabel->setContentAlignment( vgui::Label::a_center ); +*/ +} + +//====================================================================================== +// UPDATE HUD SECTIONS +//====================================================================================== +// We've got an update on player info +// Recalculate any menus that use it. +void TheWastesViewport::UpdateOnPlayerInfo() +{ + if (m_pTeamMenu) + m_pTeamMenu->Update(); + if (m_pScoreBoard) + m_pScoreBoard->Update(); +} + +void TheWastesViewport::UpdateCursorState() +{ + // Need cursor if any VGUI window is up + if ( m_pCurrentMenu || m_pTeamMenu->isVisible() || m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_arrow) ); + return; + } + else if ( m_pCurrentCommandMenu ) + { + // commandmenu doesn't have cursor if hud_capturemouse is turned off + if ( gHUD.m_pCvarStealMouse->value != 0.0f ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_arrow) ); + return; + } + } + + IN_ResetMouse(); + g_iVisibleMouse = false; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_none) ); +} + +void TheWastesViewport::UpdateHighlights() +{ + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TheWastesViewport::GetAllPlayersInfo( void ) +{ + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + GetPlayerInfo( i, &g_PlayerInfoList[i] ); + + if ( g_PlayerInfoList[i].thisplayer ) + m_pScoreBoard->m_iPlayerNum = i; // !!!HACK: this should be initialized elsewhere... maybe gotten from the engine + } +} + +void TheWastesViewport::paintBackground() +{ + if (m_pScoreBoard) + { + int x, y; + getApp()->getCursorPos(x, y); + m_pScoreBoard->cursorMoved(x, y, m_pScoreBoard); + } + + // See if the command menu is visible and needs recalculating due to some external change + if ( g_iTeamNumber != m_iCurrentTeamNumber ) + { + UpdateCommandMenu(); + + m_iCurrentTeamNumber = g_iTeamNumber; + } + + if ( g_iPlayerClass != m_iCurrentPlayerClass ) + { + UpdateCommandMenu(); + + m_iCurrentPlayerClass = g_iPlayerClass; + } + + // See if the Spectator Menu needs to be update + // no update if only second target (m_iUser3) changes + if ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) + { + UpdateSpectatorMenu(); + } + + UpdateGameInfoMenu(); + + // Update the Scoreboard, if it's visible + if ( m_pScoreBoard->isVisible() && (m_flScoreBoardLastUpdated < gHUD.m_flTime) ) + { + m_pScoreBoard->Update(); + m_flScoreBoardLastUpdated = gHUD.m_flTime + 0.5; + } + + int extents[4]; + getAbsExtents(extents[0],extents[1],extents[2],extents[3]); + VGui_ViewportPaintBackground(extents); +} + +//================================================================ +// Input Handler for Drag N Drop panels +void CDragNDropHandler::cursorMoved(int x,int y,Panel* panel) +{ + if(m_bDragging) + { + App::getInstance()->getCursorPos(x,y); + m_pPanel->setPos(m_iaDragOrgPos[0]+(x-m_iaDragStart[0]),m_iaDragOrgPos[1]+(y-m_iaDragStart[1])); + + if(m_pPanel->getParent()!=null) + { + m_pPanel->getParent()->repaint(); + } + } +} + +void CDragNDropHandler::mousePressed(MouseCode code,Panel* panel) +{ + int x,y; + App::getInstance()->getCursorPos(x,y); + m_bDragging=true; + m_iaDragStart[0]=x; + m_iaDragStart[1]=y; + m_pPanel->getPos(m_iaDragOrgPos[0],m_iaDragOrgPos[1]); + App::getInstance()->setMouseCapture(panel); + + m_pPanel->setDragged(m_bDragging); + m_pPanel->requestFocus(); +} + +void CDragNDropHandler::mouseReleased(MouseCode code,Panel* panel) +{ + m_bDragging=false; + m_pPanel->setDragged(m_bDragging); + App::getInstance()->setMouseCapture(null); +} + +//================================================================ +// Number Key Input +bool TheWastesViewport::SlotInput( int iSlot ) +{ + // If there's a menu up, give it the input + if ( m_pCurrentMenu ) + return m_pCurrentMenu->SlotInput( iSlot ); + + return FALSE; +} + +// Direct Key Input +int TheWastesViewport::KeyInput( int down, int keynum, const char *pszCurrentBinding ) +{ + // Enter gets out of Spectator Mode by bringing up the Team Menu + if (m_iUser1 && gEngfuncs.Con_IsVisible() == false ) + { + if ( down && (keynum == K_ENTER || keynum == K_KP_ENTER) ) + ShowVGUIMenu( MENU_TEAM ); + } + + // Open Text Window? + if (m_pCurrentMenu && gEngfuncs.Con_IsVisible() == false) + { + int iMenuID = m_pCurrentMenu->GetMenuID(); + + // Get number keys as Input for Team/Class menus + if (iMenuID == MENU_TEAM) + { + // Escape gets you out of Team/Class menus if the Cancel button is visible + if ( keynum == K_ESCAPE ) + { + if ( (iMenuID == MENU_TEAM && g_iTeamNumber) ) + { + HideTopMenu(); + return 0; + } + } + + for (int i = '0'; i <= '9'; i++) + { + if ( down && (keynum == i) ) + { + SlotInput( i - '0' ); + return 0; + } + } + } + + // Grab enter keys to close TextWindows + if ( down && (keynum == K_ENTER || keynum == K_KP_ENTER || keynum == K_SPACE || keynum == K_ESCAPE) ) + { + if ( iMenuID == MENU_MAPBRIEFING || iMenuID == MENU_INTRO ) + { + HideTopMenu(); + + if(iMenuID == MENU_INTRO) + ShowVGUIMenu(MENU_MAPBRIEFING); + else if(iMenuID == MENU_MAPBRIEFING) + ShowVGUIMenu(MENU_ITEMSELECTION); + + return 0; + } + } + + // Grab jump key on Team Menu as autoassign + if ( pszCurrentBinding && down && !strcmp(pszCurrentBinding, "+jump") ) + { + if (iMenuID == MENU_TEAM) + { + m_pTeamMenu->SlotInput(5); + return 0; + } + } + + } + + // if we're in a command menu, try hit one of it's buttons + if ( down && m_pCurrentCommandMenu ) + { + // Escape hides the command menu + if ( keynum == K_ESCAPE ) + { + HideCommandMenu(); + return 0; + } + + // only trap the number keys + if ( keynum >= '0' && keynum <= '9' ) + { + if ( m_pCurrentCommandMenu->KeyInput(keynum) ) + { + // a final command has been issued, so close the command menu + HideCommandMenu(); + } + + return 0; + } + } + + return 1; +} + +//================================================================ +// Message Handlers +int TheWastesViewport::MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + for (int i = 0; i < 5; i++) + m_iValidClasses[i] = READ_SHORT(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TheWastesViewport::MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iNumberOfTeams = READ_BYTE(); + + for (int i = 0; i < m_iNumberOfTeams; i++) + { + int teamNum = i + 1; + + gHUD.m_TextMessage.LocaliseTextString( READ_STRING(), m_sTeamNames[teamNum], MAX_TEAMNAME_SIZE ); + + // Set the team name buttons + if (m_pTeamButtons[i]) + m_pTeamButtons[i]->setText( m_sTeamNames[teamNum] ); + + // range check this value...m_pDisguiseButtons[5]; + if ( teamNum < 5 ) + { + // Set the disguise buttons + if ( m_pDisguiseButtons[teamNum] ) + m_pDisguiseButtons[teamNum]->setText( m_sTeamNames[teamNum] ); + } + } + + // Update the Team Menu + if (m_pTeamMenu) + m_pTeamMenu->Update(); + + return 1; +} + +int TheWastesViewport::MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int iMenu = READ_BYTE(); + + // Map briefing includes the name of the map (because it's sent down before the client knows what map it is) + if (iMenu == MENU_MAPBRIEFING) + { + strncpy( m_sMapName, READ_STRING(), sizeof(m_sMapName) ); + m_sMapName[ sizeof(m_sMapName) - 1 ] = '\0'; + } + + // Bring up the menu6 + ShowVGUIMenu( iMenu ); + + return 1; +} + +int TheWastesViewport::MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ) +{ + if (m_iGotAllMOTD) + m_szMOTD[0] = 0; + + BEGIN_READ( pbuf, iSize ); + + m_iGotAllMOTD = READ_BYTE(); + + int roomInArray = sizeof(m_szMOTD) - strlen(m_szMOTD) - 1; + + strncat( m_szMOTD, READ_STRING(), roomInArray >= 0 ? roomInArray : 0 ); + m_szMOTD[ sizeof(m_szMOTD)-1 ] = '\0'; + + if ( m_iGotAllMOTD ) + ShowVGUIMenu( MENU_INTRO ); + + return 1; +} + +int TheWastesViewport::MsgFunc_Briefing(const char *pszName,int iSize,void *pbuf) +{ + BEGIN_READ(pbuf,iSize); + + if(READ_BYTE()) + { + ShowVGUIMenu(MENU_MAPBRIEFING); + } + + return 1; +} + +int TheWastesViewport::MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iBuildState = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TheWastesViewport::MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRandomPC = READ_BYTE(); + + return 1; +} + +int TheWastesViewport::MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + strncpy( m_szServerName, READ_STRING(), MAX_SERVERNAME_LENGTH ); + + return 1; +} + +int TheWastesViewport::MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + short frags = READ_SHORT(); + short deaths = READ_SHORT(); + short alive = READ_SHORT(); + short teamnumber = READ_SHORT(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_PlayerExtraInfo[cl].frags = frags; + g_PlayerExtraInfo[cl].deaths = deaths; + g_PlayerExtraInfo[cl].alive = alive; + g_PlayerExtraInfo[cl].teamnumber = teamnumber; + + //Dont go below 0! + if ( g_PlayerExtraInfo[cl].teamnumber < 0 ) + g_PlayerExtraInfo[cl].teamnumber = 0; + + UpdateOnPlayerInfo(); + } + + return 1; +} + +// Message handler for TeamScore message +// accepts three values: +// string: team name +// short: teams kills +// short: teams deaths +// if this message is never received, then scores will simply be the combined totals of the players. +int TheWastesViewport::MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + for ( int i = 1; i <= m_pScoreBoard->m_iNumTeams; i++ ) + { + if ( !stricmp( TeamName, g_TeamInfo[i].name ) ) + break; + } + + if ( i > m_pScoreBoard->m_iNumTeams ) + return 1; + + // use this new score data instead of combined player scoresw + g_TeamInfo[i].scores_overriden = TRUE; + g_TeamInfo[i].frags = READ_SHORT(); + g_TeamInfo[i].deaths = READ_SHORT(); + + return 1; +} + +// Message handler for TeamInfo message +// accepts two values: +// byte: client number +// string: client team name +int TheWastesViewport::MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ) +{ + if (!m_pScoreBoard) + return 1; + + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + // set the players team + strncpy( g_PlayerExtraInfo[cl].teamname, READ_STRING(), MAX_TEAM_NAME ); + } + + // rebuild the list of teams + m_pScoreBoard->RebuildTeams(); + + return 1; +} + +void TheWastesViewport::DeathMsg( int killer, int victim ) +{ + m_pScoreBoard->DeathMsg(killer,victim); +} + +int TheWastesViewport::MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + short cl = READ_BYTE(); + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_IsSpectator[cl] = READ_BYTE(); + } + + return 1; +} + +int TheWastesViewport::MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iAllowSpectators = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu(); + + // If the team menu is up, update it too + if (m_pTeamMenu) + m_pTeamMenu->Update(); + + return 1; +} + +int TheWastesViewport::MsgFunc_LmsStart( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_flGameTimeDelay = READ_COORD() + gHUD.m_flTime; + + return 1; +} diff --git a/cl_dll/vgui_TheWastesViewport.h b/cl_dll/vgui_TheWastesViewport.h new file mode 100644 index 0000000..8e1aea5 --- /dev/null +++ b/cl_dll/vgui_TheWastesViewport.h @@ -0,0 +1,1116 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef THEWASTESVIEWPORT_H +#define THEWASTESVIEWPORT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// custom scheme handling +#include "vgui_SchemeManager.h" + +#define TF_DEFS_ONLY +#include "tf_defs.h" + +using namespace vgui; + +class Cursor; +class ScorePanel; +class CCommandMenu; +class CommandLabel; +class CommandButton; +class BuildButton; +class ClassButton; +class CMenuPanel; +class ServerBrowser; +class DragNDropPanel; +class CTransparentPanel; +class CTeamMenuPanel; +class CInventoryPanel; + +char* GetVGUITGAName(const char *pszName); +BitmapTGA *LoadTGAForRes(const char* pImageName); +void ScaleColors( int &r, int &g, int &b, int a ); +extern char *sTFClassSelection[]; +extern int sTFValidClassInts[]; +extern char *sLocalisedClasses[]; +extern int iTeamColors[5][3]; +extern int iNumberOfTeamColors; + +#define MAX_SERVERNAME_LENGTH 32 + +// Command Menu positions +#define MAX_MENUS 80 +#define MAX_BUTTONS 100 + +#define BUTTON_SIZE_Y YRES(30) +#define CMENU_SIZE_X XRES(160) + +#define SUBMENU_SIZE_X (CMENU_SIZE_X / 8) +#define SUBMENU_SIZE_Y (BUTTON_SIZE_Y / 6) + +#define CMENU_TOP (BUTTON_SIZE_Y * 4) + +#define MAX_TEAMNAME_SIZE 64 +#define MAX_BUTTON_SIZE 32 + +// Map Briefing Window +#define MAPBRIEF_INDENT 30 + +// Team Menu +#define TMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define TMENU_HEADER 100 +#define TMENU_SIZE_X (ScreenWidth - (TMENU_INDENT_X * 2)) +#define TMENU_SIZE_Y (TMENU_HEADER + BUTTON_SIZE_Y * 7) +#define TMENU_PLAYER_INDENT (((float)TMENU_SIZE_X / 3) * 2) +#define TMENU_INDENT_Y (((float)ScreenHeight - TMENU_SIZE_Y) / 2) + +// Class Menu +#define CLMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define CLMENU_HEADER 100 +#define CLMENU_SIZE_X (ScreenWidth - (CLMENU_INDENT_X * 2)) +#define CLMENU_SIZE_Y (CLMENU_HEADER + BUTTON_SIZE_Y * 11) +#define CLMENU_PLAYER_INDENT (((float)CLMENU_SIZE_X / 3) * 2) +#define CLMENU_INDENT_Y (((float)ScreenHeight - CLMENU_SIZE_Y) / 2) + +// Arrows +enum +{ + ARROW_UP, + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, +}; + +//============================================================================== +// VIEWPORT PIECES +//============================================================ +// Wrapper for an Image Label without a background +class CImageLabel : public Label +{ +public: + BitmapTGA *m_pTGA; + +public: + CImageLabel( const char* pImageName,int x,int y ); + CImageLabel( const char* pImageName,int x,int y,int wide,int tall ); + + virtual int getImageTall(); + virtual int getImageWide(); + + virtual void NewImage(const char *pImageName); + virtual void setImageColor(Color col); + + virtual void paintBackground() + { + // Do nothing, so the background's left transparent. + } +}; + +// Command Label +// Overridden label so we can darken it when submenus open +class CommandLabel : public Label +{ +private: + int m_iState; + +public: + CommandLabel(const char* text,int x,int y,int wide,int tall) : Label(text,x,y,wide,tall) + { + m_iState = false; + } + + void PushUp() + { + m_iState = false; + repaint(); + } + + void PushDown() + { + m_iState = true; + repaint(); + } +}; + +//============================================================ +// Command Buttons +class CommandButton : public Button +{ +private: + int m_iPlayerClass; + + // Submenus under this button + CCommandMenu *m_pSubMenu; + CCommandMenu *m_pParentMenu; + CommandLabel *m_pSubLabel; + + char m_sMainText[MAX_BUTTON_SIZE]; + char m_cBoundKey; + + SchemeHandle_t m_hTextScheme; + + void RecalculateText( void ); + +public: + bool m_bNoHighlight; + +public: + // Constructors + CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight = false); + CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall); + + void Init( void ); + + // Menu Handling + void AddSubMenu( CCommandMenu *pNewMenu ); + void AddSubLabel( CommandLabel *pSubLabel ) + { + m_pSubLabel = pSubLabel; + } + + virtual int IsNotValid( void ) + { + return false; + } + + void UpdateSubMenus( int iAdjustment ); + int GetPlayerClass() { return m_iPlayerClass; }; + CCommandMenu *GetSubMenu() { return m_pSubMenu; }; + + CCommandMenu *getParentMenu( void ); + void setParentMenu( CCommandMenu *pParentMenu ); + + // Overloaded vgui functions + virtual void paint(); + virtual void setText( const char *text ); + virtual void paintBackground(); + + void cursorEntered( void ); + void cursorExited( void ); + + void setBoundKey( char boundKey ); + char getBoundKey( void ); +}; + +//============================================================ +// Command Menus +class CCommandMenu : public Panel +{ +private: + CCommandMenu *m_pParentMenu; + int m_iXOffset; + int m_iYOffset; + + // Buttons in this menu + CommandButton *m_aButtons[ MAX_BUTTONS ]; + int m_iButtons; + +public: + CCommandMenu( CCommandMenu *pParentMenu, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) + { + m_pParentMenu = pParentMenu; + m_iXOffset = x; + m_iYOffset = y; + m_iButtons = 0; + } + + void AddButton( CommandButton *pButton ); + bool RecalculateVisibles( int iNewYPos, bool bHideAll ); + void RecalculatePositions( int iYOffset ); + void MakeVisible( CCommandMenu *pChildMenu ); + + CCommandMenu *GetParentMenu() { return m_pParentMenu; }; + int GetXOffset() { return m_iXOffset; }; + int GetYOffset() { return m_iYOffset; }; + int GetNumButtons() { return m_iButtons; }; + CommandButton *FindButtonWithSubmenu( CCommandMenu *pSubMenu ); + + void ClearButtonsOfArmedState( void ); + + + bool KeyInput( int keyNum ); + + virtual void paintBackground(); +}; + +//============================================================================== +class TheWastesViewport : public Panel +{ +private: + vgui::Cursor* _cursorNone; + vgui::Cursor* _cursorArrow; + + int m_iInitialized; + + CCommandMenu *m_pCommandMenus[ MAX_MENUS ]; + CCommandMenu *m_pCurrentCommandMenu; + float m_flMenuOpenTime; + float m_flScoreBoardLastUpdated; + int m_iNumMenus; + int m_iCurrentTeamNumber; + int m_iCurrentPlayerClass; + int m_iUser1; + int m_iUser2; + int m_iUser3; + + // VGUI Menus + void CreateTeamMenu( void ); + CMenuPanel* ShowTeamMenu( void ); + void CreateSpectatorMenu( void ); + void CreateGameInfoMenu( void ); + + // Scheme handler + CSchemeManager m_SchemeManager; + + // MOTD + int m_iGotAllMOTD; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + + // Command Menu Team buttons + CommandButton *m_pTeamButtons[6]; + CommandButton *m_pDisguiseButtons[5]; + BuildButton *m_pBuildButtons[3]; + BuildButton *m_pBuildActiveButtons[3]; + + // Server Browser + ServerBrowser *m_pServerBrowser; + + // Spectator "menu" + CTransparentPanel *m_pSpectatorMenu; + Label *m_pSpectatorLabel; + Label *m_pSpectatorHelpLabel; + int m_iAllowSpectators; + + // Round info "menu" + CTransparentPanel *m_pGameInfoMenu; + Label *m_pGameInfoLabel; + Label *m_pGameTimeLabel; + float m_flGameTimeDelay; + + // Data for specific sections of the Command Menu + int m_iValidClasses[5]; + int m_iNumberOfTeams; + int m_iBuildState; + int m_iRandomPC; + char m_sTeamNames[5][MAX_TEAMNAME_SIZE]; + + char m_sMapName[64]; +public: + TheWastesViewport(int x,int y,int wide,int tall); + void Initialize( void ); + + void CreateCommandMenu( void ); + void CreateScoreBoard( void ); + void CreateServerBrowser( void ); + CommandButton *CreateCustomButton( char *pButtonText, char *pButtonName ); + CCommandMenu *CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText ); + + void UpdateCursorState( void ); + void UpdateCommandMenu( void ); + void UpdateOnPlayerInfo( void ); + void UpdateHighlights( void ); + void UpdateSpectatorMenu( void ); + void UpdateGameInfoMenu( void ); + + int KeyInput( int down, int keynum, const char *pszCurrentBinding ); + void GetAllPlayersInfo( void ); + void DeathMsg( int killer, int victim ); + + void ShowCommandMenu( void ); + void InputSignalHideCommandMenu( void ); + void HideCommandMenu( void ); + void SetCurrentCommandMenu( CCommandMenu *pNewMenu ); + void SetCurrentMenu( CMenuPanel *pMenu ); + + void ShowScoreBoard( void ); + void HideScoreBoard( void ); + bool IsScoreBoardVisible( void ); + + bool AllowedToPrintText( void ); + + void ShowVGUIMenu( int iMenu ); + void HideVGUIMenu( void ); + void HideTopMenu( void ); + + void ToggleServerBrowser( void ); + + CMenuPanel* CreateTextWindow( int iTextToShow ); + + CCommandMenu *CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu ); + + // Data Handlers + int GetValidClasses(int iTeam) { return m_iValidClasses[iTeam]; }; + int GetNumberOfTeams() { return m_iNumberOfTeams; }; + int GetBuildState() { return m_iBuildState; }; + int IsRandomPC() { return m_iRandomPC; }; + char *GetTeamName( int iTeam ) { return m_sTeamNames[iTeam]; }; + int GetAllowSpectators() { return m_iAllowSpectators; }; + + // Message Handlers + int MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Briefing(const char *pszName,int iSize,void *pbuf); + int MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_LmsStart( const char *pszName, int iSize, void *pbuf ); + + // Input + bool SlotInput( int iSlot ); + + virtual void paintBackground(); + + CSchemeManager *GetSchemeManager( void ) { return &m_SchemeManager; } + ScorePanel *GetScoreBoard( void ) { return m_pScoreBoard; } + + void *operator new( size_t stAllocateBlock ); + +public: + // VGUI Menus + CMenuPanel *m_pCurrentMenu; + CTeamMenuPanel *m_pTeamMenu; + ScorePanel *m_pScoreBoard; + char m_szServerName[ MAX_SERVERNAME_LENGTH ]; +}; + +//============================================================ +// Command Menu Button Handlers +#define MAX_COMMAND_SIZE 256 + +class CMenuHandler_StringCommand : public ActionSignal +{ +protected: + char m_pszCommand[MAX_COMMAND_SIZE]; + int m_iCloseVGUIMenu; +public: + CMenuHandler_StringCommand( char *pszCommand ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = false; + } + + CMenuHandler_StringCommand( char *pszCommand, int iClose ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = true; + } + + virtual void actionPerformed(Panel* panel) + { + gEngfuncs.pfnClientCmd(m_pszCommand); + + if (m_iCloseVGUIMenu) + gViewPort->HideTopMenu(); + else + gViewPort->HideCommandMenu(); + } +}; + +// This works the same as CMenuHandler_StringCommand, except it watches the string command +// for specific commands, and modifies client vars based upon them. +class CMenuHandler_StringCommandWatch : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandWatch( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandWatch( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel) + { + CMenuHandler_StringCommand::actionPerformed( panel ); + + // Try to guess the player's new team (it'll be corrected if it's wrong) + if ( !strcmp( m_pszCommand, "jointeam 1" ) ) + g_iTeamNumber = 1; + else if ( !strcmp( m_pszCommand, "jointeam 2" ) ) + g_iTeamNumber = 2; + else if ( !strcmp( m_pszCommand, "jointeam 3" ) ) + g_iTeamNumber = 3; + else if ( !strcmp( m_pszCommand, "jointeam 4" ) ) + g_iTeamNumber = 4; + } +}; + +// Used instead of CMenuHandler_StringCommand for Class Selection buttons. +// Checks the state of hud_classautokill and kills the player if set +class CMenuHandler_StringCommandClassSelect : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandClassSelect( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandClassSelect( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel); +}; + +class CMenuHandler_PopupSubMenuInput : public InputSignal +{ +private: + CCommandMenu *m_pSubMenu; + Button *m_pButton; +public: + CMenuHandler_PopupSubMenuInput( Button *pButton, CCommandMenu *pSubMenu ) + { + m_pSubMenu = pSubMenu; + m_pButton = pButton; + } + + virtual void cursorMoved(int x,int y,Panel* panel) + { + //gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + } + + virtual void cursorEntered(Panel* panel) + { + gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + + if (m_pButton) + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) {}; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class CMenuHandler_LabelInput : public InputSignal +{ +private: + ActionSignal *m_pActionSignal; +public: + CMenuHandler_LabelInput( ActionSignal *pSignal ) + { + m_pActionSignal = pSignal; + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + m_pActionSignal->actionPerformed( panel ); + } + + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorEntered(Panel* panel) {}; + virtual void cursorExited(Panel* Panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +#define HIDE_TEXTWINDOW 0 +#define SHOW_MAPBRIEFING 1 +#define SHOW_MOTD 3 +#define SHOW_ITEMSELECTION 4 + +class CMenuHandler_TextWindow : public ActionSignal +{ +private: + int m_iState; +public: + CMenuHandler_TextWindow( int iState ) + { + m_iState = iState; + } + + virtual void actionPerformed(Panel* panel) + { + if (m_iState == HIDE_TEXTWINDOW) + { + gViewPort->HideTopMenu(); + } + else + { + gViewPort->HideCommandMenu(); + gViewPort->ShowVGUIMenu( m_iState ); + } + } +}; + +class CDragNDropHandler : public InputSignal +{ +private: + DragNDropPanel* m_pPanel; + bool m_bDragging; + int m_iaDragOrgPos[2]; + int m_iaDragStart[2]; + +public: + CDragNDropHandler(DragNDropPanel* pPanel) + { + m_pPanel = pPanel; + m_bDragging = false; + } + + void cursorMoved(int x,int y,Panel* panel); + void mousePressed(MouseCode code,Panel* panel); + void mouseReleased(MouseCode code,Panel* panel); + + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorEntered(Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_MenuButtonOver : public InputSignal +{ +private: + int m_iButton; + CMenuPanel *m_pMenuPanel; +public: + CHandler_MenuButtonOver( CMenuPanel *pPanel, int iButton ) + { + m_iButton = iButton; + m_pMenuPanel = pPanel; + } + + void cursorEntered(Panel *panel); + + void cursorMoved(int x,int y,Panel* panel) {}; + void mousePressed(MouseCode code,Panel* panel) {}; + void mouseReleased(MouseCode code,Panel* panel) {}; + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_ButtonHighlight : public InputSignal +{ +private: + Button *m_pButton; +public: + CHandler_ButtonHighlight( Button *pButton ) + { + m_pButton = pButton; + } + + virtual void cursorEntered(Panel* panel) + { + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) + { + m_pButton->setArmed(false); + }; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +//----------------------------------------------------------------------------- +// Purpose: Special handler for highlighting of command menu buttons +//----------------------------------------------------------------------------- +class CHandler_CommandButtonHighlight : public CHandler_ButtonHighlight +{ +private: + CommandButton *m_pCommandButton; +public: + CHandler_CommandButtonHighlight( CommandButton *pButton ) : CHandler_ButtonHighlight( pButton ) + { + m_pCommandButton = pButton; + } + + virtual void cursorEntered( Panel *panel ) + { + m_pCommandButton->cursorEntered(); + } + + virtual void cursorExited( Panel *panel ) + { + m_pCommandButton->cursorExited(); + } +}; + + +//================================================================ +// Overidden Command Buttons for special visibilities +class ClassButton : public CommandButton +{ +protected: + int m_iPlayerClass; + +public: + ClassButton( int iClass, const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + m_iPlayerClass = iClass; + } + + virtual int IsNotValid(); +}; + +class TeamButton : public CommandButton +{ +private: + int m_iTeamNumber; +public: + TeamButton( int iTeam, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iTeamNumber = iTeam; + } + + virtual int IsNotValid() + { + int iTeams = gViewPort->GetNumberOfTeams(); + // Never valid if there's only 1 team + if (iTeams == 1) + return true; + + // Auto Team's always visible + if (m_iTeamNumber == 5) + return false; + + if (iTeams >= m_iTeamNumber && m_iTeamNumber != g_iTeamNumber) + return false; + + return true; + } +}; + +class SpectateButton : public CommandButton +{ +public: + SpectateButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + } + + virtual int IsNotValid() + { + // Only visible if the server allows it + if ( gViewPort->GetAllowSpectators() != 0 ) + return false; + + return true; + } +}; + +#define DISGUISE_TEAM1 (1<<0) +#define DISGUISE_TEAM2 (1<<1) +#define DISGUISE_TEAM3 (1<<2) +#define DISGUISE_TEAM4 (1<<3) + +class DisguiseButton : public CommandButton +{ +private: + int m_iValidTeamsBits; + int m_iThisTeam; +public: + DisguiseButton( int iValidTeamNumsBits, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall,false ) + { + m_iValidTeamsBits = iValidTeamNumsBits; + } + + virtual int IsNotValid() + { + // Only visible for spies + if ( g_iPlayerClass != PC_SPY ) + return true; + + // if it's not tied to a specific team, then always show (for spies) + if ( !m_iValidTeamsBits ) + return false; + + // if we're tied to a team make sure we can change to that team + int iTmp = 1 << (gViewPort->GetNumberOfTeams() - 1); + if ( m_iValidTeamsBits & iTmp ) + return false; + + return true; + } +}; + +extern int iBuildingCosts[]; +#define BUILDSTATE_HASBUILDING (1<<0) // Data is building ID (1 = Dispenser, 2 = Sentry) +#define BUILDSTATE_BUILDING (1<<1) +#define BUILDSTATE_BASE (1<<2) +#define BUILDSTATE_CANBUILD (1<<3) // Data is building ID (0 = Dispenser, 1 = Sentry) + +class BuildButton : public CommandButton +{ +private: + int m_iBuildState; + int m_iBuildData; + +public: + enum Buildings + { + DISPENSER = 0, + SENTRYGUN = 1, + }; + + BuildButton( int iState, int iData, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iBuildState = iState; + m_iBuildData = iData; + } + + virtual int IsNotValid() + { + // Only visible for engineers + if (g_iPlayerClass != PC_ENGINEER) + return true; + + // If this isn't set, it's only active when they're not building + if (m_iBuildState & BUILDSTATE_BUILDING) + { + // Make sure the player's building + if ( !(gViewPort->GetBuildState() & BS_BUILDING) ) + return true; + } + else + { + // Make sure the player's not building + if ( gViewPort->GetBuildState() & BS_BUILDING ) + return true; + } + + if (m_iBuildState & BUILDSTATE_BASE) + { + // Only appear if we've got enough metal to build something, or something already built + if ( gViewPort->GetBuildState() & (BS_HAS_SENTRYGUN | BS_HAS_DISPENSER | BS_CANB_SENTRYGUN | BS_CANB_DISPENSER) ) + return false; + + return true; + } + + // Must have a building + if (m_iBuildState & BUILDSTATE_HASBUILDING) + { + if ( m_iBuildData == BuildButton::DISPENSER && !(gViewPort->GetBuildState() & BS_HAS_DISPENSER) ) + return true; + if ( m_iBuildData == BuildButton::SENTRYGUN && !(gViewPort->GetBuildState() & BS_HAS_SENTRYGUN) ) + return true; + } + + // Can build something + if (m_iBuildState & BUILDSTATE_CANBUILD) + { + // Make sure they've got the ammo and don't have one already + if ( m_iBuildData == BuildButton::DISPENSER && (gViewPort->GetBuildState() & BS_CANB_DISPENSER) ) + return false; + if ( m_iBuildData == BuildButton::SENTRYGUN && (gViewPort->GetBuildState() & BS_CANB_SENTRYGUN) ) + return false; + + return true; + } + + return false; + } +}; + +#define MAX_MAPNAME 256 + +class MapButton : public CommandButton +{ +private: + char m_szMapName[ MAX_MAPNAME ]; + +public: + MapButton( const char *pMapName, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + sprintf( m_szMapName, "maps/%s.bsp", pMapName ); + } + + virtual int IsNotValid() + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return true; + + // Does it match the current map name? + if ( strcmp(m_szMapName, level) ) + return true; + + return false; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class TeamOnlyCommandButton : public CommandButton +{ +private: + int m_iTeamNum; + +public: + TeamOnlyCommandButton( int iTeamNum, const char* text,int x,int y,int wide,int tall ) : + CommandButton( text, x, y, wide, tall ), m_iTeamNum(iTeamNum) {} + + virtual int IsNotValid() + { + if ( g_iTeamNumber != m_iTeamNum ) + return true; + + return CommandButton::IsNotValid(); + } +}; + +//============================================================ +// Panel that can be dragged around +class DragNDropPanel : public Panel +{ +private: + bool m_bBeingDragged; + LineBorder *m_pBorder; +public: + DragNDropPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_bBeingDragged = false; + + // Create the Drag Handler + addInputSignal( new CDragNDropHandler(this) ); + + // Create the border (for dragging) + m_pBorder = new LineBorder(); + } + + virtual void setDragged( bool bState ) + { + m_bBeingDragged = bState; + + if (m_bBeingDragged) + setBorder(m_pBorder); + else + setBorder(NULL); + } +}; + +//================================================================ +// Panel that draws itself with a transparent black background +class CTransparentPanel : public Panel +{ +private: + int m_iTransparency; +public: + CTransparentPanel(int iTrans, int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_iTransparency = iTrans; + } + + virtual void paintBackground() + { + /* if (m_iTransparency) + { + // Transparent black background + drawSetColor( 0,0,0, m_iTransparency ); + drawFilledRect(0,0,_size[0],_size[1]); + }*/ + + drawSetColor(TW_PANEL_BG_RGBA); + drawFilledRect(0,0,_size[0],_size[1]); + + drawSetColor(TW_PANEL_BORDER_RGBA); + drawOutlinedRect(0,0,_size[0],_size[1]); + } +}; + +//================================================================ +// Menu Panel that supports buffering of menus +class CMenuPanel : public CTransparentPanel +{ +private: + CMenuPanel *m_pNextMenu; + int m_iMenuID; + int m_iRemoveMe; + int m_iIsActive; + float m_flOpenTime; +public: + CMenuPanel(int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(100, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + CMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(iTrans, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + virtual void Reset( void ) + { + m_pNextMenu = NULL; + m_iIsActive = false; + m_flOpenTime = 0; + } + + void SetNextMenu( CMenuPanel *pNextPanel ) + { + if (m_pNextMenu) + m_pNextMenu->SetNextMenu( pNextPanel ); + else + m_pNextMenu = pNextPanel; + } + + void SetMenuID( int iID ) + { + m_iMenuID = iID; + } + + void SetActive( int iState ) + { + m_iIsActive = iState; + } + + virtual void Open( void ) + { + setVisible( true ); + + // Note the open time, so we can delay input for a bit + m_flOpenTime = gHUD.m_flTime; + } + + virtual void Close( void ) + { + setVisible( false ); + m_iIsActive = false; + + if ( m_iRemoveMe ) + gViewPort->removeChild( this ); + + // This MenuPanel has now been deleted. Don't append code here. + } + + int ShouldBeRemoved() { return m_iRemoveMe; }; + CMenuPanel* GetNextMenu() { return m_pNextMenu; }; + int GetMenuID() { return m_iMenuID; }; + int IsActive() { return m_iIsActive; }; + float GetOpenTime() { return m_flOpenTime; }; + + // Numeric input + virtual bool SlotInput( int iSlot ) { return false; }; + virtual void SetActiveInfo( int iInput ) {}; +}; + +//================================================================ +// Custom drawn scroll bars +class CTFScrollButton : public CommandButton +{ +private: + BitmapTGA *m_pTGA; + +public: + CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall); + + virtual void paint( void ); + virtual void paintBackground( void ); +}; + +// Custom drawn slider bar +class CTFSlider : public Slider +{ +public: + CTFSlider(int x,int y,int wide,int tall,bool vertical) : Slider(x,y,wide,tall,vertical) + { + }; + + virtual void paintBackground( void ); +}; + +// Custom drawn scrollpanel +class CTFScrollPanel : public ScrollPanel +{ +public: + CTFScrollPanel(int x,int y,int wide,int tall); +}; + +//================================================================ +// Menu Panels that take key input +//============================================================ +class CTeamMenuPanel : public CMenuPanel +{ +public: + ScrollPanel *m_pScrollPanel; + CTransparentPanel *m_pTeamWindow; + Label *m_pMapTitle; + TextPanel *m_pBriefing; + TextPanel *m_pTeamInfoPanel[6]; + CommandButton *m_pButtons[6]; + bool m_bUpdatedMapName; + CommandButton *m_pCancelButton; + CommandButton *m_pSpectateButton; + + int m_iCurrentInfo; + +public: + CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall); + + virtual bool SlotInput( int iSlot ); + virtual void Open( void ); + virtual void Update( void ); + virtual void SetActiveInfo( int iInput ); + virtual void paintBackground( void ); + + virtual void Initialize( void ); + + virtual void Reset( void ) + { + CMenuPanel::Reset(); + m_iCurrentInfo = 0; + } +}; + +#endif diff --git a/cl_dll/vgui_int.cpp b/cl_dll/vgui_int.cpp index f2913ca..dae2b4f 100644 --- a/cl_dll/vgui_int.cpp +++ b/cl_dll/vgui_int.cpp @@ -22,7 +22,8 @@ #include "const.h" #include "camera.h" #include "in_defs.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" #include "vgui_ControlConfigPanel.h" namespace @@ -87,9 +88,8 @@ void VGui_Startup() Panel* root=(Panel*)VGui_GetPanel(); root->setBgColor(128,128,0,0); //root->setNonPainted(false); - //root->setBorder(new LineBorder()); + //root->setBorder(new LineBorder(Color(TW_PANEL_BORDER_RGBA))); root->setLayout(new BorderLayout(0)); - //root->getSurfaceBase()->setEmulatedCursorVisible(true); @@ -105,7 +105,7 @@ void VGui_Startup() } else { - gViewPort = new TeamFortressViewport(0,0,root->getWide(),root->getTall()); + gViewPort = new TheWastesViewport(0,0,root->getWide(),root->getTall()); gViewPort->setParent(root); } @@ -113,7 +113,6 @@ void VGui_Startup() TexturePanel* texturePanel=new TexturePanel(); texturePanel->setParent(gViewPort); */ - } void VGui_Shutdown() diff --git a/cl_dll/vgui_teammenu.cpp b/cl_dll/vgui_teammenu.cpp index ca24a65..d3946af 100644 --- a/cl_dll/vgui_teammenu.cpp +++ b/cl_dll/vgui_teammenu.cpp @@ -23,7 +23,8 @@ #include "hud.h" #include "cl_util.h" -#include "vgui_TeamFortressViewport.h" +#include "tw_vgui.h" +#include "vgui_TheWastesViewport.h" // Team Menu Dimensions #define TEAMMENU_TITLE_X XRES(40) @@ -74,7 +75,7 @@ CTeamMenuPanel::CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,i // Create the Info Window m_pTeamWindow = new CTransparentPanel( 255, TEAMMENU_WINDOW_X, TEAMMENU_WINDOW_Y, TEAMMENU_WINDOW_SIZE_X, TEAMMENU_WINDOW_SIZE_Y ); m_pTeamWindow->setParent( this ); - m_pTeamWindow->setBorder( new LineBorder( Color(255*0.7,170*0.7,0,0 )) ); +// m_pTeamWindow->setBorder( new LineBorder( Color(255*0.7,170*0.7,0,0 )) ); // Create the Map Name Label m_pMapTitle = new Label( "", TEAMMENU_WINDOW_TITLE_X, TEAMMENU_WINDOW_TITLE_Y ); diff --git a/cl_dll/view.cpp b/cl_dll/view.cpp index c9e19d2..5d6f7fc 100644 --- a/cl_dll/view.cpp +++ b/cl_dll/view.cpp @@ -23,6 +23,7 @@ #include "screenfade.h" #include "shake.h" +ref_params_t *g_pparams; // pointer from V_CalcRefDef // Spectator Mode extern "C" @@ -49,10 +50,12 @@ extern "C" int PM_GetInfo( int ent ); void InterpolateAngles( float *start, float *end, float *output, float frac ); float AngleBetweenVectors( float * v1, float * v2 ); - } void V_DropPunchAngle ( float frametime, float *ev_punchangle ); +void V_Sway(struct ref_params_s *pparams); +void V_StopSway(); +void V_DisableFade(); void VectorAngles( const float *forward, float *angles ); /* @@ -71,6 +74,16 @@ vec3_t v_origin, v_angles; vec3_t ev_punchangle; +extern float g_lastFOV; // If this is set we dont render a view mdl + +// Thermal +int ev_thermal = 0; + +// Swaying +float ev_swayscale = 0; +float ev_scaleclamp; +int ev_swayenabled = 0; + cvar_t *scr_ofsx; cvar_t *scr_ofsy; cvar_t *scr_ofsz; @@ -497,7 +510,6 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) scr_ofsz->value = 0.0; } - V_DriftPitch ( pparams ); // ent is the player model ( visible when out of body ) @@ -747,6 +759,13 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) V_DropPunchAngle ( pparams->frametime, (float *)&ev_punchangle ); + // Client side sway, if any + // Clear sway if player is dead + if(ev_swayenabled && pparams->health) + V_Sway(pparams); + else + V_StopSway(); // reset swayangle + // smooth out stair step ups #if 1 if ( !pparams->smoothing && pparams->onground && pparams->simorg[2] - oldz > 0) @@ -884,6 +903,20 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) v_origin = pparams->vieworg; + // There is a bug with removing + // the view model, being it plays + // The current animation over again. + // So instead we just move the viewmodel + // so far away that the player will never see it. + // TODO: Do this without letting the player see his vmdl. + if(g_lastFOV) + { + view->model = NULL; + +// view->origin[2] = v_origin[2] - 64; +// view->origin[1] = v_origin[1] - 64; +// view->origin[0] = v_origin[0]; + } } void V_GetInEyePos(int entity, float * origin, float * angles ) @@ -1166,6 +1199,8 @@ void V_CalcSpectatorRefdef ( struct ref_params_s *pparams ) void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) { + g_pparams = pparams; // set global pointer + // intermission / finale rendering if ( pparams->intermission ) { @@ -1180,24 +1215,20 @@ void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) V_CalcNormalRefdef ( pparams ); } -/* -// Example of how to overlay the whole screen with red at 50 % alpha -#define SF_TEST -#if defined SF_TEST + // H&K G11 Thermal Scope + if(ev_thermal) { screenfade_t sf; - gEngfuncs.pfnGetScreenFade( &sf ); + gEngfuncs.pfnGetScreenFade(&sf); sf.fader = 255; sf.fadeg = 0; sf.fadeb = 0; - sf.fadealpha = 128; - sf.fadeFlags = FFADE_STAYOUT | FFADE_OUT; + sf.fadealpha = 225; + sf.fadeFlags = FFADE_MODULATE|FFADE_STAYOUT|FFADE_OUT; - gEngfuncs.pfnSetScreenFade( &sf ); + gEngfuncs.pfnSetScreenFade(&sf); } -#endif -*/ } /* @@ -1228,6 +1259,72 @@ void V_PunchAxis( int axis, float punch ) ev_punchangle[ axis ] = punch; } +void V_DisableFade() +{ + screenfade_t sf; + gEngfuncs.pfnGetScreenFade(&sf); + + sf.fader = 0; + sf.fadeg = 0; + sf.fadeb = 0; + sf.fadealpha = 0; + sf.fadeFlags = FFADE_IN; + + gEngfuncs.pfnSetScreenFade(&sf); + + ev_thermal = 0; +} + +/* +============= +V_SetSway + +Client side crosshair sway +============= +*/ +void V_SetSway(float scale) +{ + // Settings + ev_swayscale = scale; + ev_scaleclamp = scale * 0.1; // 1/10th of the original scale + + // Tell view logic to sway + ev_swayenabled = 1; +} + +/* +============= +V_StopSway + +Kill the sway +============= +*/ +void V_StopSway() +{ + // No more sway + ev_swayenabled = 0; +} + +/* +============= +V_Sway + +Portray sway on clients screen +============= +*/ +void V_Sway(struct ref_params_s *pparams) +{ + pparams->viewangles[ROLL] += ev_swayscale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + pparams->viewangles[PITCH] += ev_swayscale * sin(pparams->time*v_ipitch_cycle.value) * v_ipitch_level.value; + pparams->viewangles[YAW] += ev_swayscale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; + + // start to reduce sway + ev_swayscale -= 0.35*pparams->frametime; + + if(ev_swayscale < ev_scaleclamp) + ev_swayscale = ev_scaleclamp; +} + /* ============= V_Init @@ -1250,7 +1347,6 @@ void V_Init (void) cl_waterdist = gEngfuncs.pfnRegisterVariable( "cl_waterdist","4", 0 ); } - //#define TRACE_TEST #if defined( TRACE_TEST ) diff --git a/common/com_model.h b/common/com_model.h index 80f4d05..c3df10d 100644 --- a/common/com_model.h +++ b/common/com_model.h @@ -292,7 +292,6 @@ typedef struct model_s // additional model data // cache_user_t cache; // only access through Mod_Extradata - } model_t; typedef vec_t vec4_t[4]; diff --git a/common/const.h b/common/const.h index 6cc298f..e1808f0 100644 --- a/common/const.h +++ b/common/const.h @@ -639,7 +639,6 @@ #endif // Break Model Defines - #define BREAK_TYPEMASK 0x4F #define BREAK_GLASS 0x01 #define BREAK_METAL 0x02 diff --git a/common/in_buttons.h b/common/in_buttons.h index 151851a..a4b8d1d 100644 --- a/common/in_buttons.h +++ b/common/in_buttons.h @@ -32,7 +32,7 @@ #define IN_ATTACK2 (1 << 11) #define IN_RUN (1 << 12) #define IN_RELOAD (1 << 13) -#define IN_ALT1 (1 << 14) +#define IN_SPECIAL (1 << 14) #define IN_SCORE (1 << 15) // Used by client.dll for when scoreboard is held down #endif // IN_BUTTONS_H diff --git a/common/ref_params.h b/common/ref_params.h index f7f41d1..3087c9c 100644 --- a/common/ref_params.h +++ b/common/ref_params.h @@ -72,4 +72,6 @@ typedef struct ref_params_s int onlyClientDraw; // if !=0 nothing is drawn by the engine except clientDraw functions } ref_params_t; +extern ref_params_t *g_pparams; // pointer to g_pparams from CalcRefdef + #endif // !REF_PARAMSH diff --git a/common/thewastes.h b/common/thewastes.h new file mode 100644 index 0000000..9ea3273 --- /dev/null +++ b/common/thewastes.h @@ -0,0 +1,1361 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#ifndef __THEWASTES_H_ +#define __THEWASTES_H_ + +/* + This header file contains our unique stuff about The Wastes. + Include this and the matching .cpp file in the dll you need. +*/ +#include "tw_common.h" + +class CWasteWeapon; +class CWasteAmmo; + +// Damage Modifiers for various locations. +// Multiply these by the weapons base damage +// for easy damage calculation! +#define HEAD_DAMAGE 2 +#define CHEST_DAMAGE 1 +#define GUT_DAMAGE 1.1 +#define ARM_DAMAGE 0.6 +#define LEG_DAMAGE 0.65 + +/*** +* +* +* Weapons / Ammo +* +* +***/ + +// Baseline damages... these are the damages as +// if you hit someone in the chest. extremeties +// and the head uses modifiers to get a slightly +// different value from this. + +#define BERETTA_DMG_WHIP 15 +#define COLT_DMG_WHIP 15 +#define DEAGLE_DMG_WHIP 20 +#define MOSSBERG_DMG_WHIP 25 +#define BOLTRIFLE_DMG_WHIP 25 + +// Cant have more than 32(for now) :) +#define WEAPON_BERETTA 1 +#define WEAPON_COLT 2 +#define WEAPON_DEAGLE 3 +#define WEAPON_RUGER 4 +#define WEAPON_HANDCANNON 5 +#define WEAPON_SAWEDOFF 6 +#define WEAPON_MOSSBERG 7 +#define WEAPON_WINCHESTER 8 +#define WEAPON_SMG9 9 +#define WEAPON_FNFAL 10 +#define WEAPON_TOMMYGUN 11 +#define WEAPON_JACKHAMMER 12 +#define WEAPON_G11 13 +#define WEAPON_BOLTRIFLE 14 +#define WEAPON_STEN 15 +#define WEAPON_MOLOTOVCOCKTAIL 16 +#define WEAPON_FRAGGRENADE 17 +#define WEAPON_PIPEBOMB 18 +#define WEAPON_COMBATKNIFE 19 +#define WEAPON_THROWINGKNIFE 20 +#define WEAPON_BASEBALLBAT 21 +#define WEAPON_SLEDGEHAMMER 22 +#define WEAPON_KATANA 23 +#define WEAPON_SPEAR 24 +#define WEAPON_CATTLEPROD 25 +#define WEAPON_AKIMBOBERETTAS 26 +#define WEAPON_AKIMBOCOLTS 27 +#define WEAPON_AKIMBODEAGLES 28 +#define WEAPON_AKIMBOSAWEDOFFS 29 + +#define WEIGHT_BERETTA 10 +#define WEIGHT_COLT 10 +#define WEIGHT_DEAGLE 12 +#define WEIGHT_RUGER 15 +#define WEIGHT_HANDCANNON 20 +#define WEIGHT_SAWEDOFF 22 +#define WEIGHT_MOSSBERG 30 +#define WEIGHT_WINCHESTER 30 +#define WEIGHT_SMG9 20 +#define WEIGHT_BOLTRIFLE 30 +#define WEIGHT_STEN 22 +#define WEIGHT_UNIQUE 35 +#define WEIGHT_MOLOTOVCOCKTAIL 5 +#define WEIGHT_FRAGGRENADE 5 +#define WEIGHT_PIPEBOMB 5 +#define WEIGHT_COMBATKNIFE 5 +#define WEIGHT_THROWINGKNIFE 5 +#define WEIGHT_BASEBALLBAT 5 +#define WEIGHT_SLEDGEHAMMER 5 +#define WEIGHT_KATANA 5 +#define WEIGHT_SPEAR 5 +#define WEIGHT_CATTLEPROD 10 + +// Weapon Death Messages. +// Format: %s(killer) %s(victim) +#define DEATH_BERETTA_1 "%s perforated %s with a Beretta 92FS" +#define DEATH_COLT_1 "%s punctured %s's stomach with a Colt Enforcer" +#define DEATH_DEAGLE_1 "%s ripped a hole in %s's chest with a .50AE slug" +#define DEATH_RUGER_1 "%s gave %s just desserts with a .454 Casull round" +#define DEATH_HANDCANNON_1 "%s drove a 5.6mm round into %s like a freight train" +#define DEATH_SAWEDOFF_1 "%s blew away %s. twice." +#define DEATH_MOSSBERG_1 "%s marries %s in hole-y matrimony" +#define DEATH_WINCHESTER_1 "%s singlehandedly destroyed %s's will to terminate" +#define DEATH_SMG9_1 "%s gave %s some heart burn" +#define DEATH_FNFAL_1 "%s made %s wonder, \"Where the hell did that bullet come from?\"" +#define DEATH_TOMMYGUN_1 "%s made a wiseguy out of %s" +#define DEATH_JACKHAMMER_1 "%s turned %s into buckshot hamburger" +#define DEATH_G11_1 "%s gave %s some caseless loving" +#define DEATH_BOLTRIFLE_1 "%s pinpointed %s's internal organs and pulled the trigger" +#define DEATH_STEN_1 "%s showed %s that surrender is not an option" +#define DEATH_MOLOTOVCOCKTAIL_1 "%s made %s crispy on the outside and gooey on the inside" +#define DEATH_FRAGGRENADE_1 "%s turned %s into tiny little gibs" +#define DEATH_PIPEBOMB_1 "%s taught %s about homemade explosives" +#define DEATH_COMBATKNIFE_1 "%s gutted %s with his combat knife" +#define DEATH_THROWINGKNIFE_1 "%s stuck his throwing knife in %s's side" +#define DEATH_BASEBALLBAT_1 "%s smashed %s's face in with his bat" +#define DEATH_SLEDGEHAMMER_1 "%s went to work on %s's skull" +#define DEATH_KATANA_1 "%s sliced the belly of %s" +#define DEATH_SPEAR_1 "%s bludgeoned %s with his spear" +#define DEATH_CATTLEPROD_1 "%s roasted %s with 11 herbs and spices" +#define DEATH_AKIMBOBERETTAS_1 "%s John Woo'd %s with his akimbo berettas" +#define DEATH_AKIMBOCOLTS_1 "%s enforced his rules on %s with two colt enforcer rounds" +#define DEATH_AKIMBODEAGLES_1 "%s put a big hole in %s's body courtesy of two .50AE slugs" +#define DEATH_AKIMBOSAWEDOFFS_1 "%s gave %s double the pleasure from dual sawed off's" +#define DEATH_BLEED_1 "%s bled %s, real slow" + + +#define DEATH_BERETTA_2 "%s busts a cap on %s" +#define DEATH_COLT_2 "%s capped %s with a 10mm slug" +#define DEATH_DEAGLE_2 "%s drilled a 12.7mm diameter hole in %s's skull" +#define DEATH_RUGER_2 "%s schooled %s with a well placed Ruger shot" +#define DEATH_HANDCANNON_2 "%s made sure the last thing on %s's mind was a 5.56mm cartridge" +#define DEATH_SAWEDOFF_2 "%s's twin barrels leave %s twitching in the dust" +#define DEATH_MOSSBERG_2 "%s gives %s a buckshot enema" +#define DEATH_WINCHESTER_2 "%s aerated %s's chest with his Winchester" +#define DEATH_SMG9_2 "%s said to %s's corpse, \"Why yes, I guess it is whoring!\"" +#define DEATH_FNFAL_2 "%s showed %s even the FN-FAL loves fun and games!" +#define DEATH_TOMMYGUN_2 "%s riddled %s, gangsta style" +#define DEATH_JACKHAMMER_2 "%s didn't want to kill %s so much as remove him from the known universe" +#define DEATH_G11_2 "%s found the G11 first. %s obviously didn't" +#define DEATH_BOLTRIFLE_2 "%s put down %s with a well placed rifle round" +#define DEATH_STEN_2 "%s gave %s a dishonorable discharge" +#define DEATH_MOLOTOVCOCKTAIL_2 "%s burned %s at the stake and pissed on the ashes" +#define DEATH_FRAGGRENADE_2 "%s taught %s the pin trick" +#define DEATH_PIPEBOMB_2 "%s instructs %s not to play with explosives" +#define DEATH_COMBATKNIFE_2 "%s sliced %s's neck" +#define DEATH_THROWINGKNIFE_2 "%s put a throwing knife between the eyes of %s" +#define DEATH_BASEBALLBAT_2 "%s hit a homerun with %s's body" +#define DEATH_SLEDGEHAMMER_2 "%s beat %s to a pulp with his sledge" +#define DEATH_KATANA_2 "%s showed %s what happens when you \'bring out the gimp\'" +#define DEATH_SPEAR_2 "%s shoved his hand carved spear blade into %s's gut" +#define DEATH_CATTLEPROD_2 "%s gave %s quite a shock" +#define DEATH_AKIMBOBERETTAS_2 "%s riddles %s with several 9mm rounds from two berettas" +#define DEATH_AKIMBOCOLTS_2 "%s put %s in his place using two colt enforcers" +#define DEATH_AKIMBODEAGLES_2 "%s gave %s a lobotomy using two desert eagles as scalpels" +#define DEATH_AKIMBOSAWEDOFFS_2 "%s made %s's insides his outsides using two sawed-off shotguns" +#define DEATH_BLEED_2 "%s opened up %s like a leaky faucet" + +// Note, if the weapon can have an extra +// round in the chamber, account for this +// in this listing +#define CLIP_BERETTA 15 +#define CLIP_COLT 12 +#define CLIP_DEAGLE 7 +#define CLIP_RUGER 5 +#define CLIP_HANDCANNON 5 +#define CLIP_SAWEDOFF 2 +#define CLIP_MOSSBERG 8 +#define CLIP_WINCHESTER 6 +#define CLIP_SMG9 24 +#define CLIP_FNFAL 20 +#define CLIP_TOMMYGUN 50 +#define CLIP_JACKHAMMER 10 +#define CLIP_G11 45 +#define CLIP_BOLTRIFLE 5 +#define CLIP_STEN 30 + +#define GIVE_BERETTA CLIP_BERETTA*2 +#define GIVE_COLT CLIP_COLT*2 +#define GIVE_DEAGLE CLIP_DEAGLE*2 +#define GIVE_RUGER CLIP_RUGER*2 +#define GIVE_HANDCANNON CLIP_HANDCANNON*2 +#define GIVE_BUCKSHOT 8*2 +#define GIVE_SMG9 CLIP_SMG9*2 +#define GIVE_FNFAL CLIP_FNFAL*2 +#define GIVE_TOMMYGUN CLIP_TOMMYGUN*2 +#define GIVE_JACKHAMMER CLIP_JACKHAMMER*2 +#define GIVE_G11 CLIP_G11*2 +#define GIVE_BOLTRIFLE CLIP_BOLTRIFLE*2 +#define GIVE_STEN CLIP_STEN*2 +#define GIVE_WINCHESTER CLIP_WINCHESTER*2 +#define GIVE_JACKHAMMER CLIP_JACKHAMMER*2 + +#define AMMO_MAX_BERETTA 120 +#define AMMO_MAX_COLT 96 +#define AMMO_MAX_DEAGLE 56 +#define AMMO_MAX_RUGER 25 +#define AMMO_MAX_HANDCANNON 20 +#define AMMO_MAX_BUCKSHOT 16 +#define AMMO_MAX_SMG9 100 +#define AMMO_MAX_FNFAL 80 +#define AMMO_MAX_TOMMYGUN 150 +#define AMMO_MAX_JACKHAMMER 30 +#define AMMO_MAX_G11 135 +#define AMMO_MAX_BOLTRIFLE 15 +#define AMMO_MAX_STEN 90 +#define AMMO_MAX_WINCHESTER 24 +#define AMMO_MAX_JACKHAMMER 30 +#define AMMO_MAX_MOLOTOVCOCKTAIL 5 +#define AMMO_MAX_FRAGGRENADE 5 +#define AMMO_MAX_PIPEBOMB 5 +#define AMMO_MAX_THROWINGKNIFE 10 +#define AMMO_MAX_SPEARS 5 + +#define AMMO_GIVE_BERETTA CLIP_BERETTA +#define AMMO_GIVE_COLT CLIP_COLT +#define AMMO_GIVE_DEAGLE CLIP_DEAGLE +#define AMMO_GIVE_RUGER CLIP_RUGER +#define AMMO_GIVE_HANDCANNON CLIP_HANDCANNON +#define AMMO_GIVE_BUCKSHOT 8 +#define AMMO_GIVE_SMG9 CLIP_SMG9 +#define AMMO_GIVE_FNFAL CLIP_FNFAL*2 +#define AMMO_GIVE_TOMMYGUN CLIP_TOMMYGUN +#define AMMO_GIVE_JACKHAMMER CLIP_JACKHAMMER +#define AMMO_GIVE_G11 CLIP_G11 +#define AMMO_GIVE_BOLTRIFLE CLIP_BOLTRIFLE +#define AMMO_GIVE_STEN CLIP_STEN +#define AMMO_GIVE_WINCHESTER CLIP_WINCHESTER +#define AMMO_GIVE_JACKHAMMER CLIP_JACKHAMMER + +class CWasteWeapon : public CBasePlayerWeapon +{ +public: + void Spawn(); + void ItemPostFrame(); + + // Extend in child classes + virtual void SpecialMode(); + + virtual BOOL UseDecrement( void ); + + virtual char *szGetDeathNoticeString(){ return ""; }; // The death message to send to clients + virtual float flGetTiltedDamage(){ return 0; }; // The weapon returns its own damage based on several variables, including state code + virtual int iGetWeight(); // How heavy is this weapon? + + // Replication of member variables, if needed + virtual void PackWeapon(void *weapon_data){} + virtual void UnpackWeapon(void *weapon_data){} + + // Used server side + virtual BOOL bLaserActive(){ return FALSE; } +}; + +class CWasteAmmo : public CBasePlayerAmmo +{ +public: +}; + +/************* + Sidearms - included in both client and game dll, tw_sidearms.cpp +*************/ +#define SIDEARM_SHOOT 0 +#define SIDEARM_AIM 2 + +#define SIDEARM_JUMPPENALTY 1.2 // Accuracy infraction for jumping shooters. +#define SIDEARM_DUCKPENALTY 0.45 // Not really a penalty, increases accuracy if your duckin +#define SIDEARM_AIMPENALTY 1.75 // Modifier for accuracy and rate of fire if your aiming. + +#define MOVING_AIMPENALTY 1.1 + +class CSidearm : public CWasteWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + void ItemPostFrame(); + + void PrimaryAttack(); + void SecondaryAttack(); + void SpecialMode(); + virtual void SetState(int statenum); + virtual void PistolFire(float spread,float rof); + virtual void SwingWeapon(); + void Reload(); + + void Holster( int skiplocal = 0 ); + + virtual int GetWhipDmg(){ return 0; } + + BOOL Deploy(); + BOOL PlayEmptySound(); + void WeaponIdle(); + void Drop(); + + // Replication + virtual void PackWeapon(void *weapon_data); + virtual void UnpackWeapon(void *weapon_data); + + // Global Vars + unsigned short m_usFire1; + + int m_iExtraRound; // If true allows for an extra ammo in the gun. + + // damage values. these should vary from gun to gun. + int m_iBulletType; + int m_iClipSize; + + float m_fAccuracy; + float m_fRateOfFire; + + int m_iAllowFire; // Semi-Auto mode(detect input) + + int m_iState; // Whip, Aim, or Shoot? + int m_iOldState; + + virtual char *GetAnimPlayer(){return ""; } // Used so the player knows what weapon animation to play. + virtual char *GetModelV() { return ""; } + virtual char *GetModelP() { return ""; } + virtual char *GetModelW() { return ""; } +private: + // hacks to handle aim mode (animations) + BOOL m_bAimReload; +}; + +class CBeretta : public CSidearm +{ +public: + void Spawn(); + void Precache(); + int iItemSlot( void ){ return 1; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void AttachToPlayer( CBasePlayer *pPlayer ); + + char *GetAnimPlayer(){ return "onehanded"; } + char *GetModelV(){ return "models/v_beretta.mdl"; } + char *GetModelP(){ return "models/p_beretta.mdl"; } + char *GetModelW(){ return "models/w_beretta.mdl"; } + + char *GetSoundEmpty(){ return "weapons/beretta_fire_empty.wav"; } + char *GetSoundReload1(){ return "weapons/beretta_reload1.wav"; } + char *GetSoundReload2(){ return "weapons/beretta_reload2.wav"; } + char *GetSoundReloadNotEmpty(){ return "weapons/beretta_reload_not_empty.wav"; } + char *GetSoundDraw1(){ return "weapons/beretta_draw.wav"; } + char *GetSoundDraw2(){ return GetSoundDraw1(); } + + int GetWhipDmg(){ return BERETTA_DMG_WHIP; } +}; + +class CBerettaAmmo : public CWasteAmmo +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL AddAmmo( CBaseEntity *pOther ); +}; + +class CColt : public CSidearm +{ +public: + void Spawn(); + void Precache(); + int iItemSlot( void ){ return 2; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + char *GetAnimPlayer(){ return "onehanded"; } + char *GetModelV(){ return "models/v_colt.mdl"; } + char *GetModelP(){ return "models/p_colt.mdl"; } + char *GetModelW(){ return "models/w_colt.mdl"; } + + int GetWhipDmg(){ return COLT_DMG_WHIP; } +}; + +class CColtAmmo : public CWasteAmmo +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +class CDesertEagle : public CSidearm +{ +public: + void Spawn(); + void Precache(); + int iItemSlot( void ){ return 3; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void SwingWeapon(); + + char *GetAnimPlayer(){ return "onehanded"; } + char *GetModelV(){ return "models/v_deagle.mdl"; } + char *GetModelP(){ return "models/p_deagle.mdl"; } + char *GetModelW(){ return "models/w_deagle.mdl"; } + + int GetWhipDmg(){ return DEAGLE_DMG_WHIP; } +}; + +class CDesertEagleAmmo : public CWasteAmmo +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +// Accuracy and Rate of Fire modifications if a Ruger is zoomed in. +#define RUGER_AIMACC 2.50 +#define RUGER_AIMROF 2.00 + +class CRuger : public CSidearm +{ +public: + void Spawn(); + void Precache(); + int iItemSlot( void ){ return 4; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); + + void ItemPostFrame(); + void PistolFire(float spread,float rof); + void SwingWeapon(); + void SpecialMode(); + void SecondaryAttack(); + void Reload(); + void WeaponIdle(); + void Holster(int skiplocal = 0); + BOOL Deploy(); + + void SetState(int statenum); + + char *GetAnimPlayer(){ return "onehanded"; } + char *GetModelV(){ return "models/v_ruger.mdl"; } + char *GetModelP(){ return "models/p_ruger.mdl"; } + char *GetModelW(){ return "models/w_ruger.mdl"; } + + int GetWhipDmg(){ return 0; } + + // vars + BOOL m_bInZoom; + BOOL m_bStartZoom; +private: + void ClientSway(); +}; + +class CRugerAmmo : public CWasteAmmo +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +#define HANDCANNON_JUMPPENALTY 2.5 // Accuracy infraction for jumping shooters. +#define HANDCANNON_DUCKPENALTY 0.45 // Not really a penalty, increases accuracy if your duckin +#define HANDCANNON_LASERPENALTY 1.75 // Modifier for accuracy and rate of fire if your aiming. + +// Granted this is a sidearm, however +// The code for it is much different. +// Code reuse here wouldnt be that smart +// methinks. :) +class CHandcannon : public CWasteWeapon +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 5; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); + + void PrimaryAttack(); + void SecondaryAttack(); + void ItemPostFrame(); + void Reload(); + + BOOL Deploy(); + BOOL PlayEmptySound(); + void WeaponIdle(); + void Holster(int skiplocal = 0); + + // Used server side + virtual BOOL bLaserActive(); +private: + unsigned short m_usFire1; + + // damage values. these should vary from gun to gun. + int m_iBulletType; + int m_iClipSize; + + float m_fAccuracy; + float m_fRateOfFire; + + int m_iAllowFire; // Semi-Auto mode(detect input) + BOOL m_bLaserVisible; +}; + +class CHandcannonAmmo : public CWasteAmmo +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +/************* + Shotguns - included in both client and game dll, tw_shotguns.cpp +*************/ +class CShotgun : public CWasteWeapon // Pump shotgun +{ +public: + int GetItemInfo(ItemInfo *p); + void ItemPostFrame(); + void WeaponIdle(); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); + + // vars + int m_iBulletType; + int m_iClipSize; + int m_iAllowFire; + + int m_iState; + int m_iReloadState; + + unsigned int m_usFire1; +}; + +class CShotgunAmmo : public CWasteAmmo +{ +public: + void Spawn(); + void Precache(); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +#define MOSSBERG_SHOTCOUNT 8 +#define MOSSBERG_SPREAD 0.055 + +class CMossberg : public CShotgun +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 1; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + + void Reload(); + BOOL Deploy(); + BOOL PlayEmptySound(); + void WeaponIdle(); + void Holster(int skiplocal = 0); +}; + +#define WINCHESTER_SPREAD 0.030 + +class CWinchester : public CShotgun +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 2; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + + void Reload(); + BOOL Deploy(); + BOOL PlayEmptySound(); +}; + +class CWinchesterAmmo : public CWasteAmmo +{ +public: + void Spawn(); + void Precache(); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +class CSawedOff : public CWasteWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + int iItemSlot(){ return 6; } + char *szGetDeathNoticeString(); + + void FireShotgun(int barrels); + + void PrimaryAttack(); + void SecondaryAttack(); + void Reload(); + + BOOL Deploy(); + BOOL PlayEmptySound(); + void ItemPostFrame(); +private: + // vars + int m_iBulletType; + int m_iClipSize; + int m_iAllowFire; + + float m_flEjectShell; + + unsigned int m_usFire1; +}; + +class CJackHammer : public CWasteWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + int iItemSlot(){ return 3; } + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + void Reload(); + BOOL Deploy(); + BOOL PlayEmptySound(); +private: + int m_iClipSize; + + unsigned int m_usFire1; +}; + +class CJackHammerAmmo : public CWasteAmmo +{ +public: + void Spawn(); + void Precache(); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +/************* + Automatic Weapons +*************/ +class CAutomatic : public CWasteWeapon +{ +public: + int GetItemInfo(ItemInfo *p); + void ItemPostFrame(); + void Holster(int skiplocal = 0); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); +protected: + unsigned int m_usFire1; + + int m_iBulletType; + int m_iClipSize; + int m_iState; + int m_iAllowFire; + + float m_flAccuracy; + float m_flRateOfFire; +}; + +class CSmg9 : public CAutomatic +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 3; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + void ItemPostFrame(); + void Reload(); + + BOOL Deploy(); + BOOL PlayEmptySound(); + void Holster(int skiplocal = 0); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); + + // Used server side + virtual BOOL bLaserActive(); +private: + BOOL m_bBurstFire; + BOOL m_bLaserVisible; +}; + +class CSmg9Ammo : public CWasteAmmo +{ +public: + void Spawn(); + void Precache(); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +class CFnFal : public CAutomatic +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 1; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + void SpecialMode(); + void ItemPostFrame(); + + void Reload(); + BOOL Deploy(); + void Holster(int skiplocal = 0); + BOOL PlayEmptySound(); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); +private: + BOOL m_iAim; // Currently in aim mode +}; + +class CFnFalAmmo : public CWasteAmmo +{ +public: + void Spawn(); + void Precache(); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +class CTommyGun : public CAutomatic +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 2; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void Reload(); + BOOL Deploy(); + BOOL PlayEmptySound(); +}; + +class CTommyGunAmmo : public CWasteAmmo +{ +public: + void Spawn(); + void Precache(); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +class CG11 : public CAutomatic +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 4; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + void SpecialMode(); + void ItemPostFrame(); + void ClientSway(); + void Reload(); + BOOL Deploy(); + void Holster( int skiplocal = 0 ); + BOOL PlayEmptySound(); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); +private: + int m_iWeaponState; + float m_flBurstAccuracy; + + BOOL m_bThermal; + BOOL m_bActivateThermal; // Delay thermal activation +}; + +class CG11Ammo : public CWasteAmmo +{ +public: + void Spawn(); + void Precache(); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +class CBoltRifle : public CWasteWeapon +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 4; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + void SpecialMode(); + void ClientSway(); + void ItemPostFrame(); + void Reload(); + BOOL Deploy(); + void Holster(int skiplocal = 0); + BOOL PlayEmptySound(); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); +private: + int m_iBulletType; + int m_iClipSize; + int m_iAllowFire; + int m_iState; + + unsigned int m_usFire1; + + BOOL m_bReZoom; +}; + +class CBoltRifleAmmo : public CWasteAmmo +{ +public: + void Spawn(); + void Precache(); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +class CSten : public CWasteWeapon +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 5; } + int GetItemInfo(ItemInfo *p); + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void ItemPostFrame(); + void PrimaryAttack(); + void SecondaryAttack(); + void Reload(); + BOOL Deploy(); + BOOL PlayEmptySound(); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); +private: + int m_iClipSize; + int m_iBulletType; + int m_iState; + int m_iAllowFire; + + unsigned int m_usFire1; + + BOOL m_bOverheated; +}; + +class CStenAmmo : public CWasteAmmo +{ +public: + void Spawn(); + void Precache(); + BOOL AddAmmo(CBaseEntity *pOther); +}; + +/************* + Explosives +*************/ +class CMolotovCocktail : public CWasteWeapon +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 1; } + int GetItemInfo(ItemInfo *p); + char *szGetDeathNoticeString(); + void AttachToPlayer(CBasePlayer *pPlayer); + int AddDuplicate(CBasePlayerItem *pItem); + float flGetTiltedDamage(); + + void PrimaryAttack(); + void ItemPostFrame(); + void ThrowMolotov(); + BOOL Deploy(); + BOOL CanHolster(); + void Holster(int skiplocal = 0); +private: + float m_flStartAttack; + unsigned short m_usFire1; + BOOL m_bJustThrown; +}; + +class CTimedExplosiveWeapon : public CWasteWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo( ItemInfo *p ); + float flGetTiltedDamage(); + + void ItemPostFrame(); + void PrimaryAttack(); + void SecondaryAttack(); + virtual void ThrowExplosive(float flFuse); + BOOL CanHolster(); + void Holster( int skiplocal = 0 ); + + float m_flStartAttack; + float m_flFuseTimer; // How long does the fuse last on this explosive? + float m_flDamage; // How much damage do our explosives dish out ? + float m_flResistance; // Air resistance + char *m_pszExplosiveModel; // What does our explosive look like ? + BOOL m_bFusePrimed; // TRUE if the bomb we are holding is ticking! + int m_iExplosiveType; // Explosive class to use + + unsigned short m_usExplosive; + BOOL m_bJustThrown; +}; + +class CFragGrenade : public CTimedExplosiveWeapon +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 2; } + int GetItemInfo(ItemInfo *p); + void AttachToPlayer(CBasePlayer *pPlayer); + int AddDuplicate(CBasePlayerItem *pItem); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + void ItemPostFrame(); + void ThrowExplosive(float flFuse); + BOOL Deploy(); +}; + +class CPipebomb : public CTimedExplosiveWeapon +{ +public: + void Spawn(); + void Precache(); + int iItemSlot(){ return 3; } + int GetItemInfo(ItemInfo *p); + void AttachToPlayer(CBasePlayer *pPlayer); + int AddDuplicate(CBasePlayerItem *pItem); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + void ItemPostFrame(); + void ThrowExplosive(float flFuse); + BOOL Deploy(); +}; + +/************* + Melee Weapons +*************/ +class CMeleeWeapon : public CWasteWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + float flGetAngleDamage(float flDamage,CBaseEntity *pEntity); + + virtual CBaseEntity *SwingWeapon( float flKickback = 0.0f ); + virtual void PlayHitSound(); + BOOL PlayEmptySound(); + + virtual char *pszGetViewModel() = 0; + virtual char *pszGetPlayerModel() = 0; + virtual char *pszGetWorldModel() = 0; + + virtual int iGetDamageType() = 0; + + virtual float flGetRange() = 0; + + unsigned short m_usFire1; +}; + +class CThrowingWeapon : public CWasteWeapon +{ +public: + void Precache(); + int GetItemInfo(ItemInfo *p); + void AttachToPlayer(CBasePlayer *pPlayer); + int AddDuplicate(CBasePlayerItem *pItem); + + void ItemPostFrame(); + BOOL CanHolster(); + void Holster( int skiplocal = 0 ); + + virtual void ThrowObject() = 0; + + virtual int iGetMaxAmmo() = 0; + virtual char *pszGetAmmoName() = 0; + + float m_flStartAttack; + BOOL m_bJustThrown; +}; + +class CCombatKnife : public CMeleeWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + int iItemSlot(){ return 1; } + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + BOOL Deploy(); + void WeaponIdle(); + + char *pszGetViewModel(){ return "models/v_combatknife.mdl"; } + char *pszGetPlayerModel(){ return "models/p_combatknife.mdl"; } + char *pszGetWorldModel(){ return "models/w_combatknife.mdl"; } + + int iGetDamageType(){ return DMG_CLUB|DMG_NEVERGIB; } + + float flGetRange(){ return 32.0f; } +private: + BOOL m_bSecondaryAttack; // set for flGetTiltedDamage +}; + +class CBaseballBat : public CMeleeWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + int iItemSlot(){ return 3; } + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + BOOL Deploy(); + void WeaponIdle(); + + char *pszGetViewModel(){ return "models/v_baseballbat.mdl"; } + char *pszGetPlayerModel(){ return "models/p_baseballbat.mdl"; } + char *pszGetWorldModel(){ return "models/w_baseballbat.mdl"; } + + int iGetDamageType(){ return DMG_CLUB|DMG_NEVERGIB; } + + float flGetRange(){ return 48.0f; } +}; + +class CSledgeHammer : public CMeleeWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + int iItemSlot(){ return 4; } + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + BOOL Deploy(); + void WeaponIdle(); + + char *pszGetViewModel(){ return "models/v_sledge.mdl"; } + char *pszGetPlayerModel(){ return "models/p_sledge.mdl"; } + char *pszGetWorldModel(){ return "models/w_sledge.mdl"; } + + int iGetDamageType(){ return DMG_CLUB; } + + float flGetRange(){ return 48.0f; } +}; + +class CKatana : public CMeleeWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + int iItemSlot(){ return 5; } + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void SecondaryAttack(); + BOOL Deploy(); + void Holster(int skiplocal = 0); + void WeaponIdle(); + + char *pszGetViewModel(){ return "models/v_katana.mdl"; } + char *pszGetPlayerModel(){ return "models/p_katana.mdl"; } + char *pszGetWorldModel(){ return "models/w_katana.mdl"; } + + int iGetDamageType(){ return DMG_CLUB|DMG_NEVERGIB; } + + float flGetRange(); + + // Replication + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); +private: + int m_iState; +}; + +class CThrowingKnife : public CThrowingWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + int iItemSlot(){ return 2; } + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PrimaryAttack(); + void ItemPostFrame(); + void ThrowObject(); + BOOL Deploy(); + void WeaponIdle(); + + int iGetMaxAmmo(){ return AMMO_MAX_THROWINGKNIFE; } + char *pszGetAmmoName(){ return "ThrowingKnives"; } +}; + +class CSpear : public CThrowingWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + int iItemSlot(){ return 6; } + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void SwingWeapon(); + void ThrowObject(); + void PrimaryAttack(); + void SecondaryAttack(); + void ItemPostFrame(); + BOOL Deploy(); + void WeaponIdle(); + + int iGetMaxAmmo(){ return AMMO_MAX_SPEARS; } + char *pszGetAmmoName(){ return "Spears"; } +private: + unsigned short m_usFire1; +}; + +class CCattleProd : public CMeleeWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo(ItemInfo *p); + int iItemSlot(){ return 7; } + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void PlayHitSound(); + void PrimaryAttack(); + BOOL Deploy(); + void WeaponIdle(); + + char *pszGetViewModel(){ return "models/v_cattleprod.mdl"; } + char *pszGetPlayerModel(){ return "models/p_cattleprod.mdl"; } + char *pszGetWorldModel(){ return "models/w_cattleprod.mdl"; } + + int iGetDamageType(){ return DMG_SHOCK|DMG_CLUB|DMG_NEVERGIB; } + + float flGetRange(); +}; + +/************* + Akimbo Weapons +*************/ +class CAkimboWeapon : public CWasteWeapon +{ +public: + void Spawn(); + int GetItemInfo(ItemInfo *p); + + void PackWeapon(void *weapon_data); + void UnpackWeapon(void *weapon_data); + + void ItemPostFrame(); + void PrimaryAttack(); + void SecondaryAttack(); + void Reload(); + BOOL PlayEmptySound(); + void WeaponIdle(); + + unsigned short m_usFire1; + + int m_iBulletType; + int m_iClipSize; + float m_fAccuracy; + float m_fRateOfFire; + BOOL m_bSlowFire; + + int m_iCurrentHand; // which hand is firing ? +}; + +class CAkimboBerettas : public CAkimboWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo( ItemInfo *p ); + int iItemSlot(){ return 1; }; + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + BOOL Deploy(); +}; + +class CAkimboColts : public CAkimboWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo( ItemInfo *p ); + int iItemSlot(){ return 2; }; + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + BOOL Deploy(); +}; + +class CAkimboDeagles : public CAkimboWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo( ItemInfo *p ); + int iItemSlot(){ return 3; }; + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + BOOL Deploy(); +}; + +class CAkimboSawedOffs : public CAkimboWeapon +{ +public: + void Spawn(); + void Precache(); + int GetItemInfo( ItemInfo *p ); + int iItemSlot(){ return 4; } + float flGetTiltedDamage(); + char *szGetDeathNoticeString(); + + void ItemPostFrame(); + void FireSawedOffs( int iShellsFired ); + void PrimaryAttack(); + void SecondaryAttack(); + void Reload(); + BOOL Deploy(); + + int m_iAllowFire; +}; + +/*** +* +* +* Player - wasteplayer.cpp +* +* +***/ +#define MODIFY_PLAYER_SPEED(pPlayer,fSpeed) pPlayer->pev->fuser1 = fSpeed +#define HALT_PLAYER(pPlayer,fPercent,fReductionFactor) pPlayer->pev->fuser3 = fReductionFactor; pPlayer->pev->fuser2 = fPercent +#define CRIPPLE_PLAYER(pPlayer,fPercent) HALT_PLAYER(pPlayer,fPercent,-0.3f) + +/*** +* +* +* Utility Code +* +* +***/ +#ifdef CLIENT_DLL + // General event code + void CenterPrint(const char*); // Client side print to center + int GetLocalPlayerIndex(); // get local player index + + extern int g_iClientLasersEnabled[32]; // Render client side laser dot + + + // Viewport + void V_SetSway(float scale); + void V_StopSway(); + void V_DisableFade(); + + extern int ev_thermal; // Thermal Vision toggle + + // Other + extern int g_runfuncs; +#else + // Server side only + extern int gmsgLaserInfo; // Used by some weapons with laser sites +#endif + +void FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ); + +#endif \ No newline at end of file diff --git a/common/tw_common.h b/common/tw_common.h new file mode 100644 index 0000000..906e0d7 --- /dev/null +++ b/common/tw_common.h @@ -0,0 +1,454 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#ifndef __TW_COMMON_H_ +#define __TW_COMMON_H_ + +// +// This Header contains generic client / +// server module shared information +// + +#ifdef TW_BETA + #define TW_BANNER "The Wastes RC 2 (Build 200) %s NOT FOR DISTRIBUTION" +#endif + +// Bleeding / Composure / Willpower settings +#define PLAYER_BLEED_LIMIT 20 +#define PLAYER_COMPOSURE_LIMIT 80 +#define PLAYER_WILLPOWER_LIMIT_LO 40 +#define PLAYER_WILLPOWER_LIMIT_HI 60 + +// Animation enums +enum single_pistol_anim_e { + SPISTOL_IDLE = 0, + SPISTOL_RELOAD, + SPISTOL_RELOAD_EMPTY, + SPISTOL_RELOAD2, + SPISTOL_RELOAD2_EMPTY, + SPISTOL_DRAW, + SPISTOL_DRAW_EMPTY, + SPISTOL_SHOOT, + SPISTOL_SHOOT_LAST, + SPISTOL_SHOOT_EMPTY, + SPISTOL_WHIP1, + SPISTOL_WHIP1_EMPTY, + SPISTOL_WHIP2, + SPISTOL_WHIP2_EMPTY, + SPISTOL_AIM_IDLE, + SPISTOL_AIM_SHOOT, + SPISTOL_AIM_SHOOT_LAST, + SPISTOL_AIM_SHOOT_EMPTY, + SPISTOL_GOTO_AIM, + SPISTOL_GOTO_AIM_EMPTY, + SPISTOL_END_AIM, + SPISTOL_END_AIM_EMPTY, +}; + +enum single_ruger_anim_e { + RUGER_IDLE = 0, + RUGER_IDLE2, + RUGER_RELOAD, + RUGER_DRAW, + RUGER_SHOOT, + RUGER_SHOOT_EMPTY, + RUGER_AIM_IDLE, + RUGER_AIM_SHOOT, + RUGER_GOTO_AIM, + RUGER_END_AIM +}; + +enum handcannon_anim_e { + HC_IDLE = 0, + HC_SHOOT, + HC_SHOOT_LAST, + HC_SHOOT_EMPTY, + HC_RELOAD_EMPTY, + HC_RELOAD, + HC_DRAW, + HC_DRAW_EMPTY +}; + +enum single_sawedoff_anim_e { + SAWED_IDLE = 0, + SAWED_SHOOT_L, + SAWED_SHOOT_R, + SAWED_SHOOT_BOTH, + SAWED_SHOOT_EMPTY, + SAWED_RELOAD, + SAWED_DRAW, +}; + +enum mossberg_anim_e { + MOSSBERG_IDLE = 0, + MOSSBERG_IDLE2, + MOSSBERG_IDLE3, + MOSSBERG_GOTO_RELOAD, + MOSSBERG_RELOAD, + MOSSBERG_END_RELOAD, + MOSSBERG_DRAW, + MOSSBERG_SHOOT, + MOSSBERG_SHOOT2, + MOSSBERG_SHOOT_EMPTY, + MOSSBERG_WHIP, +}; + +enum winchester_anim_e { + WINNY_IDLE = 0, + WINNY_SHOOT, + WINNY_SHOOT2, + WINNY_RELOAD_MIDDLE, + WINNY_RELOAD_END, + WINNY_RELOAD_START, + WINNY_DRAW, +}; + +enum smg9_anim_e { + SMG9_IDLE = 0, + SMG9_RELOAD, + SMG9_RELOAD_EMPTY, + SMG9_DRAW, + SMG9_SHOOT, + SMG9_SHOOT_EMPTY, + SMG9_SECONDARY_IDLE, + SMG9_SECONDARY_SHOOT, + SMG9_SECONDARY_SHOOT_EMPTY, + SMG9_SECONDARY_RELOAD, + SMG9_GOTO_SECONDARY, + SMG9_END_SECONDARY, + SMG9_SECONDARY_DRAW, +}; + +enum fnfal_anim_e { + FNFAL_IDLE1 = 0, + FNFAL_IDLE2, + FNFAL_RELOAD, + FNFAL_DRAW, + FNFAL_SHOOT, + FNFAL_SHOOT_EMPTY, + FNFAL_AIM_IDLE, + FNFAL_AIM_SHOOT, + FNFAL_AIM_SHOOT_EMPTY, + FNFAL_GOTO_AIM, + FNFAL_END_AIM, +}; + +enum tommygun_anim_e { + TOMMYGUN_IDLE = 0, + TOMMYGUN_IDLE1, + TOMMYGUN_IDLE2, + TOMMYGUN_RELOAD, + TOMMYGUN_DRAW, + TOMMYGUN_SHOOT, + TOMMYGUN_SHOOT_EMPTY, +}; + +enum jackhammer_anim_e { + JACKHAMMER_IDLE = 0, + JACKHAMMER_RELOAD, + JACKHAMMER_DRAW, + JACKHAMMER_SHOOT, +}; + +enum g11_anim_e { + G11_IDLE = 0, + G11_SHOOT, + G11_BURST, + G11_SHOOT_EMPTY, + G11_DRAW, + G11_RELOAD, + G11_ACTIVATE_SCOPE, + G11_DEACTIVATE_SCOPE, +}; + +enum boltrifle_anim_e { + BOLTRIFLE_IDLE1 = 0, + BOLTRIFLE_RELOAD_EMPTY, + BOLTRIFLE_RELOAD, + BOLTRIFLE_DRAW, + BOLTRIFLE_SHOOT, + BOLTRIFLE_SHOOT_LAST, + BOLTRIFLE_STRIKE, +}; + +enum ak_pistols_anim_e { + AKPISTOLS_IDLE = 0, + AKPISTOLS_RSHOOT_LFULL, + AKPISTOLS_RSHOOTLAST_LFULL, + AKPISTOLS_RSHOOT_LEMPTY, + AKPISTOLS_RSHOOTLAST_LEMPTY, + AKPISTOLS_LSHOOT_RFULL, + AKPISTOLS_LSHOOTLAST_RFULL, + AKPISTOLS_LSHOOT_REMPTY, + AKPISTOLS_LSHOOTLAST_REMPTY, + AKPISTOLS_RELOAD_EMPTY, + AKPISTOLS_RELOAD2_EMPTY, + AKPISTOLS_RELOAD, + AKPISTOLS_DRAW, +}; + +enum sten_anim_e { + STEN_IDLE = 0, + STEN_IDLE_SILENCED, + STEN_RELOAD, + STEN_RELOAD_SILENCED, + STEN_ADDSILENCER, + STEN_REMSILENCER, + STEN_DRAW, + STEN_DRAW_SILENCED, + STEN_SHOOT, + STEN_SHOOT_SILENCED, + STEN_SHOOT_EMPTY, + STEN_SHOOT_EMPTY_SILENCED, +}; + +enum anim_molotov_e { + MOLOTOV_IDLE = 0, + MOLOTOV_LIGHT, + MOLOTOV_THROW, + MOLOTOV_HOLSTER, + MOLOTOV_DRAW, +}; + +enum anim_frag_e { + FRAG_IDLE = 0, + FRAG_DRAW, + FRAG_GOTOTHROW, + FRAG_GOTOTHROW2, + FRAG_THROW, +}; + +enum anim_pipebomb_e { + PIPEBOMB_IDLE = 0, + PIPEBOMB_DRAW, + PIPEBOMB_GOTOTHROW, + PIPEBOMB_THROW, +}; + +enum anim_combatknife_e { + COMBATKNIFE_IDLE1 = 0, + COMBATKNIFE_DRAW, + COMBATKNIFE_HOLSTER, + COMBATKNIFE_ATTACK1, + COMBATKNIFE_ATTACK1MISS, + COMBATKNIFE_ATTACK2, + COMBATKNIFE_ATTACK2HIT, + COMBATKNIFE_ATTACK3, + COMBATKNIFE_ATTACK3HIT, + COMBATKNIFE_IDLE2, + COMBATKNIFE_IDLE3, +}; + +enum anim_throwingknife_e { + TKNIFE_IDLE1 = 0, + TKNIFE_IDLE2, + TKNIFE_DRAW, + TKNIFE_GOTOTHROW, + TKNIFE_THROW, +}; + +enum anim_baseballbat_e { + BAT_IDLE = 0, + BAT_DRAW, + BAT_ATTACK1, + BAT_ATTACK1_MISS, + BAT_ATTACK2, + BAT_ATTACK2_MISS, + BAT_ATTACK3, + BAT_ATTACK3_MISS, +}; + +enum anim_sledgehammer_e { + SLEDGE_IDLE = 0, + SLEDGE_DRAW, + SLEDGE_ATTACK1, + SLEDGE_ATTACK1_MISS, + SLEDGE_ATTACK2, + SLEDGE_ATTACK2_MISS, +}; + +enum anim_katana_e { + KATANA_ST1_IDLE = 0, + KATANA_ST2_IDLE, + KATANA_ST1_ATTACK1, + KATANA_ST2_ATTACK1, + KATANA_ST2_ATTACK2, + KATANA_ST1_GOTO_ST2, + KATANA_ST2_GOTO_ST1, + KATANA_DRAW, +}; + +enum anim_spear_e { + SPEAR_IDLE1 = 0, + SPEAR_IDLE2, + SPEAR_DRAW, + SPEAR_ATTACK1, + SPEAR_ATTACK2, + SPEAR_GOTO_THROW, + SPEAR_THROW, + SPEAR_GOTO_STAB, +}; + +enum anim_cattleprod_e { + PROD_IDLE1 = 0, + PROD_DRAW, + PROD_ATTACK1, + PROD_ATTACK1MISS, + PROD_ATTACK2, + PROD_ATTACK2MISS, +}; + +enum anim_akimbo_e { + AKIMBO_IDLE1 = 0, + AKIMBO_RSHOOT, + AKIMBO_RSHOOTLAST, + AKIMBO_RSHOOT_LEMPTY, + AKIMBO_RSHOOTLAST_LEMPTY, + AKIMBO_LSHOOT, + AKIMBO_LSHOOTLAST, + AKIMBO_LSHOOT_REMPTY, + AKIMBO_LSHOOTLAST_REMPTY, + AKIMBO_LSHOOTEMPTY, + AKIMBO_RSHOOTEMPTY, + AKIMBO_RELOAD_EMPTY, + AKIMBO_RELOAD2_EMPTY, + AKIMBO_RELOAD, + AKIMBO_DRAW, +}; + +enum anim_akimbosawedoffs_e { + AKIMBOSS_IDLE1, + AKIMBOSS_RELOAD, + AKIMBOSS_DRAW, + AKIMBOSS_RSHOOT, + AKIMBOSS_LSHOOT, + AKIMBOSS_BSHOOT, +}; + +// Used by akimbo weapons +enum akimbo_hand_e { AH_LEFT, AH_RIGHT }; + +// This is a list of all the wasteland weapons, as well +// as their categories. Used mainly by the client, but +// used a few places serverside as well. +enum e_weapcategory { + CAT_NONE = 0, + CAT_MELEE, + CAT_SIDEARM, + CAT_PRIMARY, + CAT_UNIQUE, + CAT_ITEM, +}; + +typedef struct playeritem_s +{ + char *weapon_classname; + char *ammo_classname; + int num_ammospawns; + int category; +} playeritem_t; + +typedef struct playeritemlist_s +{ + playeritem_t *array; + int size; +} playeritemlist_t; + +extern playeritemlist_t g_MeleeItems; +extern playeritemlist_t g_SidearmItems; +extern playeritemlist_t g_PrimaryItems; +extern playeritemlist_t g_UniqueItems; +extern playeritemlist_t g_OtherItems; + +// Wall Penetration macros to sync between client / server +enum penetration_types_e +{ + P_NONE = 0, + P_BERETTA, + P_COLT, + P_RUGER, + P_DEAGLE, + P_HANDCANNON, + P_WINCHESTER, + P_SMG9, + P_FNFAL, + P_TOMMYGUN, + P_G11, + P_BOLTRIFLE, + P_STEN, +}; + +#define MAX_WALLCOUNT 8 // max number of walls we can penetrate + +#define MAX_THICKNESS_BERETTA 9.0 +#define MAX_THICKNESS_COLT 17.0 +#define MAX_THICKNESS_RUGER 55.0 +#define MAX_THICKNESS_DEAGLE 52.5 +#define MAX_THICKNESS_HANDCANNON 45.0 +#define MAX_THICKNESS_WINCHESTER 30.0 +#define MAX_THICKNESS_SMG9 7.0 +#define MAX_THICKNESS_FNFAL 59.0 +#define MAX_THICKNESS_TOMMYGUN 14.0 +#define MAX_THICKNESS_G11 55.0 +#define MAX_THICKNESS_BOLTRIFLE 59.0 +#define MAX_THICKNESS_STEN 9.0 + +// Must be >1, how weak the round +// gets as it goes through walls. +#define FALLOFF_BERETTA 3.0 +#define FALLOFF_COLT 2.5 +#define FALLOFF_RUGER 2.0 +#define FALLOFF_DEAGLE 2.1 +#define FALLOFF_HANDCANNON 1.75 +#define FALLOFF_WINCHESTER 2.5 +#define FALLOFF_SMG9 3.0 +#define FALLOFF_FNFAL 1.25 +#define FALLOFF_TOMMYGUN 3.0 +#define FALLOFF_G11 2.25 +#define FALLOFF_BOLTRIFLE 1.25 +#define FALLOFF_STEN 3.0 + +// prxoy director stuff +#define DRC_EVENT 3 // informs the dircetor about ann important game event + +//#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +//#define DRC_FLAG_DRAMATIC (1<<5) + +// sub commands of svc_director: +#define DRC_CMD_NONE 0 // NULL director command +#define DRC_CMD_START 1 // start director mode +#define DRC_CMD_EVENT 2 // informs about director event +#define DRC_CMD_MODE 3 // switches camera modes +#define DRC_CMD_CAMERA 4 // sets camera registers +#define DRC_CMD_TIMESCALE 5 // sets time scale +#define DRC_CMD_MESSAGE 6 // send HUD centerprint +#define DRC_CMD_SOUND 7 // plays a particular sound +#define DRC_CMD_STATUS 8 // status info about broadcast +#define DRC_CMD_BANNER 9 // banner file name for HLTV gui +#define DRC_CMD_FADE 10 // send screen fade command +#define DRC_CMD_SHAKE 11 // send screen shake command +#define DRC_CMD_STUFFTEXT 12 // like the normal svc_stufftext but as director command + +#define DRC_CMD_LAST 13 + +// HLTV_EVENT event flags +#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +#define DRC_FLAG_SIDE (1<<4) //I assume this draws the action from the side - Mugsy +#define DRC_FLAG_DRAMATIC (1<<5) // is a dramatic scene +#define DRC_FLAG_SLOWMOTION (1<<6) // would look good in SloMo +#define DRC_FLAG_FACEPLAYER (1<<7) // player is doning something (reload/defuse bomb etc) +#define DRC_FLAG_INTRO (1<<8) // is a introduction scene +#define DRC_FLAG_FINAL (1<<9) // is a final scene +#define DRC_FLAG_NO_RANDOM (1<<10) // don't randomize event data + +#endif \ No newline at end of file diff --git a/common/twm.h b/common/twm.h new file mode 100644 index 0000000..097f01e --- /dev/null +++ b/common/twm.h @@ -0,0 +1,97 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// The Wastes Model format description +// +#ifndef __TWM_H_ +#define __TWM_H_ + +#define TWM_ID (('1'<<24)+('M'<<16)+('W'<<8)+'T') // little-endian "TWM1" -> bitwise shifts rock + +#define TWM_MAJOR_VERSION 2 +#define TWM_MINOR_VERSION 0 + +typedef float twm_vert_t[3]; + +typedef struct twm_triangle_s +{ + short vert_indices[3]; // index into vertex_lump + + // Texture info + float u[3]; + float v[3]; +} twm_triangle_t; + +typedef struct twm_materialgroup_s +{ +#ifdef CLIENT_DLL + int sprite_handle; // Used for rendering +#endif + + char texturename[64]; + + // Triangle list + short num_triangles; + short *tris_indices; // indices into triangle_lump +} twm_materialgroup_t; + +typedef struct twm_info_s +{ + // Header stuff + int header_id; + short major_version; + short minor_version; + + // Vertices + short num_vertices; + twm_vert_t *vertex_lump; + + // Tris + short num_tris; + twm_triangle_t *triangle_lump; + + // Material groups + short num_materialgroups; + twm_materialgroup_t *materialgroup_lump; +} twm_info_t; + +#ifdef CLIENT_DLL + +// Extra data support for client.dll +typedef struct twm_clientinfo_s +{ + // origin info + cl_entity_t *attached_ent; + int attachment_num; // which attachment to draw from + + // update info + float fadetime; + int dead; // if true we need to remove this ent + + // triAPI info + int render_mode; + int sprite_frame; + float brightness; + float color[4]; + + // We grab a pointer to twm_info + // LEAVE AS READ ONLY! + // if we change data in here we + // would change in all models + // that use this information! + const twm_info_t *twm_info; +} twm_clientinfo_t; + +#endif // CLIENT_DLL +#endif // __TWM_H_ \ No newline at end of file diff --git a/dedicated/sys_ded.cpp b/dedicated/sys_ded.cpp index c702b12..4ab72a1 100644 --- a/dedicated/sys_ded.cpp +++ b/dedicated/sys_ded.cpp @@ -142,7 +142,7 @@ Engine is erroring out, display error in message box void Sys_ErrorMessage( int level, const char *msg ) { #ifdef _WIN32 - MessageBox( NULL, msg, "Half-Life", MB_OK ); + MessageBox( NULL, msg, "The Wastes", MB_OK ); PostQuitMessage(0); #else printf( "%s\n", msg ); @@ -358,7 +358,7 @@ int CheckExeChecksum( void ) const char *pmsg = "Your Half-Life executable appears to have been modified. Please check your system for viruses and then re-install Half-Life."; #ifdef _WIN32 - MessageBox( NULL, pmsg, "Half-Life Dedicated Server", MB_OK ); + MessageBox( NULL, pmsg, "The Wastes Dedicated Server", MB_OK ); #else printf( "%s\n", pmsg ); #endif diff --git a/dlls/activity.h b/dlls/activity.h index 5c33ac8..61a8ef6 100644 --- a/dlls/activity.h +++ b/dlls/activity.h @@ -58,10 +58,10 @@ typedef enum { ACT_DIEBACKWARD, ACT_DIEFORWARD, ACT_DIEVIOLENT, - ACT_BARNACLE_HIT, // barnacle tongue hits a monster +/* ACT_BARNACLE_HIT, // barnacle tongue hits a monster ACT_BARNACLE_PULL, // barnacle is lifting the monster ( loop ) ACT_BARNACLE_CHOMP, // barnacle latches on to the monster - ACT_BARNACLE_CHEW, // barnacle is holding the monster in its mouth ( loop ) + ACT_BARNACLE_CHEW, // barnacle is holding the monster in its mouth ( loop )*/ ACT_SLEEP, ACT_INSPECT_FLOOR, // for active idles, look at something on or near the floor ACT_INSPECT_WALL, // for active idles, look at something directly ahead of you ( doesn't HAVE to be a wall or on a wall ) diff --git a/dlls/activitymap.h b/dlls/activitymap.h index 6cfe2a4..93c517f 100644 --- a/dlls/activitymap.h +++ b/dlls/activitymap.h @@ -56,10 +56,10 @@ _A( ACT_DIESIMPLE ), _A( ACT_DIEBACKWARD ), _A( ACT_DIEFORWARD ), _A( ACT_DIEVIOLENT ), -_A( ACT_BARNACLE_HIT ), +/*_A( ACT_BARNACLE_HIT ), _A( ACT_BARNACLE_PULL ), _A( ACT_BARNACLE_CHOMP ), -_A( ACT_BARNACLE_CHEW ), +_A( ACT_BARNACLE_CHEW ),*/ _A( ACT_SLEEP ), _A( ACT_INSPECT_FLOOR ), _A( ACT_INSPECT_WALL ), diff --git a/dlls/basemonster.h b/dlls/basemonster.h index ff1d847..ed173ba 100644 --- a/dlls/basemonster.h +++ b/dlls/basemonster.h @@ -6,89 +6,326 @@ * 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. +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. * ****/ + #ifndef BASEMONSTER_H #define BASEMONSTER_H +// +// generic Monster +// class CBaseMonster : public CBaseToggle { +private: + int m_afConditions; + public: - Activity m_Activity;// what the monster is doing (animation) - Activity m_IdealActivity;// monster should switch to this activity - int m_LastHitGroup; // the last body region that took damage - int m_bitsDamageType; // what types of damage has monster (player) taken - BYTE m_rgbTimeBasedDamage[CDMG_TIMEBASED]; - MONSTERSTATE m_MonsterState;// monster's current state - MONSTERSTATE m_IdealMonsterState;// monster should change to this state - int m_afConditions; - int m_afMemory; - float m_flNextAttack; // cannot attack again until this time - EHANDLE m_hEnemy; // the entity that the monster is fighting. - EHANDLE m_hTargetEnt; // the entity that the monster is trying to reach - float m_flFieldOfView;// width of monster's field of view ( dot product ) - int m_bloodColor; // color of blood particless - Vector m_HackedGunPos; // HACK until we can query end of gun + typedef enum + { + SCRIPT_PLAYING = 0, // Playing the sequence + SCRIPT_WAIT, // Waiting on everyone in the script to be ready + SCRIPT_CLEANUP, // Cancelling the script / cleaning up + SCRIPT_WALK_TO_MARK, + SCRIPT_RUN_TO_MARK, + } SCRIPTSTATE; + + + + // these fields have been added in the process of reworking the state machine. (sjb) + EHANDLE m_hEnemy; // the entity that the monster is fighting. + EHANDLE m_hTargetEnt; // the entity that the monster is trying to reach + EHANDLE m_hOldEnemy[ MAX_OLD_ENEMIES ]; + Vector m_vecOldEnemy[ MAX_OLD_ENEMIES ]; + + float m_flFieldOfView;// width of monster's field of view ( dot product ) + float m_flWaitFinished;// if we're told to wait, this is the time that the wait will be over. + float m_flMoveWaitFinished; + + Activity m_Activity;// what the monster is doing (animation) + Activity m_IdealActivity;// monster should switch to this activity + + int m_LastHitGroup; // the last body region that took damage + + MONSTERSTATE m_MonsterState;// monster's current state + MONSTERSTATE m_IdealMonsterState;// monster should change to this state + + int m_iTaskStatus; + Schedule_t *m_pSchedule; + int m_iScheduleIndex; + + WayPoint_t m_Route[ ROUTE_SIZE ]; // Positions of movement + int m_movementGoal; // Goal that defines route + int m_iRouteIndex; // index into m_Route[] + float m_moveWaitTime; // How long I should wait for something to move + + Vector m_vecMoveGoal; // kept around for node graph moves, so we know our ultimate goal + Activity m_movementActivity; // When moving, set this activity + + int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. + int m_afSoundTypes; + + Vector m_vecLastPosition;// monster sometimes wants to return to where it started after an operation. + + int m_iHintNode; // this is the hint node that the monster is moving towards or performing active idle on. + + int m_afMemory; + + int m_iMaxHealth;// keeps track of monster's maximum health value (for re-healing, etc) + Vector m_vecEnemyLKP;// last known position of enemy. (enemy's origin) + int m_cAmmoLoaded; // how much ammo is in the weapon (used to trigger reload anim sequences) + + int m_afCapability;// tells us what a monster can/can't do. + + float m_flNextAttack; // cannot attack again until this time + + int m_bitsDamageType; // what types of damage has monster (player) taken + BYTE m_rgbTimeBasedDamage[CDMG_TIMEBASED]; + + int m_lastDamageAmount;// how much damage did monster (player) last take + // time based damage counters, decr. 1 per 2 seconds + int m_bloodColor; // color of blood particless + + int m_failSchedule; // Schedule type to choose if current schedule fails + + float m_flHungryTime;// set this is a future time to stop the monster from eating for a while. + + float m_flDistTooFar; // if enemy farther away than this, bits_COND_ENEMY_TOOFAR set in CheckEnemy + float m_flDistLook; // distance monster sees (Default 2048) + + int m_iTriggerCondition;// for scripted AI, this is the condition that will cause the activation of the monster's TriggerTarget + string_t m_iszTriggerTarget;// name of target that should be fired. + + Vector m_HackedGunPos; // HACK until we can query end of gun + +// Scripted sequence Info + SCRIPTSTATE m_scriptState; // internal cinematic state + CCineMonster *m_pCine; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; void KeyValue( KeyValueData *pkvd ); - void MakeIdealYaw( Vector vecTarget ); +// monster use function + void EXPORT MonsterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CorpseUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// overrideable Monster member functions + + virtual int BloodColor( void ) { return m_bloodColor; } + + virtual CBaseMonster *MyMonsterPointer( void ) { return this; } + virtual void Look ( int iDistance );// basic sight function for monsters + void Listen ( void ); + + virtual BOOL IsAlive( void ) { return (pev->deadflag != DEAD_DEAD); } + virtual BOOL ShouldFadeOnDeath( void ); + +// Basic Monster AI functions virtual float ChangeYaw ( int speed ); - virtual BOOL HasHumanGibs( void ); - virtual BOOL HasAlienGibs( void ); - virtual void FadeMonster( void ); // Called instead of GibMonster() when gibs are disabled - virtual void GibMonster( void ); + float VecToYaw( Vector vecDir ); + float FlYawDiff ( void ); + + float DamageForce( float damage ); + +// stuff written for new state machine + virtual void MonsterThink( void ); + void EXPORT CallMonsterThink( void ) { this->MonsterThink(); } + virtual int IRelationship ( CBaseEntity *pTarget ); + virtual void MonsterInit ( void ); + virtual void MonsterInitDead( void ); // Call after animation/pose is set up + virtual void BecomeDead( void ); + void EXPORT CorpseFallThink( void ); + + void EXPORT MonsterInitThink ( void ); + virtual void StartMonster ( void ); + virtual CBaseEntity* BestVisibleEnemy ( void );// finds best visible enemy for attack + virtual BOOL FInViewCone ( CBaseEntity *pEntity );// see if pEntity is in monster's view cone + virtual BOOL FInViewCone ( Vector *pOrigin );// see if given location is in monster's view cone + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ); + + virtual int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + virtual void Move( float flInterval = 0.1 ); + virtual void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + virtual BOOL ShouldAdvanceRoute( float flWaypointDist ); + + virtual Activity GetStoppedActivity( void ) { return ACT_IDLE; } + virtual void Stop( void ) { m_IdealActivity = GetStoppedActivity(); } + + // This will stop animation until you call ResetSequenceInfo() at some point in the future + inline void StopAnimation( void ) { pev->framerate = 0; } + + // these functions will survey conditions and set appropriate conditions bits for attack types. + virtual BOOL CheckRangeAttack1( float flDot, float flDist ); + virtual BOOL CheckRangeAttack2( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack2( float flDot, float flDist ); + + BOOL FHaveSchedule( void ); + BOOL FScheduleValid ( void ); + void ClearSchedule( void ); + BOOL FScheduleDone ( void ); + void ChangeSchedule ( Schedule_t *pNewSchedule ); + void NextScheduledTask ( void ); + Schedule_t *ScheduleInList( const char *pName, Schedule_t **pList, int listCount ); + + static Schedule_t *m_scheduleList[]; + + void MaintainSchedule ( void ); + virtual void StartTask ( Task_t *pTask ); + virtual void RunTask ( Task_t *pTask ); + virtual void ScheduleChange( void ) {} + // virtual int CanPlaySequence( void ) { return ((m_pCine == NULL) && (m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE)); } + virtual int CanPlaySequence( BOOL fDisregardState, int interruptLevel ); + virtual int CanPlaySentence( BOOL fDisregardState ) { return IsAlive(); } + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + virtual void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + virtual void SentenceStop( void ); + + Task_t *GetTask ( void ); + virtual void SetActivity ( Activity NewActivity ); + void SetSequenceByName ( char *szSequence ); + virtual void ReportAIState( void ); + + void CheckAttacks ( CBaseEntity *pTarget, float flDist ); + virtual int CheckEnemy ( CBaseEntity *pEnemy ); + void PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ); + BOOL PopEnemy( void ); + + BOOL FGetNodeRoute ( Vector vecDest ); + + inline void TaskComplete( void ) { if ( !HasConditions(bits_COND_TASK_FAILED) ) m_iTaskStatus = TASKSTATUS_COMPLETE; } + void MovementComplete( void ); + inline void TaskFail( void ) { SetConditions(bits_COND_TASK_FAILED); } + inline void TaskBegin( void ) { m_iTaskStatus = TASKSTATUS_RUNNING; } + int TaskIsRunning( void ); + inline int TaskIsComplete( void ) { return (m_iTaskStatus == TASKSTATUS_COMPLETE); } + inline int MovementIsComplete( void ) { return (m_movementGoal == MOVEGOAL_NONE); } + + int IScheduleFlags ( void ); + BOOL FRefreshRoute( void ); + BOOL FRouteClear ( void ); + void RouteSimplify( CBaseEntity *pTargetEnt ); + void AdvanceRoute ( float distance ); + virtual BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + void MakeIdealYaw( Vector vecTarget ); + virtual void SetYawSpeed ( void ) { return; };// allows different yaw_speeds for each activity + BOOL BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ); + virtual BOOL BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + int RouteClassify( int iMoveFlag ); + void InsertWaypoint ( Vector vecLocation, int afMoveFlags ); + + BOOL FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ); + virtual BOOL FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + virtual BOOL FValidateCover ( const Vector &vecCoverLocation ) { return TRUE; }; + virtual float CoverRadius( void ) { return 784; } // Default cover radius + + virtual BOOL FCanCheckAttacks ( void ); + virtual void CheckAmmo( void ) { return; }; + virtual int IgnoreConditions ( void ); + + inline void SetConditions( int iConditions ) { m_afConditions |= iConditions; } + inline void ClearConditions( int iConditions ) { m_afConditions &= ~iConditions; } + inline BOOL HasConditions( int iConditions ) { if ( m_afConditions & iConditions ) return TRUE; return FALSE; } + inline BOOL HasAllConditions( int iConditions ) { if ( (m_afConditions & iConditions) == iConditions ) return TRUE; return FALSE; } + + virtual BOOL FValidateHintType( short sHint ); + int FindHintNode ( void ); + virtual BOOL FCanActiveIdle ( void ); + void SetTurnActivity ( void ); + float FLSoundVolume ( CSound *pSound ); + + BOOL MoveToNode( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToTarget( Activity movementAct, float waitTime ); + BOOL MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToEnemy( Activity movementAct, float waitTime ); + + // Returns the time when the door will be open + float OpenDoorAndWait( entvars_t *pevDoor ); + + virtual int ISoundMask( void ); + virtual CSound* PBestSound ( void ); + virtual CSound* PBestScent ( void ); + virtual float HearingSensitivity( void ) { return 1.0; }; + + BOOL FBecomeProne ( void ); + + void SetEyePosition ( void ); + + BOOL FShouldEat( void );// see if a monster is 'hungry' + void Eat ( float flFullDuration );// make the monster 'full' for a while. + + CBaseEntity *CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ); + BOOL FacingIdeal( void ); + + BOOL FCheckAITrigger( void );// checks and, if necessary, fires the monster's trigger target. + BOOL NoFriendlyFire( void ); + + BOOL BBoxFlat( void ); + + // PrescheduleThink + virtual void PrescheduleThink( void ) { return; }; + + BOOL GetEnemy ( void ); + void MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ); + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue = 0); + + // combat functions + float UpdateTarget ( entvars_t *pevTarget ); virtual Activity GetDeathActivity ( void ); Activity GetSmallFlinchActivity( void ); - virtual void BecomeDead( void ); + virtual void Killed( entvars_t *pevAttacker, int iGib); + virtual void GibMonster( void ); BOOL ShouldGibMonster( int iGib ); void CallGibMonster( void ); - virtual BOOL ShouldFadeOnDeath( void ); - BOOL FCheckAITrigger( void );// checks and, if necessary, fires the monster's trigger target. - virtual int IRelationship ( CBaseEntity *pTarget ); + virtual BOOL HasHumanGibs( void ); + virtual BOOL HasAlienGibs( void ); + virtual void FadeMonster( void ); // Called instead of GibMonster() when gibs are disabled + + Vector ShootAtEnemy( const Vector &shootOrigin ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) * 0.75 + EyePosition() * 0.25; }; // position to shoot at + + virtual Vector GetGunPosition( void ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); int DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); - float DamageForce( float damage ); - virtual void Killed( entvars_t *pevAttacker, int iGib ); - virtual void PainSound ( void ) { return; }; void RadiusDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); void RadiusDamage(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + virtual int IsMoving( void ) { return m_movementGoal != MOVEGOAL_NONE; } - inline void SetConditions( int iConditions ) { m_afConditions |= iConditions; } - inline void ClearConditions( int iConditions ) { m_afConditions &= ~iConditions; } - inline BOOL HasConditions( int iConditions ) { if ( m_afConditions & iConditions ) return TRUE; return FALSE; } - inline BOOL HasAllConditions( int iConditions ) { if ( (m_afConditions & iConditions) == iConditions ) return TRUE; return FALSE; } + void RouteClear( void ); + void RouteNew( void ); + + virtual void DeathSound ( void ) { return; }; + virtual void AlertSound ( void ) { return; }; + virtual void IdleSound ( void ) { return; }; + virtual void PainSound ( void ) { return; }; + + virtual void StopFollowing( BOOL clearSchedule ) {} inline void Remember( int iMemory ) { m_afMemory |= iMemory; } inline void Forget( int iMemory ) { m_afMemory &= ~iMemory; } inline BOOL HasMemory( int iMemory ) { if ( m_afMemory & iMemory ) return TRUE; return FALSE; } inline BOOL HasAllMemories( int iMemory ) { if ( (m_afMemory & iMemory) == iMemory ) return TRUE; return FALSE; } - // This will stop animation until you call ResetSequenceInfo() at some point in the future - inline void StopAnimation( void ) { pev->framerate = 0; } - - virtual void ReportAIState( void ); - virtual void MonsterInitDead( void ); // Call after animation/pose is set up - void EXPORT CorpseFallThink( void ); - - virtual void Look ( int iDistance );// basic sight function for monsters - virtual CBaseEntity* BestVisibleEnemy ( void );// finds best visible enemy for attack - CBaseEntity *CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ); - virtual BOOL FInViewCone ( CBaseEntity *pEntity );// see if pEntity is in monster's view cone - virtual BOOL FInViewCone ( Vector *pOrigin );// see if given location is in monster's view cone - void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); - void MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ); - virtual BOOL IsAlive( void ) { return (pev->deadflag != DEAD_DEAD); } + BOOL ExitScriptedSequence( ); + BOOL CineCleanup( ); + CBaseEntity* DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng );// drop an item. }; -#endif + +#endif // BASEMONSTER_H diff --git a/dlls/cbase.h b/dlls/cbase.h index 0c36ae6..393e3e7 100644 --- a/dlls/cbase.h +++ b/dlls/cbase.h @@ -98,7 +98,6 @@ typedef void (CBaseEntity::*USEPTR)( CBaseEntity *pActivator, CBaseEntity *pCall #define CLASS_PLAYER_ALLY 11 #define CLASS_PLAYER_BIOWEAPON 12 // hornets and snarks.launched by players #define CLASS_ALIEN_BIOWEAPON 13 // hornets and snarks.launched by the alien menace -#define CLASS_BARNACLE 99 // special because no one pays attention to it, and it eats a wide cross-section of creatures. class CBaseEntity; class CBaseMonster; @@ -163,7 +162,7 @@ public: static TYPEDESCRIPTION m_SaveData[]; - virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue = 0); virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); virtual int TakeHealth( float flHealth, int bitsDamageType ); virtual void Killed( entvars_t *pevAttacker, int iGib ); @@ -240,7 +239,7 @@ public: void EXPORT SUB_CallUseToggle( void ) { this->Use( this, this, USE_TOGGLE, 0 ); } int ShouldToggle( USE_TYPE useType, BOOL currentState ); void FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq = 4, int iDamage = 0, entvars_t *pevAttacker = NULL ); - Vector FireBulletsPlayer( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq = 4, int iDamage = 0, entvars_t *pevAttacker = NULL, int shared_rand = 0 ); + Vector FireBulletsPlayer( CBaseEntity *pPlayerItem,ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq = 4, int iDamage = 0, entvars_t *pevAttacker = NULL, int shared_rand = 0,int penetration_type = 0 ); virtual CBaseEntity *Respawn( void ) { return NULL; } @@ -277,7 +276,6 @@ public: return NULL; } - // Ugly code to lookup all functions to make sure they are exported when set. #ifdef _DEBUG void FunctionCheck( void *pFunction, char *name ) @@ -349,16 +347,9 @@ public: int ammo_argrens; //Special stuff for grenades and satchels. float m_flStartThrow; - float m_flReleaseThrow; - int m_chargeReady; - int m_fInAttack; - - enum EGON_FIRESTATE { FIRE_OFF, FIRE_CHARGE }; - int m_fireState; + float m_flReleaseThrow; int m_fInAttack; }; - - // Ugly technique to override base member functions // Normally it's illegal to cast a pointer to a member function of a derived class to a pointer to a // member function of a base class. static_cast is a sleezy way around that problem. @@ -581,8 +572,6 @@ public: #define bits_CAP_DOORS_GROUP (bits_CAP_USE | bits_CAP_AUTO_DOORS | bits_CAP_OPEN_DOORS) -// used by suit voice to indicate damage sustained and repaired type to player - // instant damage #define DMG_GENERIC 0 // generic damage was done @@ -613,6 +602,11 @@ public: #define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer #define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) +// The Wastes +#define DMG_BULLET_CONC (1 << 25) // Screw up victim if their shot +#define DMG_BUCKSHOT (1 << 26) // Shotgun no locational damage +#define DMG_BLEEDING (1 << 27) // Bleeding to death + // these are the damage types that are allowed to gib corpses #define DMG_GIB_CORPSE ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ) @@ -642,7 +636,6 @@ public: #define SLOWFREEZE_DURATION 2 #define SLOWFREEZE_DAMAGE 1.0 - #define itbd_Paralyze 0 #define itbd_NerveGas 1 #define itbd_Poison 2 diff --git a/dlls/cdll_dll.h b/dlls/cdll_dll.h index f6ed329..d53e585 100644 --- a/dlls/cdll_dll.h +++ b/dlls/cdll_dll.h @@ -22,15 +22,15 @@ #define MAX_WEAPONS 32 // ??? -#define MAX_WEAPON_SLOTS 5 // hud item selection slots -#define MAX_ITEM_TYPES 6 // hud item selection slots +#define MAX_WEAPON_SLOTS 6 // hud item selection slots +#define MAX_ITEM_TYPES 8 // hud item selection slots -#define MAX_ITEMS 5 // hard coded item types +#define MAX_ITEMS 6 // hard coded item types #define HIDEHUD_WEAPONS ( 1<<0 ) #define HIDEHUD_FLASHLIGHT ( 1<<1 ) #define HIDEHUD_ALL ( 1<<2 ) -#define HIDEHUD_HEALTH ( 1<<3 ) +#define HIDEHUD_HEALTH ( 1<<3 ) #define MAX_AMMO_TYPES 32 // ??? #define MAX_AMMO_SLOTS 32 // not really slots @@ -40,7 +40,4 @@ #define HUD_PRINTTALK 3 #define HUD_PRINTCENTER 4 - -#define WEAPON_SUIT 31 - #endif \ No newline at end of file diff --git a/dlls/client.cpp b/dlls/client.cpp index fc278a8..0efa13b 100644 --- a/dlls/client.cpp +++ b/dlls/client.cpp @@ -38,6 +38,8 @@ #include "weaponinfo.h" #include "usercmd.h" #include "netadr.h" +#include "game.h" +#include "thewastes.h" extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; extern DLL_GLOBAL BOOL g_fGameOver; @@ -45,10 +47,12 @@ extern DLL_GLOBAL int g_iSkillLevel; extern DLL_GLOBAL ULONG g_ulFrameCount; extern void CopyToBodyQue(entvars_t* pev); +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + extern int giPrecacheGrunt; extern int gmsgSayText; -extern int g_teamplay; +extern int g_gametype; void LinkUserMessages( void ); @@ -68,7 +72,6 @@ void set_suicide_frame(entvars_t* pev) pev->nextthink = -1; } - /* =========== ClientConnect @@ -78,12 +81,21 @@ called when a player connects to a server */ BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) { + // See if this person is in the hardcode ban list + if( + strcmp( GETPLAYERAUTHID( pEntity ), "2678967" ) == 0 // Porta bella mushroom + // || strcmp( GETPLAYERAUTHID( pEntity ), "762057" ) == 0 // gage + ) + { + strcpy( szRejectReason, "You are not authorized to join this server\n" ); + return FALSE; + } + return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); // a client connecting during an intermission can cause problems // if (intermission_running) // ExitIntermission (); - } @@ -139,7 +151,10 @@ void respawn(entvars_t* pev, BOOL fCopyCorpse) } // respawn player - GetClassPtr( (CBasePlayer *)pev)->Spawn( ); + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer*)pev); + + pPlayer->StopObserver(); + pPlayer->Spawn( ); } else { // restart the entire server @@ -160,6 +175,10 @@ void ClientKill( edict_t *pEntity ) { entvars_t *pev = &pEntity->v; + // if a spectator, you cant suicide + if(pev->iuser3) + return; + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pev ); if ( pl->m_fNextSuicideTime > gpGlobals->time ) @@ -186,12 +205,15 @@ called each time a player is spawned void ClientPutInServer( edict_t *pEntity ) { CBasePlayer *pPlayer; - entvars_t *pev = &pEntity->v; - pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer = GetClassPtr((CBasePlayer*)pev); pPlayer->SetCustomDecalFrames(-1); // Assume none; + // Start off spectating + pPlayer->m_bCanSpawn = FALSE; + pPlayer->m_bObserver = FALSE; // But no HLTV :( + // Allocate a CBasePlayer for pev, and call spawn pPlayer->Spawn() ; @@ -199,6 +221,31 @@ void ClientPutInServer( edict_t *pEntity ) pPlayer->pev->effects |= EF_NOINTERP; } +void SendMsgTo(CBasePlayer *client,edict_t *pEntity, int teamonly,char text[128],char *classname) +{ + client = NULL; + + while (((client = (CBasePlayer*)UTIL_FindEntityByClassname( client, classname )) != NULL) && (!FNullEnt(client->edict()))) + { + if ( !client->pev ) + continue; + + if ( client->edict() == pEntity ) + continue; + + if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) + continue; + + if ( teamonly && g_pGameRules->PlayerRelationship(client, CBaseEntity::Instance(pEntity)) != GR_TEAMMATE ) + continue; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, client->pev ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + } +} + //// HOST_SAY // String comes in as // say blah blah blah @@ -207,7 +254,7 @@ void ClientPutInServer( edict_t *pEntity ) // void Host_Say( edict_t *pEntity, int teamonly ) { - CBasePlayer *client; + CBasePlayer *client = NULL; int j; char *p; char text[128]; @@ -265,11 +312,24 @@ void Host_Say( edict_t *pEntity, int teamonly ) if ( pc != NULL ) return; // no character found, so say nothing + // Do we want a [DEAD] symbol ? + int use_dead = (strcmp(STRING(pEntity->v.classname),"observer") == 0) ? 1 : 0; + // turn on color set 2 (color on, no sound) if ( teamonly ) - sprintf( text, "%c(TEAM) %s: ", 2, STRING( pEntity->v.netname ) ); + { + if( use_dead ) + sprintf( text, "%c(TEAM)[DEAD] %s: ", 2, STRING(pEntity->v.netname) ); + else + sprintf( text, "%c(TEAM)%s: ", 2, STRING(pEntity->v.netname) ); + } else - sprintf( text, "%c%s: ", 2, STRING( pEntity->v.netname ) ); + { + if( use_dead ) + sprintf( text, "%c[DEAD] %s: ", 2, STRING(pEntity->v.netname) ); + else + sprintf( text, "%c%s: ", 2, STRING(pEntity->v.netname) ); + } j = sizeof(text) - 2 - strlen(text); // -2 for /n and null terminator if ( (int)strlen(p) > j ) @@ -283,27 +343,13 @@ void Host_Say( edict_t *pEntity, int teamonly ) // This may return the world in single player if the client types something between levels or during spawn // so check it, or it will infinite loop - client = NULL; - while ( ((client = (CBasePlayer*)UTIL_FindEntityByClassname( client, "player" )) != NULL) && (!FNullEnt(client->edict())) ) - { - if ( !client->pev ) - continue; - - if ( client->edict() == pEntity ) - continue; + // send to all "players" - if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) - continue; + CBaseEntity *pInstance = CBaseEntity::Instance(pEntity); - if ( teamonly && g_pGameRules->PlayerRelationship(client, CBaseEntity::Instance(pEntity)) != GR_TEAMMATE ) - continue; - - MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, client->pev ); - WRITE_BYTE( ENTINDEX(pEntity) ); - WRITE_STRING( text ); - MESSAGE_END(); - - } +// if(pInstance->pev->classname = STRING(ALLOC_STRING("player"))) + SendMsgTo(client,pEntity,teamonly,text,"player"); + SendMsgTo(client,pEntity,teamonly,text,"observer"); // print to the sending client MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v ); @@ -321,7 +367,7 @@ void Host_Say( edict_t *pEntity, int teamonly ) temp = "say"; // team match? - if ( g_teamplay ) + if ( g_gametype == 2 ) { UTIL_LogPrintf( "\"%s<%i><%u><%s>\" %s \"%s\"\n", STRING( pEntity->v.netname ), @@ -352,6 +398,8 @@ called each time a player uses a "cmd" command */ extern float g_flWeaponCheat; +#include "shake.h" + // Use CMD_ARGV, CMD_ARGV, and CMD_ARGC to get pointers the character string command. void ClientCommand( edict_t *pEntity ) { @@ -380,7 +428,6 @@ void ClientCommand( edict_t *pEntity ) GetClassPtr((CBasePlayer *)pev)->GiveNamedItem( STRING(iszItem) ); } } - else if ( FStrEq(pcmd, "drop" ) ) { // player is dropping an item. @@ -471,7 +518,7 @@ void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ) MESSAGE_END(); // team match? - if ( g_teamplay ) + if ( g_gametype ) { UTIL_LogPrintf( "\"%s<%i><%u><%s>\" changed name to \"%s\"\n", STRING( pEntity->v.netname ), @@ -606,11 +653,10 @@ void StartFrame( void ) if ( g_fGameOver ) return; - gpGlobals->teamplay = teamplay.value; + gpGlobals->gametype = gametype.value; g_ulFrameCount++; } - void ClientPrecache( void ) { // setup precaches always needed @@ -618,6 +664,8 @@ void ClientPrecache( void ) // PRECACHE_SOUND("player/pl_jumpland2.wav"); // UNDONE: play 2x step sound + PRECACHE_SOUND("player/aaaggghhh.wav"); // AAAGGGGHHH !!! + PRECACHE_SOUND("player/pl_fallpain2.wav"); PRECACHE_SOUND("player/pl_fallpain3.wav"); @@ -677,23 +725,77 @@ void ClientPrecache( void ) PRECACHE_SOUND("player/pl_wade3.wav"); PRECACHE_SOUND("player/pl_wade4.wav"); + PRECACHE_SOUND("player/pl_sand1.wav"); + PRECACHE_SOUND("player/pl_sand2.wav"); + PRECACHE_SOUND("player/pl_sand3.wav"); + PRECACHE_SOUND("player/pl_sand4.wav"); + + PRECACHE_SOUND("player/pl_snow1.wav"); + PRECACHE_SOUND("player/pl_snow2.wav"); + PRECACHE_SOUND("player/pl_snow3.wav"); + PRECACHE_SOUND("player/pl_snow4.wav"); + + PRECACHE_SOUND("player/pl_rust1.wav"); + PRECACHE_SOUND("player/pl_rust2.wav"); + PRECACHE_SOUND("player/pl_rust3.wav"); + PRECACHE_SOUND("player/pl_rust4.wav"); + + PRECACHE_SOUND("player/pl_wood1.wav"); + PRECACHE_SOUND("player/pl_wood2.wav"); + PRECACHE_SOUND("player/pl_wood3.wav"); + PRECACHE_SOUND("player/pl_wood4.wav"); + PRECACHE_SOUND("debris/wood1.wav"); // hit wood texture PRECACHE_SOUND("debris/wood2.wav"); PRECACHE_SOUND("debris/wood3.wav"); + PRECACHE_SOUND("debris/ric1.wav"); + PRECACHE_SOUND("debris/ric2.wav"); + PRECACHE_SOUND("debris/ric3.wav"); + PRECACHE_SOUND("debris/ric4.wav"); + PRECACHE_SOUND("debris/ric5.wav"); + + PRECACHE_SOUND("debris/ric_con1.wav"); + PRECACHE_SOUND("debris/ric_con2.wav"); + PRECACHE_SOUND("debris/ric_con3.wav"); + + PRECACHE_SOUND("debris/ric_tinroof1.wav"); + PRECACHE_SOUND("debris/ric_tinroof2.wav"); + PRECACHE_SOUND("debris/ric_tinroof3.wav"); + + PRECACHE_SOUND("debris/ric_barrel1.wav"); + PRECACHE_SOUND("debris/ric_barrel2.wav"); + PRECACHE_SOUND("debris/ric_barrel3.wav"); + PRECACHE_SOUND("debris/ric_barrel4.wav"); + + PRECACHE_SOUND("debris/ric_drywall1.wav"); + PRECACHE_SOUND("debris/ric_drywall2.wav"); + + PRECACHE_SOUND("debris/ric_metalwall1.wav"); + PRECACHE_SOUND("debris/ric_metalwall2.wav"); + + PRECACHE_SOUND("debris/ric_wood1.wav"); + PRECACHE_SOUND("debris/ric_wood2.wav"); + PRECACHE_SOUND("debris/ric_wood3.wav"); + PRECACHE_SOUND("debris/ric_wood4.wav"); + PRECACHE_SOUND("debris/ric_wood5.wav"); + PRECACHE_SOUND("plats/train_use1.wav"); // use a train PRECACHE_SOUND("buttons/spark5.wav"); // hit computer texture PRECACHE_SOUND("buttons/spark6.wav"); - PRECACHE_SOUND("debris/glass1.wav"); - PRECACHE_SOUND("debris/glass2.wav"); - PRECACHE_SOUND("debris/glass3.wav"); + + PRECACHE_SOUND("debris/ric_glass1.wav"); + PRECACHE_SOUND("debris/ric_glass2.wav"); + PRECACHE_SOUND("debris/ric_glass3.wav"); PRECACHE_SOUND( SOUND_FLASHLIGHT_ON ); PRECACHE_SOUND( SOUND_FLASHLIGHT_OFF ); // player gib sounds - PRECACHE_SOUND("common/bodysplat.wav"); + PRECACHE_SOUND("common/bodysplat.wav"); + PRECACHE_SOUND("player/pl_crushed.wav"); + PRECACHE_SOUND("player/pl_drowned.wav"); // player pain sounds PRECACHE_SOUND("player/pl_pain2.wav"); @@ -704,16 +806,33 @@ void ClientPrecache( void ) PRECACHE_MODEL("models/player.mdl"); - // hud sounds +//Particles +// PRECACHE_MODEL("sprites/particle.spr"); +// PRECACHE_MODEL("sprites/psmoke.spr"); - PRECACHE_SOUND("common/wpn_hudoff.wav"); - PRECACHE_SOUND("common/wpn_hudon.wav"); - PRECACHE_SOUND("common/wpn_moveselect.wav"); - PRECACHE_SOUND("common/wpn_select.wav"); - PRECACHE_SOUND("common/wpn_denyselect.wav"); + // Smoke Puffs + PRECACHE_MODEL("sprites/smoke1.spr"); + PRECACHE_MODEL("sprites/smoke2.spr"); + PRECACHE_MODEL("models/shrapnel.mdl"); - // geiger sounds + PRECACHE_SOUND("player/headshot1.wav"); + PRECACHE_SOUND("player/headshot2.wav"); + PRECACHE_SOUND("player/headshot3.wav"); + PRECACHE_SOUND("player/leg_hit1.wav"); + PRECACHE_SOUND("player/arm_hit1.wav"); + PRECACHE_SOUND("player/chest_hit1.wav"); + PRECACHE_SOUND("player/blood_drip1.wav"); + PRECACHE_SOUND("player/blood_drip2.wav"); + PRECACHE_SOUND("player/blood_drip3.wav"); + + PRECACHE_SOUND("player/equip_kevlar.wav"); + PRECACHE_SOUND("player/kevlar_hit1.wav"); + PRECACHE_SOUND("player/pl_die1.wav"); + PRECACHE_SOUND("player/pl_die2.wav"); + PRECACHE_SOUND("player/pl_die3.wav"); + PRECACHE_SOUND("player/breathe1.wav"); + PRECACHE_SOUND("player/breathe2.wav"); PRECACHE_SOUND("player/geiger6.wav"); PRECACHE_SOUND("player/geiger5.wav"); @@ -738,7 +857,7 @@ const char *GetGameDescription() if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized return g_pGameRules->GetGameDescription(); else - return "Half-Life"; + return "The Wastes"; } /* @@ -872,6 +991,13 @@ void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pv pView = pViewEntity; } + // Tracking Spectators use the visibility of their target + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + if ( (pPlayer->pev->iuser2 != 0) && (pPlayer->m_hObserverTarget != NULL) ) + { + pView = pPlayer->m_hObserverTarget->edict(); + } + if ( pClient->v.flags & FL_PROXY ) { *pvs = NULL; // the spectator proxy sees @@ -933,7 +1059,6 @@ int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *h } } - // Don't send entity to local client if the client says it's predicting the entity itself. if ( ent->v.flags & FL_SKIPLOCALHOST ) { @@ -1401,7 +1526,7 @@ int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) weapon_data_t *item; entvars_t *pev = &player->v; CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); - CBasePlayerWeapon *gun; + CWasteWeapon *gun; ItemInfo II; @@ -1420,7 +1545,7 @@ int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) while ( pPlayerItem ) { - gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + gun = (CWasteWeapon *)pPlayerItem->GetWeaponPtr(); if ( gun && gun->UseDecrement() ) { // Get The ID. @@ -1430,6 +1555,9 @@ int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) if ( II.iId >= 0 && II.iId < 32 ) { item = &info[ II.iId ]; + + // Pack weapon specific data here. + gun->PackWeapon(item); item->m_iId = II.iId; item->m_iClip = gun->m_iClip; @@ -1442,12 +1570,7 @@ int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) item->fuser1 = max( gun->pev->fuser1, -0.001 ); item->fuser2 = gun->m_flStartThrow; item->fuser3 = gun->m_flReleaseThrow; - item->iuser1 = gun->m_chargeReady; - item->iuser2 = gun->m_fInAttack; - item->iuser3 = gun->m_fireState; - - -// item->m_flPumpTime = max( gun->m_flPumpTime, -0.001 ); + item->iuser2 = gun->m_fInAttack; } } pPlayerItem = pPlayerItem->m_pNext; @@ -1497,7 +1620,11 @@ void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clien cd->fov = ent->v.fov; cd->weaponanim = ent->v.weaponanim; - cd->pushmsec = ent->v.pushmsec; + cd->pushmsec = ent->v.pushmsec; + + // Spectator + cd->iuser1 = ent->v.iuser1; + cd->iuser2 = ent->v.iuser2; #if defined( CLIENT_WEAPONS ) if ( sendweapons ) @@ -1508,8 +1635,6 @@ void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clien if ( pl ) { cd->m_flNextAttack = pl->m_flNextAttack; - cd->fuser2 = pl->m_flNextAmmoBurn; - cd->fuser3 = pl->m_flAmmoStartCharge; cd->vuser1.x = pl->ammo_9mm; cd->vuser1.y = pl->ammo_357; cd->vuser1.z = pl->ammo_argrens; @@ -1518,7 +1643,6 @@ void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clien cd->ammo_rockets = pl->ammo_rockets; cd->ammo_cells = pl->ammo_uranium; cd->vuser2.x = pl->ammo_hornets; - if ( pl->m_pActiveItem ) { @@ -1536,12 +1660,6 @@ void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clien cd->vuser4.x = gun->m_iPrimaryAmmoType; cd->vuser4.y = pl->m_rgAmmo[gun->m_iPrimaryAmmoType]; cd->vuser4.z = pl->m_rgAmmo[gun->m_iSecondaryAmmoType]; - - if ( pl->m_pActiveItem->m_iId == WEAPON_RPG ) - { - cd->vuser2.y = ( ( CRpg * )pl->m_pActiveItem)->m_fSpotActive; - cd->vuser2.z = ( ( CRpg * )pl->m_pActiveItem)->m_cActiveRockets; - } } } } diff --git a/dlls/combat.cpp b/dlls/combat.cpp index 616d39c..fac1d1d 100644 --- a/dlls/combat.cpp +++ b/dlls/combat.cpp @@ -29,6 +29,9 @@ #include "animation.h" #include "weapons.h" #include "func_break.h" +#include "player.h" +#include "gamerules.h" +#include "thewastes.h" extern DLL_GLOBAL Vector g_vecAttackDir; extern DLL_GLOBAL int g_iSkillLevel; @@ -830,8 +833,6 @@ bitsDamageType indicates the type of damage sustained, ie: DMG_SHOCK Time-based damage: only occurs while the monster is within the trigger_hurt. When a monster is poisoned via an arrow etc it takes all the poison damage at once. - - GLOBALS ASSUMED SET: g_iSkillLevel ============ */ @@ -862,9 +863,10 @@ int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). vecDir = Vector( 0, 0, 0 ); - if (!FNullEnt( pevInflictor )) + if (!FNullEnt( pevInflictor ) && !(bitsDamageType & (DMG_CLUB|DMG_SHOCK))) { - CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + CBasePlayer *pInflictor = (CBasePlayer*)CBasePlayer :: Instance( pevInflictor ); + if (pInflictor) { vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); @@ -884,9 +886,7 @@ int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, // check for godmode or invincibility if ( pev->flags & FL_GODMODE ) - { return 0; - } } // if this is a player, move him around! @@ -897,7 +897,6 @@ int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, // do the damage pev->health -= flTake; - // HACKHACK Don't kill monsters in a script. Let them break their scripts first if ( m_MonsterState == MONSTERSTATE_SCRIPT ) @@ -923,8 +922,7 @@ int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, Killed( pevAttacker, GIB_NORMAL ); } - g_pevLastInflictor = NULL; - + g_pevLastInflictor = NULL; return 0; } @@ -1276,7 +1274,7 @@ BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) TraceAttack ================ */ -void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue) { Vector vecOrigin = ptr->vecEndPos - vecDir * 4; @@ -1323,7 +1321,7 @@ void CBaseMonster::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector ve //========================================================= // TraceAttack //========================================================= -void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue) { if ( pev->takedamage ) { @@ -1334,21 +1332,16 @@ void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector case HITGROUP_GENERIC: break; case HITGROUP_HEAD: - flDamage *= gSkillData.monHead; break; case HITGROUP_CHEST: - flDamage *= gSkillData.monChest; break; case HITGROUP_STOMACH: - flDamage *= gSkillData.monStomach; break; case HITGROUP_LEFTARM: case HITGROUP_RIGHTARM: - flDamage *= gSkillData.monArm; break; case HITGROUP_LEFTLEG: case HITGROUP_RIGHTLEG: - flDamage *= gSkillData.monLeg; break; default: break; @@ -1417,23 +1410,6 @@ void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting if ( iTracerFreq != 1 ) // guns that always trace also always decal tracer = 1; - switch( iBulletType ) - { - case BULLET_MONSTER_MP5: - case BULLET_MONSTER_9MM: - case BULLET_MONSTER_12MM: - default: - MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, vecTracerSrc ); - WRITE_BYTE( TE_TRACER ); - WRITE_COORD( vecTracerSrc.x ); - WRITE_COORD( vecTracerSrc.y ); - WRITE_COORD( vecTracerSrc.z ); - WRITE_COORD( tr.vecEndPos.x ); - WRITE_COORD( tr.vecEndPos.y ); - WRITE_COORD( tr.vecEndPos.z ); - MESSAGE_END(); - break; - } } // do damage, paint decals if (tr.flFraction != 1.0) @@ -1450,30 +1426,6 @@ void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting else switch(iBulletType) { default: - case BULLET_MONSTER_9MM: - pEntity->TraceAttack(pevAttacker, gSkillData.monDmg9MM, vecDir, &tr, DMG_BULLET); - - TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); - DecalGunshot( &tr, iBulletType ); - - break; - - case BULLET_MONSTER_MP5: - pEntity->TraceAttack(pevAttacker, gSkillData.monDmgMP5, vecDir, &tr, DMG_BULLET); - - TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); - DecalGunshot( &tr, iBulletType ); - - break; - - case BULLET_MONSTER_12MM: - pEntity->TraceAttack(pevAttacker, gSkillData.monDmg12MM, vecDir, &tr, DMG_BULLET); - if ( !tracer ) - { - TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); - DecalGunshot( &tr, iBulletType ); - } - break; case BULLET_NONE: // FIX pEntity->TraceAttack(pevAttacker, 50, vecDir, &tr, DMG_CLUB); @@ -1493,7 +1445,6 @@ void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting ApplyMultiDamage(pev, pevAttacker); } - /* ================ FireBullets @@ -1503,8 +1454,9 @@ Go to the trouble of combining multiple pellets into a single damage call. This version is used by Players, uses the random seed generator to sync client and server side shots. ================ */ -Vector CBaseEntity::FireBulletsPlayer ( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker, int shared_rand ) +Vector CBaseEntity::FireBulletsPlayer ( CBaseEntity *pPlayerItem,ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker, int shared_rand, int penetration_type ) { + CWasteWeapon *pWeapon = (CWasteWeapon*)pPlayerItem; static int tracerCount; TraceResult tr; Vector vecRight = gpGlobals->v_right; @@ -1517,70 +1469,211 @@ Vector CBaseEntity::FireBulletsPlayer ( ULONG cShots, Vector vecSrc, Vector vecD ClearMultiDamage(); gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; - for ( ULONG iShot = 1; iShot <= cShots; iShot++ ) + int iWallThickness = 0; + int iMaxThickness = 0; + float fFalloffRate = 0; + int iWallLimit = MAX_WALLCOUNT; + + // Penetration values + switch(penetration_type) { - //Use player's random seed. - // get circular gaussian spread - x = UTIL_SharedRandomFloat( shared_rand + iShot, -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 1 + iShot ) , -0.5, 0.5 ); - y = UTIL_SharedRandomFloat( shared_rand + ( 2 + iShot ), -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 3 + iShot ), -0.5, 0.5 ); - z = x * x + y * y; + case P_BERETTA: + iWallThickness = MAX_THICKNESS_BERETTA; + fFalloffRate = FALLOFF_BERETTA; + break; + case P_COLT: + iWallThickness = MAX_THICKNESS_COLT; + fFalloffRate = FALLOFF_COLT; + break; + case P_RUGER: + iWallThickness = MAX_THICKNESS_RUGER; + fFalloffRate = FALLOFF_RUGER; + break; + case P_DEAGLE: + iWallThickness = MAX_THICKNESS_DEAGLE; + fFalloffRate = FALLOFF_DEAGLE; + break; + case P_HANDCANNON: + iWallThickness = MAX_THICKNESS_HANDCANNON; + fFalloffRate = FALLOFF_HANDCANNON; + break; + case P_WINCHESTER: + iWallThickness = MAX_THICKNESS_WINCHESTER; + fFalloffRate = FALLOFF_WINCHESTER; + break; + case P_SMG9: + iWallThickness = MAX_THICKNESS_SMG9; + fFalloffRate = FALLOFF_SMG9; + break; + case P_FNFAL: + iWallThickness = MAX_THICKNESS_FNFAL; + fFalloffRate = FALLOFF_FNFAL; + break; + case P_TOMMYGUN: + iWallThickness = MAX_THICKNESS_TOMMYGUN; + fFalloffRate = FALLOFF_TOMMYGUN; + break; + case P_G11: + iWallThickness = MAX_THICKNESS_G11; + fFalloffRate = FALLOFF_G11; + break; + case P_BOLTRIFLE: + iWallThickness = MAX_THICKNESS_BOLTRIFLE; + fFalloffRate = FALLOFF_BOLTRIFLE; + break; + case P_STEN: + iWallThickness = MAX_THICKNESS_STEN; + fFalloffRate = FALLOFF_STEN; + break; + } - Vector vecDir = vecDirShooting + - x * vecSpread.x * vecRight + - y * vecSpread.y * vecUp; - Vector vecEnd; + iMaxThickness = iWallThickness; - vecEnd = vecSrc + vecDir * flDistance; - UTIL_TraceLine(vecSrc, vecEnd, dont_ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); - - // do damage, paint decals - if (tr.flFraction != 1.0) + // Prevent divide by zero + if( iMaxThickness == 0 ) + iMaxThickness = 1; + + do + { + for ( ULONG iShot = 1; iShot <= cShots; iShot++ ) { - CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + //Use player's random seed. + // get circular gaussian spread + x = UTIL_SharedRandomFloat( shared_rand + iShot, -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 1 + iShot ) , -0.5, 0.5 ); + y = UTIL_SharedRandomFloat( shared_rand + ( 2 + iShot ), -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 3 + iShot ), -0.5, 0.5 ); + z = x * x + y * y; - if ( iDamage ) + Vector vecDir = vecDirShooting + + x * vecSpread.x * vecRight + + y * vecSpread.y * vecUp; + Vector vecEnd; + + vecEnd = vecSrc + vecDir * flDistance; + UTIL_TraceLine(vecSrc, vecEnd, dont_ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); + + // do damage, paint decals + if (tr.flFraction != 1.0) { - pEntity->TraceAttack(pevAttacker, iDamage, vecDir, &tr, DMG_BULLET | ((iDamage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB) ); - - TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); - DecalGunshot( &tr, iBulletType ); - } - else switch(iBulletType) - { - default: - case BULLET_PLAYER_9MM: - pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg9MM, vecDir, &tr, DMG_BULLET); - break; + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); - case BULLET_PLAYER_MP5: - pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgMP5, vecDir, &tr, DMG_BULLET); - break; - - case BULLET_PLAYER_BUCKSHOT: - // make distance based! - pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgBuckshot, vecDir, &tr, DMG_BULLET); - break; - - case BULLET_PLAYER_357: - pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg357, vecDir, &tr, DMG_BULLET); - break; - - case BULLET_NONE: // FIX - pEntity->TraceAttack(pevAttacker, 50, vecDir, &tr, DMG_CLUB); - TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); - // only decal glass - if ( !FNullEnt(tr.pHit) && VARS(tr.pHit)->rendermode != 0) + // reset vuser2 + pEntity->pev->vuser2.y = 0; + + if ( iDamage ) { - UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG(0,2) ); + pEntity->TraceAttack(pevAttacker, iDamage, vecDir, &tr, DMG_BULLET | ((iDamage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB) ); + + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); } + else switch(iBulletType) + { + // Standard + default: + case BULLET_9MMP: + case BULLET_10MM: + case BULLET_SLUG: + case BULLET_9MM: + case BULLET_45ACP: + pEntity->TraceAttack(pevAttacker, g_pGameRules->ModifyTiltedDamage(this,pEntity,pWeapon->flGetTiltedDamage(),DMG_BULLET), vecDir, &tr, DMG_BULLET,iWallThickness/iMaxThickness); + break; + // Concussive + case BULLET_50AE: + case BULLET_454CASULL: + case BULLET_556MM: + pEntity->TraceAttack(pevAttacker, g_pGameRules->ModifyTiltedDamage(this,pEntity,pWeapon->flGetTiltedDamage(),DMG_BULLET_CONC), vecDir, &tr, DMG_BULLET_CONC,iWallThickness/iMaxThickness); + break; + // Buckshot + case BULLET_20GAUGE: + case BULLET_12GAUGE: + pEntity->TraceAttack(pevAttacker, g_pGameRules->ModifyTiltedDamage(this,pEntity,pWeapon->flGetTiltedDamage(),DMG_BUCKSHOT), vecDir, &tr, DMG_BUCKSHOT); + break; + // Standard but no distance drop off + case BULLET_762MM: + case BULLET_47MM: + pEntity->TraceAttack(pevAttacker, g_pGameRules->ModifyTiltedDamage(this,pEntity,pWeapon->flGetTiltedDamage(),DMG_BULLET_CONC), vecDir, &tr, DMG_BULLET,iWallThickness/iMaxThickness); + break; + case BULLET_10GAUGE: + pEntity->TraceAttack(pevAttacker, g_pGameRules->ModifyTiltedDamage(this,pEntity,pWeapon->flGetTiltedDamage(),DMG_BULLET_CONC), vecDir, &tr, DMG_BUCKSHOT,iWallThickness/iMaxThickness); + break; + case BULLET_NONE: // FIX + pEntity->TraceAttack(pevAttacker, 50, vecDir, &tr, DMG_CLUB); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + // only decal glass + if ( !FNullEnt(tr.pHit) && VARS(tr.pHit)->rendermode != 0) + { + UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG(0,2) ); + } - break; + break; + } + } + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, (flDistance * tr.flFraction) / 64.0 ); + +#if 0 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 10 ); + WRITE_BYTE( 128 ); + WRITE_BYTE( 5 ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 255 ); + WRITE_BYTE( 255 ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 255 ); + WRITE_BYTE( 10 ); + MESSAGE_END(); +#endif + + // If we have wall penetration, use it. + if(iWallThickness) + { + TraceResult new_tr; + TraceResult backtrack_tr; // For exit decals + + Vector wall_begin = tr.vecEndPos; + Vector wall_end; + + UTIL_TraceLine(tr.vecEndPos + vecDir * iWallThickness,vecEnd,ignore_monsters,ENT(pev),&new_tr); + + if(new_tr.flFraction == 1.0) + iWallThickness = 0; + else + { + // Get exit hole + UTIL_TraceLine(new_tr.vecEndPos,tr.vecEndPos,ignore_monsters,ENT(pev),&backtrack_tr); + + if(backtrack_tr.flFraction != 1.0) + { + TEXTURETYPE_PlaySound(&backtrack_tr,vecSrc,backtrack_tr.vecEndPos,iBulletType); + + // Only subtract from wall if it is from BSP (hard target) + if(backtrack_tr.pHit != NULL && VARS(backtrack_tr.pHit)->solid == SOLID_BSP) + { + wall_end = wall_begin - backtrack_tr.vecEndPos; + iWallThickness -= (int)(wall_end.Length() * fFalloffRate); + } + + // Trace ahead to get next wall. + vecSrc = backtrack_tr.vecEndPos; + } + else + iWallThickness = 0; // No more place to go! + + } } } - // make bullet trails - UTIL_BubbleTrail( vecSrc, tr.vecEndPos, (flDistance * tr.flFraction) / 64.0 ); - } - ApplyMultiDamage(pev, pevAttacker); + ApplyMultiDamage(pev, pevAttacker); + }while(iWallThickness && iWallLimit--); return Vector( x * vecSpread.x, y * vecSpread.y, 0.0 ); } diff --git a/dlls/enginecallback.h b/dlls/enginecallback.h index 19eb34b..3c7864c 100644 --- a/dlls/enginecallback.h +++ b/dlls/enginecallback.h @@ -68,6 +68,7 @@ extern enginefuncs_t g_engfuncs; #define RANDOM_LONG (*g_engfuncs.pfnRandomLong) #define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) #define GETPLAYERWONID (*g_engfuncs.pfnGetPlayerWONId) +#define GETPLAYERAUTHID (*g_engfuncs.pfnGetPlayerAuthId) inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin = NULL, edict_t *ed = NULL ) { (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ed); diff --git a/dlls/explode.h b/dlls/explode.h index 4ddeeb6..9d6d3b5 100644 --- a/dlls/explode.h +++ b/dlls/explode.h @@ -26,7 +26,6 @@ extern DLL_GLOBAL short g_sModelIndexFireball; extern DLL_GLOBAL short g_sModelIndexSmoke; - extern void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ); #endif //EXPLODE_H diff --git a/dlls/func_break.cpp b/dlls/func_break.cpp index 1e48689..91c46ae 100644 --- a/dlls/func_break.cpp +++ b/dlls/func_break.cpp @@ -37,7 +37,6 @@ extern DLL_GLOBAL Vector g_vecAttackDir; const char *CBreakable::pSpawnObjects[] = { NULL, // 0 - "item_battery", // 1 "item_healthkit", // 2 "weapon_9mmhandgun",// 3 "ammo_9mmclip", // 4 @@ -145,6 +144,8 @@ void CBreakable::Spawn( void ) { Precache( ); + pev->classname = MAKE_STRING("func_breakable"); + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) pev->takedamage = DAMAGE_NO; else @@ -314,6 +315,7 @@ void CBreakable::Precache( void ) PRECACHE_SOUND("debris/bustglass1.wav"); PRECACHE_SOUND("debris/bustglass2.wav"); + PRECACHE_SOUND("debris/bustglass3.wav"); break; case matMetal: pGibName = "models/metalplategibs.mdl"; @@ -492,7 +494,7 @@ void CBreakable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE us } -void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ,int PenetrationValue) { // random spark if this is a 'computer' object if (RANDOM_LONG(0,1) ) @@ -518,7 +520,7 @@ void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vec } } - CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType,PenetrationValue ); } //========================================================= @@ -605,11 +607,13 @@ void CBreakable::Die( void ) switch (m_Material) { case matGlass: - switch ( RANDOM_LONG(0,1) ) + switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass1.wav", fvol, ATTN_NORM, 0, pitch); break; - case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass2.wav", fvol, ATTN_NORM, 0, pitch); + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass2.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 2: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass3.wav", fvol, ATTN_NORM, 0, pitch); break; } cFlag = BREAK_GLASS; diff --git a/dlls/func_break.h b/dlls/func_break.h index 7591675..0937076 100644 --- a/dlls/func_break.h +++ b/dlls/func_break.h @@ -34,7 +34,7 @@ public: // breakables use an overridden takedamage virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); // To spark when hit - void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue = 0 ); BOOL IsBreakable( void ); BOOL SparkWhenHit( void ); diff --git a/dlls/func_tank.cpp b/dlls/func_tank.cpp index 085db64..b228e75 100644 --- a/dlls/func_tank.cpp +++ b/dlls/func_tank.cpp @@ -733,7 +733,7 @@ void CFuncTankGun::Fire( const Vector &barrelEnd, const Vector &forward, entvars { for ( i = 0; i < bulletCount; i++ ) { - switch( m_bulletType ) + /* switch( m_bulletType ) { case TANK_BULLET_9MM: FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_9MM, 1, m_iBulletDamage, pevAttacker ); @@ -750,7 +750,8 @@ void CFuncTankGun::Fire( const Vector &barrelEnd, const Vector &forward, entvars default: case TANK_BULLET_NONE: break; - } + }*/ + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_762MM, 1, m_iBulletDamage, pevAttacker ); } CFuncTank::Fire( barrelEnd, forward, pevAttacker ); } diff --git a/dlls/game.cpp b/dlls/game.cpp index 67e3329..c8c1de2 100644 --- a/dlls/game.cpp +++ b/dlls/game.cpp @@ -20,431 +20,34 @@ cvar_t displaysoundlist = {"displaysoundlist","0"}; // multiplayer server rules -cvar_t fragsleft = {"mp_fragsleft","0", FCVAR_SERVER | FCVAR_UNLOGGED }; // Don't spam console/log files/users with this changing -cvar_t timeleft = {"mp_timeleft","0" , FCVAR_SERVER | FCVAR_UNLOGGED }; // " " +cvar_t fragsleft = {"mp_fragsleft","0", FCVAR_SERVER | FCVAR_UNLOGGED }; // Don't spam console/log files/users with this changing +cvar_t timeleft = {"mp_timeleft","0" , FCVAR_SERVER | FCVAR_UNLOGGED }; // " " // multiplayer server rules -cvar_t teamplay = {"mp_teamplay","0", FCVAR_SERVER }; +cvar_t gametype = {"mp_gametype","0", FCVAR_SERVER }; cvar_t fraglimit = {"mp_fraglimit","0", FCVAR_SERVER }; +cvar_t roundtimelimit = {"mp_roundtimelimit","3",FCVAR_SERVER }; cvar_t timelimit = {"mp_timelimit","0", FCVAR_SERVER }; cvar_t friendlyfire= {"mp_friendlyfire","0", FCVAR_SERVER }; -cvar_t falldamage = {"mp_falldamage","0", FCVAR_SERVER }; cvar_t weaponstay = {"mp_weaponstay","0", FCVAR_SERVER }; -cvar_t forcerespawn= {"mp_forcerespawn","1", FCVAR_SERVER }; cvar_t flashlight = {"mp_flashlight","0", FCVAR_SERVER }; -cvar_t aimcrosshair= {"mp_autocrosshair","1", FCVAR_SERVER }; cvar_t decalfrequency = {"decalfrequency","30", FCVAR_SERVER }; cvar_t teamlist = {"mp_teamlist","hgrunt;scientist", FCVAR_SERVER }; cvar_t teamoverride = {"mp_teamoverride","1" }; cvar_t defaultteam = {"mp_defaultteam","0" }; cvar_t allowmonsters={"mp_allowmonsters","0", FCVAR_SERVER }; +cvar_t maxlives={"mp_maxlives","1",FCVAR_SERVER }; + +cvar_t allowsidearms = {"mp_allowsidearms","1",FCVAR_SERVER|FCVAR_UNLOGGED}; +cvar_t allowsecondary = {"mp_allowprimary","1",FCVAR_SERVER|FCVAR_UNLOGGED}; +cvar_t allowunique = {"mp_allowunique","1",FCVAR_SERVER|FCVAR_UNLOGGED}; +cvar_t allowexplosives = {"mp_allowexplosives","1",FCVAR_SERVER|FCVAR_UNLOGGED}; cvar_t mp_chattime = {"mp_chattime","10", FCVAR_SERVER }; // Engine Cvars cvar_t *g_psv_gravity = NULL; cvar_t *g_psv_aim = NULL; -cvar_t *g_footsteps = NULL; - -//CVARS FOR SKILL LEVEL SETTINGS -// Agrunt -cvar_t sk_agrunt_health1 = {"sk_agrunt_health1","0"}; -cvar_t sk_agrunt_health2 = {"sk_agrunt_health2","0"}; -cvar_t sk_agrunt_health3 = {"sk_agrunt_health3","0"}; - -cvar_t sk_agrunt_dmg_punch1 = {"sk_agrunt_dmg_punch1","0"}; -cvar_t sk_agrunt_dmg_punch2 = {"sk_agrunt_dmg_punch2","0"}; -cvar_t sk_agrunt_dmg_punch3 = {"sk_agrunt_dmg_punch3","0"}; - -// Apache -cvar_t sk_apache_health1 = {"sk_apache_health1","0"}; -cvar_t sk_apache_health2 = {"sk_apache_health2","0"}; -cvar_t sk_apache_health3 = {"sk_apache_health3","0"}; - -// Barney -cvar_t sk_barney_health1 = {"sk_barney_health1","0"}; -cvar_t sk_barney_health2 = {"sk_barney_health2","0"}; -cvar_t sk_barney_health3 = {"sk_barney_health3","0"}; - -// Bullsquid -cvar_t sk_bullsquid_health1 = {"sk_bullsquid_health1","0"}; -cvar_t sk_bullsquid_health2 = {"sk_bullsquid_health2","0"}; -cvar_t sk_bullsquid_health3 = {"sk_bullsquid_health3","0"}; - -cvar_t sk_bullsquid_dmg_bite1 = {"sk_bullsquid_dmg_bite1","0"}; -cvar_t sk_bullsquid_dmg_bite2 = {"sk_bullsquid_dmg_bite2","0"}; -cvar_t sk_bullsquid_dmg_bite3 = {"sk_bullsquid_dmg_bite3","0"}; - -cvar_t sk_bullsquid_dmg_whip1 = {"sk_bullsquid_dmg_whip1","0"}; -cvar_t sk_bullsquid_dmg_whip2 = {"sk_bullsquid_dmg_whip2","0"}; -cvar_t sk_bullsquid_dmg_whip3 = {"sk_bullsquid_dmg_whip3","0"}; - -cvar_t sk_bullsquid_dmg_spit1 = {"sk_bullsquid_dmg_spit1","0"}; -cvar_t sk_bullsquid_dmg_spit2 = {"sk_bullsquid_dmg_spit2","0"}; -cvar_t sk_bullsquid_dmg_spit3 = {"sk_bullsquid_dmg_spit3","0"}; - - -// Big Momma -cvar_t sk_bigmomma_health_factor1 = {"sk_bigmomma_health_factor1","1.0"}; -cvar_t sk_bigmomma_health_factor2 = {"sk_bigmomma_health_factor2","1.0"}; -cvar_t sk_bigmomma_health_factor3 = {"sk_bigmomma_health_factor3","1.0"}; - -cvar_t sk_bigmomma_dmg_slash1 = {"sk_bigmomma_dmg_slash1","50"}; -cvar_t sk_bigmomma_dmg_slash2 = {"sk_bigmomma_dmg_slash2","50"}; -cvar_t sk_bigmomma_dmg_slash3 = {"sk_bigmomma_dmg_slash3","50"}; - -cvar_t sk_bigmomma_dmg_blast1 = {"sk_bigmomma_dmg_blast1","100"}; -cvar_t sk_bigmomma_dmg_blast2 = {"sk_bigmomma_dmg_blast2","100"}; -cvar_t sk_bigmomma_dmg_blast3 = {"sk_bigmomma_dmg_blast3","100"}; - -cvar_t sk_bigmomma_radius_blast1 = {"sk_bigmomma_radius_blast1","250"}; -cvar_t sk_bigmomma_radius_blast2 = {"sk_bigmomma_radius_blast2","250"}; -cvar_t sk_bigmomma_radius_blast3 = {"sk_bigmomma_radius_blast3","250"}; - -// Gargantua -cvar_t sk_gargantua_health1 = {"sk_gargantua_health1","0"}; -cvar_t sk_gargantua_health2 = {"sk_gargantua_health2","0"}; -cvar_t sk_gargantua_health3 = {"sk_gargantua_health3","0"}; - -cvar_t sk_gargantua_dmg_slash1 = {"sk_gargantua_dmg_slash1","0"}; -cvar_t sk_gargantua_dmg_slash2 = {"sk_gargantua_dmg_slash2","0"}; -cvar_t sk_gargantua_dmg_slash3 = {"sk_gargantua_dmg_slash3","0"}; - -cvar_t sk_gargantua_dmg_fire1 = {"sk_gargantua_dmg_fire1","0"}; -cvar_t sk_gargantua_dmg_fire2 = {"sk_gargantua_dmg_fire2","0"}; -cvar_t sk_gargantua_dmg_fire3 = {"sk_gargantua_dmg_fire3","0"}; - -cvar_t sk_gargantua_dmg_stomp1 = {"sk_gargantua_dmg_stomp1","0"}; -cvar_t sk_gargantua_dmg_stomp2 = {"sk_gargantua_dmg_stomp2","0"}; -cvar_t sk_gargantua_dmg_stomp3 = {"sk_gargantua_dmg_stomp3","0"}; - - -// Hassassin -cvar_t sk_hassassin_health1 = {"sk_hassassin_health1","0"}; -cvar_t sk_hassassin_health2 = {"sk_hassassin_health2","0"}; -cvar_t sk_hassassin_health3 = {"sk_hassassin_health3","0"}; - - -// Headcrab -cvar_t sk_headcrab_health1 = {"sk_headcrab_health1","0"}; -cvar_t sk_headcrab_health2 = {"sk_headcrab_health2","0"}; -cvar_t sk_headcrab_health3 = {"sk_headcrab_health3","0"}; - -cvar_t sk_headcrab_dmg_bite1 = {"sk_headcrab_dmg_bite1","0"}; -cvar_t sk_headcrab_dmg_bite2 = {"sk_headcrab_dmg_bite2","0"}; -cvar_t sk_headcrab_dmg_bite3 = {"sk_headcrab_dmg_bite3","0"}; - - -// Hgrunt -cvar_t sk_hgrunt_health1 = {"sk_hgrunt_health1","0"}; -cvar_t sk_hgrunt_health2 = {"sk_hgrunt_health2","0"}; -cvar_t sk_hgrunt_health3 = {"sk_hgrunt_health3","0"}; - -cvar_t sk_hgrunt_kick1 = {"sk_hgrunt_kick1","0"}; -cvar_t sk_hgrunt_kick2 = {"sk_hgrunt_kick2","0"}; -cvar_t sk_hgrunt_kick3 = {"sk_hgrunt_kick3","0"}; - -cvar_t sk_hgrunt_pellets1 = {"sk_hgrunt_pellets1","0"}; -cvar_t sk_hgrunt_pellets2 = {"sk_hgrunt_pellets2","0"}; -cvar_t sk_hgrunt_pellets3 = {"sk_hgrunt_pellets3","0"}; - -cvar_t sk_hgrunt_gspeed1 = {"sk_hgrunt_gspeed1","0"}; -cvar_t sk_hgrunt_gspeed2 = {"sk_hgrunt_gspeed2","0"}; -cvar_t sk_hgrunt_gspeed3 = {"sk_hgrunt_gspeed3","0"}; - -// Houndeye -cvar_t sk_houndeye_health1 = {"sk_houndeye_health1","0"}; -cvar_t sk_houndeye_health2 = {"sk_houndeye_health2","0"}; -cvar_t sk_houndeye_health3 = {"sk_houndeye_health3","0"}; - -cvar_t sk_houndeye_dmg_blast1 = {"sk_houndeye_dmg_blast1","0"}; -cvar_t sk_houndeye_dmg_blast2 = {"sk_houndeye_dmg_blast2","0"}; -cvar_t sk_houndeye_dmg_blast3 = {"sk_houndeye_dmg_blast3","0"}; - - -// ISlave -cvar_t sk_islave_health1 = {"sk_islave_health1","0"}; -cvar_t sk_islave_health2 = {"sk_islave_health2","0"}; -cvar_t sk_islave_health3 = {"sk_islave_health3","0"}; - -cvar_t sk_islave_dmg_claw1 = {"sk_islave_dmg_claw1","0"}; -cvar_t sk_islave_dmg_claw2 = {"sk_islave_dmg_claw2","0"}; -cvar_t sk_islave_dmg_claw3 = {"sk_islave_dmg_claw3","0"}; - -cvar_t sk_islave_dmg_clawrake1 = {"sk_islave_dmg_clawrake1","0"}; -cvar_t sk_islave_dmg_clawrake2 = {"sk_islave_dmg_clawrake2","0"}; -cvar_t sk_islave_dmg_clawrake3 = {"sk_islave_dmg_clawrake3","0"}; - -cvar_t sk_islave_dmg_zap1 = {"sk_islave_dmg_zap1","0"}; -cvar_t sk_islave_dmg_zap2 = {"sk_islave_dmg_zap2","0"}; -cvar_t sk_islave_dmg_zap3 = {"sk_islave_dmg_zap3","0"}; - - -// Icthyosaur -cvar_t sk_ichthyosaur_health1 = {"sk_ichthyosaur_health1","0"}; -cvar_t sk_ichthyosaur_health2 = {"sk_ichthyosaur_health2","0"}; -cvar_t sk_ichthyosaur_health3 = {"sk_ichthyosaur_health3","0"}; - -cvar_t sk_ichthyosaur_shake1 = {"sk_ichthyosaur_shake1","0"}; -cvar_t sk_ichthyosaur_shake2 = {"sk_ichthyosaur_shake2","0"}; -cvar_t sk_ichthyosaur_shake3 = {"sk_ichthyosaur_shake3","0"}; - - -// Leech -cvar_t sk_leech_health1 = {"sk_leech_health1","0"}; -cvar_t sk_leech_health2 = {"sk_leech_health2","0"}; -cvar_t sk_leech_health3 = {"sk_leech_health3","0"}; - -cvar_t sk_leech_dmg_bite1 = {"sk_leech_dmg_bite1","0"}; -cvar_t sk_leech_dmg_bite2 = {"sk_leech_dmg_bite2","0"}; -cvar_t sk_leech_dmg_bite3 = {"sk_leech_dmg_bite3","0"}; - -// Controller -cvar_t sk_controller_health1 = {"sk_controller_health1","0"}; -cvar_t sk_controller_health2 = {"sk_controller_health2","0"}; -cvar_t sk_controller_health3 = {"sk_controller_health3","0"}; - -cvar_t sk_controller_dmgzap1 = {"sk_controller_dmgzap1","0"}; -cvar_t sk_controller_dmgzap2 = {"sk_controller_dmgzap2","0"}; -cvar_t sk_controller_dmgzap3 = {"sk_controller_dmgzap3","0"}; - -cvar_t sk_controller_speedball1 = {"sk_controller_speedball1","0"}; -cvar_t sk_controller_speedball2 = {"sk_controller_speedball2","0"}; -cvar_t sk_controller_speedball3 = {"sk_controller_speedball3","0"}; - -cvar_t sk_controller_dmgball1 = {"sk_controller_dmgball1","0"}; -cvar_t sk_controller_dmgball2 = {"sk_controller_dmgball2","0"}; -cvar_t sk_controller_dmgball3 = {"sk_controller_dmgball3","0"}; - -// Nihilanth -cvar_t sk_nihilanth_health1 = {"sk_nihilanth_health1","0"}; -cvar_t sk_nihilanth_health2 = {"sk_nihilanth_health2","0"}; -cvar_t sk_nihilanth_health3 = {"sk_nihilanth_health3","0"}; - -cvar_t sk_nihilanth_zap1 = {"sk_nihilanth_zap1","0"}; -cvar_t sk_nihilanth_zap2 = {"sk_nihilanth_zap2","0"}; -cvar_t sk_nihilanth_zap3 = {"sk_nihilanth_zap3","0"}; - -// Scientist -cvar_t sk_scientist_health1 = {"sk_scientist_health1","0"}; -cvar_t sk_scientist_health2 = {"sk_scientist_health2","0"}; -cvar_t sk_scientist_health3 = {"sk_scientist_health3","0"}; - - -// Snark -cvar_t sk_snark_health1 = {"sk_snark_health1","0"}; -cvar_t sk_snark_health2 = {"sk_snark_health2","0"}; -cvar_t sk_snark_health3 = {"sk_snark_health3","0"}; - -cvar_t sk_snark_dmg_bite1 = {"sk_snark_dmg_bite1","0"}; -cvar_t sk_snark_dmg_bite2 = {"sk_snark_dmg_bite2","0"}; -cvar_t sk_snark_dmg_bite3 = {"sk_snark_dmg_bite3","0"}; - -cvar_t sk_snark_dmg_pop1 = {"sk_snark_dmg_pop1","0"}; -cvar_t sk_snark_dmg_pop2 = {"sk_snark_dmg_pop2","0"}; -cvar_t sk_snark_dmg_pop3 = {"sk_snark_dmg_pop3","0"}; - - - -// Zombie -cvar_t sk_zombie_health1 = {"sk_zombie_health1","0"}; -cvar_t sk_zombie_health2 = {"sk_zombie_health2","0"}; -cvar_t sk_zombie_health3 = {"sk_zombie_health3","0"}; - -cvar_t sk_zombie_dmg_one_slash1 = {"sk_zombie_dmg_one_slash1","0"}; -cvar_t sk_zombie_dmg_one_slash2 = {"sk_zombie_dmg_one_slash2","0"}; -cvar_t sk_zombie_dmg_one_slash3 = {"sk_zombie_dmg_one_slash3","0"}; - -cvar_t sk_zombie_dmg_both_slash1 = {"sk_zombie_dmg_both_slash1","0"}; -cvar_t sk_zombie_dmg_both_slash2 = {"sk_zombie_dmg_both_slash2","0"}; -cvar_t sk_zombie_dmg_both_slash3 = {"sk_zombie_dmg_both_slash3","0"}; - - -//Turret -cvar_t sk_turret_health1 = {"sk_turret_health1","0"}; -cvar_t sk_turret_health2 = {"sk_turret_health2","0"}; -cvar_t sk_turret_health3 = {"sk_turret_health3","0"}; - - -// MiniTurret -cvar_t sk_miniturret_health1 = {"sk_miniturret_health1","0"}; -cvar_t sk_miniturret_health2 = {"sk_miniturret_health2","0"}; -cvar_t sk_miniturret_health3 = {"sk_miniturret_health3","0"}; - - -// Sentry Turret -cvar_t sk_sentry_health1 = {"sk_sentry_health1","0"}; -cvar_t sk_sentry_health2 = {"sk_sentry_health2","0"}; -cvar_t sk_sentry_health3 = {"sk_sentry_health3","0"}; - - -// PLAYER WEAPONS - -// Crowbar whack -cvar_t sk_plr_crowbar1 = {"sk_plr_crowbar1","0"}; -cvar_t sk_plr_crowbar2 = {"sk_plr_crowbar2","0"}; -cvar_t sk_plr_crowbar3 = {"sk_plr_crowbar3","0"}; - -// Glock Round -cvar_t sk_plr_9mm_bullet1 = {"sk_plr_9mm_bullet1","0"}; -cvar_t sk_plr_9mm_bullet2 = {"sk_plr_9mm_bullet2","0"}; -cvar_t sk_plr_9mm_bullet3 = {"sk_plr_9mm_bullet3","0"}; - -// 357 Round -cvar_t sk_plr_357_bullet1 = {"sk_plr_357_bullet1","0"}; -cvar_t sk_plr_357_bullet2 = {"sk_plr_357_bullet2","0"}; -cvar_t sk_plr_357_bullet3 = {"sk_plr_357_bullet3","0"}; - -// MP5 Round -cvar_t sk_plr_9mmAR_bullet1 = {"sk_plr_9mmAR_bullet1","0"}; -cvar_t sk_plr_9mmAR_bullet2 = {"sk_plr_9mmAR_bullet2","0"}; -cvar_t sk_plr_9mmAR_bullet3 = {"sk_plr_9mmAR_bullet3","0"}; - - -// M203 grenade -cvar_t sk_plr_9mmAR_grenade1 = {"sk_plr_9mmAR_grenade1","0"}; -cvar_t sk_plr_9mmAR_grenade2 = {"sk_plr_9mmAR_grenade2","0"}; -cvar_t sk_plr_9mmAR_grenade3 = {"sk_plr_9mmAR_grenade3","0"}; - - -// Shotgun buckshot -cvar_t sk_plr_buckshot1 = {"sk_plr_buckshot1","0"}; -cvar_t sk_plr_buckshot2 = {"sk_plr_buckshot2","0"}; -cvar_t sk_plr_buckshot3 = {"sk_plr_buckshot3","0"}; - - -// Crossbow -cvar_t sk_plr_xbow_bolt_client1 = {"sk_plr_xbow_bolt_client1","0"}; -cvar_t sk_plr_xbow_bolt_client2 = {"sk_plr_xbow_bolt_client2","0"}; -cvar_t sk_plr_xbow_bolt_client3 = {"sk_plr_xbow_bolt_client3","0"}; - -cvar_t sk_plr_xbow_bolt_monster1 = {"sk_plr_xbow_bolt_monster1","0"}; -cvar_t sk_plr_xbow_bolt_monster2 = {"sk_plr_xbow_bolt_monster2","0"}; -cvar_t sk_plr_xbow_bolt_monster3 = {"sk_plr_xbow_bolt_monster3","0"}; - - -// RPG -cvar_t sk_plr_rpg1 = {"sk_plr_rpg1","0"}; -cvar_t sk_plr_rpg2 = {"sk_plr_rpg2","0"}; -cvar_t sk_plr_rpg3 = {"sk_plr_rpg3","0"}; - - -// Zero Point Generator -cvar_t sk_plr_gauss1 = {"sk_plr_gauss1","0"}; -cvar_t sk_plr_gauss2 = {"sk_plr_gauss2","0"}; -cvar_t sk_plr_gauss3 = {"sk_plr_gauss3","0"}; - - -// Tau Cannon -cvar_t sk_plr_egon_narrow1 = {"sk_plr_egon_narrow1","0"}; -cvar_t sk_plr_egon_narrow2 = {"sk_plr_egon_narrow2","0"}; -cvar_t sk_plr_egon_narrow3 = {"sk_plr_egon_narrow3","0"}; - -cvar_t sk_plr_egon_wide1 = {"sk_plr_egon_wide1","0"}; -cvar_t sk_plr_egon_wide2 = {"sk_plr_egon_wide2","0"}; -cvar_t sk_plr_egon_wide3 = {"sk_plr_egon_wide3","0"}; - - -// Hand Grendade -cvar_t sk_plr_hand_grenade1 = {"sk_plr_hand_grenade1","0"}; -cvar_t sk_plr_hand_grenade2 = {"sk_plr_hand_grenade2","0"}; -cvar_t sk_plr_hand_grenade3 = {"sk_plr_hand_grenade3","0"}; - - -// Satchel Charge -cvar_t sk_plr_satchel1 = {"sk_plr_satchel1","0"}; -cvar_t sk_plr_satchel2 = {"sk_plr_satchel2","0"}; -cvar_t sk_plr_satchel3 = {"sk_plr_satchel3","0"}; - - -// Tripmine -cvar_t sk_plr_tripmine1 = {"sk_plr_tripmine1","0"}; -cvar_t sk_plr_tripmine2 = {"sk_plr_tripmine2","0"}; -cvar_t sk_plr_tripmine3 = {"sk_plr_tripmine3","0"}; - - -// WORLD WEAPONS -cvar_t sk_12mm_bullet1 = {"sk_12mm_bullet1","0"}; -cvar_t sk_12mm_bullet2 = {"sk_12mm_bullet2","0"}; -cvar_t sk_12mm_bullet3 = {"sk_12mm_bullet3","0"}; - -cvar_t sk_9mmAR_bullet1 = {"sk_9mmAR_bullet1","0"}; -cvar_t sk_9mmAR_bullet2 = {"sk_9mmAR_bullet2","0"}; -cvar_t sk_9mmAR_bullet3 = {"sk_9mmAR_bullet3","0"}; - -cvar_t sk_9mm_bullet1 = {"sk_9mm_bullet1","0"}; -cvar_t sk_9mm_bullet2 = {"sk_9mm_bullet2","0"}; -cvar_t sk_9mm_bullet3 = {"sk_9mm_bullet3","0"}; - - -// HORNET -cvar_t sk_hornet_dmg1 = {"sk_hornet_dmg1","0"}; -cvar_t sk_hornet_dmg2 = {"sk_hornet_dmg2","0"}; -cvar_t sk_hornet_dmg3 = {"sk_hornet_dmg3","0"}; - -// HEALTH/CHARGE -cvar_t sk_suitcharger1 = { "sk_suitcharger1","0" }; -cvar_t sk_suitcharger2 = { "sk_suitcharger2","0" }; -cvar_t sk_suitcharger3 = { "sk_suitcharger3","0" }; - -cvar_t sk_battery1 = { "sk_battery1","0" }; -cvar_t sk_battery2 = { "sk_battery2","0" }; -cvar_t sk_battery3 = { "sk_battery3","0" }; - -cvar_t sk_healthcharger1 = { "sk_healthcharger1","0" }; -cvar_t sk_healthcharger2 = { "sk_healthcharger2","0" }; -cvar_t sk_healthcharger3 = { "sk_healthcharger3","0" }; - -cvar_t sk_healthkit1 = { "sk_healthkit1","0" }; -cvar_t sk_healthkit2 = { "sk_healthkit2","0" }; -cvar_t sk_healthkit3 = { "sk_healthkit3","0" }; - -cvar_t sk_scientist_heal1 = { "sk_scientist_heal1","0" }; -cvar_t sk_scientist_heal2 = { "sk_scientist_heal2","0" }; -cvar_t sk_scientist_heal3 = { "sk_scientist_heal3","0" }; - - -// monster damage adjusters -cvar_t sk_monster_head1 = { "sk_monster_head1","2" }; -cvar_t sk_monster_head2 = { "sk_monster_head2","2" }; -cvar_t sk_monster_head3 = { "sk_monster_head3","2" }; - -cvar_t sk_monster_chest1 = { "sk_monster_chest1","1" }; -cvar_t sk_monster_chest2 = { "sk_monster_chest2","1" }; -cvar_t sk_monster_chest3 = { "sk_monster_chest3","1" }; - -cvar_t sk_monster_stomach1 = { "sk_monster_stomach1","1" }; -cvar_t sk_monster_stomach2 = { "sk_monster_stomach2","1" }; -cvar_t sk_monster_stomach3 = { "sk_monster_stomach3","1" }; - -cvar_t sk_monster_arm1 = { "sk_monster_arm1","1" }; -cvar_t sk_monster_arm2 = { "sk_monster_arm2","1" }; -cvar_t sk_monster_arm3 = { "sk_monster_arm3","1" }; - -cvar_t sk_monster_leg1 = { "sk_monster_leg1","1" }; -cvar_t sk_monster_leg2 = { "sk_monster_leg2","1" }; -cvar_t sk_monster_leg3 = { "sk_monster_leg3","1" }; - -// player damage adjusters -cvar_t sk_player_head1 = { "sk_player_head1","2" }; -cvar_t sk_player_head2 = { "sk_player_head2","2" }; -cvar_t sk_player_head3 = { "sk_player_head3","2" }; - -cvar_t sk_player_chest1 = { "sk_player_chest1","1" }; -cvar_t sk_player_chest2 = { "sk_player_chest2","1" }; -cvar_t sk_player_chest3 = { "sk_player_chest3","1" }; - -cvar_t sk_player_stomach1 = { "sk_player_stomach1","1" }; -cvar_t sk_player_stomach2 = { "sk_player_stomach2","1" }; -cvar_t sk_player_stomach3 = { "sk_player_stomach3","1" }; - -cvar_t sk_player_arm1 = { "sk_player_arm1","1" }; -cvar_t sk_player_arm2 = { "sk_player_arm2","1" }; -cvar_t sk_player_arm3 = { "sk_player_arm3","1" }; - -cvar_t sk_player_leg1 = { "sk_player_leg1","1" }; -cvar_t sk_player_leg2 = { "sk_player_leg2","1" }; -cvar_t sk_player_leg3 = { "sk_player_leg3","1" }; // END Cvars for Skill Level settings @@ -453,433 +56,36 @@ cvar_t sk_player_leg3 = { "sk_player_leg3","1" }; void GameDLLInit( void ) { // Register cvars here: - g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); g_psv_aim = CVAR_GET_POINTER( "sv_aim" ); - g_footsteps = CVAR_GET_POINTER( "mp_footsteps" ); CVAR_REGISTER (&displaysoundlist); - CVAR_REGISTER (&teamplay); + CVAR_REGISTER (&gametype); CVAR_REGISTER (&fraglimit); + CVAR_REGISTER (&roundtimelimit); CVAR_REGISTER (&timelimit); CVAR_REGISTER (&fragsleft); CVAR_REGISTER (&timeleft); CVAR_REGISTER (&friendlyfire); - CVAR_REGISTER (&falldamage); CVAR_REGISTER (&weaponstay); - CVAR_REGISTER (&forcerespawn); CVAR_REGISTER (&flashlight); - CVAR_REGISTER (&aimcrosshair); CVAR_REGISTER (&decalfrequency); CVAR_REGISTER (&teamlist); CVAR_REGISTER (&teamoverride); CVAR_REGISTER (&defaultteam); CVAR_REGISTER (&allowmonsters); + CVAR_REGISTER (&maxlives); + + CVAR_REGISTER (&allowsidearms); + CVAR_REGISTER (&allowsecondary); + CVAR_REGISTER (&allowunique); + CVAR_REGISTER (&allowexplosives); CVAR_REGISTER (&mp_chattime); -// REGISTER CVARS FOR SKILL LEVEL STUFF - // Agrunt - CVAR_REGISTER ( &sk_agrunt_health1 );// {"sk_agrunt_health1","0"}; - CVAR_REGISTER ( &sk_agrunt_health2 );// {"sk_agrunt_health2","0"}; - CVAR_REGISTER ( &sk_agrunt_health3 );// {"sk_agrunt_health3","0"}; - - CVAR_REGISTER ( &sk_agrunt_dmg_punch1 );// {"sk_agrunt_dmg_punch1","0"}; - CVAR_REGISTER ( &sk_agrunt_dmg_punch2 );// {"sk_agrunt_dmg_punch2","0"}; - CVAR_REGISTER ( &sk_agrunt_dmg_punch3 );// {"sk_agrunt_dmg_punch3","0"}; - - // Apache - CVAR_REGISTER ( &sk_apache_health1 );// {"sk_apache_health1","0"}; - CVAR_REGISTER ( &sk_apache_health2 );// {"sk_apache_health2","0"}; - CVAR_REGISTER ( &sk_apache_health3 );// {"sk_apache_health3","0"}; - - // Barney - CVAR_REGISTER ( &sk_barney_health1 );// {"sk_barney_health1","0"}; - CVAR_REGISTER ( &sk_barney_health2 );// {"sk_barney_health2","0"}; - CVAR_REGISTER ( &sk_barney_health3 );// {"sk_barney_health3","0"}; - - // Bullsquid - CVAR_REGISTER ( &sk_bullsquid_health1 );// {"sk_bullsquid_health1","0"}; - CVAR_REGISTER ( &sk_bullsquid_health2 );// {"sk_bullsquid_health2","0"}; - CVAR_REGISTER ( &sk_bullsquid_health3 );// {"sk_bullsquid_health3","0"}; - - CVAR_REGISTER ( &sk_bullsquid_dmg_bite1 );// {"sk_bullsquid_dmg_bite1","0"}; - CVAR_REGISTER ( &sk_bullsquid_dmg_bite2 );// {"sk_bullsquid_dmg_bite2","0"}; - CVAR_REGISTER ( &sk_bullsquid_dmg_bite3 );// {"sk_bullsquid_dmg_bite3","0"}; - - CVAR_REGISTER ( &sk_bullsquid_dmg_whip1 );// {"sk_bullsquid_dmg_whip1","0"}; - CVAR_REGISTER ( &sk_bullsquid_dmg_whip2 );// {"sk_bullsquid_dmg_whip2","0"}; - CVAR_REGISTER ( &sk_bullsquid_dmg_whip3 );// {"sk_bullsquid_dmg_whip3","0"}; - - CVAR_REGISTER ( &sk_bullsquid_dmg_spit1 );// {"sk_bullsquid_dmg_spit1","0"}; - CVAR_REGISTER ( &sk_bullsquid_dmg_spit2 );// {"sk_bullsquid_dmg_spit2","0"}; - CVAR_REGISTER ( &sk_bullsquid_dmg_spit3 );// {"sk_bullsquid_dmg_spit3","0"}; - - - CVAR_REGISTER ( &sk_bigmomma_health_factor1 );// {"sk_bigmomma_health_factor1","1.0"}; - CVAR_REGISTER ( &sk_bigmomma_health_factor2 );// {"sk_bigmomma_health_factor2","1.0"}; - CVAR_REGISTER ( &sk_bigmomma_health_factor3 );// {"sk_bigmomma_health_factor3","1.0"}; - - CVAR_REGISTER ( &sk_bigmomma_dmg_slash1 );// {"sk_bigmomma_dmg_slash1","50"}; - CVAR_REGISTER ( &sk_bigmomma_dmg_slash2 );// {"sk_bigmomma_dmg_slash2","50"}; - CVAR_REGISTER ( &sk_bigmomma_dmg_slash3 );// {"sk_bigmomma_dmg_slash3","50"}; - - CVAR_REGISTER ( &sk_bigmomma_dmg_blast1 );// {"sk_bigmomma_dmg_blast1","100"}; - CVAR_REGISTER ( &sk_bigmomma_dmg_blast2 );// {"sk_bigmomma_dmg_blast2","100"}; - CVAR_REGISTER ( &sk_bigmomma_dmg_blast3 );// {"sk_bigmomma_dmg_blast3","100"}; - - CVAR_REGISTER ( &sk_bigmomma_radius_blast1 );// {"sk_bigmomma_radius_blast1","250"}; - CVAR_REGISTER ( &sk_bigmomma_radius_blast2 );// {"sk_bigmomma_radius_blast2","250"}; - CVAR_REGISTER ( &sk_bigmomma_radius_blast3 );// {"sk_bigmomma_radius_blast3","250"}; - - // Gargantua - CVAR_REGISTER ( &sk_gargantua_health1 );// {"sk_gargantua_health1","0"}; - CVAR_REGISTER ( &sk_gargantua_health2 );// {"sk_gargantua_health2","0"}; - CVAR_REGISTER ( &sk_gargantua_health3 );// {"sk_gargantua_health3","0"}; - - CVAR_REGISTER ( &sk_gargantua_dmg_slash1 );// {"sk_gargantua_dmg_slash1","0"}; - CVAR_REGISTER ( &sk_gargantua_dmg_slash2 );// {"sk_gargantua_dmg_slash2","0"}; - CVAR_REGISTER ( &sk_gargantua_dmg_slash3 );// {"sk_gargantua_dmg_slash3","0"}; - - CVAR_REGISTER ( &sk_gargantua_dmg_fire1 );// {"sk_gargantua_dmg_fire1","0"}; - CVAR_REGISTER ( &sk_gargantua_dmg_fire2 );// {"sk_gargantua_dmg_fire2","0"}; - CVAR_REGISTER ( &sk_gargantua_dmg_fire3 );// {"sk_gargantua_dmg_fire3","0"}; - - CVAR_REGISTER ( &sk_gargantua_dmg_stomp1 );// {"sk_gargantua_dmg_stomp1","0"}; - CVAR_REGISTER ( &sk_gargantua_dmg_stomp2 );// {"sk_gargantua_dmg_stomp2","0"}; - CVAR_REGISTER ( &sk_gargantua_dmg_stomp3 );// {"sk_gargantua_dmg_stomp3","0"}; - - - // Hassassin - CVAR_REGISTER ( &sk_hassassin_health1 );// {"sk_hassassin_health1","0"}; - CVAR_REGISTER ( &sk_hassassin_health2 );// {"sk_hassassin_health2","0"}; - CVAR_REGISTER ( &sk_hassassin_health3 );// {"sk_hassassin_health3","0"}; - - - // Headcrab - CVAR_REGISTER ( &sk_headcrab_health1 );// {"sk_headcrab_health1","0"}; - CVAR_REGISTER ( &sk_headcrab_health2 );// {"sk_headcrab_health2","0"}; - CVAR_REGISTER ( &sk_headcrab_health3 );// {"sk_headcrab_health3","0"}; - - CVAR_REGISTER ( &sk_headcrab_dmg_bite1 );// {"sk_headcrab_dmg_bite1","0"}; - CVAR_REGISTER ( &sk_headcrab_dmg_bite2 );// {"sk_headcrab_dmg_bite2","0"}; - CVAR_REGISTER ( &sk_headcrab_dmg_bite3 );// {"sk_headcrab_dmg_bite3","0"}; - - - // Hgrunt - CVAR_REGISTER ( &sk_hgrunt_health1 );// {"sk_hgrunt_health1","0"}; - CVAR_REGISTER ( &sk_hgrunt_health2 );// {"sk_hgrunt_health2","0"}; - CVAR_REGISTER ( &sk_hgrunt_health3 );// {"sk_hgrunt_health3","0"}; - - CVAR_REGISTER ( &sk_hgrunt_kick1 );// {"sk_hgrunt_kick1","0"}; - CVAR_REGISTER ( &sk_hgrunt_kick2 );// {"sk_hgrunt_kick2","0"}; - CVAR_REGISTER ( &sk_hgrunt_kick3 );// {"sk_hgrunt_kick3","0"}; - - CVAR_REGISTER ( &sk_hgrunt_pellets1 ); - CVAR_REGISTER ( &sk_hgrunt_pellets2 ); - CVAR_REGISTER ( &sk_hgrunt_pellets3 ); - - CVAR_REGISTER ( &sk_hgrunt_gspeed1 ); - CVAR_REGISTER ( &sk_hgrunt_gspeed2 ); - CVAR_REGISTER ( &sk_hgrunt_gspeed3 ); - - // Houndeye - CVAR_REGISTER ( &sk_houndeye_health1 );// {"sk_houndeye_health1","0"}; - CVAR_REGISTER ( &sk_houndeye_health2 );// {"sk_houndeye_health2","0"}; - CVAR_REGISTER ( &sk_houndeye_health3 );// {"sk_houndeye_health3","0"}; - - CVAR_REGISTER ( &sk_houndeye_dmg_blast1 );// {"sk_houndeye_dmg_blast1","0"}; - CVAR_REGISTER ( &sk_houndeye_dmg_blast2 );// {"sk_houndeye_dmg_blast2","0"}; - CVAR_REGISTER ( &sk_houndeye_dmg_blast3 );// {"sk_houndeye_dmg_blast3","0"}; - - - // ISlave - CVAR_REGISTER ( &sk_islave_health1 );// {"sk_islave_health1","0"}; - CVAR_REGISTER ( &sk_islave_health2 );// {"sk_islave_health2","0"}; - CVAR_REGISTER ( &sk_islave_health3 );// {"sk_islave_health3","0"}; - - CVAR_REGISTER ( &sk_islave_dmg_claw1 );// {"sk_islave_dmg_claw1","0"}; - CVAR_REGISTER ( &sk_islave_dmg_claw2 );// {"sk_islave_dmg_claw2","0"}; - CVAR_REGISTER ( &sk_islave_dmg_claw3 );// {"sk_islave_dmg_claw3","0"}; - - CVAR_REGISTER ( &sk_islave_dmg_clawrake1 );// {"sk_islave_dmg_clawrake1","0"}; - CVAR_REGISTER ( &sk_islave_dmg_clawrake2 );// {"sk_islave_dmg_clawrake2","0"}; - CVAR_REGISTER ( &sk_islave_dmg_clawrake3 );// {"sk_islave_dmg_clawrake3","0"}; - - CVAR_REGISTER ( &sk_islave_dmg_zap1 );// {"sk_islave_dmg_zap1","0"}; - CVAR_REGISTER ( &sk_islave_dmg_zap2 );// {"sk_islave_dmg_zap2","0"}; - CVAR_REGISTER ( &sk_islave_dmg_zap3 );// {"sk_islave_dmg_zap3","0"}; - - - // Icthyosaur - CVAR_REGISTER ( &sk_ichthyosaur_health1 );// {"sk_ichthyosaur_health1","0"}; - CVAR_REGISTER ( &sk_ichthyosaur_health2 );// {"sk_ichthyosaur_health2","0"}; - CVAR_REGISTER ( &sk_ichthyosaur_health3 );// {"sk_ichthyosaur_health3","0"}; - - CVAR_REGISTER ( &sk_ichthyosaur_shake1 );// {"sk_ichthyosaur_health3","0"}; - CVAR_REGISTER ( &sk_ichthyosaur_shake2 );// {"sk_ichthyosaur_health3","0"}; - CVAR_REGISTER ( &sk_ichthyosaur_shake3 );// {"sk_ichthyosaur_health3","0"}; - - - - // Leech - CVAR_REGISTER ( &sk_leech_health1 );// {"sk_leech_health1","0"}; - CVAR_REGISTER ( &sk_leech_health2 );// {"sk_leech_health2","0"}; - CVAR_REGISTER ( &sk_leech_health3 );// {"sk_leech_health3","0"}; - - CVAR_REGISTER ( &sk_leech_dmg_bite1 );// {"sk_leech_dmg_bite1","0"}; - CVAR_REGISTER ( &sk_leech_dmg_bite2 );// {"sk_leech_dmg_bite2","0"}; - CVAR_REGISTER ( &sk_leech_dmg_bite3 );// {"sk_leech_dmg_bite3","0"}; - - - // Controller - CVAR_REGISTER ( &sk_controller_health1 ); - CVAR_REGISTER ( &sk_controller_health2 ); - CVAR_REGISTER ( &sk_controller_health3 ); - - CVAR_REGISTER ( &sk_controller_dmgzap1 ); - CVAR_REGISTER ( &sk_controller_dmgzap2 ); - CVAR_REGISTER ( &sk_controller_dmgzap3 ); - - CVAR_REGISTER ( &sk_controller_speedball1 ); - CVAR_REGISTER ( &sk_controller_speedball2 ); - CVAR_REGISTER ( &sk_controller_speedball3 ); - - CVAR_REGISTER ( &sk_controller_dmgball1 ); - CVAR_REGISTER ( &sk_controller_dmgball2 ); - CVAR_REGISTER ( &sk_controller_dmgball3 ); - - // Nihilanth - CVAR_REGISTER ( &sk_nihilanth_health1 );// {"sk_nihilanth_health1","0"}; - CVAR_REGISTER ( &sk_nihilanth_health2 );// {"sk_nihilanth_health2","0"}; - CVAR_REGISTER ( &sk_nihilanth_health3 );// {"sk_nihilanth_health3","0"}; - - CVAR_REGISTER ( &sk_nihilanth_zap1 ); - CVAR_REGISTER ( &sk_nihilanth_zap2 ); - CVAR_REGISTER ( &sk_nihilanth_zap3 ); - - // Scientist - CVAR_REGISTER ( &sk_scientist_health1 );// {"sk_scientist_health1","0"}; - CVAR_REGISTER ( &sk_scientist_health2 );// {"sk_scientist_health2","0"}; - CVAR_REGISTER ( &sk_scientist_health3 );// {"sk_scientist_health3","0"}; - - - // Snark - CVAR_REGISTER ( &sk_snark_health1 );// {"sk_snark_health1","0"}; - CVAR_REGISTER ( &sk_snark_health2 );// {"sk_snark_health2","0"}; - CVAR_REGISTER ( &sk_snark_health3 );// {"sk_snark_health3","0"}; - - CVAR_REGISTER ( &sk_snark_dmg_bite1 );// {"sk_snark_dmg_bite1","0"}; - CVAR_REGISTER ( &sk_snark_dmg_bite2 );// {"sk_snark_dmg_bite2","0"}; - CVAR_REGISTER ( &sk_snark_dmg_bite3 );// {"sk_snark_dmg_bite3","0"}; - - CVAR_REGISTER ( &sk_snark_dmg_pop1 );// {"sk_snark_dmg_pop1","0"}; - CVAR_REGISTER ( &sk_snark_dmg_pop2 );// {"sk_snark_dmg_pop2","0"}; - CVAR_REGISTER ( &sk_snark_dmg_pop3 );// {"sk_snark_dmg_pop3","0"}; - - - - // Zombie - CVAR_REGISTER ( &sk_zombie_health1 );// {"sk_zombie_health1","0"}; - CVAR_REGISTER ( &sk_zombie_health2 );// {"sk_zombie_health3","0"}; - CVAR_REGISTER ( &sk_zombie_health3 );// {"sk_zombie_health3","0"}; - - CVAR_REGISTER ( &sk_zombie_dmg_one_slash1 );// {"sk_zombie_dmg_one_slash1","0"}; - CVAR_REGISTER ( &sk_zombie_dmg_one_slash2 );// {"sk_zombie_dmg_one_slash2","0"}; - CVAR_REGISTER ( &sk_zombie_dmg_one_slash3 );// {"sk_zombie_dmg_one_slash3","0"}; - - CVAR_REGISTER ( &sk_zombie_dmg_both_slash1 );// {"sk_zombie_dmg_both_slash1","0"}; - CVAR_REGISTER ( &sk_zombie_dmg_both_slash2 );// {"sk_zombie_dmg_both_slash2","0"}; - CVAR_REGISTER ( &sk_zombie_dmg_both_slash3 );// {"sk_zombie_dmg_both_slash3","0"}; - - - //Turret - CVAR_REGISTER ( &sk_turret_health1 );// {"sk_turret_health1","0"}; - CVAR_REGISTER ( &sk_turret_health2 );// {"sk_turret_health2","0"}; - CVAR_REGISTER ( &sk_turret_health3 );// {"sk_turret_health3","0"}; - - - // MiniTurret - CVAR_REGISTER ( &sk_miniturret_health1 );// {"sk_miniturret_health1","0"}; - CVAR_REGISTER ( &sk_miniturret_health2 );// {"sk_miniturret_health2","0"}; - CVAR_REGISTER ( &sk_miniturret_health3 );// {"sk_miniturret_health3","0"}; - - - // Sentry Turret - CVAR_REGISTER ( &sk_sentry_health1 );// {"sk_sentry_health1","0"}; - CVAR_REGISTER ( &sk_sentry_health2 );// {"sk_sentry_health2","0"}; - CVAR_REGISTER ( &sk_sentry_health3 );// {"sk_sentry_health3","0"}; - - - // PLAYER WEAPONS - - // Crowbar whack - CVAR_REGISTER ( &sk_plr_crowbar1 );// {"sk_plr_crowbar1","0"}; - CVAR_REGISTER ( &sk_plr_crowbar2 );// {"sk_plr_crowbar2","0"}; - CVAR_REGISTER ( &sk_plr_crowbar3 );// {"sk_plr_crowbar3","0"}; - - // Glock Round - CVAR_REGISTER ( &sk_plr_9mm_bullet1 );// {"sk_plr_9mm_bullet1","0"}; - CVAR_REGISTER ( &sk_plr_9mm_bullet2 );// {"sk_plr_9mm_bullet2","0"}; - CVAR_REGISTER ( &sk_plr_9mm_bullet3 );// {"sk_plr_9mm_bullet3","0"}; - - // 357 Round - CVAR_REGISTER ( &sk_plr_357_bullet1 );// {"sk_plr_357_bullet1","0"}; - CVAR_REGISTER ( &sk_plr_357_bullet2 );// {"sk_plr_357_bullet2","0"}; - CVAR_REGISTER ( &sk_plr_357_bullet3 );// {"sk_plr_357_bullet3","0"}; - - // MP5 Round - CVAR_REGISTER ( &sk_plr_9mmAR_bullet1 );// {"sk_plr_9mmAR_bullet1","0"}; - CVAR_REGISTER ( &sk_plr_9mmAR_bullet2 );// {"sk_plr_9mmAR_bullet2","0"}; - CVAR_REGISTER ( &sk_plr_9mmAR_bullet3 );// {"sk_plr_9mmAR_bullet3","0"}; - - - // M203 grenade - CVAR_REGISTER ( &sk_plr_9mmAR_grenade1 );// {"sk_plr_9mmAR_grenade1","0"}; - CVAR_REGISTER ( &sk_plr_9mmAR_grenade2 );// {"sk_plr_9mmAR_grenade2","0"}; - CVAR_REGISTER ( &sk_plr_9mmAR_grenade3 );// {"sk_plr_9mmAR_grenade3","0"}; - - - // Shotgun buckshot - CVAR_REGISTER ( &sk_plr_buckshot1 );// {"sk_plr_buckshot1","0"}; - CVAR_REGISTER ( &sk_plr_buckshot2 );// {"sk_plr_buckshot2","0"}; - CVAR_REGISTER ( &sk_plr_buckshot3 );// {"sk_plr_buckshot3","0"}; - - - // Crossbow - CVAR_REGISTER ( &sk_plr_xbow_bolt_monster1 );// {"sk_plr_xbow_bolt1","0"}; - CVAR_REGISTER ( &sk_plr_xbow_bolt_monster2 );// {"sk_plr_xbow_bolt2","0"}; - CVAR_REGISTER ( &sk_plr_xbow_bolt_monster3 );// {"sk_plr_xbow_bolt3","0"}; - - CVAR_REGISTER ( &sk_plr_xbow_bolt_client1 );// {"sk_plr_xbow_bolt1","0"}; - CVAR_REGISTER ( &sk_plr_xbow_bolt_client2 );// {"sk_plr_xbow_bolt2","0"}; - CVAR_REGISTER ( &sk_plr_xbow_bolt_client3 );// {"sk_plr_xbow_bolt3","0"}; - - - // RPG - CVAR_REGISTER ( &sk_plr_rpg1 );// {"sk_plr_rpg1","0"}; - CVAR_REGISTER ( &sk_plr_rpg2 );// {"sk_plr_rpg2","0"}; - CVAR_REGISTER ( &sk_plr_rpg3 );// {"sk_plr_rpg3","0"}; - - - // Gauss Gun - CVAR_REGISTER ( &sk_plr_gauss1 );// {"sk_plr_gauss1","0"}; - CVAR_REGISTER ( &sk_plr_gauss2 );// {"sk_plr_gauss2","0"}; - CVAR_REGISTER ( &sk_plr_gauss3 );// {"sk_plr_gauss3","0"}; - - - // Egon Gun - CVAR_REGISTER ( &sk_plr_egon_narrow1 );// {"sk_plr_egon_narrow1","0"}; - CVAR_REGISTER ( &sk_plr_egon_narrow2 );// {"sk_plr_egon_narrow2","0"}; - CVAR_REGISTER ( &sk_plr_egon_narrow3 );// {"sk_plr_egon_narrow3","0"}; - - CVAR_REGISTER ( &sk_plr_egon_wide1 );// {"sk_plr_egon_wide1","0"}; - CVAR_REGISTER ( &sk_plr_egon_wide2 );// {"sk_plr_egon_wide2","0"}; - CVAR_REGISTER ( &sk_plr_egon_wide3 );// {"sk_plr_egon_wide3","0"}; - - - // Hand Grendade - CVAR_REGISTER ( &sk_plr_hand_grenade1 );// {"sk_plr_hand_grenade1","0"}; - CVAR_REGISTER ( &sk_plr_hand_grenade2 );// {"sk_plr_hand_grenade2","0"}; - CVAR_REGISTER ( &sk_plr_hand_grenade3 );// {"sk_plr_hand_grenade3","0"}; - - - // Satchel Charge - CVAR_REGISTER ( &sk_plr_satchel1 );// {"sk_plr_satchel1","0"}; - CVAR_REGISTER ( &sk_plr_satchel2 );// {"sk_plr_satchel2","0"}; - CVAR_REGISTER ( &sk_plr_satchel3 );// {"sk_plr_satchel3","0"}; - - - // Tripmine - CVAR_REGISTER ( &sk_plr_tripmine1 );// {"sk_plr_tripmine1","0"}; - CVAR_REGISTER ( &sk_plr_tripmine2 );// {"sk_plr_tripmine2","0"}; - CVAR_REGISTER ( &sk_plr_tripmine3 );// {"sk_plr_tripmine3","0"}; - - - // WORLD WEAPONS - CVAR_REGISTER ( &sk_12mm_bullet1 );// {"sk_12mm_bullet1","0"}; - CVAR_REGISTER ( &sk_12mm_bullet2 );// {"sk_12mm_bullet2","0"}; - CVAR_REGISTER ( &sk_12mm_bullet3 );// {"sk_12mm_bullet3","0"}; - - CVAR_REGISTER ( &sk_9mmAR_bullet1 );// {"sk_9mm_bullet1","0"}; - CVAR_REGISTER ( &sk_9mmAR_bullet2 );// {"sk_9mm_bullet1","0"}; - CVAR_REGISTER ( &sk_9mmAR_bullet3 );// {"sk_9mm_bullet1","0"}; - - CVAR_REGISTER ( &sk_9mm_bullet1 );// {"sk_9mm_bullet1","0"}; - CVAR_REGISTER ( &sk_9mm_bullet2 );// {"sk_9mm_bullet2","0"}; - CVAR_REGISTER ( &sk_9mm_bullet3 );// {"sk_9mm_bullet3","0"}; - - - // HORNET - CVAR_REGISTER ( &sk_hornet_dmg1 );// {"sk_hornet_dmg1","0"}; - CVAR_REGISTER ( &sk_hornet_dmg2 );// {"sk_hornet_dmg2","0"}; - CVAR_REGISTER ( &sk_hornet_dmg3 );// {"sk_hornet_dmg3","0"}; - - // HEALTH/SUIT CHARGE DISTRIBUTION - CVAR_REGISTER ( &sk_suitcharger1 ); - CVAR_REGISTER ( &sk_suitcharger2 ); - CVAR_REGISTER ( &sk_suitcharger3 ); - - CVAR_REGISTER ( &sk_battery1 ); - CVAR_REGISTER ( &sk_battery2 ); - CVAR_REGISTER ( &sk_battery3 ); - - CVAR_REGISTER ( &sk_healthcharger1 ); - CVAR_REGISTER ( &sk_healthcharger2 ); - CVAR_REGISTER ( &sk_healthcharger3 ); - - CVAR_REGISTER ( &sk_healthkit1 ); - CVAR_REGISTER ( &sk_healthkit2 ); - CVAR_REGISTER ( &sk_healthkit3 ); - - CVAR_REGISTER ( &sk_scientist_heal1 ); - CVAR_REGISTER ( &sk_scientist_heal2 ); - CVAR_REGISTER ( &sk_scientist_heal3 ); - -// monster damage adjusters - CVAR_REGISTER ( &sk_monster_head1 ); - CVAR_REGISTER ( &sk_monster_head2 ); - CVAR_REGISTER ( &sk_monster_head3 ); - - CVAR_REGISTER ( &sk_monster_chest1 ); - CVAR_REGISTER ( &sk_monster_chest2 ); - CVAR_REGISTER ( &sk_monster_chest3 ); - - CVAR_REGISTER ( &sk_monster_stomach1 ); - CVAR_REGISTER ( &sk_monster_stomach2 ); - CVAR_REGISTER ( &sk_monster_stomach3 ); - - CVAR_REGISTER ( &sk_monster_arm1 ); - CVAR_REGISTER ( &sk_monster_arm2 ); - CVAR_REGISTER ( &sk_monster_arm3 ); - - CVAR_REGISTER ( &sk_monster_leg1 ); - CVAR_REGISTER ( &sk_monster_leg2 ); - CVAR_REGISTER ( &sk_monster_leg3 ); - -// player damage adjusters - CVAR_REGISTER ( &sk_player_head1 ); - CVAR_REGISTER ( &sk_player_head2 ); - CVAR_REGISTER ( &sk_player_head3 ); - - CVAR_REGISTER ( &sk_player_chest1 ); - CVAR_REGISTER ( &sk_player_chest2 ); - CVAR_REGISTER ( &sk_player_chest3 ); - - CVAR_REGISTER ( &sk_player_stomach1 ); - CVAR_REGISTER ( &sk_player_stomach2 ); - CVAR_REGISTER ( &sk_player_stomach3 ); - - CVAR_REGISTER ( &sk_player_arm1 ); - CVAR_REGISTER ( &sk_player_arm2 ); - CVAR_REGISTER ( &sk_player_arm3 ); - - CVAR_REGISTER ( &sk_player_leg1 ); - CVAR_REGISTER ( &sk_player_leg2 ); - CVAR_REGISTER ( &sk_player_leg3 ); // END REGISTER CVARS FOR SKILL LEVEL STUFF SERVER_COMMAND( "exec skill.cfg\n" ); diff --git a/dlls/game.h b/dlls/game.h index 31f4e34..0b71575 100644 --- a/dlls/game.h +++ b/dlls/game.h @@ -18,28 +18,30 @@ extern void GameDLLInit( void ); - extern cvar_t displaysoundlist; // multiplayer server rules -extern cvar_t teamplay; +extern cvar_t gametype; extern cvar_t fraglimit; +extern cvar_t roundtimelimit; extern cvar_t timelimit; extern cvar_t friendlyfire; -extern cvar_t falldamage; extern cvar_t weaponstay; -extern cvar_t forcerespawn; extern cvar_t flashlight; -extern cvar_t aimcrosshair; extern cvar_t decalfrequency; extern cvar_t teamlist; extern cvar_t teamoverride; extern cvar_t defaultteam; extern cvar_t allowmonsters; +extern cvar_t maxlives; + +extern cvar_t allowsidearms; +extern cvar_t allowsecondary; +extern cvar_t allowunique; +extern cvar_t allowexplosives; // Engine Cvars extern cvar_t *g_psv_gravity; extern cvar_t *g_psv_aim; -extern cvar_t *g_footsteps; #endif // GAME_H diff --git a/dlls/game_deathmatch.cpp b/dlls/game_deathmatch.cpp new file mode 100644 index 0000000..866dd13 --- /dev/null +++ b/dlls/game_deathmatch.cpp @@ -0,0 +1,1938 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +// +// multiplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "shake.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "client.h" + +#include "game.h" +#include "items.h" +#include "voice_gamemgr.h" +#include "thewastes.h" + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; +extern int gmsgBriefing; +extern int gmsgServerName; +extern int gmsgSpectator; + +extern int g_gametype; + +#define ITEM_RESPAWN_TIME 30 +#define WEAPON_RESPAWN_TIME 35 +#define UNIQUE_RESPAWN_TIME 60 +#define AMMO_RESPAWN_TIME 20 + +float g_flIntermissionStartTime = 0; + +CVoiceGameMgr g_VoiceGameMgr; + +class CMultiplayGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool CanPlayerHearPlayer(CBasePlayer *pListener, CBasePlayer *pTalker) + { + if ( g_gametype == 2 ) + { + if ( g_pGameRules->PlayerRelationship( pListener, pTalker ) != GR_TEAMMATE ) + { + return false; + } + } + + return true; + } +}; +static CMultiplayGameMgrHelper g_GameMgrHelper; + +//********************************************************* +// Rules for the half-life multiplayer game. +//********************************************************* + +CHalfLifeMultiplay :: CHalfLifeMultiplay() +{ + g_VoiceGameMgr.Init(&g_GameMgrHelper, gpGlobals->maxClients); + + RefreshSkillData(); + m_flIntermissionEndTime = 0; + g_flIntermissionStartTime = 0; + m_flWeaponAllowCheck = 0; + + m_iLaserOn = m_iLaserOff = 0; + + // 11/8/98 + // Modified by YWB: Server .cfg file is now a cvar, so that + // server ops can run multiple game servers, with different server .cfg files, + // from a single installed directory. + // Mapcyclefile is already a cvar. + + // 3/31/99 + // Added lservercfg file cvar, since listen and dedicated servers should not + // share a single config file. (sjb) + if ( IS_DEDICATED_SERVER() ) + { + // dedicated server + char *servercfgfile = (char *)CVAR_GET_STRING( "servercfgfile" ); + + if ( servercfgfile && servercfgfile[0] ) + { + char szCommand[256]; + + ALERT( at_console, "Executing dedicated server config file\n" ); + sprintf( szCommand, "exec %s\n", servercfgfile ); + SERVER_COMMAND( szCommand ); + } + } + else + { + // listen server + char *lservercfgfile = (char *)CVAR_GET_STRING( "lservercfgfile" ); + + if ( lservercfgfile && lservercfgfile[0] ) + { + char szCommand[256]; + + ALERT( at_console, "Executing listen server config file\n" ); + sprintf( szCommand, "exec %s\n", lservercfgfile ); + SERVER_COMMAND( szCommand ); + } + } +} + +BOOL CHalfLifeMultiplay::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + if(FStrEq(pcmd,"join_game")) + { + // Client wants to be put in the game (used for intro stuff) + if( !pPlayer->m_bCanSpawn ) + { + pPlayer->m_bCanSpawn = TRUE; + pPlayer->m_bObserver = FALSE; + respawn(pPlayer->pev,FALSE); + } + + return TRUE; + } + else if(FStrEq(pcmd,"spectate")) + { + // Client wants to spectate + if(pPlayer->m_bCanSpawn) + { + pPlayer->m_bCanSpawn = FALSE; + pPlayer->Killed(pPlayer->pev,FALSE); + } + else + { + pPlayer->m_bObserver = TRUE; + pPlayer->Spawn(); + } + + return TRUE; + } + else if(FStrEq(pcmd,"setrandom")) + { + // Client wants random weapons + pPlayer->m_Inventory.SetRandom( TRUE ); + + return TRUE; + } + else if(FStrEq(pcmd,"unsetrandom")) + { + // Client doesnt want random weapons + pPlayer->m_Inventory.SetRandom( FALSE ); + + return TRUE; + } + else if(FStrEq(pcmd,"setweapons")) + { + pPlayer->m_Inventory.SetInventory( 0, atoi( CMD_ARGV(1) ) ); + pPlayer->m_Inventory.SetInventory( 1, atoi( CMD_ARGV(2) ) ); + pPlayer->m_Inventory.SetInventory( 2, atoi( CMD_ARGV(3) ) ); + pPlayer->m_Inventory.SetInventory( 3, atoi( CMD_ARGV(4) ) ); + pPlayer->m_Inventory.SetInventory( 4, atoi( CMD_ARGV(5) ) ); + + return TRUE; + } + + return CGameRules::ClientCommand(pPlayer, pcmd); +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::RefreshSkillData( void ) +{ +// load all default values + CGameRules::RefreshSkillData(); +} + +// longest the intermission can last, in seconds +#define MAX_INTERMISSION_TIME 120 + +extern cvar_t timeleft, fragsleft; +extern cvar_t mp_chattime; + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: Think ( void ) +{ + g_VoiceGameMgr.Update(gpGlobals->frametime); + + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + if ( g_fGameOver ) // someone else quit the game already + { + // bounds check + int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); + if ( time < 10 ) + CVAR_SET_STRING( "mp_chattime", "10" ); + else if ( time > MAX_INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( MAX_INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = g_flIntermissionStartTime + mp_chattime.value; + + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->time ) + { + if ( m_iEndIntermissionButtonHit // check that someone has pressed a key, or the max intermission time is over + || ( ( g_flIntermissionStartTime + MAX_INTERMISSION_TIME ) < gpGlobals->time) ) + ChangeLevel(); // intermission is over + } + + return; + } + + float flTimeLimit = timelimit.value * 60; + float flFragLimit = fraglimit.value; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any player is over the frag limit + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && pPlayer->pev->frags >= flFragLimit ) + { + GoToIntermission(); + return; + } + + + if ( pPlayer ) + { + remain = flFragLimit - pPlayer->pev->frags; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; + + SendLaserInfo(); +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsMultiplayer( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsDeathmatch( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsCoOp( void ) +{ + return gpGlobals->coop; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + + if ( !pWeapon->CanDeploy() ) + { + // that weapon can't deploy anyway. + return FALSE; + } + + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + // can't put away the active item. + return FALSE; + } + + CBasePlayerWeapon *pCastWeap = (CBasePlayerWeapon*)pWeapon; + if( pCastWeap != NULL ) + { + if( !pCastWeap->IsUseable() ) + return FALSE; + + if( pCastWeap->m_iClip == 0 && + pPlayer->m_rgAmmo[ pCastWeap->m_iPrimaryAmmoType ] < 1 ) + { + // This gun has no ammo + return FALSE; + } + } + + pCastWeap = (CBasePlayerWeapon*)pPlayer->m_pActiveItem; + if( pCastWeap != NULL && pCastWeap->m_iClip == 0 && + pPlayer->m_rgAmmo[ pCastWeap->m_iPrimaryAmmoType ] < 1 ) + { + // Current gun has no ammo + return TRUE; + } + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + + CBasePlayerItem *pCheck; + CBasePlayerItem *pBest;// this will be used in the event that we don't find a weapon in the same category. + int iBestWeight; + int i; + + iBestWeight = -1;// no weapon lower than -1 can be autoswitched to + pBest = NULL; + + if ( !pCurrentWeapon->CanHolster() ) + { + // can't put this gun away right now, so can't switch. + return FALSE; + } + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pCheck = pPlayer->m_rgpPlayerItems[ i ]; + + while ( pCheck ) + { + if ( pCheck->iWeight() > -1 && pCheck->iWeight() == pCurrentWeapon->iWeight() && pCheck != pCurrentWeapon ) + { + // this weapon is from the same category. + if ( pCheck->CanDeploy() ) + { + if ( pPlayer->SwitchWeapon( pCheck ) ) + { + return TRUE; + } + } + } + else if ( pCheck->iWeight() > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of + { + //ALERT ( at_console, "Considering %s\n", STRING( pCheck->pev->classname ) ); + // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight + // that the player was using. This will end up leaving the player with his heaviest-weighted + // weapon. + if ( pCheck->CanDeploy() ) + { + // if this weapon is useable, flag it as the best + iBestWeight = pCheck->iWeight(); + pBest = pCheck; + } + } + + pCheck = pCheck->m_pNext; + } + } + + // if we make it here, we've checked all the weapons and found no useable + // weapon in the same catagory as the current weapon. + + // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always + // at least get the crowbar, but ya never know. + if ( !pBest ) + { + pCurrentWeapon->Holster(); // We add this because some weapons remove themselves (like molotovs and grenades) + return FALSE; + } + + pPlayer->SwitchWeapon( pBest ); + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + g_VoiceGameMgr.ClientConnected(pEntity); + return TRUE; +} + +extern int gmsgSayText; +extern int gmsgGameMode; + +void CHalfLifeMultiplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 0 ); // game mode none + MESSAGE_END(); +} + +void CHalfLifeMultiplay :: InitHUD( CBasePlayer *pl ) +{ + // notify other clients of player joining the game + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs( "%s has joined the game\n", + ( pl->pev->netname && STRING(pl->pev->netname)[0] != 0 ) ? STRING(pl->pev->netname) : "unconnected" ) ); + + // team match? + if ( g_gametype == 2) + { + UTIL_LogPrintf( "\"%s<%i><%u><%s>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERWONID( pl->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pl->edict() ), "model" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%u><%i>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERWONID( pl->edict() ), + GETPLAYERUSERID( pl->edict() ) ); + } + + UpdateGameMode( pl ); + + // sending just one score makes the hud scoreboard active; otherwise + // it is just disabled for single play + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( ENTINDEX(pl->edict()) ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + MESSAGE_END(); + + // Send their spectator state + MESSAGE_BEGIN( MSG_ONE, gmsgSpectator, NULL, pl->edict() ); + WRITE_BYTE( pl->entindex() ); + WRITE_BYTE( (pl->pev->iuser1 != 0) ); + MESSAGE_END(); + + SendMOTDToClient( pl->edict() ); + + // loop through all active players and send their score info to the new client + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + // FIXME: Probably don't need to cast this just to read m_iDeaths + CBasePlayer *plr = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( plr ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( i ); // client number + WRITE_SHORT( plr->pev->frags ); + WRITE_SHORT( plr->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( GetTeamIndex( plr->m_szTeamName ) + 1 ); + MESSAGE_END(); + } + } + + if ( g_fGameOver ) + { + MESSAGE_BEGIN( MSG_ONE, SVC_INTERMISSION, NULL, pl->edict() ); + MESSAGE_END(); + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: ClientDisconnected( edict_t *pClient ) +{ + if ( pClient ) + { + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + + if ( pPlayer ) + { + FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 ); + + // team match? + if ( g_gametype == 2 ) + { + UTIL_LogPrintf( "\"%s<%i><%u><%s>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERWONID( pPlayer->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%u><%i>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERWONID( pPlayer->edict() ), + GETPLAYERUSERID( pPlayer->edict() ) ); + } + + pPlayer->RemoveAllItems();// destroy all of the players weapons and items + + // Tell all clients this player isn't a spectator anymore + MESSAGE_BEGIN( MSG_ALL, gmsgSpectator ); + WRITE_BYTE( ENTINDEX(pClient) ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + CBasePlayer *client = NULL; + while ( ((client = (CBasePlayer*)UTIL_FindEntityByClassname( client, "player" )) != NULL) && (!FNullEnt(client->edict())) ) + { + if ( !client->pev ) + continue; + if ( client == pPlayer ) + continue; + + // If a spectator was chasing this player, move him/her onto the next player + if ( client->m_hObserverTarget == pPlayer ) + { + int iMode = client->pev->iuser1; + client->pev->iuser1 = 0; + client->m_hObserverTarget = NULL; + client->Observer_SetMode( iMode ); + } + } + } + } +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + + if(pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED >= 10) + { + // Fade if the player isnt gonna die from it + if(pPlayer->m_flFallVelocity*DAMAGE_FOR_FALL_SPEED < pPlayer->pev->health) + { + UTIL_ScreenFade(pPlayer,RGB_FALL,TIME_FALL,HOLD_FALL,ALPHA_FALL,FLAGS_FALL); + + // Only cripple if player fell far enough + if(pPlayer->m_flFallVelocity*DAMAGE_FOR_FALL_SPEED > 40) + CRIPPLE_PLAYER(pPlayer,pPlayer->m_flFallVelocity*DAMAGE_FOR_FALL_SPEED); + } + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; + } + + return 0; +} + +//========================================================= +// ModifyTiltedDamage +// +// Calculate damage tilt based on modifiers such as distance. +//========================================================= +float CHalfLifeMultiplay::ModifyTiltedDamage(CBaseEntity *pAttacker,CBaseEntity *pVictim,float flCurDamage,int iDamageType) +{ + float fTiltedDamage = flCurDamage; + Vector vecDistance; + vecDistance = pAttacker->pev->origin - pVictim->pev->origin; + + if( strcmp( STRING(pVictim->pev->classname), "player" ) != 0 ) + return flCurDamage; + + // Damage reduction range + if( iDamageType == DMG_BULLET ) + fTiltedDamage = max( 0, fTiltedDamage - (vecDistance.Length()/30.0f) ); + else if( iDamageType == DMG_BUCKSHOT ) + fTiltedDamage = max( 0, fTiltedDamage - (vecDistance.Length()/25.0f) ); + + // Each person incurs a 5% damage penalty + // if not on the ground + if(!(pAttacker->pev->flags & FL_ONGROUND)) + fTiltedDamage *= 0.95f; + + if(!(pVictim->pev->flags & FL_ONGROUND)) + fTiltedDamage *= 0.95f; + + // Each person incurs a 5% damage INCREASE + // if crouching + if(pAttacker->pev->flags & FL_DUCKING) + fTiltedDamage *= 1.05f; + + if(pVictim->pev->flags & FL_DUCKING) + fTiltedDamage *= 1.05f; + +#if 0 + char dmg[1024]; + sprintf( dmg, "Damage: %f Distance: %f", fTiltedDamage, vecDistance.Length() ); + UTIL_SayTextAll( dmg, UTIL_FindEntityByClassname( NULL, "player" ) ); +#endif + + return fTiltedDamage; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerThink( CBasePlayer *pPlayer ) +{ + if ( g_fGameOver ) + { + // check for button presses + if ( pPlayer->m_afButtonPressed & ( IN_DUCK | IN_ATTACK | IN_ATTACK2 | IN_USE | IN_JUMP ) ) + m_iEndIntermissionButtonHit = TRUE; + + // clear attack/use commands from player + pPlayer->m_afButtonPressed = 0; + pPlayer->pev->button = 0; + pPlayer->m_afButtonReleased = 0; + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + BOOL addDefault; + CBaseEntity *pWeaponEntity = NULL; + + addDefault = TRUE; + + while ( pWeaponEntity = UTIL_FindEntityByClassname( pWeaponEntity, "game_player_equip" )) + { + pWeaponEntity->Touch( pPlayer ); + addDefault = FALSE; + } + + if ( addDefault ) + { + pPlayer->m_Inventory.SpawnInventory(pPlayer); + } + + // Clear any screen fades + UTIL_ScreenFade(pPlayer,Vector(0,0,0),0,0,0,FFADE_OUT); + CRIPPLE_PLAYER(pPlayer,0); +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeMultiplay :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeMultiplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + DeathNotice( pVictim, pKiller, pInflictor); + + pVictim->m_iDeaths += 1; + + FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 ); + CBasePlayer *peKiller = NULL; + CBaseEntity *ktmp = CBaseEntity::Instance( pKiller ); + if ( ktmp && (ktmp->Classify() == CLASS_PLAYER) ) + peKiller = (CBasePlayer*)ktmp; + + if ( pVictim->pev == pKiller ) + { // killed self + pKiller->frags -= 1; + } + else if ( ktmp && ktmp->IsPlayer() ) + { + int Frags = IPointsForKill( peKiller, pVictim ); + // if a player dies in a deathmatch game and the killer is a client, award the killer some points + pKiller->frags += Frags; + + if(peKiller != NULL) + peKiller->m_iTempFrags += Frags; + + FireTargets( "game_playerkill", ktmp, ktmp, USE_TOGGLE, 0 ); + } + else + { // killed by the world + pKiller->frags -= 1; + } + + // update the scores + // killed scores + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); + WRITE_SHORT( pVictim->pev->frags ); + WRITE_SHORT( pVictim->m_iDeaths ); + WRITE_SHORT( 1 ); + WRITE_SHORT( GetTeamIndex( pVictim->m_szTeamName ) + 1 ); + MESSAGE_END(); + + // killers score, if it's a player + CBaseEntity *ep = CBaseEntity::Instance( pKiller ); + if ( ep && ep->Classify() == CLASS_PLAYER ) + { + CBasePlayer *PK = (CBasePlayer*)ep; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(PK->edict()) ); + WRITE_SHORT( PK->pev->frags ); + WRITE_SHORT( PK->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( GetTeamIndex( PK->m_szTeamName) + 1 ); + MESSAGE_END(); + + // let the killer paint another decal as soon as he'd like. + PK->m_flNextDecalTime = gpGlobals->time; + } +} + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeMultiplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor) +{ + // Work out what killed the player, and send a message to all clients about it + CBaseEntity *Killer = CBaseEntity::Instance( pKiller ); + CBasePlayer *pKPlayer = NULL; + CWasteWeapon *pWeapon = NULL; + + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_index = 0; + + if ( pKiller->flags & FL_CLIENT ) + { + killer_index = ENTINDEX(ENT(pKiller)); + + if ( pevInflictor ) + { + if ( pevInflictor == pKiller ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + CBasePlayer *pPlayer = (CBasePlayer*)CBaseEntity::Instance( pKiller ); + + if ( pPlayer->m_pActiveItem ) + killer_weapon_name = pPlayer->m_pActiveItem->pszName(); + } + else + killer_weapon_name = STRING( pevInflictor->classname ); // it's just that easy + } + } + else + { + killer_weapon_name = STRING( pevInflictor->classname ); + } + + // strip the monster_* or weapon_* from the inflictor's classname + if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) + killer_weapon_name += 7; + else if ( strncmp( killer_weapon_name, "monster_", 8 ) == 0 ) + killer_weapon_name += 8; + else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) + killer_weapon_name += 5; + + // If player was attacked within a second of dying, + // dont count it as bleeding + if( pVictim->pev->vuser2.y == 2 ) + { + if( gpGlobals->time < pVictim->m_flLastAttack + 1.0f ) + pVictim->pev->vuser2.y = 0; + } + + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_BYTE( pVictim->pev->vuser2.y ); // special kill ? + WRITE_STRING( killer_weapon_name ); // what they were killed by (should this be a string?) + + if(Killer->IsPlayer()) + { + pKPlayer = (CBasePlayer*)Killer; + pWeapon = (CWasteWeapon*)pKPlayer->m_pActiveItem; + } + + if(pKPlayer != NULL) + { + if(pVictim->pev->vuser2.y == 2) + { + char szBledString[128]; + + switch(RANDOM_LONG(0,1)) + { + case 0: + strcpy(szBledString, DEATH_BLEED_1); + break; + case 1: + strcpy(szBledString, DEATH_BLEED_2); + break; + } + + WRITE_STRING( szBledString ); + } + else if(pKPlayer->HasWeapons() && pWeapon != NULL) + WRITE_STRING( pWeapon->szGetDeathNoticeString() ); + else + WRITE_STRING( "%s killed %s... this message shouldnt exist" ); // If this happens, oops! + } + else + WRITE_STRING(""); // client.dll populates this + + MESSAGE_END(); + + if ( pVictim->pev == pKiller ) + { + // killed self + + // team match? + if ( g_gametype == 2 ) + { + UTIL_LogPrintf( "\"%s<%i><%u><%s>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERWONID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%u><%i>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERWONID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + else if ( pKiller->flags & FL_CLIENT ) + { + // team match? + if ( g_gametype == 2 ) + { + UTIL_LogPrintf( "\"%s<%i><%u><%s>\" killed \"%s<%i><%u><%s>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERWONID( ENT(pKiller) ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( ENT(pKiller) ), "model" ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERWONID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%u><%i>\" killed \"%s<%i><%u><%i>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERWONID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERWONID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + else + { + // killed by the world + + // team match? + if ( g_gametype == 2 ) + { + UTIL_LogPrintf( "\"%s<%i><%u><%s>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERWONID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%u><%i>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERWONID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + + MESSAGE_BEGIN ( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); //command length in bytes + WRITE_BYTE ( DRC_EVENT ); + WRITE_SHORT( ENTINDEX(pVictim->edict()) ); + if (pevInflictor) + WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity + else + WRITE_SHORT( ENTINDEX(ENT(pKiller)) ); // index number of secondary entity + WRITE_LONG ( 7 | DRC_FLAG_DRAMATIC ); + MESSAGE_END(); + +// Print a standard message + // TODO: make this go direct to console + return; // just remove for now +/* + char szText[ 128 ]; + + if ( pKiller->flags & FL_MONSTER ) + { + // killed by a monster + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " was killed by a monster.\n" ); + return; + } + + if ( pKiller == pVictim->pev ) + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " commited suicide.\n" ); + } + else if ( pKiller->flags & FL_CLIENT ) + { + strcpy ( szText, STRING( pKiller->netname ) ); + + strcat( szText, " : " ); + strcat( szText, killer_weapon_name ); + strcat( szText, " : " ); + + strcat ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, "\n" ); + } + else if ( FClassnameIs ( pKiller, "worldspawn" ) ) + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " fell or drowned or something.\n" ); + } + else if ( pKiller->solid == SOLID_BSP ) + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " was mooshed.\n" ); + } + else + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " died mysteriously.\n" ); + } + + UTIL_ClientPrintAll( szText ); +*/ +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeMultiplay :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeMultiplay :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + if ( weaponstay.value > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return gpGlobals->time + 0; // weapon respawns almost instantly + } + } + + CBasePlayerWeapon *pWeap = (CBasePlayerWeapon*)pWeapon; + + if( pWeap != NULL ) + if( pWeap->iWeight() == WEIGHT_UNIQUE ) + return gpGlobals->time + UNIQUE_RESPAWN_TIME; + + return gpGlobals->time + WEAPON_RESPAWN_TIME; +} + +// when we are within this close to running out of entities, items +// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn +#define ENTITY_INTOLERANCE 100 + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeMultiplay :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon && pWeapon->m_iId && (pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + if ( NUMBER_OF_ENTITIES() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) + return 0; + + // we're past the entity tolerance level, so delay the respawn + return FlWeaponRespawnTime( pWeapon ); + } + + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeMultiplay :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon->pev->spawnflags & SF_NORESPAWN ) + { + return GR_WEAPON_RESPAWN_NO; + } + + return GR_WEAPON_RESPAWN_YES; +} + +//========================================================= +// CanHaveWeapon - returns FALSE if the player is not allowed +// to pick up this weapon +//========================================================= +BOOL CHalfLifeMultiplay::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pItem ) +{ + if ( weaponstay.value > 0 ) + { + if ( pItem->iFlags() & ITEM_FLAG_LIMITINWORLD ) + return CGameRules::CanHavePlayerItem( pPlayer, pItem ); + + // check if the player already has this weapon + for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + CBasePlayerItem *it = pPlayer->m_rgpPlayerItems[i]; + + while ( it != NULL ) + { + if ( it->m_iId == pItem->m_iId ) + { + return FALSE; + } + + it = it->m_pNext; + } + } + } + + return CGameRules::CanHavePlayerItem( pPlayer, pItem ); +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::ItemShouldRespawn( CItem *pItem ) +{ + if ( pItem->pev->spawnflags & SF_NORESPAWN ) + { + return GR_ITEM_RESPAWN_NO; + } + + return GR_ITEM_RESPAWN_YES; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeMultiplay::FlItemRespawnTime( CItem *pItem ) +{ + return gpGlobals->time + ITEM_RESPAWN_TIME; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ +// if ( pEntity->pev->flags & FL_MONSTER ) +// return FALSE; + + if( !CVAR_GET_FLOAT( "mp_allowsidearms" ) ) + { + playeritemlist_t *pCurList = &g_SidearmItems; + + for( int i = 0;i < pCurList->size; i++ ) + { + playeritem_t *pItem = &pCurList->array[i]; + + if( FStrEq( STRING( pEntity->pev->classname ), pItem->weapon_classname ) ) + return FALSE; + } + } + + if( !CVAR_GET_FLOAT( "mp_allowprimary" ) ) + { + playeritemlist_t *pCurList = &g_PrimaryItems; + + for( int i = 0;i < pCurList->size; i++ ) + { + playeritem_t *pItem = &pCurList->array[i]; + + if( FStrEq( STRING( pEntity->pev->classname ), pItem->weapon_classname ) ) + return FALSE; + } + } + + if( !CVAR_GET_FLOAT( "mp_allowunique" ) ) + { + playeritemlist_t *pCurList = &g_UniqueItems; + + for( int i = 0;i < pCurList->size; i++ ) + { + playeritem_t *pItem = &pCurList->array[i]; + + if( FStrEq( STRING( pEntity->pev->classname ), pItem->weapon_classname ) ) + return FALSE; + } + } + + if( !CVAR_GET_FLOAT( "mp_allowexplosives" ) ) + { + playeritemlist_t *pCurList = &g_OtherItems; + + for( int i = 0;i < pCurList->size; i++ ) + { + playeritem_t *pItem = &pCurList->array[i]; + + if( FStrEq( STRING( pEntity->pev->classname ), pItem->weapon_classname ) ) + return FALSE; + } + } + + // TODO: Remove when molotovs are put back in :) + if( FStrEq( STRING( pEntity->pev->classname ), "weapon_molotov" ) ) + return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + if ( pAmmo->pev->spawnflags & SF_NORESPAWN ) + { + return GR_AMMO_RESPAWN_NO; + } + + return GR_AMMO_RESPAWN_YES; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return gpGlobals->time + AMMO_RESPAWN_TIME; +} + +//========================================================= +//========================================================= +Vector CHalfLifeMultiplay::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlHealthChargerRechargeTime( void ) +{ + return 60; +} + + +float CHalfLifeMultiplay::FlHEVChargerRechargeTime( void ) +{ + return 30; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_ACTIVE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_NO; +} + +edict_t *CHalfLifeMultiplay::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = CGameRules::GetPlayerSpawnSpot( pPlayer ); + if ( IsMultiplayer() && pentSpawnSpot->v.target ) + { + FireTargets( STRING(pentSpawnSpot->v.target), pPlayer, pPlayer, USE_TOGGLE, 0 ); + } + + return pentSpawnSpot; +} + + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life deathmatch has only enemies + return GR_NOTTEAMMATE; +} + +BOOL CHalfLifeMultiplay :: PlayFootstepSounds( CBasePlayer *pl, float fvol ) +{ +//if ( g_footsteps && g_footsteps->value == 0 ) +// return FALSE; + + if ( pl->IsOnLadder() || pl->pev->velocity.Length2D() > 220 ) + return TRUE; // only make step sounds in multiplayer if the player is moving fast enough + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: FAllowFlashlight( void ) +{ + return flashlight.value != 0; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FAllowMonsters( void ) +{ + return ( allowmonsters.value != 0 ); +} + +//========================================================= +//======== CHalfLifeMultiplay private functions =========== +#define INTERMISSION_TIME 6 + +void CHalfLifeMultiplay :: GoToIntermission( void ) +{ + if ( g_fGameOver ) + return; // intermission has already been triggered, so ignore. + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); + + // bounds check + int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); + if ( time < 10 ) + CVAR_SET_STRING( "mp_chattime", "10" ); + else if ( time > MAX_INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( MAX_INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = gpGlobals->time + ( (int)mp_chattime.value ); + g_flIntermissionStartTime = gpGlobals->time; + + g_fGameOver = TRUE; + m_iEndIntermissionButtonHit = FALSE; +} + +#define MAX_RULE_BUFFER 1024 + +typedef struct mapcycle_item_s +{ + struct mapcycle_item_s *next; + + char mapname[ 32 ]; + int minplayers, maxplayers; + char rulebuffer[ MAX_RULE_BUFFER ]; +} mapcycle_item_t; + +typedef struct mapcycle_s +{ + struct mapcycle_item_s *items; + struct mapcycle_item_s *next_item; +} mapcycle_t; + +/* +============== +DestroyMapCycle + +Clean up memory used by mapcycle when switching it +============== +*/ +void DestroyMapCycle( mapcycle_t *cycle ) +{ + mapcycle_item_t *p, *n, *start; + p = cycle->items; + if ( p ) + { + start = p; + p = p->next; + while ( p != start ) + { + n = p->next; + delete p; + p = n; + } + + delete cycle->items; + } + cycle->items = NULL; + cycle->next_item = NULL; +} + +static char com_token[ 1500 ]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + +/* +============== +COM_TokenWaiting + +Returns 1 if additional data is waiting to be processed on this line +============== +*/ +int COM_TokenWaiting( char *buffer ) +{ + char *p; + + p = buffer; + while ( *p && *p!='\n') + { + if ( !isspace( *p ) || isalnum( *p ) ) + return 1; + + p++; + } + + return 0; +} + + + +/* +============== +ReloadMapCycleFile + + +Parses mapcycle.txt file into mapcycle_t structure +============== +*/ +int ReloadMapCycleFile( char *filename, mapcycle_t *cycle ) +{ + char szBuffer[ MAX_RULE_BUFFER ]; + char szMap[ 32 ]; + int length; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( filename, &length ); + int hasbuffer; + mapcycle_item_s *item, *newlist = NULL, *next; + + if ( pFileList && length ) + { + // the first map name in the file becomes the default + while ( 1 ) + { + hasbuffer = 0; + memset( szBuffer, 0, MAX_RULE_BUFFER ); + + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) <= 0 ) + break; + + strcpy( szMap, com_token ); + + // Any more tokens on this line? + if ( COM_TokenWaiting( pFileList ) ) + { + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) > 0 ) + { + hasbuffer = 1; + strcpy( szBuffer, com_token ); + } + } + + // Check map + if ( IS_MAP_VALID( szMap ) ) + { + // Create entry + char *s; + + item = new mapcycle_item_s; + + strcpy( item->mapname, szMap ); + + item->minplayers = 0; + item->maxplayers = 0; + + memset( item->rulebuffer, 0, MAX_RULE_BUFFER ); + + if ( hasbuffer ) + { + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "minplayers" ); + if ( s && s[0] ) + { + item->minplayers = atoi( s ); + item->minplayers = max( item->minplayers, 0 ); + item->minplayers = min( item->minplayers, gpGlobals->maxClients ); + } + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "maxplayers" ); + if ( s && s[0] ) + { + item->maxplayers = atoi( s ); + item->maxplayers = max( item->maxplayers, 0 ); + item->maxplayers = min( item->maxplayers, gpGlobals->maxClients ); + } + + // Remove keys + // + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "minplayers" ); + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "maxplayers" ); + + strcpy( item->rulebuffer, szBuffer ); + } + + item->next = cycle->items; + cycle->items = item; + } + else + { + ALERT( at_console, "Skipping %s from mapcycle, not a valid map\n", szMap ); + } + + } + + FREE_FILE( aFileList ); + } + + // Fixup circular list pointer + item = cycle->items; + + // Reverse it to get original order + while ( item ) + { + next = item->next; + item->next = newlist; + newlist = item; + item = next; + } + cycle->items = newlist; + item = cycle->items; + + // Didn't parse anything + if ( !item ) + { + return 0; + } + + while ( item->next ) + { + item = item->next; + } + item->next = cycle->items; + + cycle->next_item = item->next; + + return 1; +} + +/* +============== +CountPlayers + +Determine the current # of active players on the server for map cycling logic +============== +*/ +int CountPlayers( void ) +{ + int num = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex( i ); + + if ( pEnt ) + { + num = num + 1; + } + } + + return num; +} + +/* +============== +ExtractCommandString + +Parse commands/key value pairs to issue right after map xxx command is issued on server + level transition +============== +*/ +void ExtractCommandString( char *s, char *szCommand ) +{ + // Now make rules happen + char pkey[512]; + char value[512]; // use two buffers so compares + // work without stomping on each other + char *o; + + if ( *s == '\\' ) + s++; + + while (1) + { + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + strcat( szCommand, pkey ); + if ( strlen( value ) > 0 ) + { + strcat( szCommand, " " ); + strcat( szCommand, value ); + } + strcat( szCommand, "\n" ); + + if (!*s) + return; + s++; + } +} + +/* +============== +ChangeLevel + +Server is changing to a new level, check mapcycle.txt for map name and setup info +============== +*/ +void CHalfLifeMultiplay :: ChangeLevel( void ) +{ + static char szPreviousMapCycleFile[ 256 ]; + static mapcycle_t mapcycle; + + char szNextMap[32]; + char szFirstMapInList[32]; + char szCommands[ 1500 ]; + char szRules[ 1500 ]; + int minplayers = 0, maxplayers = 0; + strcpy( szFirstMapInList, "hldm1" ); // the absolute default level is hldm1 + + int curplayers; + BOOL do_cycle = TRUE; + + // find the map to change to + char *mapcfile = (char*)CVAR_GET_STRING( "mapcyclefile" ); + ASSERT( mapcfile != NULL ); + + szCommands[ 0 ] = '\0'; + szRules[ 0 ] = '\0'; + + curplayers = CountPlayers(); + + // Has the map cycle filename changed? + if ( stricmp( mapcfile, szPreviousMapCycleFile ) ) + { + strcpy( szPreviousMapCycleFile, mapcfile ); + + DestroyMapCycle( &mapcycle ); + + if ( !ReloadMapCycleFile( mapcfile, &mapcycle ) || ( !mapcycle.items ) ) + { + ALERT( at_console, "Unable to load map cycle file %s\n", mapcfile ); + do_cycle = FALSE; + } + } + + if ( do_cycle && mapcycle.items ) + { + BOOL keeplooking = FALSE; + BOOL found = FALSE; + mapcycle_item_s *item; + + // Assume current map + strcpy( szNextMap, STRING(gpGlobals->mapname) ); + strcpy( szFirstMapInList, STRING(gpGlobals->mapname) ); + + // Traverse list + for ( item = mapcycle.next_item; item->next != mapcycle.next_item; item = item->next ) + { + keeplooking = FALSE; + + ASSERT( item != NULL ); + + if ( item->minplayers != 0 ) + { + if ( curplayers >= item->minplayers ) + { + found = TRUE; + minplayers = item->minplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( item->maxplayers != 0 ) + { + if ( curplayers <= item->maxplayers ) + { + found = TRUE; + maxplayers = item->maxplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( keeplooking ) + continue; + + found = TRUE; + break; + } + + if ( !found ) + { + item = mapcycle.next_item; + } + + // Increment next item pointer + mapcycle.next_item = item->next; + + // Perform logic on current item + strcpy( szNextMap, item->mapname ); + + ExtractCommandString( item->rulebuffer, szCommands ); + strcpy( szRules, item->rulebuffer ); + } + + if ( !IS_MAP_VALID(szNextMap) ) + { + strcpy( szNextMap, szFirstMapInList ); + } + + g_fGameOver = TRUE; + + ALERT( at_console, "CHANGE LEVEL: %s\n", szNextMap ); + if ( minplayers || maxplayers ) + { + ALERT( at_console, "PLAYER COUNT: min %i max %i current %i\n", minplayers, maxplayers, curplayers ); + } + if ( strlen( szRules ) > 0 ) + { + ALERT( at_console, "RULES: %s\n", szRules ); + } + + CHANGE_LEVEL( szNextMap, NULL ); + if ( strlen( szCommands ) > 0 ) + { + SERVER_COMMAND( szCommands ); + } +} + +#define MAX_MOTD_CHUNK 60 +#define MAX_MOTD_LENGTH 1536 // (MAX_MOTD_CHUNK * 4) + +void CHalfLifeMultiplay :: SendMOTDToClient( edict_t *client ) +{ + // read from the MOTD.txt file + int need_briefing = 1; + int length, char_count = 0; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( (char *)CVAR_GET_STRING( "motdfile" ), &length ); + + // send the server name + MESSAGE_BEGIN( MSG_ONE, gmsgServerName, NULL, client ); + WRITE_STRING( CVAR_GET_STRING("hostname") ); + MESSAGE_END(); + + // Send the message of the day + // read it chunk-by-chunk, and send it in parts + + while ( pFileList && *pFileList && char_count < MAX_MOTD_LENGTH ) + { + char chunk[MAX_MOTD_CHUNK+1]; + + need_briefing = 0; + + if ( strlen( pFileList ) < MAX_MOTD_CHUNK ) + { + strcpy( chunk, pFileList ); + } + else + { + strncpy( chunk, pFileList, MAX_MOTD_CHUNK ); + chunk[MAX_MOTD_CHUNK] = 0; // strncpy doesn't always append the null terminator + } + + char_count += strlen( chunk ); + if ( char_count < MAX_MOTD_LENGTH ) + pFileList = aFileList + char_count; + else + *pFileList = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgMOTD, NULL, client ); + WRITE_BYTE( *pFileList ? FALSE : TRUE ); // FALSE means there is still more message to come + WRITE_STRING( chunk ); + MESSAGE_END(); + } + + if(need_briefing) + { + MESSAGE_BEGIN(MSG_ONE,gmsgBriefing,NULL,client); + WRITE_BYTE(1); + MESSAGE_END(); + } + + + FREE_FILE( aFileList ); +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::ClearLevel( void ) +{ + // TODO: Remove guns, reset glass, etc +} + +#define SET_LASER( var, idx, val ) \ + var |= val << idx + +void CHalfLifeMultiplay::SetLaserBeam( int entindex, BOOL value ) +{ + if( value == TRUE ) + { + SET_LASER( m_iLaserOn, entindex, 1 ); + SET_LASER( m_iLaserOff, entindex, 0 ); + + } + else + { + SET_LASER( m_iLaserOff, entindex, 1 ); + SET_LASER( m_iLaserOn, entindex, 0 ); + } + +/* char sz[128]; + sprintf( sz, "Lasers: On: %i Off: %i\n", m_iLaserOn, m_iLaserOff ); + UTIL_SayTextAll( sz, UTIL_FindEntityByClassname( NULL, "player" ) );*/ +} + +void CHalfLifeMultiplay::SendLaserInfo() +{ +} \ No newline at end of file diff --git a/dlls/game_lastmanstanding.cpp b/dlls/game_lastmanstanding.cpp new file mode 100644 index 0000000..c814233 --- /dev/null +++ b/dlls/game_lastmanstanding.cpp @@ -0,0 +1,253 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// lastmanstanding_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "shake.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +#include "game.h" +#include "items.h" +#include "voice_gamemgr.h" +#include "thewastes.h" + +extern int gmsgScoreInfo; +extern int gmsgLmsStart; +extern void respawn(entvars_t *pev, BOOL fCopyCorpse); + +extern DLL_GLOBAL BOOL g_fGameOver; + +#define RESTART_TIME_LONG 10.0f +#define RESTART_TIME_SHORT 5.0f +#define DELAY_TIME 5.0f + +enum lms_state_e { + RS_CHECKING, + RS_BEGIN_MATCH, + RS_MATCH_IN_PROGRESS, + RS_END_MATCH, +}; + +CWastesLMS::CWastesLMS() : CHalfLifeMultiplay() +{ + m_bRoundInProgress = FALSE; + m_iRoundState = RS_CHECKING; + m_flNextStateCheck = gpGlobals->time + 30.0f; + m_flRoundDelay = 0.0f; + + // Validate cvars + if( CVAR_GET_FLOAT( "mp_lives" ) < 1.0f ) + CVAR_SET_FLOAT( "mp_lives", 1.0f ); +} + +void CWastesLMS::Think() +{ + CHalfLifeMultiplay::Think(); + + if( g_fGameOver ) + return; + + switch( m_iRoundState ) + { + case RS_CHECKING: + break; + case RS_BEGIN_MATCH: + break; + case RS_MATCH_IN_PROGRESS: + break; + case RS_END_MATCH: + break; + } + +/* // Round Delay Timer + if(m_flRoundDelay && gpGlobals->time >= m_flRoundDelay) + m_flRoundDelay = 0.0f; + + // Round is over; do not run code if game is doing a restart round check + // Do not run if no round is in progress. +#if 0 + if(m_bRoundInProgress && m_flRoundTimeLimit && !m_flNextStateCheck && gpGlobals->time >= m_flRoundTimeLimit) + { + UTIL_SayText("ROUND IS OVER O NO",UTIL_FindEntityByClassname(NULL,"player")); + m_flRoundTimeLimit = 0.0; + + StartRound(); + } +#endif + + // Game Timer, check it + if(m_flNextStateCheck && gpGlobals->time >= m_flNextStateCheck) + { + m_flNextStateCheck = gpGlobals->time + RESTART_TIME_SHORT; + + // Round in progress + if(m_bRoundInProgress) + { + int iPlayersLeft = GetActivePlayers(); + + // Should round be over? + if(iPlayersLeft < 2) + { + // Go through active players list, find the one left alive. + if(iPlayersLeft) + { + for(int i = 1;i <= gpGlobals->maxClients;i++) + { + CBasePlayer *pPlayer = (CBasePlayer*)UTIL_PlayerByIndex(i); + + if(pPlayer != NULL && pPlayer->IsAlive()) + { + pPlayer->pev->frags += pPlayer->m_iTempFrags; + pPlayer->m_iTempFrags = 0; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(pPlayer->edict()) ); + WRITE_SHORT( pPlayer->pev->frags ); + WRITE_SHORT( pPlayer->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( GetTeamIndex( pPlayer->m_szTeamName ) + 1 ); + MESSAGE_END(); + + break; + } + } + + UTIL_SayTextAll("ROUND OVER, HERE IS YOUR MONEY KTHX",UTIL_FindEntityByClassname(NULL,"player")); + } + else + UTIL_SayTextAll("NO ONE ALIVE WTS",UTIL_FindEntityByClassname(NULL,"observer")); + + StartRound(); + } + } + else if( GetActivePlayers() > 1 ) + StartRound(); + } + + CHalfLifeMultiplay::Think();*/ +} + +BOOL CWastesLMS::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + if(gpGlobals->time >= m_flRoundDelay && m_bRoundInProgress) + return CHalfLifeMultiplay::FPlayerCanTakeDamage(pPlayer,pAttacker); + return FALSE; +} + +void CWastesLMS::PlayerSpawn(CBasePlayer *pPlayer) +{ + if(!ShouldSpectate()) + { + CHalfLifeMultiplay::PlayerSpawn(pPlayer); + } + else + { + pPlayer->pev->health = 0; + pPlayer->StartObserver(pPlayer->pev->origin,Vector(0,0,0)); + } +} + +void CWastesLMS::PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor) +{ + if(m_bRoundInProgress) + { + CHalfLifeMultiplay::PlayerKilled(pVictim,pKiller,pInflictor); + } +} + +BOOL CWastesLMS::ShouldSpectate() +{ + if(m_bRoundInProgress) + return TRUE; + return FALSE; +} + +void CWastesLMS::EndMultiplayerGame() +{ + // Quit + m_bRoundInProgress = FALSE; + m_iRoundState = RS_CHECKING; + m_flNextStateCheck = 0.0; + + CHalfLifeMultiplay::EndMultiplayerGame(); +} + +int CWastesLMS::GetActivePlayers() +{ + int iRet = 0; + for(int i = 1;i <= gpGlobals->maxClients;i++) + { + CBasePlayer *pPlayer = (CBasePlayer*)UTIL_PlayerByIndex(i); + + if(pPlayer == NULL ) + continue; + + if(pPlayer->IsAlive()) + iRet++; + } +// char szText[1024]; +// sprintf( szText, "Active Players: %i", iRet ); +// UTIL_SayTextAll( szText, UTIL_FindEntityByClassname( NULL, "player" ) ); + return iRet; +} + +void CWastesLMS::StartRound() +{ + int lives = (int)CVAR_GET_FLOAT( "mp_lives" ); + + ClearLevel(); + + for( int i = 1;i <= gpGlobals->maxClients;i++ ) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex( i ); + + if( pEnt == NULL ) + continue; + + if( pEnt->IsPlayer() ) + { + CBasePlayer *pPlayer = (CBasePlayer*)pEnt; + + pPlayer->m_iLivesLeft = lives; + } + } +#if 0 + m_bRoundInProgress = FALSE; + ClearLevel(); + + // Go through each player, let them respawn, + // And start the round! :D + for(int i = 1;i <= gpGlobals->maxClients;i++) + { + CBasePlayer *pEnt = (CBasePlayer*)UTIL_PlayerByIndex(i); + if(pEnt == NULL || !pEnt->IsPlayer() || !pEnt->pev->netname) + continue; + + pEnt->RemoveAllItems(); + respawn(pEnt->pev,FALSE); + } + + m_flRoundDelay = gpGlobals->time + DELAY_TIME; // 3 seconds after round starts we can start inflicting pain on our enemies + m_bRoundInProgress = TRUE; + + MESSAGE_BEGIN( MSG_ALL, gmsgLmsStart ); + WRITE_COORD( DELAY_TIME ); + MESSAGE_END(); +#endif +} \ No newline at end of file diff --git a/dlls/game_singleplay.cpp b/dlls/game_singleplay.cpp new file mode 100644 index 0000000..693db06 --- /dev/null +++ b/dlls/game_singleplay.cpp @@ -0,0 +1,327 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +// +// singleplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "items.h" +#include "thewastes.h" + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +//========================================================= +//========================================================= +CHalfLifeRules::CHalfLifeRules( void ) +{ + RefreshSkillData(); +} + +//========================================================= +//========================================================= +void CHalfLifeRules::Think ( void ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsMultiplayer( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsDeathmatch ( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsCoOp( void ) +{ + return FALSE; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return TRUE; +} + +void CHalfLifeRules :: InitHUD( CBasePlayer *pl ) +{ +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: ClientDisconnected( edict_t *pClient ) +{ +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + // subtract off the speed at which a player is allowed to fall without being hurt, + // so damage will be based on speed beyond that, not the entire fall + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: AllowAutoTargetCrosshair( void ) +{ + return 0; +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerThink( CBasePlayer *pPlayer ) +{ +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeRules :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeRules :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeRules :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// Deathnotice +//========================================================= +void CHalfLifeRules::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeRules :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeRules :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + return -1; +} + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeRules :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeRules :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + return GR_WEAPON_RESPAWN_NO; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::ItemShouldRespawn( CItem *pItem ) +{ + return GR_ITEM_RESPAWN_NO; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeRules::FlItemRespawnTime( CItem *pItem ) +{ + return -1; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + return GR_AMMO_RESPAWN_NO; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return -1; +} + +//========================================================= +//========================================================= +Vector CHalfLifeRules::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlHealthChargerRechargeTime( void ) +{ + return 0;// don't recharge +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // why would a single player in half life need this? + return GR_NOTTEAMMATE; +} + +BOOL CHalfLifeRules :: FAllowMonsters( void ) +{ + return TRUE; +} diff --git a/dlls/game_teamplay.cpp b/dlls/game_teamplay.cpp new file mode 100644 index 0000000..e146887 --- /dev/null +++ b/dlls/game_teamplay.cpp @@ -0,0 +1,657 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "game.h" +#include "thewastes.h" + +static char team_names[MAX_TEAMS][MAX_TEAMNAME_LENGTH]; +static int team_scores[MAX_TEAMS]; +static int num_teams = 0; + +extern DLL_GLOBAL BOOL g_fGameOver; + +CHalfLifeTeamplay :: CHalfLifeTeamplay() +{ + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + + memset( team_names, 0, sizeof(team_names) ); + memset( team_scores, 0, sizeof(team_scores) ); + num_teams = 0; + + // Copy over the team from the server config + m_szTeamList[0] = 0; + + // Cache this because the team code doesn't want to deal with changing this in the middle of a game + strncpy( m_szTeamList, teamlist.string, TEAMPLAY_TEAMLISTLENGTH ); + + edict_t *pWorld = INDEXENT(0); + if ( pWorld && pWorld->v.team ) + { + if ( teamoverride.value ) + { + const char *pTeamList = STRING(pWorld->v.team); + if ( pTeamList && strlen(pTeamList) ) + { + strncpy( m_szTeamList, pTeamList, TEAMPLAY_TEAMLISTLENGTH ); + } + } + } + // Has the server set teams + if ( strlen( m_szTeamList ) ) + m_teamLimit = TRUE; + else + m_teamLimit = FALSE; + + RecountTeams(); +} + +extern cvar_t timeleft, fragsleft; + +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + +void CHalfLifeTeamplay :: Think ( void ) +{ + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + g_VoiceGameMgr.Update(gpGlobals->frametime); + + if ( g_fGameOver ) // someone else quit the game already + { + CHalfLifeMultiplay::Think(); + return; + } + + float flTimeLimit = CVAR_GET_FLOAT("mp_timelimit") * 60; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + float flFragLimit = fraglimit.value; + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any team is over the frag limit + for ( int i = 0; i < num_teams; i++ ) + { + if ( team_scores[i] >= flFragLimit ) + { + GoToIntermission(); + return; + } + + remain = flFragLimit - team_scores[i]; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + +//========================================================= +// ClientCommand +// the user has typed a command which is unrecognized by everything else; +// this check to see if the gamerules knows anything about the command +//========================================================= +BOOL CHalfLifeTeamplay :: ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + if ( FStrEq( pcmd, "menuselect" ) ) + { + if ( CMD_ARGC() < 2 ) + return TRUE; + + int slot = atoi( CMD_ARGV(1) ); + + // select the item from the current menu + + return TRUE; + } + + return FALSE; +} + +extern int gmsgGameMode; +extern int gmsgSayText; +extern int gmsgTeamInfo; +extern int gmsgTeamNames; +extern int gmsgScoreInfo; + +void CHalfLifeTeamplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 2 ); // game mode teamplay + MESSAGE_END(); +} + + +const char *CHalfLifeTeamplay::SetDefaultPlayerTeam( CBasePlayer *pPlayer ) +{ + // copy out the team name from the model + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + strncpy( pPlayer->m_szTeamName, mdls, TEAM_NAME_LENGTH ); + + RecountTeams(); + + // update the current player of the team he is joining + if ( pPlayer->m_szTeamName[0] == '\0' || !IsValidTeam( pPlayer->m_szTeamName ) || defaultteam.value ) + { + const char *pTeamName = NULL; + + if ( defaultteam.value ) + { + pTeamName = team_names[0]; + } + else + { + pTeamName = TeamWithFewestPlayers(); + } + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + } + + return pPlayer->m_szTeamName; +} + + +//========================================================= +// InitHUD +//========================================================= +void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer ) +{ + int i; + + SetDefaultPlayerTeam( pPlayer ); + CHalfLifeMultiplay::InitHUD( pPlayer ); + + // Send down the team names + MESSAGE_BEGIN( MSG_ONE, gmsgTeamNames, NULL, pPlayer->edict() ); + WRITE_BYTE( num_teams ); + for ( i = 0; i < num_teams; i++ ) + { + WRITE_STRING( team_names[ i ] ); + } + MESSAGE_END(); + + RecountTeams(); + + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + // update the current player of the team he is joining + char text[1024]; + if ( !strcmp( mdls, pPlayer->m_szTeamName ) ) + { + sprintf( text, "* you are on team \'%s\'\n", pPlayer->m_szTeamName ); + } + else + { + sprintf( text, "* assigned to team %s\n", pPlayer->m_szTeamName ); + } + + ChangePlayerTeam( pPlayer, pPlayer->m_szTeamName, FALSE, FALSE ); + UTIL_SayText( text, pPlayer ); + int clientIndex = pPlayer->entindex(); + RecountTeams(); + // update this player with all the other players team info + // loop through all active players and send their team info to the new client + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + if ( plr && IsValidTeam( plr->TeamID() ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgTeamInfo, NULL, pPlayer->edict() ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + } + } +} + + +void CHalfLifeTeamplay::ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) +{ + int damageFlags = DMG_GENERIC; + int clientIndex = pPlayer->entindex(); + + if ( !bGib ) + { + damageFlags |= DMG_NEVERGIB; + } + else + { + damageFlags |= DMG_ALWAYSGIB; + } + + if ( bKill ) + { + // kill the player, remove a death, and let them start on the new team + m_DisableDeathMessages = TRUE; + m_DisableDeathPenalty = TRUE; + + entvars_t *pevWorld = VARS( INDEXENT(0) ); + pPlayer->TakeDamage( pevWorld, pevWorld, 900, damageFlags ); + + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + } + + // copy out the team name from the model + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + + // notify everyone's HUD of the team change + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( clientIndex ); + WRITE_STRING( pPlayer->m_szTeamName ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( clientIndex ); + WRITE_SHORT( pPlayer->pev->frags ); + WRITE_SHORT( pPlayer->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( g_pGameRules->GetTeamIndex( pPlayer->m_szTeamName ) + 1 ); + MESSAGE_END(); +} + + +//========================================================= +// ClientUserInfoChanged +//========================================================= +void CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) +{ + char text[1024]; + + // prevent skin/color/model changes + char *mdls = g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ); + + if ( !stricmp( mdls, pPlayer->m_szTeamName ) ) + return; + + if ( defaultteam.value ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + sprintf( text, "* Not allowed to change teams in this game!\n" ); + UTIL_SayText( text, pPlayer ); + return; + } + + if ( defaultteam.value || !IsValidTeam( mdls ) ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + sprintf( text, "* Can't change team to \'%s\'\n", mdls ); + UTIL_SayText( text, pPlayer ); + sprintf( text, "* Server limits teams to \'%s\'\n", m_szTeamList ); + UTIL_SayText( text, pPlayer ); + return; + } + // notify everyone of the team change + sprintf( text, "* %s has changed to team \'%s\'\n", STRING(pPlayer->pev->netname), mdls ); + UTIL_SayTextAll( text, pPlayer ); + + UTIL_LogPrintf( "\"%s<%i><%u><%s>\" joined team \"%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERWONID( pPlayer->edict() ), + pPlayer->m_szTeamName, + mdls ); + + ChangePlayerTeam( pPlayer, mdls, TRUE, TRUE ); + // recound stuff + RecountTeams( TRUE ); +} + +extern int gmsgDeathMsg; + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeTeamplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor) +{ + if ( m_DisableDeathMessages ) + return; + + if ( pVictim && pKiller && pKiller->flags & FL_CLIENT ) + { + CBasePlayer *pk = (CBasePlayer*) CBaseEntity::Instance( pKiller ); + CWasteWeapon *pWeapon = (CWasteWeapon*)pk->m_pActiveItem; + + if ( pk ) + { + if ( (pk != pVictim) && (PlayerRelationship( pVictim, pk ) == GR_TEAMMATE) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( ENTINDEX(ENT(pKiller)) ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_BYTE( pVictim->pev->vuser2.y ); // head shot ? + WRITE_STRING( "teammate" ); // flag this as a teammate kill + + if(pk != NULL) + { + if(pVictim->pev->vuser2.y == 2) + { + char szBledString[128]; + + switch(RANDOM_LONG(0,1)) + { + case 0: + strcpy(szBledString,"%s bled %s, real slow"); + break; + case 1: + strcpy(szBledString,"%s opened up %s like a leaky faucet"); + break; + } + + WRITE_STRING( szBledString ); + } + else if(pk->HasWeapons() && pWeapon != NULL) + WRITE_STRING( pWeapon->szGetDeathNoticeString() ); + else + WRITE_STRING( "%s killed %s with... nothing :(" ); + } + MESSAGE_END(); + return; + } + } + } + + CHalfLifeMultiplay::DeathNotice( pVictim, pKiller, pevInflictor ); +} + +//========================================================= +//========================================================= +void CHalfLifeTeamplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor) +{ + if ( !m_DisableDeathPenalty ) + { + CHalfLifeMultiplay::PlayerKilled( pVictim, pKiller, pInflictor); + RecountTeams(); + } +} + + +//========================================================= +// IsTeamplay +//========================================================= +BOOL CHalfLifeTeamplay::IsTeamplay( void ) +{ + return TRUE; +} + +BOOL CHalfLifeTeamplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + if ( pAttacker && PlayerRelationship( pPlayer, pAttacker ) == GR_TEAMMATE ) + { + // my teammate hit me. + if ( (friendlyfire.value == 0) && (pAttacker != pPlayer) ) + { + // friendly fire is off, and this hit came from someone other than myself, then don't get hurt + return FALSE; + } + } + + return CHalfLifeMultiplay::FPlayerCanTakeDamage( pPlayer, pAttacker ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life multiplay has a simple concept of Player Relationships. + // you are either on another player's team, or you are not. + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() ) + return GR_NOTTEAMMATE; + + if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) + { + return GR_TEAMMATE; + } + + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeTeamplay::ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) +{ + // always autoaim, unless target is a teammate + CBaseEntity *pTgt = CBaseEntity::Instance( target ); + if ( pTgt && pTgt->IsPlayer() ) + { + if ( PlayerRelationship( pPlayer, pTgt ) == GR_TEAMMATE ) + return FALSE; // don't autoaim at teammates + } + + return CHalfLifeMultiplay::ShouldAutoAim( pPlayer, target ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + if ( !pKilled ) + return 0; + + if ( !pAttacker ) + return 1; + + if ( pAttacker != pKilled && PlayerRelationship( pAttacker, pKilled ) == GR_TEAMMATE ) + return -1; + + return 1; +} + +//========================================================= +//========================================================= +const char *CHalfLifeTeamplay::GetTeamID( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL || pEntity->pev == NULL ) + return ""; + + // return their team name + return pEntity->TeamID(); +} + + +int CHalfLifeTeamplay::GetTeamIndex( const char *pTeamName ) +{ + if ( pTeamName && *pTeamName != 0 ) + { + // try to find existing team + for ( int tm = 0; tm < num_teams; tm++ ) + { + if ( !stricmp( team_names[tm], pTeamName ) ) + return tm; + } + } + + return -1; // No match +} + + +const char *CHalfLifeTeamplay::GetIndexedTeamName( int teamIndex ) +{ + if ( teamIndex < 0 || teamIndex >= num_teams ) + return ""; + + return team_names[ teamIndex ]; +} + + +BOOL CHalfLifeTeamplay::IsValidTeam( const char *pTeamName ) +{ + if ( !m_teamLimit ) // Any team is valid if the teamlist isn't set + return TRUE; + + return ( GetTeamIndex( pTeamName ) != -1 ) ? TRUE : FALSE; +} + +const char *CHalfLifeTeamplay::TeamWithFewestPlayers( void ) +{ + int i; + int minPlayers = MAX_TEAMS; + int teamCount[ MAX_TEAMS ]; + char *pTeamName = NULL; + + memset( teamCount, 0, MAX_TEAMS * sizeof(int) ); + + // loop through all clients, count number of players on each team + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + int team = GetTeamIndex( plr->TeamID() ); + if ( team >= 0 ) + teamCount[team] ++; + } + } + + // Find team with least players + for ( i = 0; i < num_teams; i++ ) + { + if ( teamCount[i] < minPlayers ) + { + minPlayers = teamCount[i]; + pTeamName = team_names[i]; + } + } + + return pTeamName; +} + + +//========================================================= +//========================================================= +void CHalfLifeTeamplay::RecountTeams( bool bResendInfo ) +{ + char *pName; + char teamlist[TEAMPLAY_TEAMLISTLENGTH]; + + // loop through all teams, recounting everything + num_teams = 0; + + // Copy all of the teams from the teamlist + // make a copy because strtok is destructive + strcpy( teamlist, m_szTeamList ); + pName = teamlist; + pName = strtok( pName, ";" ); + while ( pName != NULL && *pName ) + { + if ( GetTeamIndex( pName ) < 0 ) + { + strcpy( team_names[num_teams], pName ); + num_teams++; + } + pName = strtok( NULL, ";" ); + } + + if ( num_teams < 2 ) + { + num_teams = 0; + m_teamLimit = FALSE; + } + + // Sanity check + memset( team_scores, 0, sizeof(team_scores) ); + + // loop through all clients + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + const char *pTeamName = plr->TeamID(); + // try add to existing team + int tm = GetTeamIndex( pTeamName ); + + if ( tm < 0 ) // no team match found + { + if ( !m_teamLimit ) + { + // add to new team + tm = num_teams; + num_teams++; + team_scores[tm] = 0; + strncpy( team_names[tm], pTeamName, MAX_TEAMNAME_LENGTH ); + } + } + + if ( tm >= 0 ) + { + team_scores[tm] += plr->pev->frags; + } + + if ( bResendInfo ) //Someone's info changed, let's send the team info again. + { + if ( plr && IsValidTeam( plr->TeamID() ) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo, NULL ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + } + } + } + } +} diff --git a/dlls/gamerules.cpp b/dlls/gamerules.cpp index a9b4831..31a31ad 100644 --- a/dlls/gamerules.cpp +++ b/dlls/gamerules.cpp @@ -23,8 +23,8 @@ #include "weapons.h" #include "gamerules.h" #include "teamplay_gamerules.h" -#include "skill.h" #include "game.h" +#include "pm_shared.h" extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); @@ -33,7 +33,7 @@ extern DLL_GLOBAL BOOL g_fGameOver; extern int gmsgDeathMsg; // client dll messages extern int gmsgMOTD; -int g_teamplay = 0; +int g_gametype = 0; //========================================================= //========================================================= @@ -112,196 +112,6 @@ BOOL CGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeap //========================================================= void CGameRules::RefreshSkillData ( void ) { - int iSkill; - - iSkill = (int)CVAR_GET_FLOAT("skill"); - g_iSkillLevel = iSkill; - - if ( iSkill < 1 ) - { - iSkill = 1; - } - else if ( iSkill > 3 ) - { - iSkill = 3; - } - - gSkillData.iSkillLevel = iSkill; - - ALERT ( at_console, "\nGAME SKILL LEVEL:%d\n",iSkill ); - - //Agrunt - gSkillData.agruntHealth = GetSkillCvar( "sk_agrunt_health" ); - gSkillData.agruntDmgPunch = GetSkillCvar( "sk_agrunt_dmg_punch"); - - // Apache - gSkillData.apacheHealth = GetSkillCvar( "sk_apache_health"); - - // Barney - gSkillData.barneyHealth = GetSkillCvar( "sk_barney_health"); - - // Big Momma - gSkillData.bigmommaHealthFactor = GetSkillCvar( "sk_bigmomma_health_factor" ); - gSkillData.bigmommaDmgSlash = GetSkillCvar( "sk_bigmomma_dmg_slash" ); - gSkillData.bigmommaDmgBlast = GetSkillCvar( "sk_bigmomma_dmg_blast" ); - gSkillData.bigmommaRadiusBlast = GetSkillCvar( "sk_bigmomma_radius_blast" ); - - // Bullsquid - gSkillData.bullsquidHealth = GetSkillCvar( "sk_bullsquid_health"); - gSkillData.bullsquidDmgBite = GetSkillCvar( "sk_bullsquid_dmg_bite"); - gSkillData.bullsquidDmgWhip = GetSkillCvar( "sk_bullsquid_dmg_whip"); - gSkillData.bullsquidDmgSpit = GetSkillCvar( "sk_bullsquid_dmg_spit"); - - // Gargantua - gSkillData.gargantuaHealth = GetSkillCvar( "sk_gargantua_health"); - gSkillData.gargantuaDmgSlash = GetSkillCvar( "sk_gargantua_dmg_slash"); - gSkillData.gargantuaDmgFire = GetSkillCvar( "sk_gargantua_dmg_fire"); - gSkillData.gargantuaDmgStomp = GetSkillCvar( "sk_gargantua_dmg_stomp"); - - // Hassassin - gSkillData.hassassinHealth = GetSkillCvar( "sk_hassassin_health"); - - // Headcrab - gSkillData.headcrabHealth = GetSkillCvar( "sk_headcrab_health"); - gSkillData.headcrabDmgBite = GetSkillCvar( "sk_headcrab_dmg_bite"); - - // Hgrunt - gSkillData.hgruntHealth = GetSkillCvar( "sk_hgrunt_health"); - gSkillData.hgruntDmgKick = GetSkillCvar( "sk_hgrunt_kick"); - gSkillData.hgruntShotgunPellets = GetSkillCvar( "sk_hgrunt_pellets"); - gSkillData.hgruntGrenadeSpeed = GetSkillCvar( "sk_hgrunt_gspeed"); - - // Houndeye - gSkillData.houndeyeHealth = GetSkillCvar( "sk_houndeye_health"); - gSkillData.houndeyeDmgBlast = GetSkillCvar( "sk_houndeye_dmg_blast"); - - // ISlave - gSkillData.slaveHealth = GetSkillCvar( "sk_islave_health"); - gSkillData.slaveDmgClaw = GetSkillCvar( "sk_islave_dmg_claw"); - gSkillData.slaveDmgClawrake = GetSkillCvar( "sk_islave_dmg_clawrake"); - gSkillData.slaveDmgZap = GetSkillCvar( "sk_islave_dmg_zap"); - - // Icthyosaur - gSkillData.ichthyosaurHealth = GetSkillCvar( "sk_ichthyosaur_health"); - gSkillData.ichthyosaurDmgShake = GetSkillCvar( "sk_ichthyosaur_shake"); - - // Leech - gSkillData.leechHealth = GetSkillCvar( "sk_leech_health"); - - gSkillData.leechDmgBite = GetSkillCvar( "sk_leech_dmg_bite"); - - // Controller - gSkillData.controllerHealth = GetSkillCvar( "sk_controller_health"); - gSkillData.controllerDmgZap = GetSkillCvar( "sk_controller_dmgzap"); - gSkillData.controllerSpeedBall = GetSkillCvar( "sk_controller_speedball"); - gSkillData.controllerDmgBall = GetSkillCvar( "sk_controller_dmgball"); - - // Nihilanth - gSkillData.nihilanthHealth = GetSkillCvar( "sk_nihilanth_health"); - gSkillData.nihilanthZap = GetSkillCvar( "sk_nihilanth_zap"); - - // Scientist - gSkillData.scientistHealth = GetSkillCvar( "sk_scientist_health"); - - // Snark - gSkillData.snarkHealth = GetSkillCvar( "sk_snark_health"); - gSkillData.snarkDmgBite = GetSkillCvar( "sk_snark_dmg_bite"); - gSkillData.snarkDmgPop = GetSkillCvar( "sk_snark_dmg_pop"); - - // Zombie - gSkillData.zombieHealth = GetSkillCvar( "sk_zombie_health"); - gSkillData.zombieDmgOneSlash = GetSkillCvar( "sk_zombie_dmg_one_slash"); - gSkillData.zombieDmgBothSlash = GetSkillCvar( "sk_zombie_dmg_both_slash"); - - //Turret - gSkillData.turretHealth = GetSkillCvar( "sk_turret_health"); - - // MiniTurret - gSkillData.miniturretHealth = GetSkillCvar( "sk_miniturret_health"); - - // Sentry Turret - gSkillData.sentryHealth = GetSkillCvar( "sk_sentry_health"); - -// PLAYER WEAPONS - - // Crowbar whack - gSkillData.plrDmgCrowbar = GetSkillCvar( "sk_plr_crowbar"); - - // Glock Round - gSkillData.plrDmg9MM = GetSkillCvar( "sk_plr_9mm_bullet"); - - // 357 Round - gSkillData.plrDmg357 = GetSkillCvar( "sk_plr_357_bullet"); - - // MP5 Round - gSkillData.plrDmgMP5 = GetSkillCvar( "sk_plr_9mmAR_bullet"); - - // M203 grenade - gSkillData.plrDmgM203Grenade = GetSkillCvar( "sk_plr_9mmAR_grenade"); - - // Shotgun buckshot - gSkillData.plrDmgBuckshot = GetSkillCvar( "sk_plr_buckshot"); - - // Crossbow - gSkillData.plrDmgCrossbowClient = GetSkillCvar( "sk_plr_xbow_bolt_client"); - gSkillData.plrDmgCrossbowMonster = GetSkillCvar( "sk_plr_xbow_bolt_monster"); - - // RPG - gSkillData.plrDmgRPG = GetSkillCvar( "sk_plr_rpg"); - - // Gauss gun - gSkillData.plrDmgGauss = GetSkillCvar( "sk_plr_gauss"); - - // Egon Gun - gSkillData.plrDmgEgonNarrow = GetSkillCvar( "sk_plr_egon_narrow"); - gSkillData.plrDmgEgonWide = GetSkillCvar( "sk_plr_egon_wide"); - - // Hand Grendade - gSkillData.plrDmgHandGrenade = GetSkillCvar( "sk_plr_hand_grenade"); - - // Satchel Charge - gSkillData.plrDmgSatchel = GetSkillCvar( "sk_plr_satchel"); - - // Tripmine - gSkillData.plrDmgTripmine = GetSkillCvar( "sk_plr_tripmine"); - - // MONSTER WEAPONS - gSkillData.monDmg12MM = GetSkillCvar( "sk_12mm_bullet"); - gSkillData.monDmgMP5 = GetSkillCvar ("sk_9mmAR_bullet" ); - gSkillData.monDmg9MM = GetSkillCvar( "sk_9mm_bullet"); - - // MONSTER HORNET - gSkillData.monDmgHornet = GetSkillCvar( "sk_hornet_dmg"); - - // PLAYER HORNET -// Up to this point, player hornet damage and monster hornet damage were both using -// monDmgHornet to determine how much damage to do. In tuning the hivehand, we now need -// to separate player damage and monster hivehand damage. Since it's so late in the project, we've -// added plrDmgHornet to the SKILLDATA struct, but not to the engine CVar list, so it's inaccesible -// via SKILLS.CFG. Any player hivehand tuning must take place in the code. (sjb) - gSkillData.plrDmgHornet = 7; - - - // HEALTH/CHARGE - gSkillData.suitchargerCapacity = GetSkillCvar( "sk_suitcharger" ); - gSkillData.batteryCapacity = GetSkillCvar( "sk_battery" ); - gSkillData.healthchargerCapacity = GetSkillCvar ( "sk_healthcharger" ); - gSkillData.healthkitCapacity = GetSkillCvar ( "sk_healthkit" ); - gSkillData.scientistHeal = GetSkillCvar ( "sk_scientist_heal" ); - - // monster damage adj - gSkillData.monHead = GetSkillCvar( "sk_monster_head" ); - gSkillData.monChest = GetSkillCvar( "sk_monster_chest" ); - gSkillData.monStomach = GetSkillCvar( "sk_monster_stomach" ); - gSkillData.monLeg = GetSkillCvar( "sk_monster_leg" ); - gSkillData.monArm = GetSkillCvar( "sk_monster_arm" ); - - // player damage adj - gSkillData.plrHead = GetSkillCvar( "sk_player_head" ); - gSkillData.plrChest = GetSkillCvar( "sk_player_chest" ); - gSkillData.plrStomach = GetSkillCvar( "sk_player_stomach" ); - gSkillData.plrLeg = GetSkillCvar( "sk_player_leg" ); - gSkillData.plrArm = GetSkillCvar( "sk_player_arm" ); } //========================================================= @@ -316,29 +126,24 @@ CGameRules *InstallGameRules( void ) if ( !gpGlobals->deathmatch ) { // generic half-life - g_teamplay = 0; + g_gametype = 0; return new CHalfLifeRules; } else { - if ( teamplay.value > 0 ) + switch((int)CVAR_GET_FLOAT("mp_gametype")) { - // teamplay - - g_teamplay = 1; - return new CHalfLifeTeamplay; - } - if ((int)gpGlobals->deathmatch == 1) - { - // vanilla deathmatch - g_teamplay = 0; - return new CHalfLifeMultiplay; - } - else - { - // vanilla deathmatch?? - g_teamplay = 0; + default: + case 0: + g_gametype = 0; return new CHalfLifeMultiplay; + // TODO: Change this out later :) +/* case 1: + g_gametype = 1; + return new CWastesLMS; + case 2: + g_gametype = 2; + return new CHalfLifeTeamplay;*/ } } } diff --git a/dlls/gamerules.h b/dlls/gamerules.h index 9d97f87..13e104b 100644 --- a/dlls/gamerules.h +++ b/dlls/gamerules.h @@ -15,6 +15,7 @@ //========================================================= // GameRules //========================================================= +#include "tw_common.h" //#include "weapons.h" //#include "items.h" @@ -72,7 +73,7 @@ public: virtual BOOL IsDeathmatch( void ) = 0;//is this a deathmatch game? virtual BOOL IsTeamplay( void ) { return FALSE; };// is this deathmatch game being played with team rules? virtual BOOL IsCoOp( void ) = 0;// is this a coop game? - virtual const char *GetGameDescription( void ) { return "Half-Life"; } // this is the game name that gets seen in the server browser + virtual const char *GetGameDescription( void ) { return "The Wastes"; } // this is the game name that gets seen in the server browser // Client connection/disconnection virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) = 0;// a client just connected to the server (player hasn't spawned yet) @@ -84,6 +85,7 @@ public: virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ) = 0;// this client just hit the ground after a fall. How much damage? virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) {return TRUE;};// can this player take damage from this attacker? virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) { return TRUE; } + virtual float ModifyTiltedDamage(CBaseEntity *pAttacker,CBaseEntity *pVictim,float flCurDamage,int iDamageType){ return flCurDamage; } // Client spawn/respawn control virtual void PlayerSpawn( CBasePlayer *pPlayer ) = 0;// called by CBasePlayer::Spawn just before releasing player into the game @@ -98,7 +100,7 @@ public: // Client kills/scoring virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) = 0;// how many points do I award whoever kills this player? - virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) = 0;// Called each time a player dies + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor) = 0;// Called each time a player dies virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor )= 0;// Call this from within a GameRules class to report an obituary. // Weapon retrieval virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? @@ -112,7 +114,7 @@ public: // Item retrieval virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// is this player allowed to take this item? - virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// call each time a player picks up an item (battery, healthkit, longjump) + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// call each time a player picks up an item (healthkit, longjump) // Item spawn/respawn control virtual int ItemShouldRespawn( CItem *pItem ) = 0;// Should this item respawn? @@ -157,6 +159,20 @@ public: // Immediately end a multiplayer game virtual void EndMultiplayerGame( void ) {} + +// Spectator mode + virtual BOOL ShouldSpectate(){ return FALSE; } + +// Inventory rules + virtual BOOL AcceptInventory( playeritem_t *item ) + { + if( item->category != CAT_UNIQUE ) + return TRUE; + return FALSE; + } + +// Laser beam + virtual void SetLaserBeam( int entindex, BOOL value ){} }; extern CGameRules *InstallGameRules( void ); @@ -282,6 +298,7 @@ public: // Client damage rules virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + virtual float ModifyTiltedDamage(CBaseEntity *pAttacker,CBaseEntity *pVictim,float flCurDamage,int iDamageType); virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); // Client spawn/respawn control @@ -291,13 +308,13 @@ public: virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); - virtual BOOL AllowAutoTargetCrosshair( void ); + virtual BOOL AllowAutoTargetCrosshair( void ){ return FALSE; } virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); // Client kills/scoring virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); - virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor); // Weapon retrieval virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); @@ -349,12 +366,61 @@ public: // Immediately end a multiplayer game virtual void EndMultiplayerGame( void ) { GoToIntermission(); } + virtual const char *GetGameDescription( void ) { return "Anarchy"; } + + virtual void ClearLevel( void ); + +// Laserbeam + virtual void SetLaserBeam( int entindex, BOOL value ); + virtual void SendLaserInfo(); protected: virtual void ChangeLevel( void ); virtual void GoToIntermission( void ); float m_flIntermissionEndTime; + float m_flWeaponAllowCheck; BOOL m_iEndIntermissionButtonHit; void SendMOTDToClient( edict_t *client ); + + unsigned int m_iLaserOn; + unsigned int m_iLaserOff; +}; + +//========================================================= +// CWastesLMS - Last Man Standing +//========================================================= +class CWastesLMS : public CHalfLifeMultiplay +{ +private: + BOOL m_bRoundInProgress; + int m_iRoundState; + float m_flNextStateCheck; + float m_flRoundDelay; // While this is active, do not inflict damage ;| + float m_flRoundEndTime; + + int GetActivePlayers(); + void StartRound(); +public: + CWastesLMS(); + + void Think( void ); + +// Functions to verify the single/multiplayer status of a game + const char *GetGameDescription( void ) { return "Survival"; } + +// Client spawn/respawn control + void PlayerSpawn(CBasePlayer *pPlayer); + +// Client damage rules + BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + +// Client kills/scoring + void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor); + +// Spectator mode + BOOL ShouldSpectate(); + +// Immediately end a multiplayer game + void EndMultiplayerGame( void ); }; extern DLL_GLOBAL CGameRules* g_pGameRules; diff --git a/dlls/ggrenade.cpp b/dlls/ggrenade.cpp index 9c52a0f..c72dcbc 100644 --- a/dlls/ggrenade.cpp +++ b/dlls/ggrenade.cpp @@ -340,7 +340,6 @@ void CGrenade :: TumbleThink( void ) } } - void CGrenade:: Spawn( void ) { pev->movetype = MOVETYPE_BOUNCE; @@ -355,7 +354,6 @@ void CGrenade:: Spawn( void ) m_fRegisteredSound = FALSE; } - CGrenade *CGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) { CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); @@ -377,7 +375,7 @@ CGrenade *CGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector v // Explode on contact pGrenade->SetTouch( ExplodeTouch ); - pGrenade->pev->dmg = gSkillData.plrDmgM203Grenade; + pGrenade->pev->dmg = 50; return pGrenade; } diff --git a/dlls/h_cycler.cpp b/dlls/h_cycler.cpp index 2f43d41..7e529cb 100644 --- a/dlls/h_cycler.cpp +++ b/dlls/h_cycler.cpp @@ -67,7 +67,10 @@ IMPLEMENT_SAVERESTORE( CCycler, CBaseMonster ); class CGenericCycler : public CCycler { public: - void Spawn( void ) { GenericCyclerSpawn( (char *)STRING(pev->model), Vector(-16, -16, 0), Vector(16, 16, 72) ); } + void Spawn( void ) + { + GenericCyclerSpawn( (char *)STRING(pev->model), Vector(-16, -16, 0), Vector(16, 16, 72) ); + } }; LINK_ENTITY_TO_CLASS( cycler, CGenericCycler ); @@ -111,13 +114,13 @@ void CCycler :: GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax) UTIL_SetSize(pev, vecMin, vecMax); } - void CCycler :: Spawn( ) { InitBoneControllers(); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_NONE; - pev->takedamage = DAMAGE_YES; +// pev->takedamage = DAMAGE_YES; + pev->takedamage = DAMAGE_NO; pev->effects = 0; pev->health = 80000;// no cycler should die pev->yaw_speed = 5; diff --git a/dlls/h_export.cpp b/dlls/h_export.cpp index f29d0bf..52ce3ad 100644 --- a/dlls/h_export.cpp +++ b/dlls/h_export.cpp @@ -29,7 +29,6 @@ enginefuncs_t g_engfuncs; globalvars_t *gpGlobals; - #ifdef _WIN32 // Required DLL entry point diff --git a/dlls/hl.dsp b/dlls/hl.dsp new file mode 100644 index 0000000..cdb120c --- /dev/null +++ b/dlls/hl.dsp @@ -0,0 +1,583 @@ +# Microsoft Developer Studio Project File - Name="hl" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=hl - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "hl.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "hl.mak" CFG="hl - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "hl - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "hl - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "hl - Win32 Profile" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "hl - Win32 Release Crippled" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/GoldSrc/dlls", ELEBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "hl - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\obj\Releasehl" +# PROP Intermediate_Dir "..\obj\Releasehl" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MT /W3 /Zi /O2 /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /Fr /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /def:".\thewastes.def" /out:"../../dlls/thewastes.dll" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\hl___Win" +# PROP BASE Intermediate_Dir ".\hl___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\obj\debughl" +# PROP Intermediate_Dir "..\obj\debughl" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MTd /W3 /Gm /ZI /Od /I "..\dlls" /I "..\engine" /I "..\common" /I "..\game_shared" /I "..\pm_shared" /I "..\\" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /i "..\engine" /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 user32.lib advapi32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /def:".\thewastes.def" /out:"../../dlls/thewastes.dll" /implib:".\Debug\hl.lib" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "hl - Win32 Profile" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\hl___Win" +# PROP BASE Intermediate_Dir ".\hl___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\Profilehl" +# PROP Intermediate_Dir ".\Profilehl" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MT /W3 /GX /Zi /O2 /I "..\engine" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /YX /c +# SUBTRACT BASE CPP /Fr +# ADD CPP /nologo /G5 /MT /W3 /Zi /O2 /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /YX /FD /c +# SUBTRACT CPP /Fr +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /debug /machine:I386 /def:".\hl.def" +# SUBTRACT BASE LINK32 /profile +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /profile /debug /machine:I386 /def:".\thewastes.def" + +!ELSEIF "$(CFG)" == "hl - Win32 Release Crippled" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "hl___Win32_Release_Crippled" +# PROP BASE Intermediate_Dir "hl___Win32_Release_Crippled" +# PROP BASE Ignore_Export_Lib 1 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\obj\Releasehl_Crippled" +# PROP Intermediate_Dir "..\obj\Releasehl_Crippled" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MT /W3 /Zi /O2 /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /D "TW_BETA" /Fr /YX /FD /c +# ADD CPP /nologo /G5 /MT /W3 /Zi /O2 /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /D "TW_BETA" /D "TW_CRIPPLED" /Fr /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /def:".\thewastes.def" /out:"../../dlls/thewastes.dll" +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /def:".\thewastes.def" /out:"../../dlls/thewastes.dll" +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "hl - Win32 Release" +# Name "hl - Win32 Debug" +# Name "hl - Win32 Profile" +# Name "hl - Win32 Release Crippled" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\animating.cpp +# End Source File +# Begin Source File + +SOURCE=.\animation.cpp +# End Source File +# Begin Source File + +SOURCE=.\bmodels.cpp +# End Source File +# Begin Source File + +SOURCE=.\buttons.cpp +# End Source File +# Begin Source File + +SOURCE=.\cbase.cpp +# End Source File +# Begin Source File + +SOURCE=.\client.cpp +# End Source File +# Begin Source File + +SOURCE=.\combat.cpp +# End Source File +# Begin Source File + +SOURCE=.\doors.cpp +# End Source File +# Begin Source File + +SOURCE=.\effects.cpp +# End Source File +# Begin Source File + +SOURCE=.\explode.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_break.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_tank.cpp +# End Source File +# Begin Source File + +SOURCE=.\game.cpp +# End Source File +# Begin Source File + +SOURCE=.\game_deathmatch.cpp +# End Source File +# Begin Source File + +SOURCE=.\game_lastmanstanding.cpp +# End Source File +# Begin Source File + +SOURCE=.\game_singleplay.cpp +# End Source File +# Begin Source File + +SOURCE=.\game_teamplay.cpp +# End Source File +# Begin Source File + +SOURCE=.\gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\ggrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\globals.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_ai.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_cycler.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_export.cpp +# End Source File +# Begin Source File + +SOURCE=.\items.cpp +# End Source File +# Begin Source File + +SOURCE=.\lights.cpp +# End Source File +# Begin Source File + +SOURCE=.\maprules.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsters.cpp +# End Source File +# Begin Source File + +SOURCE=.\mortar.cpp +# End Source File +# Begin Source File + +SOURCE=.\nodes.cpp +# End Source File +# Begin Source File + +SOURCE=.\observer.cpp +# End Source File +# Begin Source File + +SOURCE=.\pathcorner.cpp +# End Source File +# Begin Source File + +SOURCE=.\plane.cpp +# End Source File +# Begin Source File + +SOURCE=.\plats.cpp +# End Source File +# Begin Source File + +SOURCE=.\player.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_math.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.c +# End Source File +# Begin Source File + +SOURCE=.\schedule.cpp +# End Source File +# Begin Source File + +SOURCE=.\scripted.cpp +# End Source File +# Begin Source File + +SOURCE=.\skill.cpp +# End Source File +# Begin Source File + +SOURCE=.\sound.cpp +# End Source File +# Begin Source File + +SOURCE=.\soundent.cpp +# End Source File +# Begin Source File + +SOURCE=.\spectator.cpp +# End Source File +# Begin Source File + +SOURCE=.\subs.cpp +# End Source File +# Begin Source File + +SOURCE=.\thewastes.cpp +# End Source File +# Begin Source File + +SOURCE=.\triggers.cpp +# End Source File +# Begin Source File + +SOURCE=.\turret.cpp +# End Source File +# Begin Source File + +SOURCE=.\wpn_shared\tw_akimbos.cpp +# End Source File +# Begin Source File + +SOURCE=.\wpn_shared\tw_automatics.cpp +# End Source File +# Begin Source File + +SOURCE=.\wpn_shared\tw_explosives.cpp +# End Source File +# Begin Source File + +SOURCE=.\wpn_shared\tw_melee.cpp +# End Source File +# Begin Source File + +SOURCE=.\wpn_shared\tw_shotguns.cpp +# End Source File +# Begin Source File + +SOURCE=.\wpn_shared\tw_sidearms.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_gamemgr.cpp +# End Source File +# Begin Source File + +SOURCE=.\weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\world.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\activity.h +# End Source File +# Begin Source File + +SOURCE=.\activitymap.h +# End Source File +# Begin Source File + +SOURCE=.\animation.h +# End Source File +# Begin Source File + +SOURCE=.\basemonster.h +# End Source File +# Begin Source File + +SOURCE=.\cbase.h +# End Source File +# Begin Source File + +SOURCE=.\cdll_dll.h +# End Source File +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\decals.h +# End Source File +# Begin Source File + +SOURCE=.\doors.h +# End Source File +# Begin Source File + +SOURCE=.\effects.h +# End Source File +# Begin Source File + +SOURCE=..\engine\eiface.h +# End Source File +# Begin Source File + +SOURCE=.\enginecallback.h +# End Source File +# Begin Source File + +SOURCE=.\explode.h +# End Source File +# Begin Source File + +SOURCE=.\extdll.h +# End Source File +# Begin Source File + +SOURCE=.\func_break.h +# End Source File +# Begin Source File + +SOURCE=.\game.h +# End Source File +# Begin Source File + +SOURCE=.\gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\items.h +# End Source File +# Begin Source File + +SOURCE=.\maprules.h +# End Source File +# Begin Source File + +SOURCE=.\monsterevent.h +# End Source File +# Begin Source File + +SOURCE=.\monsters.h +# End Source File +# Begin Source File + +SOURCE=.\nodes.h +# End Source File +# Begin Source File + +SOURCE=.\plane.h +# End Source File +# Begin Source File + +SOURCE=.\player.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_defs.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_info.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_materials.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_movevars.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.h +# End Source File +# Begin Source File + +SOURCE=..\engine\progdefs.h +# End Source File +# Begin Source File + +SOURCE=.\saverestore.h +# End Source File +# Begin Source File + +SOURCE=.\schedule.h +# End Source File +# Begin Source File + +SOURCE=..\common\screenfade.h +# End Source File +# Begin Source File + +SOURCE=.\scripted.h +# End Source File +# Begin Source File + +SOURCE=.\scriptevent.h +# End Source File +# Begin Source File + +SOURCE=..\engine\shake.h +# End Source File +# Begin Source File + +SOURCE=.\skill.h +# End Source File +# Begin Source File + +SOURCE=.\soundent.h +# End Source File +# Begin Source File + +SOURCE=.\spectator.h +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.h +# End Source File +# Begin Source File + +SOURCE=..\common\thewastes.h +# End Source File +# Begin Source File + +SOURCE=.\trains.h +# End Source File +# Begin Source File + +SOURCE=..\common\tw_common.h +# End Source File +# Begin Source File + +SOURCE=.\util.h +# End Source File +# Begin Source File + +SOURCE=.\vector.h +# End Source File +# Begin Source File + +SOURCE=.\weapons.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/dlls/hlgl.def b/dlls/hlgl.def new file mode 100644 index 0000000..883f3e5 --- /dev/null +++ b/dlls/hlgl.def @@ -0,0 +1,15 @@ +LIBRARY hlgl +EXPORTS + GiveFnptrsToDll @1 + GetEntityInterfaces @2 + SetChangeParms @3 + SetNewParms @4 + ClientKill @5 + PutClientInServer @6 + PlayerPreThink @7 + PlayerPostThink @8 + ClientConnect @9 + ClientDisconnect @10 + StartFrame @11 +SECTIONS + .data READ WRITE diff --git a/dlls/items.cpp b/dlls/items.cpp index c1e8110..ead356e 100644 --- a/dlls/items.cpp +++ b/dlls/items.cpp @@ -25,7 +25,6 @@ #include "cbase.h" #include "weapons.h" #include "player.h" -#include "skill.h" #include "items.h" #include "gamerules.h" @@ -58,18 +57,12 @@ void CWorldItem::Spawn( void ) switch (m_iType) { - case 44: // ITEM_BATTERY: - pEntity = CBaseEntity::Create( "item_battery", pev->origin, pev->angles ); - break; case 42: // ITEM_ANTIDOTE: pEntity = CBaseEntity::Create( "item_antidote", pev->origin, pev->angles ); break; case 43: // ITEM_SECURITY: pEntity = CBaseEntity::Create( "item_security", pev->origin, pev->angles ); break; - case 45: // ITEM_SUIT: - pEntity = CBaseEntity::Create( "item_suit", pev->origin, pev->angles ); - break; } if (!pEntity) @@ -160,8 +153,6 @@ void CItem::Materialize( void ) { if ( pev->effects & EF_NODRAW ) { - // changing from invisible state to visible. - EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); pev->effects &= ~EF_NODRAW; pev->effects |= EF_MUZZLEFLASH; } @@ -169,174 +160,3 @@ void CItem::Materialize( void ) SetTouch( ItemTouch ); } -#define SF_SUIT_SHORTLOGON 0x0001 - -class CItemSuit : public CItem -{ - void Spawn( void ) - { - Precache( ); - SET_MODEL(ENT(pev), "models/w_suit.mdl"); - CItem::Spawn( ); - } - void Precache( void ) - { - PRECACHE_MODEL ("models/w_suit.mdl"); - } - BOOL MyTouch( CBasePlayer *pPlayer ) - { - if ( pPlayer->pev->weapons & (1<spawnflags & SF_SUIT_SHORTLOGON ) - EMIT_SOUND_SUIT(pPlayer->edict(), "!HEV_A0"); // short version of suit logon, - else - EMIT_SOUND_SUIT(pPlayer->edict(), "!HEV_AAx"); // long version of suit logon - - pPlayer->pev->weapons |= (1<pev->deadflag != DEAD_NO ) - { - return FALSE; - } - - if ((pPlayer->pev->armorvalue < MAX_NORMAL_BATTERY) && - (pPlayer->pev->weapons & (1<pev->armorvalue += gSkillData.batteryCapacity; - pPlayer->pev->armorvalue = min(pPlayer->pev->armorvalue, MAX_NORMAL_BATTERY); - - EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); - - MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); - WRITE_STRING( STRING(pev->classname) ); - MESSAGE_END(); - - - // Suit reports new power level - // For some reason this wasn't working in release build -- round it. - pct = (int)( (float)(pPlayer->pev->armorvalue * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5); - pct = (pct / 5); - if (pct > 0) - pct--; - - sprintf( szcharge,"!HEV_%1dP", pct ); - - //EMIT_SOUND_SUIT(ENT(pev), szcharge); - pPlayer->SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC); - return TRUE; - } - return FALSE; - } -}; - -LINK_ENTITY_TO_CLASS(item_battery, CItemBattery); - - -class CItemAntidote : public CItem -{ - void Spawn( void ) - { - Precache( ); - SET_MODEL(ENT(pev), "models/w_antidote.mdl"); - CItem::Spawn( ); - } - void Precache( void ) - { - PRECACHE_MODEL ("models/w_antidote.mdl"); - } - BOOL MyTouch( CBasePlayer *pPlayer ) - { - pPlayer->SetSuitUpdate("!HEV_DET4", FALSE, SUIT_NEXT_IN_1MIN); - - pPlayer->m_rgItems[ITEM_ANTIDOTE] += 1; - return TRUE; - } -}; - -LINK_ENTITY_TO_CLASS(item_antidote, CItemAntidote); - - -class CItemSecurity : public CItem -{ - void Spawn( void ) - { - Precache( ); - SET_MODEL(ENT(pev), "models/w_security.mdl"); - CItem::Spawn( ); - } - void Precache( void ) - { - PRECACHE_MODEL ("models/w_security.mdl"); - } - BOOL MyTouch( CBasePlayer *pPlayer ) - { - pPlayer->m_rgItems[ITEM_SECURITY] += 1; - return TRUE; - } -}; - -LINK_ENTITY_TO_CLASS(item_security, CItemSecurity); - -class CItemLongJump : public CItem -{ - void Spawn( void ) - { - Precache( ); - SET_MODEL(ENT(pev), "models/w_longjump.mdl"); - CItem::Spawn( ); - } - void Precache( void ) - { - PRECACHE_MODEL ("models/w_longjump.mdl"); - } - BOOL MyTouch( CBasePlayer *pPlayer ) - { - if ( pPlayer->m_fLongJump ) - { - return FALSE; - } - - if ( ( pPlayer->pev->weapons & (1<m_fLongJump = TRUE;// player now has longjump module - - g_engfuncs.pfnSetPhysicsKeyValue( pPlayer->edict(), "slj", "1" ); - - MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); - WRITE_STRING( STRING(pev->classname) ); - MESSAGE_END(); - - EMIT_SOUND_SUIT( pPlayer->edict(), "!HEV_A1" ); // Play the longjump sound UNDONE: Kelly? correct sound? - return TRUE; - } - return FALSE; - } -}; - -LINK_ENTITY_TO_CLASS( item_longjump, CItemLongJump ); diff --git a/dlls/maprules.cpp b/dlls/maprules.cpp index f5f7f69..5976235 100644 --- a/dlls/maprules.cpp +++ b/dlls/maprules.cpp @@ -335,7 +335,6 @@ void CGameText::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE use } } - // // CGameTeamMaster / game_team_master -- "Masters" like multisource, but based on the team of the activator // Only allows mastered entity to fire if the team matches my team diff --git a/dlls/monsters.cpp b/dlls/monsters.cpp new file mode 100644 index 0000000..b0d7ed3 --- /dev/null +++ b/dlls/monsters.cpp @@ -0,0 +1,3391 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "weapons.h" +#include "scripted.h" +#include "decals.h" +#include "soundent.h" +#include "gamerules.h" + +#define MONSTER_CUT_CORNER_DIST 8 // 8 means the monster's bounding box is contained without the box of the node in WC + + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +extern DLL_GLOBAL BOOL g_fDrawLines; +extern DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +extern DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot + +extern CGraph WorldGraph;// the world node graph + + + +// Global Savedata for monster +// UNDONE: Save schedule data? Can this be done? We may +// lose our enemy pointer or other data (goal ent, target, etc) +// that make the current schedule invalid, perhaps it's best +// to just pick a new one when we start up again. +TYPEDESCRIPTION CBaseMonster::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_hEnemy, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseMonster, m_hTargetEnt, FIELD_EHANDLE ), + DEFINE_ARRAY( CBaseMonster, m_hOldEnemy, FIELD_EHANDLE, MAX_OLD_ENEMIES ), + DEFINE_ARRAY( CBaseMonster, m_vecOldEnemy, FIELD_POSITION_VECTOR, MAX_OLD_ENEMIES ), + DEFINE_FIELD( CBaseMonster, m_flFieldOfView, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flWaitFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flMoveWaitFinished, FIELD_TIME ), + + DEFINE_FIELD( CBaseMonster, m_Activity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealActivity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_LastHitGroup, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_MonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealMonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iTaskStatus, FIELD_INTEGER ), + + //Schedule_t *m_pSchedule; + + DEFINE_FIELD( CBaseMonster, m_iScheduleIndex, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afConditions, FIELD_INTEGER ), + //WayPoint_t m_Route[ ROUTE_SIZE ]; +// DEFINE_FIELD( CBaseMonster, m_movementGoal, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_iRouteIndex, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_moveWaitTime, FIELD_FLOAT ), + + DEFINE_FIELD( CBaseMonster, m_vecMoveGoal, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_movementActivity, FIELD_INTEGER ), + + // int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. +// DEFINE_FIELD( CBaseMonster, m_afSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_vecLastPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_iHintNode, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afMemory, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iMaxHealth, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_vecEnemyLKP, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_cAmmoLoaded, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afCapability, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_bitsDamageType, FIELD_INTEGER ), + DEFINE_ARRAY( CBaseMonster, m_rgbTimeBasedDamage, FIELD_CHARACTER, CDMG_TIMEBASED ), + DEFINE_FIELD( CBaseMonster, m_bloodColor, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_failSchedule, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flHungryTime, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flDistTooFar, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flDistLook, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_iTriggerCondition, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iszTriggerTarget, FIELD_STRING ), + + DEFINE_FIELD( CBaseMonster, m_HackedGunPos, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseMonster, m_scriptState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_pCine, FIELD_CLASSPTR ), +}; + +//IMPLEMENT_SAVERESTORE( CBaseMonster, CBaseToggle ); +int CBaseMonster::Save( CSave &save ) +{ + if ( !CBaseToggle::Save(save) ) + return 0; + return save.WriteFields( "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); +} + +int CBaseMonster::Restore( CRestore &restore ) +{ + if ( !CBaseToggle::Restore(restore) ) + return 0; + int status = restore.ReadFields( "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + // We don't save/restore routes yet + RouteClear(); + + // We don't save/restore schedules yet + m_pSchedule = NULL; + m_iTaskStatus = TASKSTATUS_NEW; + + // Reset animation + m_Activity = ACT_RESET; + + // If we don't have an enemy, clear conditions like see enemy, etc. + if ( m_hEnemy == NULL ) + m_afConditions = 0; + + return status; +} + + +//========================================================= +// Eat - makes a monster full for a little while. +//========================================================= +void CBaseMonster :: Eat ( float flFullDuration ) +{ + m_flHungryTime = gpGlobals->time + flFullDuration; +} + +//========================================================= +// FShouldEat - returns true if a monster is hungry. +//========================================================= +BOOL CBaseMonster :: FShouldEat ( void ) +{ + if ( m_flHungryTime > gpGlobals->time ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// Listen - monsters dig through the active sound list for +// any sounds that may interest them. (smells, too!) +//========================================================= +void CBaseMonster :: Listen ( void ) +{ + int iSound; + int iMySounds; + float hearingSensitivity; + CSound *pCurrentSound; + + m_iAudibleList = SOUNDLIST_EMPTY; + ClearConditions(bits_COND_HEAR_SOUND | bits_COND_SMELL | bits_COND_SMELL_FOOD); + m_afSoundTypes = 0; + + iMySounds = ISoundMask(); + + if ( m_pSchedule ) + { + //!!!WATCH THIS SPOT IF YOU ARE HAVING SOUND RELATED BUGS! + // Make sure your schedule AND personal sound masks agree! + iMySounds &= m_pSchedule->iSoundMask; + } + + iSound = CSoundEnt::ActiveList(); + + // UNDONE: Clear these here? + ClearConditions( bits_COND_HEAR_SOUND | bits_COND_SMELL_FOOD | bits_COND_SMELL ); + hearingSensitivity = HearingSensitivity( ); + + while ( iSound != SOUNDLIST_EMPTY ) + { + pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); + + if ( pCurrentSound && + ( pCurrentSound->m_iType & iMySounds ) && + ( pCurrentSound->m_vecOrigin - EarPosition() ).Length() <= pCurrentSound->m_iVolume * hearingSensitivity ) + + //if ( ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & iMySounds ) && ( g_pSoundEnt->m_SoundPool[ iSound ].m_vecOrigin - EarPosition()).Length () <= g_pSoundEnt->m_SoundPool[ iSound ].m_iVolume * hearingSensitivity ) + { + // the monster cares about this sound, and it's close enough to hear. + //g_pSoundEnt->m_SoundPool[ iSound ].m_iNextAudible = m_iAudibleList; + pCurrentSound->m_iNextAudible = m_iAudibleList; + + if ( pCurrentSound->FIsSound() ) + { + // this is an audible sound. + SetConditions( bits_COND_HEAR_SOUND ); + } + else + { + // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent +// if ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + if ( pCurrentSound->m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + { + // the detected scent is a food item, so set both conditions. + // !!!BUGBUG - maybe a virtual function to determine whether or not the scent is food? + SetConditions( bits_COND_SMELL_FOOD ); + SetConditions( bits_COND_SMELL ); + } + else + { + // just a normal scent. + SetConditions( bits_COND_SMELL ); + } + } + +// m_afSoundTypes |= g_pSoundEnt->m_SoundPool[ iSound ].m_iType; + m_afSoundTypes |= pCurrentSound->m_iType; + + m_iAudibleList = iSound; + } + +// iSound = g_pSoundEnt->m_SoundPool[ iSound ].m_iNext; + iSound = pCurrentSound->m_iNext; + } +} + +//========================================================= +// FLSoundVolume - subtracts the volume of the given sound +// from the distance the sound source is from the caller, +// and returns that value, which is considered to be the 'local' +// volume of the sound. +//========================================================= +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) +{ + return ( pSound->m_iVolume - ( ( pSound->m_vecOrigin - pev->origin ).Length() ) ); +} + +//========================================================= +// FValidateHintType - tells use whether or not the monster cares +// about the type of Hint Node given +//========================================================= +BOOL CBaseMonster :: FValidateHintType ( short sHint ) +{ + return FALSE; +} + +//========================================================= +// Look - Base class monster function to find enemies or +// food by sight. iDistance is distance ( in units ) that the +// monster can see. +// +// Sets the sight bits of the m_afConditions mask to indicate +// which types of entities were sighted. +// Function also sets the Looker's m_pLink +// to the head of a link list that contains all visible ents. +// (linked via each ent's m_pLink field) +// +//========================================================= +void CBaseMonster :: Look ( int iDistance ) +{ + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); + + m_pLink = NULL; + + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + + // See no evil if prisoner is set + if ( !FBitSet( pev->spawnflags, SF_MONSTER_PRISONER ) ) + { + CBaseEntity *pList[100]; + + Vector delta = Vector( iDistance, iDistance, iDistance ); + + // Find only monsters/clients in box, NOT limited to PVS + int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + pSightEnt = pList[i]; + // !!!temporarily only considering other monsters and clients, don't see prisoners + if ( pSightEnt != this && + !FBitSet( pSightEnt->pev->spawnflags, SF_MONSTER_PRISONER ) && + pSightEnt->pev->health > 0 ) + { + // the looker will want to consider this entity + // don't check anything else about an entity that can't be seen, or an entity that you don't care about. + if ( IRelationship( pSightEnt ) != R_NO && FInViewCone( pSightEnt ) && !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && FVisible( pSightEnt ) ) + { + if ( pSightEnt->IsPlayer() ) + { + if ( pev->spawnflags & SF_MONSTER_WAIT_TILL_SEEN ) + { + CBaseMonster *pClient; + + pClient = pSightEnt->MyMonsterPointer(); + // don't link this client in the list if the monster is wait till seen and the player isn't facing the monster + if ( pSightEnt && !pClient->FInViewCone( this ) ) + { + // we're not in the player's view cone. + continue; + } + else + { + // player sees us, become normal now. + pev->spawnflags &= ~SF_MONSTER_WAIT_TILL_SEEN; + } + } + + // if we see a client, remember that (mostly for scripted AI) + iSighted |= bits_COND_SEE_CLIENT; + } + + pSightEnt->m_pLink = m_pLink; + m_pLink = pSightEnt; + + if ( pSightEnt == m_hEnemy ) + { + // we know this ent is visible, so if it also happens to be our enemy, store that now. + iSighted |= bits_COND_SEE_ENEMY; + } + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_NM: + iSighted |= bits_COND_SEE_NEMESIS; + break; + case R_HT: + iSighted |= bits_COND_SEE_HATE; + break; + case R_DL: + iSighted |= bits_COND_SEE_DISLIKE; + break; + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_AL: + break; + default: + ALERT ( at_aiconsole, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + } + + SetConditions( iSighted ); +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CBaseMonster :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER; +} + +//========================================================= +// PBestSound - returns a pointer to the sound the monster +// should react to. Right now responds only to nearest sound. +//========================================================= +CSound* CBaseMonster :: PBestSound ( void ) +{ + int iThisSound; + int iBestSound = -1; + float flBestDist = 8192;// so first nearby sound will become best so far. + float flDist; + CSound *pSound; + + iThisSound = m_iAudibleList; + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! monster %s has no audible sounds!\n", STRING(pev->classname) ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisSound ); + + if ( pSound && pSound->FIsSound() ) + { + flDist = ( pSound->m_vecOrigin - EarPosition()).Length(); + + if ( flDist < flBestDist ) + { + iBestSound = iThisSound; + flBestDist = flDist; + } + } + + iThisSound = pSound->m_iNextAudible; + } + if ( iBestSound >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestSound ); + return pSound; + } +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; +} + +//========================================================= +// PBestScent - returns a pointer to the scent the monster +// should react to. Right now responds only to nearest scent +//========================================================= +CSound* CBaseMonster :: PBestScent ( void ) +{ + int iThisScent; + int iBestScent = -1; + float flBestDist = 8192;// so first nearby smell will become best so far. + float flDist; + CSound *pSound; + + iThisScent = m_iAudibleList;// smells are in the sound list. + + if ( iThisScent == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! PBestScent() has empty soundlist!\n" ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisScent != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisScent ); + + if ( pSound->FIsScent() ) + { + flDist = ( pSound->m_vecOrigin - pev->origin ).Length(); + + if ( flDist < flBestDist ) + { + iBestScent = iThisScent; + flBestDist = flDist; + } + } + + iThisScent = pSound->m_iNextAudible; + } + if ( iBestScent >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestScent ); + + return pSound; + } +#if _DEBUG + ALERT( at_error, "NULL Return from PBestScent\n" ); +#endif + return NULL; +} + + + +//========================================================= +// Monster Think - calls out to core AI functions and handles this +// monster's specific animation events +//========================================================= +void CBaseMonster :: MonsterThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1;// keep monster thinking. + + float flInterval = StudioFrameAdvance( ); // animate +// start or end a fidget +// This needs a better home -- switching animations over time should be encapsulated on a per-activity basis +// perhaps MaintainActivity() or a ShiftAnimationOverTime() or something. + if ( m_MonsterState != MONSTERSTATE_SCRIPT && m_MonsterState != MONSTERSTATE_DEAD && m_Activity == ACT_IDLE && m_fSequenceFinished ) + { + int iSequence; + + if ( m_fSequenceLoops ) + { + // animation does loop, which means we're playing subtle idle. Might need to + // fidget. + iSequence = LookupActivity ( m_Activity ); + } + else + { + // animation that just ended doesn't loop! That means we just finished a fidget + // and should return to our heaviest weighted idle (the subtle one) + iSequence = LookupActivityHeaviest ( m_Activity ); + } + if ( iSequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = iSequence; // Set to new anim (if it's there) + ResetSequenceInfo( ); + } + } + + DispatchAnimEvents( flInterval ); + + if ( !MovementIsComplete() ) + { + Move( flInterval ); + } +#if _DEBUG + else + { + if ( !TaskIsRunning() && !TaskIsComplete() ) + ALERT( at_error, "Schedule stalled!!\n" ); + } +#endif +} + +//========================================================= +// CBaseMonster - USE - will make a monster angry at whomever +// activated it. +//========================================================= +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_IdealMonsterState = MONSTERSTATE_ALERT; +} + +//========================================================= +// Ignore conditions - before a set of conditions is allowed +// to interrupt a monster's schedule, this function removes +// conditions that we have flagged to interrupt the current +// schedule, but may not want to interrupt the schedule every +// time. (Pain, for instance) +//========================================================= +int CBaseMonster :: IgnoreConditions ( void ) +{ + int iIgnoreConditions = 0; + + if ( !FShouldEat() ) + { + // not hungry? Ignore food smell. + iIgnoreConditions |= bits_COND_SMELL_FOOD; + } + + if ( m_MonsterState == MONSTERSTATE_SCRIPT && m_pCine ) + iIgnoreConditions |= m_pCine->IgnoreConditions(); + + return iIgnoreConditions; +} + +//========================================================= +// RouteClear - zeroes out the monster's route array and goal +//========================================================= +void CBaseMonster :: RouteClear ( void ) +{ + RouteNew(); + m_movementGoal = MOVEGOAL_NONE; + m_movementActivity = ACT_IDLE; + Forget( bits_MEMORY_MOVE_FAILED ); +} + +//========================================================= +// Route New - clears out a route to be changed, but keeps +// goal intact. +//========================================================= +void CBaseMonster :: RouteNew ( void ) +{ + m_Route[ 0 ].iType = 0; + m_iRouteIndex = 0; +} + +//========================================================= +// FRouteClear - returns TRUE is the Route is cleared out +// ( invalid ) +//========================================================= +BOOL CBaseMonster :: FRouteClear ( void ) +{ + if ( m_Route[ m_iRouteIndex ].iType == 0 || m_movementGoal == MOVEGOAL_NONE ) + return TRUE; + + return FALSE; +} + +//========================================================= +// FRefreshRoute - after calculating a path to the monster's +// target, this function copies as many waypoints as possible +// from that path to the monster's Route array +//========================================================= +BOOL CBaseMonster :: FRefreshRoute ( void ) +{ + CBaseEntity *pPathCorner; + int i; + BOOL returnCode; + + RouteNew(); + + returnCode = FALSE; + + switch( m_movementGoal ) + { + case MOVEGOAL_PATHCORNER: + { + // monster is on a path_corner loop + pPathCorner = m_pGoalEnt; + i = 0; + + while ( pPathCorner && i < ROUTE_SIZE ) + { + m_Route[ i ].iType = bits_MF_TO_PATHCORNER; + m_Route[ i ].vecLocation = pPathCorner->pev->origin; + + pPathCorner = pPathCorner->GetNextTarget(); + + // Last path_corner in list? + if ( !pPathCorner ) + m_Route[i].iType |= bits_MF_IS_GOAL; + + i++; + } + } + returnCode = TRUE; + break; + + case MOVEGOAL_ENEMY: + returnCode = BuildRoute( m_vecEnemyLKP, bits_MF_TO_ENEMY, m_hEnemy ); + break; + + case MOVEGOAL_LOCATION: + returnCode = BuildRoute( m_vecMoveGoal, bits_MF_TO_LOCATION, NULL ); + break; + + case MOVEGOAL_TARGETENT: + if (m_hTargetEnt != NULL) + { + returnCode = BuildRoute( m_hTargetEnt->pev->origin, bits_MF_TO_TARGETENT, m_hTargetEnt ); + } + break; + + case MOVEGOAL_NODE: + returnCode = FGetNodeRoute( m_vecMoveGoal ); +// if ( returnCode ) +// RouteSimplify( NULL ); + break; + } + + return returnCode; +} + + +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_ENEMY; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_LOCATION; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_TARGETENT; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_NODE; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +#ifdef _DEBUG +void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ) +{ + int i; + + if ( m_Route[m_iRouteIndex].iType == 0 ) + { + ALERT( at_aiconsole, "Can't draw route!\n" ); + return; + } + +// UTIL_ParticleEffect ( m_Route[ m_iRouteIndex ].vecLocation, g_vecZero, 255, 25 ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.x ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.y ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.z ); + + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + for ( i = m_iRouteIndex ; i < ROUTE_SIZE - 1; i++ ) + { + if ( (m_Route[ i ].iType & bits_MF_IS_GOAL) || (m_Route[ i+1 ].iType == 0) ) + break; + + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( m_Route[ i ].vecLocation.x ); + WRITE_COORD( m_Route[ i ].vecLocation.y ); + WRITE_COORD( m_Route[ i ].vecLocation.z ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.x ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.y ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 8 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + +// UTIL_ParticleEffect ( m_Route[ i ].vecLocation, g_vecZero, 255, 25 ); + } +} +#endif + + +int ShouldSimplify( int routeType ) +{ + routeType &= ~bits_MF_IS_GOAL; + + if ( (routeType == bits_MF_TO_PATHCORNER) || (routeType & bits_MF_DONT_SIMPLIFY) ) + return FALSE; + return TRUE; +} + +//========================================================= +// RouteSimplify +// +// Attempts to make the route more direct by cutting out +// unnecessary nodes & cutting corners. +// +//========================================================= +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) +{ + // BUGBUG: this doesn't work 100% yet + int i, count, outCount; + Vector vecStart; + WayPoint_t outRoute[ ROUTE_SIZE * 2 ]; // Any points except the ends can turn into 2 points in the simplified route + + count = 0; + + for ( i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( !m_Route[i].iType ) + break; + else + count++; + if ( m_Route[i].iType & bits_MF_IS_GOAL ) + break; + } + // Can't simplify a direct route! + if ( count < 2 ) + { +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); + return; + } + + outCount = 0; + vecStart = pev->origin; + for ( i = 0; i < count-1; i++ ) + { + // Don't eliminate path_corners + if ( !ShouldSimplify( m_Route[m_iRouteIndex+i].iType ) ) + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + } + else if ( CheckLocalMove ( vecStart, m_Route[m_iRouteIndex+i+1].vecLocation, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + // Skip vert + continue; + } + else + { + Vector vecTest, vecSplit; + + // Halfway between this and next + vecTest = (m_Route[m_iRouteIndex+i+1].vecLocation + m_Route[m_iRouteIndex+i].vecLocation) * 0.5; + + // Halfway between this and previous + vecSplit = (m_Route[m_iRouteIndex+i].vecLocation + vecStart) * 0.5; + + int iType = (m_Route[m_iRouteIndex+i].iType | bits_MF_TO_DETOUR) & ~bits_MF_NOT_TO_MASK; + if ( CheckLocalMove ( vecStart, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecTest; + } + else if ( CheckLocalMove ( vecSplit, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecSplit; + outRoute[outCount+1].iType = iType; + outRoute[outCount+1].vecLocation = vecTest; + outCount++; // Adding an extra point + } + else + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + } + } + // Get last point + vecStart = outRoute[ outCount ].vecLocation; + outCount++; + } + ASSERT( i < count ); + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + + // Terminate + outRoute[outCount].iType = 0; + ASSERT( outCount < (ROUTE_SIZE*2) ); + +// Copy the simplified route, disable for testing + m_iRouteIndex = 0; + for ( i = 0; i < ROUTE_SIZE && i < outCount; i++ ) + { + m_Route[i] = outRoute[i]; + } + + // Terminate route + if ( i < ROUTE_SIZE ) + m_Route[i].iType = 0; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT( "simplify" ) != 0 ) + DrawRoute( pev, outRoute, 0, 255, 0, 0 ); +// else + DrawRoute( pev, m_Route, m_iRouteIndex, 0, 255, 0 ); +#endif +} + +//========================================================= +// FBecomeProne - tries to send a monster into PRONE state. +// right now only used when a barnacle snatches someone, so +// may have some special case stuff for that. +//========================================================= +BOOL CBaseMonster :: FBecomeProne ( void ) +{ + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + m_IdealMonsterState = MONSTERSTATE_PRONE; + return TRUE; +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 512 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + if ( flDist <= 64 && flDot >= 0.7 && m_hEnemy != NULL && FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 64 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckAttacks - sets all of the bits for attacks that the +// monster is capable of carrying out on the passed entity. +//========================================================= +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pTarget->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + // we know the enemy is in front now. We'll find which attacks the monster is capable of by + // checking for corresponding Activities in the model file, then do the simple checks to validate + // those attack types. + + // Clear all attack conditions + ClearConditions( bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK1 |bits_COND_CAN_MELEE_ATTACK2 ); + + if ( m_afCapability & bits_CAP_RANGE_ATTACK1 ) + { + if ( CheckRangeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_RANGE_ATTACK2 ) + { + if ( CheckRangeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK2 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK1 ) + { + if ( CheckMeleeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK2 ) + { + if ( CheckMeleeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK2 ); + } +} + +//========================================================= +// CanCheckAttacks - prequalifies a monster to do more fine +// checking of potential attacks. +//========================================================= +BOOL CBaseMonster :: FCanCheckAttacks ( void ) +{ + if ( HasConditions(bits_COND_SEE_ENEMY) && !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckEnemy - part of the Condition collection process, +// gets and stores data and conditions pertaining to a monster's +// enemy. Returns TRUE if Enemy LKP was updated. +//========================================================= +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) +{ + float flDistToEnemy; + int iUpdatedLKP;// set this to TRUE if you update the EnemyLKP in this function. + + iUpdatedLKP = FALSE; + ClearConditions ( bits_COND_ENEMY_FACING_ME ); + + if ( !FVisible( pEnemy ) ) + { + ASSERT(!HasConditions(bits_COND_SEE_ENEMY)); + SetConditions( bits_COND_ENEMY_OCCLUDED ); + } + else + ClearConditions( bits_COND_ENEMY_OCCLUDED ); + + if ( !pEnemy->IsAlive() ) + { + SetConditions ( bits_COND_ENEMY_DEAD ); + ClearConditions( bits_COND_SEE_ENEMY | bits_COND_ENEMY_OCCLUDED ); + return FALSE; + } + + Vector vecEnemyPos = pEnemy->pev->origin; + // distance to enemy's origin + flDistToEnemy = ( vecEnemyPos - pev->origin ).Length(); + vecEnemyPos.z += pEnemy->pev->size.z * 0.5; + // distance to enemy's head + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + else + { + // distance to enemy's feet + vecEnemyPos.z -= pEnemy->pev->size.z; + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + CBaseMonster *pEnemyMonster; + + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + + pEnemyMonster = pEnemy->MyMonsterPointer(); + + if ( pEnemyMonster ) + { + if ( pEnemyMonster->FInViewCone ( this ) ) + { + SetConditions ( bits_COND_ENEMY_FACING_ME ); + } + else + ClearConditions( bits_COND_ENEMY_FACING_ME ); + } + + if (pEnemy->pev->velocity != Vector( 0, 0, 0)) + { + // trail the enemy a bit + m_vecEnemyLKP = m_vecEnemyLKP - pEnemy->pev->velocity * RANDOM_FLOAT( -0.05, 0 ); + } + else + { + // UNDONE: use pev->oldorigin? + } + } + else if ( !HasConditions(bits_COND_ENEMY_OCCLUDED|bits_COND_SEE_ENEMY) && ( flDistToEnemy <= 256 ) ) + { + // if the enemy is not occluded, and unseen, that means it is behind or beside the monster. + // if the enemy is near enough the monster, we go ahead and let the monster know where the + // enemy is. + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + } + + if ( flDistToEnemy >= m_flDistTooFar ) + { + // enemy is very far away from monster + SetConditions( bits_COND_ENEMY_TOOFAR ); + } + else + ClearConditions( bits_COND_ENEMY_TOOFAR ); + + if ( FCanCheckAttacks() ) + { + CheckAttacks ( m_hEnemy, flDistToEnemy ); + } + + if ( m_movementGoal == MOVEGOAL_ENEMY ) + { + for ( int i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( m_Route[ i ].iType == (bits_MF_IS_GOAL|bits_MF_TO_ENEMY) ) + { + // UNDONE: Should we allow monsters to override this distance (80?) + if ( (m_Route[ i ].vecLocation - m_vecEnemyLKP).Length() > 80 ) + { + // Refresh + FRefreshRoute(); + return iUpdatedLKP; + } + } + } + } + + return iUpdatedLKP; +} + +//========================================================= +// PushEnemy - remember the last few enemies, always remember the player +//========================================================= +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) +{ + int i; + + if (pEnemy == NULL) + return; + + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (i = 0; i < MAX_OLD_ENEMIES; i++) + { + if (m_hOldEnemy[i] == pEnemy) + return; + if (m_hOldEnemy[i] == NULL) // someone died, reuse their slot + break; + } + if (i >= MAX_OLD_ENEMIES) + return; + + m_hOldEnemy[i] = pEnemy; + m_vecOldEnemy[i] = vecLastKnownPos; +} + +//========================================================= +// PopEnemy - try remembering the last few enemies +//========================================================= +BOOL CBaseMonster :: PopEnemy( ) +{ + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (int i = MAX_OLD_ENEMIES - 1; i >= 0; i--) + { + if (m_hOldEnemy[i] != NULL) + { + if (m_hOldEnemy[i]->IsAlive( )) // cheat and know when they die + { + m_hEnemy = m_hOldEnemy[i]; + m_vecEnemyLKP = m_vecOldEnemy[i]; + // ALERT( at_console, "remembering\n"); + return TRUE; + } + else + { + m_hOldEnemy[i] = NULL; + } + } + } + return FALSE; +} + +//========================================================= +// SetActivity +//========================================================= +void CBaseMonster :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + iSequence = LookupActivity ( NewActivity ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + // don't reset frame between walk and run + if ( !(m_Activity == ACT_WALK || m_Activity == ACT_RUN) || !(NewActivity == ACT_WALK || NewActivity == ACT_RUN)) + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; + + +} + +//========================================================= +// SetSequenceByName +//========================================================= +void CBaseMonster :: SetSequenceByName ( char *szSequence ) +{ + int iSequence; + + iSequence = LookupSequence ( szSequence ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence named:%f\n", STRING(pev->classname), szSequence ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// CheckLocalMove - returns TRUE if the caller can walk a +// straight line from its current origin to the given +// location. If so, don't use the node graph! +// +// if a valid pointer to a int is passed, the function +// will fill that int with the distance that the check +// reached before hitting something. THIS ONLY HAPPENS +// IF THE LOCAL MOVE CHECK FAILS! +// +// !!!PERFORMANCE - should we try to load balance this? +// DON"T USE SETORIGIN! +//========================================================= +#define LOCAL_STEP_SIZE 16 +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + Vector vecStartPos;// record monster's position before trying the move + float flYaw; + float flDist; + float flStep, stepSize; + int iReturn; + + vecStartPos = pev->origin; + + + flYaw = UTIL_VecToYaw ( vecEnd - vecStart );// build a yaw that points to the goal. + flDist = ( vecEnd - vecStart ).Length2D();// get the distance. + iReturn = LOCALMOVE_VALID;// assume everything will be ok. + + // move the monster to the start of the local move that's to be checked. + UTIL_SetOrigin( pev, vecStart );// !!!BUGBUG - won't this fire triggers? - nope, SetOrigin doesn't fire + + if ( !(pev->flags & (FL_FLY|FL_SWIM)) ) + { + DROP_TO_FLOOR( ENT( pev ) );//make sure monster is on the floor! + } + + //pev->origin.z = vecStartPos.z;//!!!HACKHACK + +// pev->origin = vecStart; + +/* + if ( flDist > 1024 ) + { + // !!!PERFORMANCE - this operation may be too CPU intensive to try checks this large. + // We don't lose much here, because a distance this great is very likely + // to have something in the way. + + // since we've actually moved the monster during the check, undo the move. + pev->origin = vecStartPos; + return FALSE; + } +*/ + // this loop takes single steps to the goal. + for ( flStep = 0 ; flStep < flDist ; flStep += LOCAL_STEP_SIZE ) + { + stepSize = LOCAL_STEP_SIZE; + + if ( (flStep + LOCAL_STEP_SIZE) >= (flDist-1) ) + stepSize = (flDist - flStep) - 1; + +// UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, WALKMOVE_CHECKONLY ) ) + {// can't take the next step, fail! + + if ( pflDist != NULL ) + { + *pflDist = flStep; + } + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + { + // if this step hits target ent, the move is legal. + iReturn = LOCALMOVE_VALID; + break; + } + else + { + // If we're going toward an entity, and we're almost getting there, it's OK. +// if ( pTarget && fabs( flDist - iStep ) < LOCAL_STEP_SIZE ) +// fReturn = TRUE; +// else + iReturn = LOCALMOVE_INVALID; + break; + } + + } + } + + if ( iReturn == LOCALMOVE_VALID && !(pev->flags & (FL_FLY|FL_SWIM) ) && (!pTarget || (pTarget->pev->flags & FL_ONGROUND)) ) + { + // The monster can move to a spot UNDER the target, but not to it. Don't try to triangulate, go directly to the node graph. + // UNDONE: Magic # 64 -- this used to be pev->size.z but that won't work for small creatures like the headcrab + if ( fabs(vecEnd.z - pev->origin.z) > 64 ) + { + iReturn = LOCALMOVE_INVALID_DONT_TRIANGULATE; + } + } + /* + // uncommenting this block will draw a line representing the nearest legal move. + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, pev->origin.x); + WRITE_COORD(MSG_BROADCAST, pev->origin.y); + WRITE_COORD(MSG_BROADCAST, pev->origin.z); + WRITE_COORD(MSG_BROADCAST, vecStart.x); + WRITE_COORD(MSG_BROADCAST, vecStart.y); + WRITE_COORD(MSG_BROADCAST, vecStart.z); + */ + + // since we've actually moved the monster during the check, undo the move. + UTIL_SetOrigin( pev, vecStartPos ); + + return iReturn; +} + + +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) +{ + float flTravelTime = 0; + + //ALERT(at_aiconsole, "A door. "); + CBaseEntity *pcbeDoor = CBaseEntity::Instance(pevDoor); + if (pcbeDoor && !pcbeDoor->IsLockedByMaster()) + { + //ALERT(at_aiconsole, "unlocked! "); + pcbeDoor->Use(this, this, USE_ON, 0.0); + //ALERT(at_aiconsole, "pevDoor->nextthink = %d ms\n", (int)(1000*pevDoor->nextthink)); + //ALERT(at_aiconsole, "pevDoor->ltime = %d ms\n", (int)(1000*pevDoor->ltime)); + //ALERT(at_aiconsole, "pev-> nextthink = %d ms\n", (int)(1000*pev->nextthink)); + //ALERT(at_aiconsole, "pev->ltime = %d ms\n", (int)(1000*pev->ltime)); + flTravelTime = pevDoor->nextthink - pevDoor->ltime; + //ALERT(at_aiconsole, "Waiting %d ms\n", (int)(1000*flTravelTime)); + if ( pcbeDoor->pev->targetname ) + { + edict_t *pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pcbeDoor->pev->targetname)); + + if ( VARS( pentTarget ) != pcbeDoor->pev ) + { + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs ( pentTarget, STRING(pcbeDoor->pev->classname) ) ) + { + CBaseEntity *pDoor = Instance(pentTarget); + if ( pDoor ) + pDoor->Use(this, this, USE_ON, 0.0); + } + } + } + } + } + + return gpGlobals->time + flTravelTime; +} + + +//========================================================= +// AdvanceRoute - poorly named function that advances the +// m_iRouteIndex. If it goes beyond ROUTE_SIZE, the route +// is refreshed. +//========================================================= +void CBaseMonster :: AdvanceRoute ( float distance ) +{ + + if ( m_iRouteIndex == ROUTE_SIZE - 1 ) + { + // time to refresh the route. + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Refresh Route!!\n" ); + } + } + else + { + if ( ! (m_Route[ m_iRouteIndex ].iType & bits_MF_IS_GOAL) ) + { + // If we've just passed a path_corner, advance m_pGoalEnt + if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_PATHCORNER ) + m_pGoalEnt = m_pGoalEnt->GetNextTarget(); + + // IF both waypoints are nodes, then check for a link for a door and operate it. + // + if ( (m_Route[m_iRouteIndex].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE + && (m_Route[m_iRouteIndex+1].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE) + { + //ALERT(at_aiconsole, "SVD: Two nodes. "); + + int iSrcNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex].vecLocation, this ); + int iDestNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex+1].vecLocation, this ); + + int iLink; + WorldGraph.HashSearch(iSrcNode, iDestNode, iLink); + + if ( iLink >= 0 && WorldGraph.m_pLinkPool[iLink].m_pLinkEnt != NULL ) + { + //ALERT(at_aiconsole, "A link. "); + if ( WorldGraph.HandleLinkEnt ( iSrcNode, WorldGraph.m_pLinkPool[iLink].m_pLinkEnt, m_afCapability, CGraph::NODEGRAPH_DYNAMIC ) ) + { + //ALERT(at_aiconsole, "usable."); + entvars_t *pevDoor = WorldGraph.m_pLinkPool[iLink].m_pLinkEnt; + if (pevDoor) + { + m_flMoveWaitFinished = OpenDoorAndWait( pevDoor ); +// ALERT( at_aiconsole, "Wating for door %.2f\n", m_flMoveWaitFinished-gpGlobals->time ); + } + } + } + //ALERT(at_aiconsole, "\n"); + } + m_iRouteIndex++; + } + else // At goal!!! + { + if ( distance < m_flGroundSpeed * 0.2 /* FIX */ ) + { + MovementComplete(); + } + } + } +} + + +int CBaseMonster :: RouteClassify( int iMoveFlag ) +{ + int movementGoal; + + movementGoal = MOVEGOAL_NONE; + + if ( iMoveFlag & bits_MF_TO_TARGETENT ) + movementGoal = MOVEGOAL_TARGETENT; + else if ( iMoveFlag & bits_MF_TO_ENEMY ) + movementGoal = MOVEGOAL_ENEMY; + else if ( iMoveFlag & bits_MF_TO_PATHCORNER ) + movementGoal = MOVEGOAL_PATHCORNER; + else if ( iMoveFlag & bits_MF_TO_NODE ) + movementGoal = MOVEGOAL_NODE; + else if ( iMoveFlag & bits_MF_TO_LOCATION ) + movementGoal = MOVEGOAL_LOCATION; + + return movementGoal; +} + +//========================================================= +// BuildRoute +//========================================================= +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) +{ + float flDist; + Vector vecApex; + int iLocalMove; + + RouteNew(); + m_movementGoal = RouteClassify( iMoveFlag ); + +// so we don't end up with no moveflags + m_Route[ 0 ].vecLocation = vecGoal; + m_Route[ 0 ].iType = iMoveFlag | bits_MF_IS_GOAL; + +// check simple local move + iLocalMove = CheckLocalMove( pev->origin, vecGoal, pTarget, &flDist ); + + if ( iLocalMove == LOCALMOVE_VALID ) + { + // monster can walk straight there! + return TRUE; + } +// try to triangulate around any obstacles. + else if ( iLocalMove != LOCALMOVE_INVALID_DONT_TRIANGULATE && FTriangulate( pev->origin, vecGoal, flDist, pTarget, &vecApex ) ) + { + // there is a slightly more complicated path that allows the monster to reach vecGoal + m_Route[ 0 ].vecLocation = vecApex; + m_Route[ 0 ].iType = (iMoveFlag | bits_MF_TO_DETOUR); + + m_Route[ 1 ].vecLocation = vecGoal; + m_Route[ 1 ].iType = iMoveFlag | bits_MF_IS_GOAL; + + /* + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z ); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z + 128 ); + */ + + RouteSimplify( pTarget ); + return TRUE; + } + +// last ditch, try nodes + if ( FGetNodeRoute( vecGoal ) ) + { +// ALERT ( at_console, "Can get there on nodes\n" ); + m_vecMoveGoal = vecGoal; + RouteSimplify( pTarget ); + return TRUE; + } + + // b0rk + return FALSE; +} + + +//========================================================= +// InsertWaypoint - Rebuilds the existing route so that the +// supplied vector and moveflags are the first waypoint in +// the route, and fills the rest of the route with as much +// of the pre-existing route as possible +//========================================================= +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) +{ + int i, type; + + + // we have to save some Index and Type information from the real + // path_corner or node waypoint that the monster was trying to reach. This makes sure that data necessary + // to refresh the original path exists even in the new waypoints that don't correspond directy to a path_corner + // or node. + type = afMoveFlags | (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK); + + for ( i = ROUTE_SIZE-1; i > 0; i-- ) + m_Route[i] = m_Route[i-1]; + + m_Route[ m_iRouteIndex ].vecLocation = vecLocation; + m_Route[ m_iRouteIndex ].iType = type; +} + +//========================================================= +// FTriangulate - tries to overcome local obstacles by +// triangulating a path around them. +// +// iApexDist is how far the obstruction that we are trying +// to triangulate around is from the monster. +//========================================================= +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) +{ + Vector vecDir; + Vector vecForward; + Vector vecLeft;// the spot we'll try to triangulate to on the left + Vector vecRight;// the spot we'll try to triangulate to on the right + Vector vecTop;// the spot we'll try to triangulate to on the top + Vector vecBottom;// the spot we'll try to triangulate to on the bottom + Vector vecFarSide;// the spot that we'll move to after hitting the triangulated point, before moving on to our normal goal. + int i; + float sizeX, sizeZ; + + // If the hull width is less than 24, use 24 because CheckLocalMove uses a min of + // 24. + sizeX = pev->size.x; + if (sizeX < 24.0) + sizeX = 24.0; + else if (sizeX > 48.0) + sizeX = 48.0; + sizeZ = pev->size.z; + //if (sizeZ < 24.0) + // sizeZ = 24.0; + + vecForward = ( vecEnd - vecStart ).Normalize(); + + Vector vecDirUp(0,0,1); + vecDir = CrossProduct ( vecForward, vecDirUp); + + // start checking right about where the object is, picking two equidistant starting points, one on + // the left, one on the right. As we progress through the loop, we'll push these away from the obstacle, + // hoping to find a way around on either side. pev->size.x is added to the ApexDist in order to help select + // an apex point that insures that the monster is sufficiently past the obstacle before trying to turn back + // onto its original course. + + vecLeft = pev->origin + ( vecForward * ( flDist + sizeX ) ) - vecDir * ( sizeX * 3 ); + vecRight = pev->origin + ( vecForward * ( flDist + sizeX ) ) + vecDir * ( sizeX * 3 ); + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = pev->origin + (vecForward * flDist) + (vecDirUp * sizeZ * 3); + vecBottom = pev->origin + (vecForward * flDist) - (vecDirUp * sizeZ * 3); + } + + vecFarSide = m_Route[ m_iRouteIndex ].vecLocation; + + vecDir = vecDir * sizeX * 2; + if (pev->movetype == MOVETYPE_FLY) + vecDirUp = vecDirUp * sizeZ * 2; + + for ( i = 0 ; i < 8; i++ ) + { +// Debug, Draw the triangulation +#if 0 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecRight.x ); + WRITE_COORD( vecRight.y ); + WRITE_COORD( vecRight.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecLeft.x ); + WRITE_COORD( vecLeft.y ); + WRITE_COORD( vecLeft.z ); + MESSAGE_END(); +#endif + +#if 0 + if (pev->movetype == MOVETYPE_FLY) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecTop.x ); + WRITE_COORD( vecTop.y ); + WRITE_COORD( vecTop.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecBottom.x ); + WRITE_COORD( vecBottom.y ); + WRITE_COORD( vecBottom.z ); + MESSAGE_END(); + } +#endif + + if ( CheckLocalMove( pev->origin, vecRight, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecRight, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecRight; + } + + return TRUE; + } + } + if ( CheckLocalMove( pev->origin, vecLeft, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecLeft, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecLeft; + } + + return TRUE; + } + } + + if (pev->movetype == MOVETYPE_FLY) + { + if ( CheckLocalMove( pev->origin, vecTop, pTargetEnt, NULL ) == LOCALMOVE_VALID) + { + if ( CheckLocalMove ( vecTop, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecTop; + //ALERT(at_aiconsole, "triangulate over\n"); + } + + return TRUE; + } + } +#if 1 + if ( CheckLocalMove( pev->origin, vecBottom, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecBottom, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecBottom; + //ALERT(at_aiconsole, "triangulate under\n"); + } + + return TRUE; + } + } +#endif + } + + vecRight = vecRight + vecDir; + vecLeft = vecLeft - vecDir; + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = vecTop + vecDirUp; + vecBottom = vecBottom - vecDirUp; + } + } + + return FALSE; +} + +//========================================================= +// Move - take a single step towards the next ROUTE location +//========================================================= +#define DIST_TO_CHECK 200 + +void CBaseMonster :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + Vector vecDir; + Vector vecApex; + CBaseEntity *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + // If we still have a movement goal, then this is probably a route truncated by SimplifyRoute() + // so refresh it. + if ( m_movementGoal == MOVEGOAL_NONE || !FRefreshRoute() ) + { + ALERT( at_aiconsole, "Tried to move with no route!\n" ); + TaskFail(); + return; + } + } + + if ( m_flMoveWaitFinished > gpGlobals->time ) + return; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + return; + } +#else +// Debug, draw the route +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 200, 0 ); +#endif + + // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer + // to that entity for the CheckLocalMove and Triangulate functions. + pTargetEnt = NULL; + + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D(); + + MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + ChangeYaw ( pev->yaw_speed ); + + // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint + if ( flWaypointDist < DIST_TO_CHECK ) + { + flCheckDist = flWaypointDist; + } + else + { + flCheckDist = DIST_TO_CHECK; + } + + if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) + { + // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) + pTargetEnt = m_hEnemy; + } + else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) + { + pTargetEnt = m_hTargetEnt; + } + + // !!!BUGBUG - CheckDist should be derived from ground speed. + // If this fails, it should be because of some dynamic entity blocking this guy. + // We've already checked this path, so we should wait and time out if the entity doesn't move + flDist = 0; + if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) + { + CBaseEntity *pBlocker; + + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); + if (pBlocker) + { + DispatchBlocked( edict(), pBlocker->edict() ); + } + + if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // No, Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + return; + } + // Ok, still enough room to take a step + } + else + { + // try to triangulate around whatever is in the way. + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); + RouteSimplify( pTargetEnt ); + } + else + { +// ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); + Stop(); + // Only do this once until your route is cleared + if ( m_moveWaitTime > 0 && !(m_afMemory & bits_MEMORY_MOVE_FAILED) ) + { + FRefreshRoute(); + if ( FRouteClear() ) + { + TaskFail(); + } + else + { + // Don't get stuck + if ( (gpGlobals->time - m_flMoveWaitFinished) < 0.2 ) + Remember( bits_MEMORY_MOVE_FAILED ); + + m_flMoveWaitFinished = gpGlobals->time + 0.1; + } + } + else + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to move (%d)!\n", STRING(pev->classname), HasMemory( bits_MEMORY_MOVE_FAILED ) ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + } + return; + } + } + } + + // close enough to the target, now advance to the next target. This is done before actually reaching + // the target so that we get a nice natural turn while moving. + if ( ShouldAdvanceRoute( flWaypointDist ) )///!!!BUGBUG- magic number + { + AdvanceRoute( flWaypointDist ); + } + + // Might be waiting for a door + if ( m_flMoveWaitFinished > gpGlobals->time ) + { + Stop(); + return; + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < m_flGroundSpeed * flInterval) + { + flInterval = flCheckDist / m_flGroundSpeed; + // ALERT( at_console, "%.02f\n", flInterval ); + } + MoveExecute( pTargetEnt, vecDir, flInterval ); + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } +} + + +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= MONSTER_CUT_CORNER_DIST ) + { + // ALERT( at_console, "cut %f\n", flWaypointDist ); + return TRUE; + } + + return FALSE; +} + + +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ +// float flYaw = UTIL_VecToYaw ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin );// build a yaw that points to the goal. +// WALK_MOVE( ENT(pev), flYaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); + if ( m_IdealActivity != m_movementActivity ) + m_IdealActivity = m_movementActivity; + + float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + float flStep; + while (flTotal > 0.001) + { + // don't walk more than 16 units or stairs stop working + flStep = min( 16.0, flTotal ); + UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flStep, MOVE_NORMAL ); + flTotal -= flStep; + } + // ALERT( at_console, "dist %f\n", m_flGroundSpeed * pev->framerate * flInterval ); +} + + +//========================================================= +// MonsterInit - after a monster is spawned, it needs to +// be dropped into the world, checked for mobility problems, +// and put on the proper path, if any. This function does +// all of those things after the monster spawns. Any +// initialization that should take place for all monsters +// goes here. +//========================================================= +void CBaseMonster :: MonsterInit ( void ) +{ + if (!g_pGameRules->FAllowMonsters()) + { + pev->flags |= FL_KILLME; // Post this because some monster code modifies class data after calling this function +// REMOVE_ENTITY(ENT(pev)); + return; + } + + // Set fields common to all monsters + pev->effects = 0; + pev->takedamage = DAMAGE_AIM; + pev->ideal_yaw = pev->angles.y; + pev->max_health = pev->health; + pev->deadflag = DEAD_NO; + m_IdealMonsterState = MONSTERSTATE_IDLE;// Assume monster will be idle, until proven otherwise + + m_IdealActivity = ACT_IDLE; + + SetBits (pev->flags, FL_MONSTER); + if ( pev->spawnflags & SF_MONSTER_HITMONSTERCLIP ) + pev->flags |= FL_MONSTERCLIP; + + ClearSchedule(); + RouteClear(); + InitBoneControllers( ); // FIX: should be done in Spawn + + m_iHintNode = NO_NODE; + + m_afMemory = MEMORY_CLEAR; + + m_hEnemy = NULL; + + m_flDistTooFar = 1024.0; + m_flDistLook = 2048.0; + + // set eye position + SetEyePosition(); + + SetThink( MonsterInitThink ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse ( MonsterUse ); +} + +//========================================================= +// MonsterInitThink - Calls StartMonster. Startmonster is +// virtual, but this function cannot be +//========================================================= +void CBaseMonster :: MonsterInitThink ( void ) +{ + StartMonster(); +} + +//========================================================= +// StartMonster - final bit of initization before a monster +// is turned over to the AI. +//========================================================= +void CBaseMonster :: StartMonster ( void ) +{ + // update capabilities + if ( LookupActivity ( ACT_RANGE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK1; + } + if ( LookupActivity ( ACT_RANGE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK2; + } + if ( LookupActivity ( ACT_MELEE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK1; + } + if ( LookupActivity ( ACT_MELEE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK2; + } + + // Raise monster off the floor one unit, then drop to floor + if ( pev->movetype != MOVETYPE_FLY && !FBitSet( pev->spawnflags, SF_MONSTER_FALL_TO_GROUND ) ) + { + pev->origin.z += 1; + DROP_TO_FLOOR ( ENT(pev) ); + // Try to move the monster to make sure it's not stuck in a brush. + if (!WALK_MOVE ( ENT(pev), 0, 0, WALKMOVE_NORMAL ) ) + { + ALERT(at_error, "Monster %s stuck in wall--level design error", STRING(pev->classname)); + pev->effects = EF_BRIGHTFIELD; + } + } + else + { + pev->flags &= ~FL_ONGROUND; + } + + if ( !FStringNull(pev->target) )// this monster has a target + { + // Find the monster's initial target entity, stash it + m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING( pev->target ) ) ); + + if ( !m_pGoalEnt ) + { + ALERT(at_error, "ReadyMonster()--%s couldn't find target %s", STRING(pev->classname), STRING(pev->target)); + } + else + { + // Monster will start turning towards his destination + MakeIdealYaw ( m_pGoalEnt->pev->origin ); + + // JAY: How important is this error message? Big Momma doesn't obey this rule, so I took it out. +#if 0 + // At this point, we expect only a path_corner as initial goal + if (!FClassnameIs( m_pGoalEnt->pev, "path_corner")) + { + ALERT(at_warning, "ReadyMonster--monster's initial goal '%s' is not a path_corner", STRING(pev->target)); + } +#endif + + // set the monster up to walk a path corner path. + // !!!BUGBUG - this is a minor bit of a hack. + // JAYJAY + m_movementGoal = MOVEGOAL_PATHCORNER; + + if ( pev->movetype == MOVETYPE_FLY ) + m_movementActivity = ACT_FLY; + else + m_movementActivity = ACT_WALK; + + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Create Route!\n" ); + } + } + } + + //SetActivity ( m_IdealActivity ); + + // Delay drop to floor to make sure each door in the level has had its chance to spawn + // Spread think times so that they don't all happen at the same time (Carmack) + SetThink ( CallMonsterThink ); + pev->nextthink += RANDOM_FLOAT(0.1, 0.4); // spread think times. + + if ( !FStringNull(pev->targetname) )// wait until triggered + { + // UNDONE: Some scripted sequence monsters don't have an idle? + SetActivity( ACT_IDLE ); + } +} + + +void CBaseMonster :: MovementComplete( void ) +{ + switch( m_iTaskStatus ) + { + case TASKSTATUS_NEW: + case TASKSTATUS_RUNNING: + m_iTaskStatus = TASKSTATUS_RUNNING_TASK; + break; + + case TASKSTATUS_RUNNING_MOVEMENT: + TaskComplete(); + break; + + case TASKSTATUS_RUNNING_TASK: + ALERT( at_error, "Movement completed twice!\n" ); + break; + + case TASKSTATUS_COMPLETE: + break; + } + m_movementGoal = MOVEGOAL_NONE; +} + + +int CBaseMonster::TaskIsRunning( void ) +{ + if ( m_iTaskStatus != TASKSTATUS_COMPLETE && + m_iTaskStatus != TASKSTATUS_RUNNING_MOVEMENT ) + return 1; + + return 0; +} + +//========================================================= +// IRelationship - returns an integer that describes the +// relationship between two types of monster. +//========================================================= +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) +{ + static int iEnemy[14][14] = + { // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN + /*NONE*/ { R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*MACHINE*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_DL, R_DL }, + /*PLAYER*/ { R_NO ,R_DL ,R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_DL, R_DL }, + /*HUMANPASSIVE*/{ R_NO ,R_NO ,R_AL ,R_AL ,R_HT ,R_FR ,R_NO ,R_HT ,R_DL ,R_FR ,R_NO ,R_AL, R_NO, R_NO }, + /*HUMANMILITAR*/{ R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_HT ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_HT, R_NO, R_NO }, + /*ALIENMILITAR*/{ R_NO ,R_DL ,R_HT ,R_DL ,R_HT ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPASSIVE*/{ R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*ALIENMONSTER*/{ R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREY */{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_FR ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREDATO*/{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_DL, R_NO, R_NO }, + /*INSECT*/ { R_FR ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR, R_NO, R_NO }, + /*PLAYERALLY*/ { R_NO ,R_DL ,R_AL ,R_AL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_NO, R_NO }, + /*PBIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_NO, R_DL }, + /*ABIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_AL ,R_NO ,R_DL ,R_DL ,R_NO ,R_NO ,R_DL, R_DL, R_NO } + }; + + return iEnemy[ Classify() ][ pTarget->Classify() ]; +} + +//========================================================= +// FindCover - tries to find a nearby node that will hide +// the caller from its enemy. +// +// If supplied, search will return a node at least as far +// away as MinDist, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +// UNDONE: Should this find the nearest node? + +//float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) + +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + int iThreatNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_console, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for findcover!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iThreatNode = WorldGraph.FindNearestNode ( vecThreat, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "FindCover() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + if ( iThreatNode == NO_NODE ) + { + // ALERT ( at_aiconsole, "FindCover() - Threat has no nearest node!\n" ); + iThreatNode = iMyNode; + // return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // could use an optimization here!! + flDist = ( pev->origin - node.m_vecOrigin ).Length(); + + // DON'T do the trace check on a node that is farther away than a node that we've already found to + // provide cover! Also make sure the node is within the mins/maxs of the search. + if ( flDist >= flMinDist && flDist < flMaxDist ) + { + UTIL_TraceLine ( node.m_vecOrigin + vecViewOffset, vecLookersOffset, ignore_monsters, ignore_glass, ENT(pev), &tr ); + + // if this node will block the threat's line of sight to me... + if ( tr.flFraction != 1.0 ) + { + // ..and is also closer to me than the threat, or the same distance from myself and the threat the node is good. + if ( ( iMyNode == iThreatNode ) || WorldGraph.PathLength( iMyNode, nodeNumber, iMyHullIndex, m_afCapability ) <= WorldGraph.PathLength( iThreatNode, nodeNumber, iMyHullIndex, m_afCapability ) ) + { + if ( FValidateCover ( node.m_vecOrigin ) && MoveToLocation( ACT_RUN, 0, node.m_vecOrigin ) ) + { + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( node.m_vecOrigin.x ); + WRITE_COORD( node.m_vecOrigin.y ); + WRITE_COORD( node.m_vecOrigin.z ); + + WRITE_COORD( vecLookersOffset.x ); + WRITE_COORD( vecLookersOffset.y ); + WRITE_COORD( vecLookersOffset.z ); + MESSAGE_END(); + */ + + return TRUE; + } + } + } + } + } + return FALSE; +} + + +//========================================================= +// BuildNearestRoute - tries to build a route as close to the target +// as possible, even if there isn't a path to the final point. +// +// If supplied, search will return a node at least as far +// away as MinDist from vecThreat, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_console, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for BuildNearestRoute!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "BuildNearestRoute() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // can I get there? + if (WorldGraph.NextNodeInRoute( iMyNode, nodeNumber, iMyHullIndex, 0 ) != iMyNode) + { + flDist = ( vecThreat - node.m_vecOrigin ).Length(); + + // is it close? + if ( flDist > flMinDist && flDist < flMaxDist) + { + // can I see where I want to be from there? + UTIL_TraceLine( node.m_vecOrigin + pev->view_ofs, vecLookersOffset, ignore_monsters, edict(), &tr ); + + if (tr.flFraction == 1.0) + { + // try to actually get there + if ( BuildRoute ( node.m_vecOrigin, bits_MF_TO_LOCATION, NULL ) ) + { + flMaxDist = flDist; + m_vecMoveGoal = node.m_vecOrigin; + return TRUE; // UNDONE: keep looking for something closer! + } + } + } + } + } + + return FALSE; +} + + + +//========================================================= +// BestVisibleEnemy - this functions searches the link +// list whose head is the caller's m_pLink field, and returns +// a pointer to the enemy entity in that list that is nearest the +// caller. +// +// !!!UNDONE - currently, this only returns the closest enemy. +// we'll want to consider distance, relationship, attack types, back turned, etc. +//========================================================= +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) +{ + CBaseEntity *pReturn; + CBaseEntity *pNextEnt; + int iNearest; + int iDist; + int iBestRelationship; + + iNearest = 8192;// so first visible entity will become the closest. + pNextEnt = m_pLink; + pReturn = NULL; + iBestRelationship = R_NO; + + while ( pNextEnt != NULL ) + { + if ( pNextEnt->IsAlive() ) + { + if ( IRelationship( pNextEnt) > iBestRelationship ) + { + // this entity is disliked MORE than the entity that we + // currently think is the best visible enemy. No need to do + // a distance check, just get mad at this one for now. + iBestRelationship = IRelationship ( pNextEnt ); + iNearest = ( pNextEnt->pev->origin - pev->origin ).Length(); + pReturn = pNextEnt; + } + else if ( IRelationship( pNextEnt) == iBestRelationship ) + { + // this entity is disliked just as much as the entity that + // we currently think is the best visible enemy, so we only + // get mad at it if it is closer. + iDist = ( pNextEnt->pev->origin - pev->origin ).Length(); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = IRelationship ( pNextEnt ); + pReturn = pNextEnt; + } + } + } + + pNextEnt = pNextEnt->m_pLink; + } + + return pReturn; +} + + +//========================================================= +// MakeIdealYaw - gets a yaw value for the caller that would +// face the supplied vector. Value is stuffed into the monster's +// ideal_yaw +//========================================================= +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) +{ + Vector vecProjection; + + // strafing monster needs to face 90 degrees away from its goal + if ( m_movementActivity == ACT_STRAFE_LEFT ) + { + vecProjection.x = -vecTarget.y; + vecProjection.y = vecTarget.x; + + pev->ideal_yaw = UTIL_VecToYaw( vecProjection - pev->origin ); + } + else if ( m_movementActivity == ACT_STRAFE_RIGHT ) + { + vecProjection.x = vecTarget.y; + vecProjection.y = vecTarget.x; + + pev->ideal_yaw = UTIL_VecToYaw( vecProjection - pev->origin ); + } + else + { + pev->ideal_yaw = UTIL_VecToYaw ( vecTarget - pev->origin ); + } +} + +//========================================================= +// FlYawDiff - returns the difference ( in degrees ) between +// monster's current yaw and ideal_yaw +// +// Positive result is left turn, negative is right turn +//========================================================= +float CBaseMonster::FlYawDiff ( void ) +{ + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + + if ( flCurrentYaw == pev->ideal_yaw ) + { + return 0; + } + + + return UTIL_AngleDiff( pev->ideal_yaw, flCurrentYaw ); +} + + +//========================================================= +// Changeyaw - turns a monster towards its ideal_yaw +//========================================================= +float CBaseMonster::ChangeYaw ( int yawSpeed ) +{ + float ideal, current, move, speed; + + current = UTIL_AngleMod( pev->angles.y ); + ideal = pev->ideal_yaw; + if (current != ideal) + { + speed = (float)yawSpeed * gpGlobals->frametime * 10; + move = ideal - current; + + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + + if (move > 0) + {// turning to the monster's left + if (move > speed) + move = speed; + } + else + {// turning to the monster's right + if (move < -speed) + move = -speed; + } + + pev->angles.y = UTIL_AngleMod (current + move); + + // turn head in desired direction only if they have a turnable head + if (m_afCapability & bits_CAP_TURN_HEAD) + { + float yaw = pev->ideal_yaw - pev->angles.y; + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + // yaw *= 0.8; + SetBoneController( 0, yaw ); + } + } + else + move = 0; + + return move; +} + +//========================================================= +// VecToYaw - turns a directional vector into a yaw value +// that points down that vector. +//========================================================= +float CBaseMonster::VecToYaw ( Vector vecDir ) +{ + if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0) + return pev->angles.y; + + return UTIL_VecToYaw( vecDir ); +} + + +//========================================================= +// SetEyePosition +// +// queries the monster's model for $eyeposition and copies +// that vector to the monster's view_ofs +// +//========================================================= +void CBaseMonster :: SetEyePosition ( void ) +{ + Vector vecEyePosition; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetEyePosition( pmodel, vecEyePosition ); + + pev->view_ofs = vecEyePosition; + + if ( pev->view_ofs == g_vecZero ) + { + ALERT ( at_aiconsole, "%s has no view_ofs!\n", STRING ( pev->classname ) ); + } +} + +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCRIPT_EVENT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_DYING; + // Kill me now! (and fade out when CineCleanup() is called) +#if _DEBUG + ALERT( at_aiconsole, "Death event: %s\n", STRING(pev->classname) ); +#endif + pev->health = 0; + } +#if _DEBUG + else + ALERT( at_aiconsole, "INVALID death event:%s\n", STRING(pev->classname) ); +#endif + break; + case SCRIPT_EVENT_NOT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_NO; + // This is for life/death sequences where the player can determine whether a character is dead or alive after the script + pev->health = pev->max_health; + } + break; + + case SCRIPT_EVENT_SOUND: // Play a named wave file + EMIT_SOUND( edict(), CHAN_BODY, pEvent->options, 1.0, ATTN_IDLE ); + break; + + case SCRIPT_EVENT_SOUND_VOICE: + EMIT_SOUND( edict(), CHAN_VOICE, pEvent->options, 1.0, ATTN_IDLE ); + break; + + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time + if (RANDOM_LONG(0,2) == 0) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, ATTN_IDLE, 0, 100 ); + break; + + case SCRIPT_EVENT_FIREEVENT: // Fire a trigger + FireTargets( pEvent->options, this, this, USE_TOGGLE, 0 ); + break; + + case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on + if ( m_pCine ) + m_pCine->AllowInterrupt( FALSE ); + break; + + case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now + if ( m_pCine ) + m_pCine->AllowInterrupt( TRUE ); + break; + +#if 0 + case SCRIPT_EVENT_INAIR: // Don't DROP_TO_FLOOR() + case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to + break; +#endif + + case MONSTER_EVENT_BODYDROP_HEAVY: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM, 0, 90 ); + } + else + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM, 0, 90 ); + } + } + break; + + case MONSTER_EVENT_BODYDROP_LIGHT: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM ); + } + } + break; + + case MONSTER_EVENT_SWISHSOUND: + { + // NO MONSTER may use this anim event unless that monster's precache precaches this sound!!! + EMIT_SOUND( ENT(pev), CHAN_BODY, "zombie/claw_miss2.wav", 1, ATTN_NORM ); + break; + } + + default: + ALERT( at_aiconsole, "Unhandled animation event %d for %s\n", pEvent->event, STRING(pev->classname) ); + break; + + } +} + + +// Combat + +Vector CBaseMonster :: GetGunPosition( ) +{ + UTIL_MakeVectors(pev->angles); + + // Vector vecSrc = pev->origin + gpGlobals->v_forward * 10; + //vecSrc.z = pevShooter->absmin.z + pevShooter->size.z * 0.7; + //vecSrc.z = pev->origin.z + (pev->view_ofs.z - 4); + Vector vecSrc = pev->origin + + gpGlobals->v_forward * m_HackedGunPos.y + + gpGlobals->v_right * m_HackedGunPos.x + + gpGlobals->v_up * m_HackedGunPos.z; + + return vecSrc; +} + + + + + +//========================================================= +// NODE GRAPH +//========================================================= + + + + + +//========================================================= +// FGetNodeRoute - tries to build an entire node path from +// the callers origin to the passed vector. If this is +// possible, ROUTE_SIZE waypoints will be copied into the +// callers m_Route. TRUE is returned if the operation +// succeeds (path is valid) or FALSE if failed (no path +// exists ) +//========================================================= +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) +{ + int iPath[ MAX_PATH_SIZE ]; + int iSrcNode, iDestNode; + int iResult; + int i; + int iNumToCopy; + + iSrcNode = WorldGraph.FindNearestNode ( pev->origin, this ); + iDestNode = WorldGraph.FindNearestNode ( vecDest, this ); + + if ( iSrcNode == -1 ) + { + // no node nearest self +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near self!\n" ); + return FALSE; + } + else if ( iDestNode == -1 ) + { + // no node nearest target +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near target!\n" ); + return FALSE; + } + + // valid src and dest nodes were found, so it's safe to proceed with + // find shortest path + int iNodeHull = WorldGraph.HullIndex( this ); // make this a monster virtual function + iResult = WorldGraph.FindShortestPath ( iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability ); + + if ( !iResult ) + { +#if 1 + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; +#else + BOOL bRoutingSave = WorldGraph.m_fRoutingComplete; + WorldGraph.m_fRoutingComplete = FALSE; + iResult = WorldGraph.FindShortestPath(iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability); + WorldGraph.m_fRoutingComplete = bRoutingSave; + if ( !iResult ) + { + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; + } + else + { + ALERT ( at_aiconsole, "Routing is inconsistent!" ); + } +#endif + } + + // there's a valid path within iPath now, so now we will fill the route array + // up with as many of the waypoints as it will hold. + + // don't copy ROUTE_SIZE entries if the path returned is shorter + // than ROUTE_SIZE!!! + if ( iResult < ROUTE_SIZE ) + { + iNumToCopy = iResult; + } + else + { + iNumToCopy = ROUTE_SIZE; + } + + for ( i = 0 ; i < iNumToCopy; i++ ) + { + m_Route[ i ].vecLocation = WorldGraph.m_pNodes[ iPath[ i ] ].m_vecOrigin; + m_Route[ i ].iType = bits_MF_TO_NODE; + } + + if ( iNumToCopy < ROUTE_SIZE ) + { + m_Route[ iNumToCopy ].vecLocation = vecDest; + m_Route[ iNumToCopy ].iType |= bits_MF_IS_GOAL; + } + + return TRUE; +} + +//========================================================= +// FindHintNode +//========================================================= +int CBaseMonster :: FindHintNode ( void ) +{ + int i; + TraceResult tr; + + if ( !WorldGraph.m_fGraphPresent ) + { + ALERT ( at_aiconsole, "find_hintnode: graph not ready!\n" ); + return NO_NODE; + } + + if ( WorldGraph.m_iLastActiveIdleSearch >= WorldGraph.m_cNodes ) + { + WorldGraph.m_iLastActiveIdleSearch = 0; + } + + for ( i = 0; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastActiveIdleSearch) % WorldGraph.m_cNodes; + CNode &node = WorldGraph.Node( nodeNumber ); + + if ( node.m_sHintType ) + { + // this node has a hint. Take it if it is visible, the monster likes it, and the monster has an animation to match the hint's activity. + if ( FValidateHintType ( node.m_sHintType ) ) + { + if ( !node.m_sHintActivity || LookupActivity ( node.m_sHintActivity ) != ACTIVITY_NOT_AVAILABLE ) + { + UTIL_TraceLine ( pev->origin + pev->view_ofs, node.m_vecOrigin + pev->view_ofs, ignore_monsters, ENT(pev), &tr ); + + if ( tr.flFraction == 1.0 ) + { + WorldGraph.m_iLastActiveIdleSearch = nodeNumber + 1; // next monster that searches for hint nodes will start where we left off. + return nodeNumber;// take it! + } + } + } + } + } + + WorldGraph.m_iLastActiveIdleSearch = 0;// start at the top of the list for the next search. + + return NO_NODE; +} + + +void CBaseMonster::ReportAIState( void ) +{ + ALERT_TYPE level = at_console; + + static const char *pStateNames[] = { "None", "Idle", "Combat", "Alert", "Hunt", "Prone", "Scripted", "Dead" }; + + ALERT( level, "%s: ", STRING(pev->classname) ); + if ( (int)m_MonsterState < ARRAYSIZE(pStateNames) ) + ALERT( level, "State: %s, ", pStateNames[m_MonsterState] ); + int i = 0; + while ( activity_map[i].type != 0 ) + { + if ( activity_map[i].type == (int)m_Activity ) + { + ALERT( level, "Activity %s, ", activity_map[i].name ); + break; + } + i++; + } + + if ( m_pSchedule ) + { + const char *pName = NULL; + pName = m_pSchedule->pName; + if ( !pName ) + pName = "Unknown"; + ALERT( level, "Schedule %s, ", pName ); + Task_t *pTask = GetTask(); + if ( pTask ) + ALERT( level, "Task %d (#%d), ", pTask->iTask, m_iScheduleIndex ); + } + else + ALERT( level, "No Schedule, " ); + + if ( m_hEnemy != NULL ) + ALERT( level, "\nEnemy is %s", STRING(m_hEnemy->pev->classname) ); + else + ALERT( level, "No enemy" ); + + if ( IsMoving() ) + { + ALERT( level, " Moving " ); + if ( m_flMoveWaitFinished > gpGlobals->time ) + ALERT( level, ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->time ); + else if ( m_IdealActivity == GetStoppedActivity() ) + ALERT( level, ": In stopped anim. " ); + } + + ALERT( level, "\n" ); + ALERT( level, "Yaw speed:%3.1f,Health: %3.1f\n", pev->yaw_speed, pev->health ); + if ( pev->spawnflags & SF_MONSTER_PRISONER ) + ALERT( level, " PRISONER! " ); + if ( pev->spawnflags & SF_MONSTER_PREDISASTER ) + ALERT( level, " Pre-Disaster! " ); + ALERT( level, "\n" ); +} + +//========================================================= +// KeyValue +// +// !!! netname entvar field is used in squadmonster for groupname!!! +//========================================================= +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "TriggerTarget")) + { + m_iszTriggerTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TriggerCondition") ) + { + m_iTriggerCondition = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseToggle::KeyValue( pkvd ); + } +} + +//========================================================= +// FCheckAITrigger - checks the monster's AI Trigger Conditions, +// if there is a condition, then checks to see if condition is +// met. If yes, the monster's TriggerTarget is fired. +// +// Returns TRUE if the target is fired. +//========================================================= +BOOL CBaseMonster :: FCheckAITrigger ( void ) +{ + BOOL fFireTarget; + + if ( m_iTriggerCondition == AITRIGGER_NONE ) + { + // no conditions, so this trigger is never fired. + return FALSE; + } + + fFireTarget = FALSE; + + switch ( m_iTriggerCondition ) + { + case AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER: + if ( m_hEnemy != NULL && m_hEnemy->IsPlayer() && HasConditions ( bits_COND_SEE_ENEMY ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_UNCONDITIONAL: + if ( HasConditions ( bits_COND_SEE_CLIENT ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_NOT_IN_COMBAT: + if ( HasConditions ( bits_COND_SEE_CLIENT ) && + m_MonsterState != MONSTERSTATE_COMBAT && + m_MonsterState != MONSTERSTATE_PRONE && + m_MonsterState != MONSTERSTATE_SCRIPT) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_TAKEDAMAGE: + if ( m_afConditions & ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_DEATH: + if ( pev->deadflag != DEAD_NO ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HALFHEALTH: + if ( IsAlive() && pev->health <= ( pev->max_health / 2 ) ) + { + fFireTarget = TRUE; + } + break; +/* + + // !!!UNDONE - no persistant game state that allows us to track these two. + + case AITRIGGER_SQUADMEMBERDIE: + break; + case AITRIGGER_SQUADLEADERDIE: + break; +*/ + case AITRIGGER_HEARWORLD: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_WORLD ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARPLAYER: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_PLAYER ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARCOMBAT: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_COMBAT ) + { + fFireTarget = TRUE; + } + break; + } + + if ( fFireTarget ) + { + // fire the target, then set the trigger conditions to NONE so we don't fire again + ALERT ( at_aiconsole, "AI Trigger Fire Target\n" ); + FireTargets( STRING( m_iszTriggerTarget ), this, this, USE_TOGGLE, 0 ); + m_iTriggerCondition = AITRIGGER_NONE; + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CanPlaySequence - determines whether or not the monster +// can play the scripted sequence or AI sequence that is +// trying to possess it. If DisregardState is set, the monster +// will be sucked into the script no matter what state it is +// in. ONLY Scripted AI ents should allow this. +//========================================================= +int CBaseMonster :: CanPlaySequence( BOOL fDisregardMonsterState, int interruptLevel ) +{ + if ( m_pCine || !IsAlive() || m_MonsterState == MONSTERSTATE_PRONE ) + { + // monster is already running a scripted sequence or dead! + return FALSE; + } + + if ( fDisregardMonsterState ) + { + // ok to go, no matter what the monster state. (scripted AI) + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE ) + { + // ok to go, but only in these states + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_ALERT && interruptLevel >= SS_INTERRUPT_BY_NAME ) + return TRUE; + + // unknown situation + return FALSE; +} + + +//========================================================= +// FindLateralCover - attempts to locate a spot in the world +// directly to the left or right of the caller that will +// conceal them from view of pSightEnt +//========================================================= +#define COVER_CHECKS 5// how many checks are made +#define COVER_DELTA 48// distance between checks + +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) +{ + TraceResult tr; + Vector vecBestOnLeft; + Vector vecBestOnRight; + Vector vecLeftTest; + Vector vecRightTest; + Vector vecStepRight; + int i; + + UTIL_MakeVectors ( pev->angles ); + vecStepRight = gpGlobals->v_right * COVER_DELTA; + vecStepRight.z = 0; + + vecLeftTest = vecRightTest = pev->origin; + + for ( i = 0 ; i < COVER_CHECKS ; i++ ) + { + vecLeftTest = vecLeftTest - vecStepRight; + vecRightTest = vecRightTest + vecStepRight; + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine( vecThreat + vecViewOffset, vecLeftTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + if ( FValidateCover ( vecLeftTest ) && CheckLocalMove( pev->origin, vecLeftTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecLeftTest ) ) + { + return TRUE; + } + } + } + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine(vecThreat + vecViewOffset, vecRightTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if ( tr.flFraction != 1.0 ) + { + if ( FValidateCover ( vecRightTest ) && CheckLocalMove( pev->origin, vecRightTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecRightTest ) ) + { + return TRUE; + } + } + } + } + + return FALSE; +} + + +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) +{ + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy ) + { + return ( (pEnemy->BodyTarget( shootOrigin ) - pEnemy->pev->origin) + m_vecEnemyLKP - shootOrigin ).Normalize(); + } + else + return gpGlobals->v_forward; +} + + + +//========================================================= +// FacingIdeal - tells us if a monster is facing its ideal +// yaw. Created this function because many spots in the +// code were checking the yawdiff against this magic +// number. Nicer to have it in one place if we're gonna +// be stuck with it. +//========================================================= +BOOL CBaseMonster :: FacingIdeal( void ) +{ + if ( fabs( FlYawDiff() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!! + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CBaseMonster :: FCanActiveIdle ( void ) +{ + /* + if ( m_MonsterState == MONSTERSTATE_IDLE && m_IdealMonsterState == MONSTERSTATE_IDLE && !IsMoving() ) + { + return TRUE; + } + */ + return FALSE; +} + + +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( pszSentence && IsAlive() ) + { + if ( pszSentence[0] == '!' ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, PITCH_NORM ); + else + SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, PITCH_NORM ); + } +} + + +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + PlaySentence( pszSentence, duration, volume, attenuation ); +} + + +void CBaseMonster::SentenceStop( void ) +{ + EMIT_SOUND( edict(), CHAN_VOICE, "common/null.wav", 1.0, ATTN_IDLE ); +} + + +void CBaseMonster::CorpseFallThink( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + SetThink ( NULL ); + + SetSequenceBox( ); + UTIL_SetOrigin( pev, pev->origin );// link into world. + } + else + pev->nextthink = gpGlobals->time + 0.1; +} + +// Call after animation/pose is set up +void CBaseMonster :: MonsterInitDead( void ) +{ + InitBoneControllers(); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_TOSS;// so he'll fall to ground + + pev->frame = 0; + ResetSequenceInfo( ); + pev->framerate = 0; + + // Copy health + pev->max_health = pev->health; + pev->deadflag = DEAD_DEAD; + + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + UTIL_SetOrigin( pev, pev->origin ); + + // Setup health counters, etc. + BecomeDead(); + SetThink( CorpseFallThink ); + pev->nextthink = gpGlobals->time + 0.5; +} + +//========================================================= +// BBoxIsFlat - check to see if the monster's bounding box +// is lying flat on a surface (traces from all four corners +// are same length.) +//========================================================= +BOOL CBaseMonster :: BBoxFlat ( void ) +{ + TraceResult tr; + Vector vecPoint; + float flXSize, flYSize; + float flLength; + float flLength2; + + flXSize = pev->size.x / 2; + flYSize = pev->size.y / 2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y + flYSize; + vecPoint.z = pev->origin.z; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength = (vecPoint - tr.vecEndPos).Length(); + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y - flYSize; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y + flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y - flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + return TRUE; +} + +//========================================================= +// Get Enemy - tries to find the best suitable enemy for the monster. +//========================================================= +BOOL CBaseMonster :: GetEnemy ( void ) +{ + CBaseEntity *pNewEnemy; + + if ( HasConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_NEMESIS) ) + { + pNewEnemy = BestVisibleEnemy(); + + if ( pNewEnemy != m_hEnemy && pNewEnemy != NULL) + { + // DO NOT mess with the monster's m_hEnemy pointer unless the schedule the monster is currently running will be interrupted + // by COND_NEW_ENEMY. This will eliminate the problem of monsters getting a new enemy while they are in a schedule that doesn't care, + // and then not realizing it by the time they get to a schedule that does. I don't feel this is a good permanent fix. + + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + PushEnemy( m_hEnemy, m_vecEnemyLKP ); + SetConditions(bits_COND_NEW_ENEMY); + m_hEnemy = pNewEnemy; + m_vecEnemyLKP = m_hEnemy->pev->origin; + } + // if the new enemy has an owner, take that one as well + if (pNewEnemy->pev->owner != NULL) + { + CBaseEntity *pOwner = GetMonsterPointer( pNewEnemy->pev->owner ); + if ( pOwner && (pOwner->pev->flags & FL_MONSTER) && IRelationship( pOwner ) != R_NO ) + PushEnemy( pOwner, m_vecEnemyLKP ); + } + } + } + } + + // remember old enemies + if (m_hEnemy == NULL && PopEnemy( )) + { + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + SetConditions(bits_COND_NEW_ENEMY); + } + } + } + + if ( m_hEnemy != NULL ) + { + // monster has an enemy. + return TRUE; + } + + return FALSE;// monster has no enemy +} + + +//========================================================= +// DropItem - dead monster drops named item +//========================================================= +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) +{ + if ( !pszItemName ) + { + ALERT ( at_console, "DropItem() - No item name!\n" ); + return NULL; + } + + CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, edict() ); + + if ( pItem ) + { + // do we want this behavior to be default?! (sjb) + pItem->pev->velocity = pev->velocity; + pItem->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 0, 100 ), 0 ); + return pItem; + } + else + { + ALERT ( at_console, "DropItem() - Didn't create!\n" ); + return FALSE; + } + +} + + +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) +{ + // if flagged to fade out or I have an owner (I came from a monster spawner) + if ( (pev->spawnflags & SF_MONSTER_FADECORPSE) || !FNullEnt( pev->owner ) ) + return TRUE; + + return FALSE; +} diff --git a/dlls/monsters.h b/dlls/monsters.h index b0f5d51..c9e0bda 100644 --- a/dlls/monsters.h +++ b/dlls/monsters.h @@ -6,14 +6,13 @@ * 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. +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. * ****/ #ifndef MONSTERS_H -#include "skill.h" #define MONSTERS_H /* @@ -24,6 +23,11 @@ */ +// CHECKLOCALMOVE result types +#define LOCALMOVE_INVALID 0 // move is not possible +#define LOCALMOVE_INVALID_DONT_TRIANGULATE 1 // move is not possible, don't try to triangulate +#define LOCALMOVE_VALID 2 // move is possible + // Hit Group standards #define HITGROUP_GENERIC 0 #define HITGROUP_HEAD 1 @@ -34,6 +38,34 @@ #define HITGROUP_LEFTLEG 6 #define HITGROUP_RIGHTLEG 7 +// Monster Spawnflags +#define SF_MONSTER_WAIT_TILL_SEEN 1// spawnflag that makes monsters wait until player can see them before attacking. +#define SF_MONSTER_GAG 2 // no idle noises from this monster +#define SF_MONSTER_HITMONSTERCLIP 4 +// 8 +#define SF_MONSTER_PRISONER 16 // monster won't attack anyone, no one will attacke him. +// 32 +// 64 +#define SF_MONSTER_WAIT_FOR_SCRIPT 128 //spawnflag that makes monsters wait to check for attacking until the script is done or they've been attacked +#define SF_MONSTER_PREDISASTER 256 //this is a predisaster scientist or barney. Influences how they speak. +#define SF_MONSTER_FADECORPSE 512 // Fade out corpse after death +#define SF_MONSTER_FALL_TO_GROUND 0x80000000 + +// specialty spawnflags +#define SF_MONSTER_TURRET_AUTOACTIVATE 32 +#define SF_MONSTER_TURRET_STARTINACTIVE 64 +#define SF_MONSTER_WAIT_UNTIL_PROVOKED 64 // don't attack the player unless provoked + + + +// MoveToOrigin stuff +#define MOVE_START_TURN_DIST 64 // when this far away from moveGoal, start turning to face next goal +#define MOVE_STUCK_DIST 32 // if a monster can't step this far, it is stuck. + + +// MoveToOrigin stuff +#define MOVE_NORMAL 0// normal move in the direction monster is facing +#define MOVE_STRAFE 1// moves in direction specified, no matter which way monster is facing // spawn flags 256 and above are already taken by the engine extern void UTIL_MoveToOrigin( edict_t* pent, const Vector &vecGoal, float flDist, int iMoveType ); @@ -59,7 +91,50 @@ BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTarget #define R_NM 3// (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what +// these bits represent the monster's memory +#define MEMORY_CLEAR 0 +#define bits_MEMORY_PROVOKED ( 1 << 0 )// right now only used for houndeyes. +#define bits_MEMORY_INCOVER ( 1 << 1 )// monster knows it is in a covered position. +#define bits_MEMORY_SUSPICIOUS ( 1 << 2 )// Ally is suspicious of the player, and will move to provoked more easily +#define bits_MEMORY_PATH_FINISHED ( 1 << 3 )// Finished monster path (just used by big momma for now) +#define bits_MEMORY_ON_PATH ( 1 << 4 )// Moving on a path +#define bits_MEMORY_MOVE_FAILED ( 1 << 5 )// Movement has already failed +#define bits_MEMORY_FLINCHED ( 1 << 6 )// Has already flinched #define bits_MEMORY_KILLED ( 1 << 7 )// HACKHACK -- remember that I've already called my Killed() +#define bits_MEMORY_CUSTOM4 ( 1 << 28 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM3 ( 1 << 29 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM2 ( 1 << 30 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM1 ( 1 << 31 ) // Monster-specific memory + +// trigger conditions for scripted AI +// these MUST match the CHOICES interface in halflife.fgd for the base monster +enum +{ + AITRIGGER_NONE = 0, + AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER, + AITRIGGER_TAKEDAMAGE, + AITRIGGER_HALFHEALTH, + AITRIGGER_DEATH, + AITRIGGER_SQUADMEMBERDIE, + AITRIGGER_SQUADLEADERDIE, + AITRIGGER_HEARWORLD, + AITRIGGER_HEARPLAYER, + AITRIGGER_HEARCOMBAT, + AITRIGGER_SEEPLAYER_UNCONDITIONAL, + AITRIGGER_SEEPLAYER_NOT_IN_COMBAT, +}; +/* + 0 : "No Trigger" + 1 : "See Player" + 2 : "Take Damage" + 3 : "50% Health Remaining" + 4 : "Death" + 5 : "Squad Member Dead" + 6 : "Squad Leader Dead" + 7 : "Hear World" + 8 : "Hear Player" + 9 : "Hear Combat" +*/ // // A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. @@ -85,4 +160,10 @@ public: }; +#define CUSTOM_SCHEDULES\ + static Schedule_t *m_scheduleList[]; + +#define DEFINE_CUSTOM_SCHEDULES(derivedClass)\ + Schedule_t *derivedClass::m_scheduleList[] = + #endif //MONSTERS_H diff --git a/dlls/mortar.cpp b/dlls/mortar.cpp index eb44416..8928e44 100644 --- a/dlls/mortar.cpp +++ b/dlls/mortar.cpp @@ -284,7 +284,6 @@ void CMortar::MortarExplode( void ) EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "weapons/mortarhit.wav", 1.0, 0.55, 0, pitch); // ForceSound( SNDRADIUS_MP5, bits_SOUND_COMBAT ); - // ExplodeModel( pev->origin, 400, g_sModelIndexShrapnel, 30 ); RadiusDamage ( pev, VARS(pev->owner), pev->dmg, CLASS_NONE, DMG_BLAST ); diff --git a/dlls/nodes.cpp b/dlls/nodes.cpp new file mode 100644 index 0000000..e2ed502 --- /dev/null +++ b/dlls/nodes.cpp @@ -0,0 +1,3640 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.cpp - AI node tree stuff. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "nodes.h" +#include "animation.h" +#include "doors.h" + +#define HULL_STEP_SIZE 16// how far the test hull moves on each step +#define NODE_HEIGHT 8 // how high to lift nodes off the ground after we drop them all (make stair/ramp mapping easier) + +// to help eliminate node clutter by level designers, this is used to cap how many other nodes +// any given node is allowed to 'see' in the first stage of graph creation "LinkVisibleNodes()". +#define MAX_NODE_INITIAL_LINKS 128 +#define MAX_NODES 1024 + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +CGraph WorldGraph; + +LINK_ENTITY_TO_CLASS( info_node, CNodeEnt ); +LINK_ENTITY_TO_CLASS( info_node_air, CNodeEnt ); +#ifdef __linux__ +#include +#define CreateDirectory(p, n) mkdir(p, 0777) +#endif +//========================================================= +// CGraph - InitGraph - prepares the graph for use. Frees any +// memory currently in use by the world graph, NULLs +// all pointers, and zeros the node count. +//========================================================= +void CGraph :: InitGraph( void) +{ + + // Make the graph unavailable + // + m_fGraphPresent = FALSE; + m_fGraphPointersSet = FALSE; + m_fRoutingComplete = FALSE; + + // Free the link pool + // + if ( m_pLinkPool ) + { + free ( m_pLinkPool ); + m_pLinkPool = NULL; + } + + // Free the node info + // + if ( m_pNodes ) + { + free ( m_pNodes ); + m_pNodes = NULL; + } + + if ( m_di ) + { + free ( m_di ); + m_di = NULL; + } + + // Free the routing info. + // + if ( m_pRouteInfo ) + { + free ( m_pRouteInfo ); + m_pRouteInfo = NULL; + } + + if (m_pHashLinks) + { + free(m_pHashLinks); + m_pHashLinks = NULL; + } + + // Zero node and link counts + // + m_cNodes = 0; + m_cLinks = 0; + m_nRouteInfo = 0; + + m_iLastActiveIdleSearch = 0; + m_iLastCoverSearch = 0; +} + +//========================================================= +// CGraph - AllocNodes - temporary function that mallocs a +// reasonable number of nodes so we can build the path which +// will be saved to disk. +//========================================================= +int CGraph :: AllocNodes ( void ) +{ +// malloc all of the nodes + WorldGraph.m_pNodes = (CNode *)calloc ( sizeof ( CNode ), MAX_NODES ); + +// could not malloc space for all the nodes! + if ( !WorldGraph.m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", WorldGraph.m_cNodes ); + return FALSE; + } + + return TRUE; +} + +//========================================================= +// CGraph - LinkEntForLink - sometimes the ent that blocks +// a path is a usable door, in which case the monster just +// needs to face the door and fire it. In other cases, the +// monster needs to operate a button or lever to get the +// door to open. This function will return a pointer to the +// button if the monster needs to hit a button to open the +// door, or returns a pointer to the door if the monster +// need only use the door. +// +// pNode is the node the monster will be standing on when it +// will need to stop and trigger the ent. +//========================================================= +entvars_t* CGraph :: LinkEntForLink ( CLink *pLink, CNode *pNode ) +{ + edict_t *pentSearch; + edict_t *pentTrigger; + entvars_t *pevTrigger; + entvars_t *pevLinkEnt; + TraceResult tr; + + pevLinkEnt = pLink->m_pLinkEnt; + if ( !pevLinkEnt ) + return NULL; + + pentSearch = NULL;// start search at the top of the ent list. + + if ( FClassnameIs ( pevLinkEnt, "func_door" ) || FClassnameIs ( pevLinkEnt, "func_door_rotating" ) ) + { + + ///!!!UNDONE - check for TOGGLE or STAY open doors here. If a door is in the way, and is + // TOGGLE or STAY OPEN, even monsters that can't open doors can go that way. + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only, so the door is all the monster has to worry about + return pevLinkEnt; + } + + while ( 1 ) + { + pentTrigger = FIND_ENTITY_BY_TARGET ( pentSearch, STRING( pevLinkEnt->targetname ) );// find the button or trigger + + if ( FNullEnt( pentTrigger ) ) + {// no trigger found + + // right now this is a problem among auto-open doors, or any door that opens through the use + // of a trigger brush. Trigger brushes have no models, and don't show up in searches. Just allow + // monsters to open these sorts of doors for now. + return pevLinkEnt; + } + + pentSearch = pentTrigger; + pevTrigger = VARS( pentTrigger ); + + if ( FClassnameIs(pevTrigger, "func_button") || FClassnameIs(pevTrigger, "func_rot_button" ) ) + {// only buttons are handled right now. + + // trace from the node to the trigger, make sure it's one we can see from the node. + // !!!HACKHACK Use bodyqueue here cause there are no ents we really wish to ignore! + UTIL_TraceLine ( pNode->m_vecOrigin, VecBModelOrigin( pevTrigger ), ignore_monsters, g_pBodyQueueHead, &tr ); + + + if ( VARS(tr.pHit) == pevTrigger ) + {// good to go! + return VARS( tr.pHit ); + } + } + } + } + else + { + ALERT ( at_aiconsole, "Unsupported PathEnt:\n'%s'\n", STRING ( pevLinkEnt->classname ) ); + return NULL; + } +} + +//========================================================= +// CGraph - HandleLinkEnt - a brush ent is between two +// nodes that would otherwise be able to see each other. +// Given the monster's capability, determine whether +// or not the monster can go this way. +//========================================================= +int CGraph :: HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ) +{ + edict_t *pentWorld; + CBaseEntity *pDoor; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( FNullEnt ( pevLinkEnt ) ) + { + ALERT ( at_aiconsole, "dead path ent!\n" ); + return TRUE; + } + pentWorld = NULL; + +// func_door + if ( FClassnameIs( pevLinkEnt, "func_door" ) || FClassnameIs( pevLinkEnt, "func_door_rotating" ) ) + {// ent is a door. + + pDoor = ( CBaseEntity::Instance( pevLinkEnt ) ); + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only. + + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + {// let monster right through if he can open doors + return TRUE; + } + else + { + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState()== TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + + return FALSE; + } + } + else + {// door must be opened with a button or trigger field. + + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState() == TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + { + if ( !( pevLinkEnt->spawnflags & SF_DOOR_NOMONSTERS ) || queryType == NODEGRAPH_STATIC ) + return TRUE; + } + + return FALSE; + } + } +// func_breakable + else if ( FClassnameIs( pevLinkEnt, "func_breakable" ) && queryType == NODEGRAPH_STATIC ) + { + return TRUE; + } + else + { + ALERT ( at_aiconsole, "Unhandled Ent in Path %s\n", STRING( pevLinkEnt->classname ) ); + return FALSE; + } + + return FALSE; +} + +#if 0 +//========================================================= +// FindNearestLink - finds the connection (line) nearest +// the given point. Returns FALSE if fails, or TRUE if it +// has stuffed the index into the nearest link pool connection +// into the passed int pointer, and a BOOL telling whether or +// not the point is along the line into the passed BOOL pointer. +//========================================================= +int CGraph :: FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ) +{ + int i, j;// loops + + int iNearestLink;// index into the link pool, this is the nearest node at any time. + float flMinDist;// the distance of of the nearest case so far + float flDistToLine;// the distance of the current test case + + BOOL fCurrentAlongLine; + BOOL fSuccess; + + //float flConstant;// line constant + Vector vecSpot1, vecSpot2; + Vector2D vec2Spot1, vec2Spot2, vec2TestPoint; + Vector2D vec2Normal;// line normal + Vector2D vec2Line; + + TraceResult tr; + + iNearestLink = -1;// prepare for failure + fSuccess = FALSE; + + flMinDist = 9999;// anything will be closer than this + +// go through all of the nodes, and each node's connections + int cSkip = 0;// how many links proper pairing allowed us to skip + int cChecked = 0;// how many links were checked + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + vecSpot1 = m_pNodes[ i ].m_vecOrigin; + + if ( m_pNodes[ i ].m_cNumLinks <= 0 ) + {// this shouldn't happen! + ALERT ( at_aiconsole, "**Node %d has no links\n", i ); + continue; + } + + for ( j = 0 ; j < m_pNodes[ i ].m_cNumLinks ; j++ ) + { + /* + !!!This optimization only works when the node graph consists of properly linked pairs. + if ( INodeLink ( i, j ) <= i ) + { + // since we're going through the nodes in order, don't check + // any connections whose second node is lower in the list + // than the node we're currently working with. This eliminates + // redundant checks. + cSkip++; + continue; + } + */ + + vecSpot2 = PNodeLink ( i, j )->m_vecOrigin; + + // these values need a little attention now and then, or sometimes ramps cause trouble. + if ( fabs ( vecSpot1.z - vecTestPoint.z ) > 48 && fabs ( vecSpot2.z - vecTestPoint.z ) > 48 ) + { + // if both endpoints of the line are 32 units or more above or below the monster, + // the monster won't be able to get to them, so we do a bit of trivial rejection here. + // this may change if monsters are allowed to jump down. + // + // !!!LATER: some kind of clever X/Y hashing should be used here, too + continue; + } + +// now we have two endpoints for a line segment that we've not already checked. +// since all lines that make it this far are within -/+ 32 units of the test point's +// Z Plane, we can get away with doing the point->line check in 2d. + + cChecked++; + + vec2Spot1 = vecSpot1.Make2D(); + vec2Spot2 = vecSpot2.Make2D(); + vec2TestPoint = vecTestPoint.Make2D(); + + // get the line normal. + vec2Line = ( vec2Spot1 - vec2Spot2 ).Normalize(); + vec2Normal.x = -vec2Line.y; + vec2Normal.y = vec2Line.x; + + if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot1 ) ) > 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot1 ).Length(); + fCurrentAlongLine = FALSE; + } + else if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot2 ) ) < 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot2 ).Length(); + fCurrentAlongLine = FALSE; + } + else + {// point inside line + flDistToLine = fabs( DotProduct ( vec2TestPoint - vec2Spot2, vec2Normal ) ); + fCurrentAlongLine = TRUE; + } + + if ( flDistToLine < flMinDist ) + {// just found a line nearer than any other so far + + UTIL_TraceLine ( vecTestPoint, SourceNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + + if ( tr.flFraction != 1.0 ) + {// crap. can't see the first node of this link, try to see the other + + UTIL_TraceLine ( vecTestPoint, DestNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + if ( tr.flFraction != 1.0 ) + {// can't use this link, cause can't see either node! + continue; + } + + } + + fSuccess = TRUE;// we know there will be something to return. + flMinDist = flDistToLine; + iNearestLink = m_pNodes [ i ].m_iFirstLink + j; + *piNearestLink = m_pNodes[ i ].m_iFirstLink + j; + *pfAlongLine = fCurrentAlongLine; + } + } + } + +/* + if ( fSuccess ) + { + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.z + NODE_HEIGHT); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.z + NODE_HEIGHT); + } +*/ + + ALERT ( at_aiconsole, "%d Checked\n", cChecked ); + return fSuccess; +} + +#endif + +int CGraph::HullIndex( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + return NODE_FLY_HULL; + + if ( pEntity->pev->mins == Vector( -12, -12, 0 ) ) + return NODE_SMALL_HULL; + else if ( pEntity->pev->mins == VEC_HUMAN_HULL_MIN ) + return NODE_HUMAN_HULL; + else if ( pEntity->pev->mins == Vector ( -32, -32, 0 ) ) + return NODE_LARGE_HULL; + +// ALERT ( at_aiconsole, "Unknown Hull Mins!\n" ); + return NODE_HUMAN_HULL; +} + + +int CGraph::NodeType( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + { + if (pEntity->pev->waterlevel != 0) + { + return bits_NODE_WATER; + } + else + { + return bits_NODE_AIR; + } + } + return bits_NODE_LAND; +} + + +// Sum up graph weights on the path from iStart to iDest to determine path length +float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) +{ + float distance = 0; + int iNext; + + int iMaxLoop = m_cNodes; + + int iCurrentNode = iStart; + int iCap = CapIndex( afCapMask ); + + while (iCurrentNode != iDest) + { + if (iMaxLoop-- <= 0) + { + ALERT( at_console, "Route Failure\n" ); + return 0; + } + + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + } + + int iLink; + HashSearch(iCurrentNode, iNext, iLink); + if (iLink < 0) + { + ALERT(at_console, "HashLinks is broken from %d to %d.\n", iCurrentNode, iDest); + return 0; + } + CLink &link = Link(iLink); + distance += link.m_flWeight; + + iCurrentNode = iNext; + } + + return distance; +} + + +// Parse the routing table at iCurrentNode for the next node on the shortest path to iDest +int CGraph::NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ) +{ + int iNext = iCurrentNode; + int nCount = iDest+1; + char *pRoute = m_pRouteInfo + m_pNodes[ iCurrentNode ].m_pNextBestNode[iHull][iCap]; + + // Until we decode the next best node + // + while (nCount > 0) + { + char ch = *pRoute++; + //ALERT(at_aiconsole, "C(%d)", ch); + if (ch < 0) + { + // Sequence phrase + // + ch = -ch; + if (nCount <= ch) + { + iNext = iDest; + nCount = 0; + //ALERT(at_aiconsole, "SEQ: iNext/iDest=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "SEQ: nCount + ch (%d + %d)\n", nCount, ch); + nCount = nCount - ch; + } + } + else + { + //ALERT(at_aiconsole, "C(%d)", *pRoute); + + // Repeat phrase + // + if (nCount <= ch+1) + { + iNext = iCurrentNode + *pRoute; + if (iNext >= m_cNodes) iNext -= m_cNodes; + else if (iNext < 0) iNext += m_cNodes; + nCount = 0; + //ALERT(at_aiconsole, "REP: iNext=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "REP: nCount - ch+1 (%d - %d+1)\n", nCount, ch); + nCount = nCount - ch - 1; + } + pRoute++; + } + } + + return iNext; +} + + +//========================================================= +// CGraph - FindShortestPath +// +// accepts a capability mask (afCapMask), and will only +// find a path usable by a monster with those capabilities +// returns the number of nodes copied into supplied array +//========================================================= +int CGraph :: FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask) +{ + int iVisitNode; + int iCurrentNode; + int iNumPathNodes; + int iHullMask; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( iStart < 0 || iStart > m_cNodes ) + {// The start node is bad? + ALERT ( at_aiconsole, "Can't build a path, iStart is %d!\n", iStart ); + return FALSE; + } + + if (iStart == iDest) + { + piPath[0] = iStart; + piPath[1] = iDest; + return 2; + } + + // Is routing information present. + // + if (m_fRoutingComplete) + { + int iCap = CapIndex( afCapMask ); + + iNumPathNodes = 0; + piPath[iNumPathNodes++] = iStart; + iCurrentNode = iStart; + int iNext; + + //ALERT(at_aiconsole, "GOAL: %d to %d\n", iStart, iDest); + + // Until we arrive at the destination + // + while (iCurrentNode != iDest) + { + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + break; + } + if (iNumPathNodes >= MAX_PATH_SIZE) + { + //ALERT(at_aiconsole, "SVD: Don't return the entire path.\n"); + break; + } + piPath[iNumPathNodes++] = iNext; + iCurrentNode = iNext; + } + //ALERT( at_aiconsole, "SVD: Path with %d nodes.\n", iNumPathNodes); + } + else + { + CQueuePriority queue; + + switch( iHull ) + { + case NODE_SMALL_HULL: + iHullMask = bits_LINK_SMALL_HULL; + break; + case NODE_HUMAN_HULL: + iHullMask = bits_LINK_HUMAN_HULL; + break; + case NODE_LARGE_HULL: + iHullMask = bits_LINK_LARGE_HULL; + break; + case NODE_FLY_HULL: + iHullMask = bits_LINK_FLY_HULL; + break; + } + + // Mark all the nodes as unvisited. + // + for ( int i = 0; i < m_cNodes; i++) + { + m_pNodes[ i ].m_flClosestSoFar = -1.0; + } + + m_pNodes[ iStart ].m_flClosestSoFar = 0.0; + m_pNodes[ iStart ].m_iPreviousNode = iStart;// tag this as the origin node + queue.Insert( iStart, 0.0 );// insert start node + + while ( !queue.Empty() ) + { + // now pull a node out of the queue + float flCurrentDistance; + iCurrentNode = queue.Remove(flCurrentDistance); + + // For straight-line weights, the following Shortcut works. For arbitrary weights, + // it doesn't. + // + if (iCurrentNode == iDest) break; + + CNode *pCurrentNode = &m_pNodes[ iCurrentNode ]; + + for ( i = 0 ; i < pCurrentNode->m_cNumLinks ; i++ ) + {// run through all of this node's neighbors + + iVisitNode = INodeLink ( iCurrentNode, i ); + if ( ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo & iHullMask ) != iHullMask ) + {// monster is too large to walk this connection + //ALERT ( at_aiconsole, "fat ass %d/%d\n",m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo, iMonsterHull ); + continue; + } + // check the connection from the current node to the node we're about to mark visited and push into the queue + if ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt != NULL ) + {// there's a brush ent in the way! Don't mark this node or put it into the queue unless the monster can negotiate it + + if ( !HandleLinkEnt ( iCurrentNode, m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt, afCapMask, NODEGRAPH_STATIC ) ) + {// monster should not try to go this way. + continue; + } + } + float flOurDistance = flCurrentDistance + m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i].m_flWeight; + if ( m_pNodes[ iVisitNode ].m_flClosestSoFar < -0.5 + || flOurDistance < m_pNodes[ iVisitNode ].m_flClosestSoFar - 0.001 ) + { + m_pNodes[iVisitNode].m_flClosestSoFar = flOurDistance; + m_pNodes[iVisitNode].m_iPreviousNode = iCurrentNode; + + queue.Insert ( iVisitNode, flOurDistance ); + } + } + } + if ( m_pNodes[iDest].m_flClosestSoFar < -0.5 ) + {// Destination is unreachable, no path found. + return 0; + } + + // the queue is not empty + + // now we must walk backwards through the m_iPreviousNode field, and count how many connections there are in the path + iCurrentNode = iDest; + iNumPathNodes = 1;// count the dest + + while ( iCurrentNode != iStart ) + { + iNumPathNodes++; + iCurrentNode = m_pNodes[ iCurrentNode ].m_iPreviousNode; + } + + iCurrentNode = iDest; + for ( i = iNumPathNodes - 1 ; i >= 0 ; i-- ) + { + piPath[ i ] = iCurrentNode; + iCurrentNode = m_pNodes [ iCurrentNode ].m_iPreviousNode; + } + } + +#if 0 + + if (m_fRoutingComplete) + { + // This will draw the entire path that was generated for the monster. + + for ( int i = 0 ; i < iNumPathNodes - 1 ; i++ ) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); + } + } + +#endif +#if 0 // MAZE map + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); +#endif + + return iNumPathNodes; +} + +inline ULONG Hash(void *p, int len) +{ + CRC32_t ulCrc; + CRC32_INIT(&ulCrc); + CRC32_PROCESS_BUFFER(&ulCrc, p, len); + return CRC32_FINAL(ulCrc); +} + +void inline CalcBounds(int &Lower, int &Upper, int Goal, int Best) +{ + int Temp = 2*Goal - Best; + if (Best > Goal) + { + Lower = max(0, Temp); + Upper = Best; + } + else + { + Upper = min(255, Temp); + Lower = Best; + } +} + +// Convert from [-8192,8192] to [0, 255] +// +inline int CALC_RANGE(int x, int lower, int upper) +{ + return NUM_RANGES*(x-lower)/((upper-lower+1)); +} + + +void inline UpdateRange(int &minValue, int &maxValue, int Goal, int Best) +{ + int Lower, Upper; + CalcBounds(Lower, Upper, Goal, Best); + if (Upper < maxValue) maxValue = Upper; + if (minValue < Lower) minValue = Lower; +} + +void CGraph :: CheckNode(Vector vecOrigin, int iNode) +{ + // Have we already seen this point before?. + // + if (m_di[iNode].m_CheckedEvent == m_CheckedCounter) return; + m_di[iNode].m_CheckedEvent = m_CheckedCounter; + + float flDist = ( vecOrigin - m_pNodes[ iNode ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + TraceResult tr; + + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ iNode ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + m_iNearest = iNode; + m_flShortest = flDist; + + UpdateRange(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[iNode].m_Region[0]); + UpdateRange(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[iNode].m_Region[1]); + UpdateRange(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[iNode].m_Region[2]); + + // From maxCircle, calculate maximum bounds box. All points must be + // simultaneously inside all bounds of the box. + // + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]); + } + } +} + +//========================================================= +// CGraph - FindNearestNode - returns the index of the node nearest +// the given vector -1 is failure (couldn't find a valid +// near node ) +//========================================================= +int CGraph :: FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ) +{ + return FindNearestNode( vecOrigin, NodeType( pEntity ) ); +} + +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) +{ + int i; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return -1; + } + + // Check with the cache + // + ULONG iHash = (CACHE_SIZE-1) & Hash((void *)(const float *)vecOrigin, sizeof(vecOrigin)); + if (m_Cache[iHash].v == vecOrigin) + { + //ALERT(at_aiconsole, "Cache Hit.\n"); + return m_Cache[iHash].n; + } + else + { + //ALERT(at_aiconsole, "Cache Miss.\n"); + } + + // Mark all points as unchecked. + // + m_CheckedCounter++; + if (m_CheckedCounter == 0) + { + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + m_CheckedCounter++; + } + + m_iNearest = -1; + m_flShortest = 999999.0; // just a big number. + + // If we can find a visible point, then let CalcBounds set the limits, but if + // we have no visible point at all to start with, then don't restrict the limits. + // +#if 1 + m_minX = 0; m_maxX = 255; + m_minY = 0; m_maxY = 255; + m_minZ = 0; m_maxZ = 255; + m_minBoxX = 0; m_maxBoxX = 255; + m_minBoxY = 0; m_maxBoxY = 255; + m_minBoxZ = 0; m_maxBoxZ = 255; +#else + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]) + CalcBounds(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[m_iNearest].m_Region[0]); + CalcBounds(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[m_iNearest].m_Region[1]); + CalcBounds(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[m_iNearest].m_Region[2]); +#endif + + int halfX = (m_minX+m_maxX)/2; + int halfY = (m_minY+m_maxY)/2; + int halfZ = (m_minZ+m_maxZ)/2; + + int j; + + for (i = halfX; i >= m_minX; i--) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = max(m_minY,halfY+1); i <= m_maxY; i++) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = min(m_maxZ,halfZ); i >= m_minZ; i--) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + + for (i = max(m_minX,halfX+1); i <= m_maxX; i++) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = min(m_maxY,halfY); i >= m_minY; i--) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = max(m_minZ,halfZ+1); i <= m_maxZ; i++) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + +#if 0 + // Verify our answers. + // + int iNearestCheck = -1; + m_flShortest = 8192;// find nodes within this radius + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + float flDist = ( vecOrigin - m_pNodes[ i ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ i ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + iNearestCheck = i; + m_flShortest = flDist; + } + } + } + + if (iNearestCheck != m_iNearest) + { + ALERT( at_aiconsole, "NOT closest %d(%f,%f,%f) %d(%f,%f,%f).\n", + iNearestCheck, + m_pNodes[iNearestCheck].m_vecOriginPeek.x, + m_pNodes[iNearestCheck].m_vecOriginPeek.y, + m_pNodes[iNearestCheck].m_vecOriginPeek.z, + m_iNearest, + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.x), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.y), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.z)); + } + if (m_iNearest == -1) + { + ALERT(at_aiconsole, "All that work for nothing.\n"); + } +#endif + m_Cache[iHash].v = vecOrigin; + m_Cache[iHash].n = m_iNearest; + return m_iNearest; +} + +//========================================================= +// CGraph - ShowNodeConnections - draws a line from the given node +// to all connected nodes +//========================================================= +void CGraph :: ShowNodeConnections ( int iNode ) +{ + Vector vecSpot; + CNode *pNode; + CNode *pLinkNode; + int i; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + if ( iNode < 0 ) + { + ALERT( at_aiconsole, "Can't show connections for node %d\n", iNode ); + return; + } + + pNode = &m_pNodes[ iNode ]; + + UTIL_ParticleEffect( pNode->m_vecOrigin, g_vecZero, 255, 20 );// show node position + + if ( pNode->m_cNumLinks <= 0 ) + {// no connections! + ALERT ( at_aiconsole, "**No Connections!\n" ); + } + + for ( i = 0 ; i < pNode->m_cNumLinks ; i++ ) + { + + pLinkNode = &Node( NodeLink( iNode, i).m_iDestNode ); + vecSpot = pLinkNode->m_vecOrigin; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + NODE_HEIGHT ); + MESSAGE_END(); + + } +} + +//========================================================= +// CGraph - LinkVisibleNodes - the first, most basic +// function of node graph creation, this connects every +// node to every other node that it can see. Expects a +// pointer to an empty connection pool and a file pointer +// to write progress to. Returns the total number of initial +// links. +// +// If there's a problem with this process, the index +// of the offending node will be written to piBadNode +//========================================================= +int CGraph :: LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ) +{ + int i,j,z; + edict_t *pTraceEnt; + int cTotalLinks, cLinksThisNode, cMaxInitialLinks; + TraceResult tr; + + // !!!BUGBUG - this function returns 0 if there is a problem in the middle of connecting the graph + // it also returns 0 if none of the nodes in a level can see each other. piBadNode is ALWAYS read + // by BuildNodeGraph() if this function returns a 0, so make sure that it doesn't get some random + // number back. + *piBadNode = 0; + + + if ( m_cNodes <= 0 ) + { + ALERT ( at_aiconsole, "No Nodes!\n" ); + return FALSE; + } + + // if the file pointer is bad, don't blow up, just don't write the + // file. + if ( !file ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\ncan't write to file." ); + } + else + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "LinkVisibleNodes - Initial Connections\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cTotalLinks = 0;// start with no connections + + // to keep track of the maximum number of initial links any node had so far. + // this lets us keep an eye on MAX_NODE_INITIAL_LINKS to ensure that we are + // being generous enough. + cMaxInitialLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + cLinksThisNode = 0;// reset this count for each node. + + if ( file ) + { + fprintf ( file, "Node #%4d:\n\n", i ); + } + + for ( z = 0 ; z < MAX_NODE_INITIAL_LINKS ; z++ ) + {// clear out the important fields in the link pool for this node + pLinkPool [ cTotalLinks + z ].m_iSrcNode = i;// so each link knows which node it originates from + pLinkPool [ cTotalLinks + z ].m_iDestNode = 0; + pLinkPool [ cTotalLinks + z ].m_pLinkEnt = NULL; + } + + m_pNodes [ i ].m_iFirstLink = cTotalLinks; + + // now build a list of every other node that this node can see + for ( j = 0 ; j < m_cNodes ; j++ ) + { + if ( j == i ) + {// don't connect to self! + continue; + } + +#if 0 + + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_WATER) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_WATER) ) + { + // don't connect water nodes to air nodes or land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#else + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_GROUP_REALM) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_GROUP_REALM) ) + { + // don't connect air nodes to water nodes to land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#endif + + tr.pHit = NULL;// clear every time so we don't get stuck with last trace's hit ent + pTraceEnt = 0; + + UTIL_TraceLine ( m_pNodes[ i ].m_vecOrigin, + m_pNodes[ j ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + + if ( tr.fStartSolid ) + continue; + + if ( tr.flFraction != 1.0 ) + {// trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way. + + pTraceEnt = tr.pHit;// store the ent that the trace hit, for comparison + + UTIL_TraceLine ( m_pNodes[ j ].m_vecOrigin, + m_pNodes[ i ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + +// there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep +// track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated +// as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded +// graphs are prepared for use. + if ( tr.pHit == pTraceEnt && !FClassnameIs( tr.pHit, "worldspawn" ) ) + { + // get a pointer + pLinkPool [ cTotalLinks ].m_pLinkEnt = VARS( tr.pHit ); + + // record the modelname, so that we can save/load node trees + memcpy( pLinkPool [ cTotalLinks ].m_szLinkEntModelname, STRING( VARS(tr.pHit)->model ), 4 ); + + // set the flag for this ent that indicates that it is attached to the world graph + // if this ent is removed from the world, it must also be removed from the connections + // that it formerly blocked. + if ( !FBitSet( VARS( tr.pHit )->flags, FL_GRAPHED ) ) + { + VARS( tr.pHit )->flags += FL_GRAPHED; + } + } + else + {// even if the ent wasn't there, these nodes couldn't be connected. Skip. + continue; + } + } + + if ( file ) + { + fprintf ( file, "%4d", j ); + + if ( !FNullEnt( pLinkPool[ cTotalLinks ].m_pLinkEnt ) ) + {// record info about the ent in the way, if any. + fprintf ( file, " Entity on connection: %s, name: %s Model: %s", STRING( VARS( pTraceEnt )->classname ), STRING ( VARS( pTraceEnt )->targetname ), STRING ( VARS(tr.pHit)->model ) ); + } + + fprintf ( file, "\n", j ); + } + + pLinkPool [ cTotalLinks ].m_iDestNode = j; + cLinksThisNode++; + cTotalLinks++; + + // If we hit this, either a level designer is placing too many nodes in the same area, or + // we need to allow for a larger initial link pool. + if ( cLinksThisNode == MAX_NODE_INITIAL_LINKS ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nNode %d has NodeLinks > MAX_NODE_INITIAL_LINKS", i ); + fprintf ( file, "** NODE %d HAS NodeLinks > MAX_NODE_INITIAL_LINKS **\n", i ); + *piBadNode = i; + return FALSE; + } + else if ( cTotalLinks > MAX_NODE_INITIAL_LINKS * m_cNodes ) + {// this is paranoia + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nTotalLinks > MAX_NODE_INITIAL_LINKS * NUMNODES" ); + *piBadNode = i; + return FALSE; + } + + if ( cLinksThisNode == 0 ) + { + fprintf ( file, "**NO INITIAL LINKS**\n" ); + } + + // record the connection info in the link pool + WorldGraph.m_pNodes [ i ].m_cNumLinks = cLinksThisNode; + + // keep track of the most initial links ANY node had, so we can figure out + // if we have a large enough default link pool + if ( cLinksThisNode > cMaxInitialLinks ) + { + cMaxInitialLinks = cLinksThisNode; + } + } + + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + } + + fprintf ( file, "\n%4d Total Initial Connections - %4d Maximum connections for a single node.\n", cTotalLinks, cMaxInitialLinks ); + fprintf ( file, "----------------------------------------------------------------------------\n\n\n" ); + + return cTotalLinks; +} + +//========================================================= +// CGraph - RejectInlineLinks - expects a pointer to a link +// pool, and a pointer to and already-open file ( if you +// want status reports written to disk ). RETURNS the number +// of connections that were rejected +//========================================================= +int CGraph :: RejectInlineLinks ( CLink *pLinkPool, FILE *file ) +{ + int i,j,k; + + int cRejectedLinks; + + BOOL fRestartLoop;// have to restart the J loop if we eliminate a link. + + CNode *pSrcNode; + CNode *pCheckNode;// the node we are testing for (one of pSrcNode's connections) + CNode *pTestNode;// the node we are checking against ( also one of pSrcNode's connections) + + float flDistToTestNode, flDistToCheckNode; + + Vector2D vec2DirToTestNode, vec2DirToCheckNode; + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "InLine Rejection:\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cRejectedLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + pSrcNode = &m_pNodes[ i ]; + + if ( file ) + { + fprintf ( file, "Node %3d:\n", i ); + } + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + pCheckNode = &m_pNodes[ pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vec2DirToCheckNode = ( pCheckNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + flDistToCheckNode = vec2DirToCheckNode.Length(); + vec2DirToCheckNode = vec2DirToCheckNode.Normalize(); + + pLinkPool[ pSrcNode->m_iFirstLink + j ].m_flWeight = flDistToCheckNode; + + fRestartLoop = FALSE; + for ( k = 0 ; k < pSrcNode->m_cNumLinks && !fRestartLoop ; k++ ) + { + if ( k == j ) + {// don't check against same node + continue; + } + + pTestNode = &m_pNodes [ pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode ]; + + vec2DirToTestNode = ( pTestNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + + flDistToTestNode = vec2DirToTestNode.Length(); + vec2DirToTestNode = vec2DirToTestNode.Normalize(); + + if ( DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) >= 0.998 ) + { + // there's a chance that TestNode intersects the line to CheckNode. If so, we should disconnect the link to CheckNode. + if ( flDistToTestNode < flDistToCheckNode ) + { + if ( file ) + { + fprintf ( file, "REJECTED NODE %3d through Node %3d, Dot = %8f\n", pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode, pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode, DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) ); + } + + pLinkPool[ pSrcNode->m_iFirstLink + j ] = pLinkPool[ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + pSrcNode->m_cNumLinks--; + j--; + + cRejectedLinks++;// keeping track of how many links are cut, so that we can return that value. + + fRestartLoop = TRUE; + } + } + } + } + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n\n" ); + } + } + + return cRejectedLinks; +} + +//========================================================= +// TestHull is a modelless clip hull that verifies reachable +// nodes by walking from every node to each of it's connections +//========================================================= +class CTestHull : public CBaseMonster +{ + +public: + void Spawn( entvars_t *pevMasterNode ); + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void EXPORT CallBuildNodeGraph ( void ); + void BuildNodeGraph ( void ); + void EXPORT ShowBadNode ( void ); + void EXPORT DropDelay ( void ); + void EXPORT PathFind ( void ); + + Vector vecBadNodeOrigin; +}; + +LINK_ENTITY_TO_CLASS( testhull, CTestHull ); + +//========================================================= +// CTestHull::Spawn +//========================================================= +void CTestHull :: Spawn( entvars_t *pevMasterNode ) +{ + SET_MODEL(ENT(pev), "models/player.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 50; + pev->yaw_speed = 8; + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so we don't need the test hull + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( DropDelay ); + pev->nextthink = gpGlobals->time + 1; + } + + // Make this invisible + // UNDONE: Shouldn't we just use EF_NODRAW? This doesn't need to go to the client. + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; +} + +//========================================================= +// TestHull::DropDelay - spawns TestHull on top of +// the 0th node and drops it to the ground. +//========================================================= +void CTestHull::DropDelay ( void ) +{ + UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding..." ); + + UTIL_SetOrigin ( VARS(pev), WorldGraph.m_pNodes[ 0 ].m_vecOrigin ); + + SetThink ( CallBuildNodeGraph ); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// nodes start out as ents in the world. As they are spawned, +// the node info is recorded then the ents are discarded. +//========================================================= +void CNodeEnt :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "hinttype")) + { + m_sHintType = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + + if (FStrEq(pkvd->szKeyName, "activity")) + { + m_sHintActivity = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +//========================================================= +//========================================================= +void CNodeEnt :: Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so discard all these node ents as soon as they spawn + REMOVE_ENTITY( edict() ); + return; + } + + if ( WorldGraph.m_cNodes == 0 ) + {// this is the first node to spawn, spawn the test hull entity that builds and walks the node tree + CTestHull *pHull = GetClassPtr((CTestHull *)NULL); + pHull->Spawn( pev ); + } + + if ( WorldGraph.m_cNodes >= MAX_NODES ) + { + ALERT ( at_aiconsole, "cNodes > MAX_NODES\n" ); + return; + } + + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOriginPeek = + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOrigin = pev->origin; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_flHintYaw = pev->angles.y; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintType = m_sHintType; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintActivity = m_sHintActivity; + + if (FClassnameIs( pev, "info_node_air" )) + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = bits_NODE_AIR; + else + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = 0; + + WorldGraph.m_cNodes++; + + REMOVE_ENTITY( edict() ); +} + +//========================================================= +// CTestHull - ShowBadNode - makes a bad node fizzle. When +// there's a problem with node graph generation, the test +// hull will be placed up the bad node's location and will generate +// particles +//========================================================= +void CTestHull :: ShowBadNode( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->angles.y = pev->angles.y + 4; + + UTIL_MakeVectors ( pev->angles ); + + UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +extern BOOL gTouchDisabled; +void CTestHull::CallBuildNodeGraph( void ) +{ + // TOUCH HACK -- Don't allow this entity to call anyone's "touch" function + gTouchDisabled = TRUE; + BuildNodeGraph(); + gTouchDisabled = FALSE; + // Undo TOUCH HACK +} + +//========================================================= +// BuildNodeGraph - think function called by the empty walk +// hull that is spawned by the first node to spawn. This +// function links all nodes that can see each other, then +// eliminates all inline links, then uses a monster-sized +// hull that walks between each node and each of its links +// to ensure that a monster can actually fit through the space +//========================================================= +void CTestHull :: BuildNodeGraph( void ) +{ + TraceResult tr; + FILE *file; + + char szNrpFilename [MAX_PATH];// text node report filename + + CLink *pTempPool; // temporary link pool + + CNode *pSrcNode;// node we're currently working with + CNode *pDestNode;// the other node in comparison operations + + BOOL fSkipRemainingHulls;//if smallest hull can't fit, don't check any others + BOOL fPairsValid;// are all links in the graph evenly paired? + + int i, j, hull; + + int iBadNode;// this is the node that caused graph generation to fail + + int cMaxInitialLinks = 0; + int cMaxValidLinks = 0; + + int iPoolIndex = 0; + int cPoolLinks;// number of links in the pool. + + Vector vecDirToCheckNode; + Vector vecDirToTestNode; + Vector vecStepCheckDir; + Vector vecTraceSpot; + Vector vecSpot; + + Vector2D vec2DirToCheckNode; + Vector2D vec2DirToTestNode; + Vector2D vec2StepCheckDir; + Vector2D vec2TraceSpot; + Vector2D vec2Spot; + + float flYaw;// use this stuff to walk the hull between nodes + float flDist; + int step; + + SetThink ( SUB_Remove );// no matter what happens, the hull gets rid of itself. + pev->nextthink = gpGlobals->time; + +// malloc a swollen temporary connection pool that we trim down after we know exactly how many connections there are. + pTempPool = (CLink *)calloc ( sizeof ( CLink ) , ( WorldGraph.m_cNodes * MAX_NODE_INITIAL_LINKS ) ); + if ( !pTempPool ) + { + ALERT ( at_aiconsole, "**Could not malloc TempPool!\n" ); + return; + } + + + // make sure directories have been made + GET_GAME_DIR( szNrpFilename ); + strcat( szNrpFilename, "/maps" ); + CreateDirectory( szNrpFilename, NULL ); + strcat( szNrpFilename, "/graphs" ); + CreateDirectory( szNrpFilename, NULL ); + + strcat( szNrpFilename, "/" ); + strcat( szNrpFilename, STRING( gpGlobals->mapname ) ); + strcat( szNrpFilename, ".nrp" ); + + file = fopen ( szNrpFilename, "w+" ); + + if ( !file ) + {// file error + ALERT ( at_aiconsole, "Couldn't create %s!\n", szNrpFilename ); + + if ( pTempPool ) + { + free ( pTempPool ); + } + + return; + } + + fprintf( file, "Node Graph Report for map: %s.bsp\n", STRING(gpGlobals->mapname) ); + fprintf ( file, "%d Total Nodes\n\n", WorldGraph.m_cNodes ); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + {// print all node numbers and their locations to the file. + WorldGraph.m_pNodes[ i ].m_cNumLinks = 0; + WorldGraph.m_pNodes[ i ].m_iFirstLink = 0; + memset(WorldGraph.m_pNodes[ i ].m_pNextBestNode, 0, sizeof(WorldGraph.m_pNodes[ i ].m_pNextBestNode)); + + fprintf ( file, "Node# %4d\n", i ); + fprintf ( file, "Location %4d,%4d,%4d\n",(int)WorldGraph.m_pNodes[ i ].m_vecOrigin.x, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.y, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.z ); + fprintf ( file, "HintType: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintType ); + fprintf ( file, "HintActivity: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintActivity ); + fprintf ( file, "HintYaw: %4f\n", WorldGraph.m_pNodes[ i ].m_flHintYaw ); + fprintf ( file, "-------------------------------------------------------------------------------\n" ); + } + fprintf ( file, "\n\n" ); + + + // Automatically recognize WATER nodes and drop the LAND nodes to the floor. + // + for ( i = 0; i < WorldGraph.m_cNodes; i++) + { + if (WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_AIR) + { + // do nothing + } + else if (UTIL_PointContents(WorldGraph.m_pNodes[ i ].m_vecOrigin) == CONTENTS_WATER) + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_WATER; + } + else + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_LAND; + + // trace to the ground, then pop up 8 units and place node there to make it + // easier for them to connect (think stairs, chairs, and bumps in the floor). + // After the routing is done, push them back down. + // + TraceResult tr; + + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + // This trace is ONLY used if we hit an entity flagged with FL_WORLDBRUSH + TraceResult trEnt; + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + dont_ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &trEnt ); + + + // Did we hit something closer than the floor? + if ( trEnt.flFraction < tr.flFraction ) + { + // If it was a world brush entity, copy the node location + if ( trEnt.pHit && (trEnt.pHit->v.flags & FL_WORLDBRUSH) ) + tr.vecEndPos = trEnt.vecEndPos; + } + + WorldGraph.m_pNodes[i].m_vecOriginPeek.z = + WorldGraph.m_pNodes[i].m_vecOrigin.z = tr.vecEndPos.z + NODE_HEIGHT; + } + } + + cPoolLinks = WorldGraph.LinkVisibleNodes( pTempPool, file, &iBadNode ); + + if ( !cPoolLinks ) + { + ALERT ( at_aiconsole, "**ConnectVisibleNodes FAILED!\n" ); + + SetThink ( ShowBadNode );// send the hull off to show the offending node. + //pev->solid = SOLID_NOT; + pev->origin = WorldGraph.m_pNodes[ iBadNode ].m_vecOrigin; + + if ( pTempPool ) + { + free ( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + +// send the walkhull to all of this node's connections now. We'll do this here since +// so much of it relies on being able to control the test hull. + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "Walk Rejection:\n"); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + pSrcNode = &WorldGraph.m_pNodes[ i ]; + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Node %4d:\n\n", i ); + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + // assume that all hulls can walk this link, then eliminate the ones that can't. + pTempPool [ pSrcNode->m_iFirstLink + j ].m_afLinkInfo = bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL | bits_LINK_FLY_HULL; + + + // do a check for each hull size. + + // if we can't fit a tiny hull through a connection, no other hulls with fit either, so we + // should just fall out of the loop. Do so by setting the SkipRemainingHulls flag. + fSkipRemainingHulls = FALSE; + for ( hull = 0 ; hull < MAX_NODE_HULLS; hull++ ) + { + if (fSkipRemainingHulls && (hull == NODE_HUMAN_HULL || hull == NODE_LARGE_HULL)) // skip the remaining walk hulls + continue; + + switch ( hull ) + { + case NODE_SMALL_HULL: + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + break; + case NODE_HUMAN_HULL: + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + break; + case NODE_LARGE_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + break; + case NODE_FLY_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + // UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + break; + } + + UTIL_SetOrigin ( pev, pSrcNode->m_vecOrigin );// place the hull on the node + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + ALERT ( at_aiconsole, "OFFGROUND!\n" ); + } + + // now build a yaw that points to the dest node, and get the distance. + if ( j < 0 ) + { + ALERT ( at_aiconsole, "**** j = %d ****\n", j ); + if ( pTempPool ) + { + free ( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + return; + } + + pDestNode = &WorldGraph.m_pNodes [ pTempPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vecSpot = pDestNode->m_vecOrigin; + //vecSpot.z = pev->origin.z; + + if (hull < NODE_FLY_HULL) + { + int SaveFlags = pev->flags; + int MoveMode = WALKMOVE_WORLDONLY; + if (pSrcNode->m_afNodeInfo & bits_NODE_WATER) + { + pev->flags |= FL_SWIM; + MoveMode = WALKMOVE_NORMAL; + } + + flYaw = UTIL_VecToYaw ( pDestNode->m_vecOrigin - pev->origin ); + + flDist = ( vecSpot - pev->origin ).Length2D(); + + int fWalkFailed = FALSE; + + // in this loop we take tiny steps from the current node to the nodes that it links to, one at a time. + // pev->angles.y = flYaw; + for ( step = 0 ; step < flDist && !fWalkFailed ; step += HULL_STEP_SIZE ) + { + float stepSize = HULL_STEP_SIZE; + + if ( (step + stepSize) >= (flDist-1) ) + stepSize = (flDist - step) - 1; + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, MoveMode ) ) + {// can't take the next step + + fWalkFailed = TRUE; + break; + } + } + + if (!fWalkFailed && (pev->origin - vecSpot).Length() > 64) + { + // ALERT( at_console, "bogus walk\n"); + // we thought we + fWalkFailed = TRUE; + } + + if (fWalkFailed) + { + + //pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + + // now me must eliminate the hull that couldn't walk this connection + switch ( hull ) + { + case NODE_SMALL_HULL: // if this hull can't fit, nothing can, so drop the connection + fprintf ( file, "NODE_SMALL_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_HUMAN_HULL: + fprintf ( file, "NODE_HUMAN_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_LARGE_HULL: + fprintf ( file, "NODE_LARGE_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_LARGE_HULL; + break; + } + } + pev->flags = SaveFlags; + } + else + { + TraceResult tr; + + UTIL_TraceHull( pSrcNode->m_vecOrigin + Vector( 0, 0, 32 ), pDestNode->m_vecOriginPeek + Vector( 0, 0, 32 ), ignore_monsters, large_hull, ENT( pev ), &tr ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_FLY_HULL; + } + } + } + + if (pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo == 0) + { + fprintf ( file, "Rejected Node %3d - Unreachable by ", pTempPool [ pSrcNode->m_iFirstLink + j ].m_iDestNode ); + pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + fprintf ( file, "Any Hull\n" ); + + pSrcNode->m_cNumLinks--; + cPoolLinks--;// we just removed a link, so decrement the total number of links in the pool. + j--; + } + + } + } + fprintf ( file, "-------------------------------------------------------------------------------\n\n\n"); + + cPoolLinks -= WorldGraph.RejectInlineLinks ( pTempPool, file ); + +// now malloc a pool just large enough to hold the links that are actually used + WorldGraph.m_pLinkPool = (CLink *) calloc ( sizeof ( CLink ), cPoolLinks ); + + if ( !WorldGraph.m_pLinkPool ) + {// couldn't make the link pool! + ALERT ( at_aiconsole, "Couldn't malloc LinkPool!\n" ); + if ( pTempPool ) + { + free ( pTempPool ); + } + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + WorldGraph.m_cLinks = cPoolLinks; + +//copy only the used portions of the TempPool into the graph's link pool + int iFinalPoolIndex = 0; + int iOldFirstLink; + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + iOldFirstLink = WorldGraph.m_pNodes[ i ].m_iFirstLink;// store this, because we have to re-assign it before entering the copy loop + + WorldGraph.m_pNodes[ i ].m_iFirstLink = iFinalPoolIndex; + + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + WorldGraph.m_pLinkPool[ iFinalPoolIndex++ ] = pTempPool[ iOldFirstLink + j ]; + } + } + + + // Node sorting numbers linked nodes close to each other + // + WorldGraph.SortNodes(); + + // This is used for HashSearch + // + WorldGraph.BuildLinkLookups(); + + fPairsValid = TRUE; // assume that the connection pairs are all valid to start + + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Link Pairings:\n"); + +// link integrity check. The idea here is that if Node A links to Node B, node B should +// link to node A. If not, we have a situation that prevents us from using a basic +// optimization in the FindNearestLink function. + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + int iLink; + WorldGraph.HashSearch(WorldGraph.INodeLink(i,j), i, iLink); + if (iLink < 0) + { + fPairsValid = FALSE;// unmatched link pair. + fprintf ( file, "WARNING: Node %3d does not connect back to Node %3d\n", WorldGraph.INodeLink(i, j), i); + } + } + } + + // !!!LATER - if all connections are properly paired, when can enable an optimization in the pathfinding code + // (in the find nearest line function) + if ( fPairsValid ) + { + fprintf ( file, "\nAll Connections are Paired!\n"); + } + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Total Number of Connections in Pool: %d\n", cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Connection Pool: %d bytes\n", sizeof ( CLink ) * cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + + + ALERT ( at_aiconsole, "%d Nodes, %d Connections\n", WorldGraph.m_cNodes, cPoolLinks ); + + // This is used for FindNearestNode + // + WorldGraph.BuildRegionTables(); + + + // Push all of the LAND nodes down to the ground now. Leave the water and air nodes alone. + // + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + if ((WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_LAND)) + { + WorldGraph.m_pNodes[ i ].m_vecOrigin.z -= NODE_HEIGHT; + } + } + + + if ( pTempPool ) + {// free the temp pool + free ( pTempPool ); + } + + if ( file ) + { + fclose ( file ); + } + + // We now have some graphing capabilities. + // + WorldGraph.m_fGraphPresent = TRUE;//graph is in memory. + WorldGraph.m_fGraphPointersSet = TRUE;// since the graph was generated, the pointers are ready + WorldGraph.m_fRoutingComplete = FALSE; // Optimal routes aren't computed, yet. + + // Compute and compress the routing information. + // + WorldGraph.ComputeStaticRoutingTables(); + +// save the node graph for this level + WorldGraph.FSaveGraph( (char *)STRING( gpGlobals->mapname ) ); + ALERT( at_console, "Done.\n"); +} + + +//========================================================= +// returns a hardcoded path. +//========================================================= +void CTestHull :: PathFind ( void ) +{ + int iPath[ 50 ]; + int iPathSize; + int i; + CNode *pNode, *pNextNode; + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + iPathSize = WorldGraph.FindShortestPath ( iPath, 0, 19, 0, 0 ); // UNDONE use hull constant + + if ( !iPathSize ) + { + ALERT ( at_aiconsole, "No Path!\n" ); + return; + } + + ALERT ( at_aiconsole, "%d\n", iPathSize ); + + pNode = &WorldGraph.m_pNodes[ iPath [ 0 ] ]; + + for ( i = 0 ; i < iPathSize - 1 ; i++ ) + { + + pNextNode = &WorldGraph.m_pNodes[ iPath [ i + 1 ] ]; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( pNode->m_vecOrigin.x ); + WRITE_COORD( pNode->m_vecOrigin.y ); + WRITE_COORD( pNode->m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( pNextNode->m_vecOrigin.x); + WRITE_COORD( pNextNode->m_vecOrigin.y); + WRITE_COORD( pNextNode->m_vecOrigin.z + NODE_HEIGHT); + MESSAGE_END(); + + pNode = pNextNode; + } + +} + + +//========================================================= +// CStack Constructor +//========================================================= +CStack :: CStack( void ) +{ + m_level = 0; +} + +//========================================================= +// pushes a value onto the stack +//========================================================= +void CStack :: Push( int value ) +{ + if ( m_level >= MAX_STACK_NODES ) + { + printf("Error!\n"); + return; + } + m_stack[m_level] = value; + m_level++; +} + +//========================================================= +// pops a value off of the stack +//========================================================= +int CStack :: Pop( void ) +{ + if ( m_level <= 0 ) + return -1; + + m_level--; + return m_stack[ m_level ]; +} + +//========================================================= +// returns the value on the top of the stack +//========================================================= +int CStack :: Top ( void ) +{ + return m_stack[ m_level - 1 ]; +} + +//========================================================= +// copies every element on the stack into an array LIFO +//========================================================= +void CStack :: CopyToArray ( int *piArray ) +{ + int i; + + for ( i = 0 ; i < m_level ; i++ ) + { + piArray[ i ] = m_stack[ i ]; + } +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueue :: CQueue( void ) +{ + m_cSize = 0; + m_head = 0; + m_tail = -1; +} + +//========================================================= +// inserts a value into the queue +//========================================================= +void CQueue :: Insert ( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_tail++; + + if ( m_tail == MAX_STACK_NODES ) + {//wrap around + m_tail = 0; + } + + m_queue[ m_tail ].Id = iValue; + m_queue[ m_tail ].Priority = fPriority; + m_cSize++; +} + +//========================================================= +// removes a value from the queue (FIFO) +//========================================================= +int CQueue :: Remove ( float &fPriority ) +{ + if ( m_head == MAX_STACK_NODES ) + {// wrap + m_head = 0; + } + + m_cSize--; + fPriority = m_queue[ m_head ].Priority; + return m_queue[ m_head++ ].Id; +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueuePriority :: CQueuePriority( void ) +{ + m_cSize = 0; +} + +//========================================================= +// inserts a value into the priority queue +//========================================================= +void CQueuePriority :: Insert( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_heap[ m_cSize ].Priority = fPriority; + m_heap[ m_cSize ].Id = iValue; + m_cSize++; + Heap_SiftUp(); +} + +//========================================================= +// removes the smallest item from the priority queue +// +//========================================================= +int CQueuePriority :: Remove( float &fPriority ) +{ + int iReturn = m_heap[ 0 ].Id; + fPriority = m_heap[ 0 ].Priority; + + m_cSize--; + + m_heap[ 0 ] = m_heap[ m_cSize ]; + + Heap_SiftDown(0); + return iReturn; +} + +#define HEAP_LEFT_CHILD(x) (2*(x)+1) +#define HEAP_RIGHT_CHILD(x) (2*(x)+2) +#define HEAP_PARENT(x) (((x)-1)/2) + +void CQueuePriority::Heap_SiftDown(int iSubRoot) +{ + int parent = iSubRoot; + int child = HEAP_LEFT_CHILD(parent); + + struct tag_HEAP_NODE Ref = m_heap[ parent ]; + + while (child < m_cSize) + { + int rightchild = HEAP_RIGHT_CHILD(parent); + if (rightchild < m_cSize) + { + if ( m_heap[ rightchild ].Priority < m_heap[ child ].Priority ) + { + child = rightchild; + } + } + if ( Ref.Priority <= m_heap[ child ].Priority ) + break; + + m_heap[ parent ] = m_heap[ child ]; + parent = child; + child = HEAP_LEFT_CHILD(parent); + } + m_heap[ parent ] = Ref; +} + +void CQueuePriority::Heap_SiftUp(void) +{ + int child = m_cSize-1; + while (child) + { + int parent = HEAP_PARENT(child); + if ( m_heap[ parent ].Priority <= m_heap[ child ].Priority ) + break; + + struct tag_HEAP_NODE Tmp; + Tmp = m_heap[ child ]; + m_heap[ child ] = m_heap[ parent ]; + m_heap[ parent ] = Tmp; + + child = parent; + } +} + +//========================================================= +// CGraph - FLoadGraph - attempts to load a node graph from disk. +// if the current level is maps/snar.bsp, maps/graphs/snar.nod +// will be loaded. If file cannot be loaded, the node tree +// will be created and saved to disk. +//========================================================= +int CGraph :: FLoadGraph ( char *szMapName ) +{ + char szFilename[MAX_PATH]; + int iVersion; + int length; + byte *aMemFile; + byte *pMemFile; + + // make sure the directories have been made + char szDirName[MAX_PATH]; + GET_GAME_DIR( szDirName ); + strcat( szDirName, "/maps" ); + CreateDirectory( szDirName, NULL ); + strcat( szDirName, "/graphs" ); + CreateDirectory( szDirName, NULL ); + + strcpy ( szFilename, "maps/graphs/" ); + strcat ( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + pMemFile = aMemFile = LOAD_FILE_FOR_ME(szFilename, &length); + + if ( !aMemFile ) + { + return FALSE; + } + else + { + // Read the graph version number + // + length -= sizeof(int); + if (length < 0) goto ShortFile; + memcpy(&iVersion, pMemFile, sizeof(int)); + pMemFile += sizeof(int); + + if ( iVersion != GRAPH_VERSION ) + { + // This file was written by a different build of the dll! + // + ALERT ( at_aiconsole, "**ERROR** Graph version is %d, expected %d\n",iVersion, GRAPH_VERSION ); + goto ShortFile; + } + + // Read the graph class + // + length -= sizeof(CGraph); + if (length < 0) goto ShortFile; + memcpy(this, pMemFile, sizeof(CGraph)); + pMemFile += sizeof(CGraph); + + // Set the pointers to zero, just in case we run out of memory. + // + m_pNodes = NULL; + m_pLinkPool = NULL; + m_di = NULL; + m_pRouteInfo = NULL; + m_pHashLinks = NULL; + + + // Malloc for the nodes + // + m_pNodes = ( CNode * )calloc ( sizeof ( CNode ), m_cNodes ); + + if ( !m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read in all the nodes + // + length -= sizeof(CNode) * m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_pNodes, pMemFile, sizeof(CNode)*m_cNodes); + pMemFile += sizeof(CNode) * m_cNodes; + + + // Malloc for the link pool + // + m_pLinkPool = ( CLink * )calloc ( sizeof ( CLink ), m_cLinks ); + + if ( !m_pLinkPool ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d link!\n", m_cLinks ); + goto NoMemory; + } + + // Read in all the links + // + length -= sizeof(CLink)*m_cLinks; + if (length < 0) goto ShortFile; + memcpy(m_pLinkPool, pMemFile, sizeof(CLink)*m_cLinks); + pMemFile += sizeof(CLink)*m_cLinks; + + // Malloc for the sorting info. + // + m_di = (DIST_INFO *)calloc( sizeof(DIST_INFO), m_cNodes ); + if ( !m_di ) + { + ALERT ( at_aiconsole, "***ERROR**\nCouldn't malloc %d entries sorting nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read it in. + // + length -= sizeof(DIST_INFO)*m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_di, pMemFile, sizeof(DIST_INFO)*m_cNodes); + pMemFile += sizeof(DIST_INFO)*m_cNodes; + + // Malloc for the routing info. + // + m_fRoutingComplete = FALSE; + m_pRouteInfo = (char *)calloc( sizeof(char), m_nRouteInfo ); + if ( !m_pRouteInfo ) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d route bytes!\n", m_nRouteInfo ); + goto NoMemory; + } + m_CheckedCounter = 0; + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + + // Read in the route information. + // + length -= sizeof(char)*m_nRouteInfo; + if (length < 0) goto ShortFile; + memcpy(m_pRouteInfo, pMemFile, sizeof(char)*m_nRouteInfo); + pMemFile += sizeof(char)*m_nRouteInfo; + m_fRoutingComplete = TRUE;; + + // malloc for the hash links + // + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d hash link bytes!\n", m_nHashLinks ); + goto NoMemory; + } + + // Read in the hash link information + // + length -= sizeof(short)*m_nHashLinks; + if (length < 0) goto ShortFile; + memcpy(m_pHashLinks, pMemFile, sizeof(short)*m_nHashLinks); + pMemFile += sizeof(short)*m_nHashLinks; + + // Set the graph present flag, clear the pointers set flag + // + m_fGraphPresent = TRUE; + m_fGraphPointersSet = FALSE; + + FREE_FILE(aMemFile); + + if (length != 0) + { + ALERT ( at_aiconsole, "***WARNING***:Node graph was longer than expected by %d bytes.!\n", length); + } + + return TRUE; + } + +ShortFile: +NoMemory: + FREE_FILE(aMemFile); + return FALSE; +} + +//========================================================= +// CGraph - FSaveGraph - It's not rocket science. +// this WILL overwrite existing files. +//========================================================= +int CGraph :: FSaveGraph ( char *szMapName ) +{ + + int iVersion = GRAPH_VERSION; + char szFilename[MAX_PATH]; + FILE *file; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + // make sure directories have been made + GET_GAME_DIR( szFilename ); + strcat( szFilename, "/maps" ); + CreateDirectory( szFilename, NULL ); + strcat( szFilename, "/graphs" ); + CreateDirectory( szFilename, NULL ); + + strcat( szFilename, "/" ); + strcat( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + file = fopen ( szFilename, "wb" ); + + ALERT ( at_aiconsole, "Created: %s\n", szFilename ); + + if ( !file ) + {// couldn't create + ALERT ( at_aiconsole, "Couldn't Create: %s\n", szFilename ); + return FALSE; + } + else + { + // write the version + fwrite ( &iVersion, sizeof ( int ), 1, file ); + + // write the CGraph class + fwrite ( this, sizeof ( CGraph ), 1, file ); + + // write the nodes + fwrite ( m_pNodes, sizeof ( CNode ), m_cNodes, file ); + + // write the links + fwrite ( m_pLinkPool, sizeof ( CLink ), m_cLinks, file ); + + fwrite ( m_di, sizeof(DIST_INFO), m_cNodes, file ); + + // Write the route info. + // + if ( m_pRouteInfo && m_nRouteInfo ) + { + fwrite ( m_pRouteInfo, sizeof( char ), m_nRouteInfo, file ); + } + + if (m_pHashLinks && m_nHashLinks) + { + fwrite(m_pHashLinks, sizeof(short), m_nHashLinks, file); + } + fclose ( file ); + return TRUE; + } +} + +//========================================================= +// CGraph - FSetGraphPointers - Takes the modelnames of +// all of the brush ents that block connections in the node +// graph and resolves them into pointers to those entities. +// this is done after loading the graph from disk, whereupon +// the pointers are not valid. +//========================================================= +int CGraph :: FSetGraphPointers ( void ) +{ + int i; + edict_t *pentLinkEnt; + + for ( i = 0 ; i < m_cLinks ; i++ ) + {// go through all of the links + + if ( m_pLinkPool[ i ].m_pLinkEnt != NULL ) + { + char name[5]; + // when graphs are saved, any valid pointers are will be non-zero, signifying that we should + // reset those pointers upon reloading. Any pointers that were NULL when the graph was saved + // will be NULL when reloaded, and will ignored by this function. + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + memcpy( name, m_pLinkPool[ i ].m_szLinkEntModelname, 4 ); + name[4] = 0; + pentLinkEnt = FIND_ENTITY_BY_STRING( NULL, "model", name ); + + if ( FNullEnt ( pentLinkEnt ) ) + { + // the ent isn't around anymore? Either there is a major problem, or it was removed from the world + // ( like a func_breakable that's been destroyed or something ). Make sure that LinkEnt is null. + ALERT ( at_aiconsole, "**Could not find model %s\n", name ); + m_pLinkPool[ i ].m_pLinkEnt = NULL; + } + else + { + m_pLinkPool[ i ].m_pLinkEnt = VARS( pentLinkEnt ); + + if ( !FBitSet( m_pLinkPool[ i ].m_pLinkEnt->flags, FL_GRAPHED ) ) + { + m_pLinkPool[ i ].m_pLinkEnt->flags += FL_GRAPHED; + } + } + } + } + + // the pointers are now set. + m_fGraphPointersSet = TRUE; + return TRUE; +} + +//========================================================= +// CGraph - CheckNODFile - this function checks the date of +// the BSP file that was just loaded and the date of the a +// ssociated .NOD file. If the NOD file is not present, or +// is older than the BSP file, we rebuild it. +// +// returns FALSE if the .NOD file doesn't qualify and needs +// to be rebuilt. +// +// !!!BUGBUG - the file times we get back are 20 hours ahead! +// since this happens consistantly, we can still correctly +// determine which of the 2 files is newer. This needs fixed, +// though. ( I now suspect that we are getting GMT back from +// these functions and must compensate for local time ) (sjb) +//========================================================= +int CGraph :: CheckNODFile ( char *szMapName ) +{ + int retValue; + + char szBspFilename[MAX_PATH]; + char szGraphFilename[MAX_PATH]; + + + strcpy ( szBspFilename, "maps/" ); + strcat ( szBspFilename, szMapName ); + strcat ( szBspFilename, ".bsp" ); + + strcpy ( szGraphFilename, "maps/graphs/" ); + strcat ( szGraphFilename, szMapName ); + strcat ( szGraphFilename, ".nod" ); + + retValue = TRUE; + + int iCompare; + if (COMPARE_FILE_TIME(szBspFilename, szGraphFilename, &iCompare)) + { + if ( iCompare > 0 ) + {// BSP file is newer. + ALERT ( at_aiconsole, ".NOD File will be updated\n\n" ); + retValue = FALSE; + } + } + else + { + retValue = FALSE; + } + + return retValue; +} + +#define ENTRY_STATE_EMPTY -1 + +struct tagNodePair +{ + short iSrc; + short iDest; +}; + +void CGraph::HashInsert(int iSrcNode, int iDestNode, int iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + m_pHashLinks[i] = iKey; +} + +void CGraph::HashSearch(int iSrcNode, int iDestNode, int &iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + CLink &link = Link(m_pHashLinks[i]); + if (iSrcNode == link.m_iSrcNode && iDestNode == link.m_iDestNode) + { + break; + } + else + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + } + iKey = m_pHashLinks[i]; +} + +#define NUMBER_OF_PRIMES 177 + +int Primes[NUMBER_OF_PRIMES] = +{ 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, +71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, +157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, +241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, +347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, +439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, +547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, +643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, +751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, +859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, +977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 0 }; + +void CGraph::HashChoosePrimes(int TableSize) +{ + int LargestPrime = TableSize/2; + if (LargestPrime > Primes[NUMBER_OF_PRIMES-2]) + { + LargestPrime = Primes[NUMBER_OF_PRIMES-2]; + } + int Spacing = LargestPrime/16; + + // Pick a set primes that are evenly spaced from (0 to LargestPrime) + // We divide this interval into 16 equal sized zones. We want to find + // one prime number that best represents that zone. + // + for (int iZone = 1, iPrime = 0; iPrime < 16; iZone += Spacing) + { + // Search for a prime number that is less than the target zone + // number given by iZone. + // + int Lower = Primes[0]; + for (int jPrime = 0; Primes[jPrime] != 0; jPrime++) + { + if (jPrime != 0 && TableSize % Primes[jPrime] == 0) continue; + int Upper = Primes[jPrime]; + if (Lower <= iZone && iZone <= Upper) + { + // Choose the closest lower prime number. + // + if (iZone - Lower <= Upper - iZone) + { + m_HashPrimes[iPrime++] = Lower; + } + else + { + m_HashPrimes[iPrime++] = Upper; + } + break; + } + Lower = Upper; + } + } + + // Alternate negative and positive numbers + // + for (iPrime = 0; iPrime < 16; iPrime += 2) + { + m_HashPrimes[iPrime] = TableSize-m_HashPrimes[iPrime]; + } + + // Shuffle the set of primes to reduce correlation with bits in + // hash key. + // + for (iPrime = 0; iPrime < 16-1; iPrime++) + { + int Pick = RANDOM_LONG(0, 15-iPrime); + int Temp = m_HashPrimes[Pick]; + m_HashPrimes[Pick] = m_HashPrimes[15-iPrime]; + m_HashPrimes[15-iPrime] = Temp; + } +} + +// Renumber nodes so that nodes that link together are together. +// +#define UNNUMBERED_NODE -1 +void CGraph::SortNodes(void) +{ + // We are using m_iPreviousNode to be the new node number. + // After assigning new node numbers to everything, we move + // things and patchup the links. + // + int iNodeCnt = 0; + m_pNodes[0].m_iPreviousNode = iNodeCnt++; + for (int i = 1; i < m_cNodes; i++) + { + m_pNodes[i].m_iPreviousNode = UNNUMBERED_NODE; + } + + for (i = 0; i < m_cNodes; i++) + { + // Run through all of this node's neighbors + // + for (int j = 0 ; j < m_pNodes[i].m_cNumLinks; j++ ) + { + int iDestNode = INodeLink(i, j); + if (m_pNodes[iDestNode].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[iDestNode].m_iPreviousNode = iNodeCnt++; + } + } + } + + // Assign remaining node numbers to unlinked nodes. + // + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[i].m_iPreviousNode = iNodeCnt++; + } + } + + // Alter links to reflect new node numbers. + // + for (i = 0; i < m_cLinks; i++) + { + m_pLinkPool[i].m_iSrcNode = m_pNodes[m_pLinkPool[i].m_iSrcNode].m_iPreviousNode; + m_pLinkPool[i].m_iDestNode = m_pNodes[m_pLinkPool[i].m_iDestNode].m_iPreviousNode; + } + + // Rearrange nodes to reflect new node numbering. + // + for (i = 0; i < m_cNodes; i++) + { + while (m_pNodes[i].m_iPreviousNode != i) + { + // Move current node off to where it should be, and bring + // that other node back into the current slot. + // + int iDestNode = m_pNodes[i].m_iPreviousNode; + CNode TempNode = m_pNodes[iDestNode]; + m_pNodes[iDestNode] = m_pNodes[i]; + m_pNodes[i] = TempNode; + } + } +} + +void CGraph::BuildLinkLookups(void) +{ + m_nHashLinks = 3*m_cLinks/2 + 3; + + HashChoosePrimes(m_nHashLinks); + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT(at_aiconsole, "Couldn't allocated Link Lookup Table.\n"); + return; + } + for (int i = 0; i < m_nHashLinks; i++) + { + m_pHashLinks[i] = ENTRY_STATE_EMPTY; + } + + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + HashInsert(link.m_iSrcNode, link.m_iDestNode, i); + } +#if 0 + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + int iKey; + HashSearch(link.m_iSrcNode, link.m_iDestNode, iKey); + if (iKey != i) + { + ALERT(at_aiconsole, "HashLinks don't match (%d versus %d)\n", i, iKey); + } + } +#endif +} + +void CGraph::BuildRegionTables(void) +{ + if (m_di) free(m_di); + + // Go ahead and setup for range searching the nodes for FindNearestNodes + // + m_di = (DIST_INFO *)calloc(sizeof(DIST_INFO), m_cNodes); + if (!m_di) + { + ALERT(at_aiconsole, "Couldn't allocated node ordering array.\n"); + return; + } + + // Calculate regions for all the nodes. + // + // + for (int i = 0; i < 3; i++) + { + m_RegionMin[i] = 999999999.0; // just a big number out there; + m_RegionMax[i] = -999999999.0; // just a big number out there; + } + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_vecOrigin.x < m_RegionMin[0]) + m_RegionMin[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y < m_RegionMin[1]) + m_RegionMin[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z < m_RegionMin[2]) + m_RegionMin[2] = m_pNodes[i].m_vecOrigin.z; + + if (m_pNodes[i].m_vecOrigin.x > m_RegionMax[0]) + m_RegionMax[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y > m_RegionMax[1]) + m_RegionMax[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z > m_RegionMax[2]) + m_RegionMax[2] = m_pNodes[i].m_vecOrigin.z; + } + for (i = 0; i < m_cNodes; i++) + { + m_pNodes[i].m_Region[0] = CALC_RANGE(m_pNodes[i].m_vecOrigin.x, m_RegionMin[0], m_RegionMax[0]); + m_pNodes[i].m_Region[1] = CALC_RANGE(m_pNodes[i].m_vecOrigin.y, m_RegionMin[1], m_RegionMax[1]); + m_pNodes[i].m_Region[2] = CALC_RANGE(m_pNodes[i].m_vecOrigin.z, m_RegionMin[2], m_RegionMax[2]); + } + + for (i = 0; i < 3; i++) + { + for (int j = 0; j < NUM_RANGES; j++) + { + m_RangeStart[i][j] = 255; + m_RangeEnd[i][j] = 0; + } + for (j = 0; j < m_cNodes; j++) + { + m_di[j].m_SortedBy[i] = j; + } + + for (j = 0; j < m_cNodes - 1; j++) + { + int jNode = m_di[j].m_SortedBy[i]; + int jCodeX = m_pNodes[jNode].m_Region[0]; + int jCodeY = m_pNodes[jNode].m_Region[1]; + int jCodeZ = m_pNodes[jNode].m_Region[2]; + int jCode; + switch (i) + { + case 0: + jCode = (jCodeX << 16) + (jCodeY << 8) + jCodeZ; + break; + case 1: + jCode = (jCodeY << 16) + (jCodeZ << 8) + jCodeX; + break; + case 2: + jCode = (jCodeZ << 16) + (jCodeX << 8) + jCodeY; + break; + } + + for (int k = j+1; k < m_cNodes; k++) + { + int kNode = m_di[k].m_SortedBy[i]; + int kCodeX = m_pNodes[kNode].m_Region[0]; + int kCodeY = m_pNodes[kNode].m_Region[1]; + int kCodeZ = m_pNodes[kNode].m_Region[2]; + int kCode; + switch (i) + { + case 0: + kCode = (kCodeX << 16) + (kCodeY << 8) + kCodeZ; + break; + case 1: + kCode = (kCodeY << 16) + (kCodeZ << 8) + kCodeX; + break; + case 2: + kCode = (kCodeZ << 16) + (kCodeX << 8) + kCodeY; + break; + } + + if (kCode < jCode) + { + // Swap j and k entries. + // + int Tmp = m_di[j].m_SortedBy[i]; + m_di[j].m_SortedBy[i] = m_di[k].m_SortedBy[i]; + m_di[k].m_SortedBy[i] = Tmp; + } + } + } + } + + // Generate lookup tables. + // + for (i = 0; i < m_cNodes; i++) + { + int CodeX = m_pNodes[m_di[i].m_SortedBy[0]].m_Region[0]; + int CodeY = m_pNodes[m_di[i].m_SortedBy[1]].m_Region[1]; + int CodeZ = m_pNodes[m_di[i].m_SortedBy[2]].m_Region[2]; + + if (i < m_RangeStart[0][CodeX]) + { + m_RangeStart[0][CodeX] = i; + } + if (i < m_RangeStart[1][CodeY]) + { + m_RangeStart[1][CodeY] = i; + } + if (i < m_RangeStart[2][CodeZ]) + { + m_RangeStart[2][CodeZ] = i; + } + if (m_RangeEnd[0][CodeX] < i) + { + m_RangeEnd[0][CodeX] = i; + } + if (m_RangeEnd[1][CodeY] < i) + { + m_RangeEnd[1][CodeY] = i; + } + if (m_RangeEnd[2][CodeZ] < i) + { + m_RangeEnd[2][CodeZ] = i; + } + } + + // Initialize the cache. + // + memset(m_Cache, 0, sizeof(m_Cache)); +} + +void CGraph :: ComputeStaticRoutingTables( void ) +{ + int nRoutes = m_cNodes*m_cNodes; +#define FROM_TO(x,y) ((x)*m_cNodes+(y)) + short *Routes = new short[nRoutes]; + + int *pMyPath = new int[m_cNodes]; + unsigned short *BestNextNodes = new unsigned short[m_cNodes]; + char *pRoute = new char[m_cNodes*2]; + + + if (Routes && pMyPath && BestNextNodes && pRoute) + { + int nTotalCompressedSize = 0; + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + + // Initialize Routing table to uncalculated. + // + for (int iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + Routes[FROM_TO(iFrom, iTo)] = -1; + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = m_cNodes-1; iTo >= 0; iTo--) + { + if (Routes[FROM_TO(iFrom, iTo)] != -1) continue; + + int cPathSize = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + + // Use the computed path to update the routing table. + // + if (cPathSize > 1) + { + for (int iNode = 0; iNode < cPathSize-1; iNode++) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode+1]; + for (int iNode1 = iNode+1; iNode1 < cPathSize; iNode1++) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#if 0 + // Well, at first glance, this should work, but actually it's safer + // to be told explictly that you can take a series of node in a + // particular direction. Some links don't appear to have links in + // the opposite direction. + // + for (iNode = cPathSize-1; iNode >= 1; iNode--) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode-1]; + for (int iNode1 = iNode-1; iNode1 >= 0; iNode1--) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#endif + } + else + { + Routes[FROM_TO(iFrom, iTo)] = iFrom; + Routes[FROM_TO(iTo, iFrom)] = iTo; + } + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + BestNextNodes[iTo] = Routes[FROM_TO(iFrom, iTo)]; + } + + // Compress this node's routing table. + // + int iLastNode = 9999999; // just really big. + int cSequence = 0; + int cRepeats = 0; + int CompressedSize = 0; + char *p = pRoute; + for (int i = 0; i < m_cNodes; i++) + { + BOOL CanRepeat = ((BestNextNodes[i] == iLastNode) && cRepeats < 127); + BOOL CanSequence = (BestNextNodes[i] == i && cSequence < 128); + + if (cRepeats) + { + if (CanRepeat) + { + cRepeats++; + } + else + { + // Emit the repeat phrase. + // + CompressedSize += 2; // (count-1, iLastNode-i) + *p++ = cRepeats - 1; + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + cRepeats = 0; + + if (CanSequence) + { + // Start a sequence. + // + cSequence++; + } + else + { + // Start another repeat. + // + cRepeats++; + } + } + } + else if (cSequence) + { + if (CanSequence) + { + cSequence++; + } + else + { + // It may be advantageous to combine + // a single-entry sequence phrase with the + // next repeat phrase. + // + if (cSequence == 1 && CanRepeat) + { + // Combine with repeat phrase. + // + cRepeats = 2; + cSequence = 0; + } + else + { + // Emit the sequence phrase. + // + CompressedSize += 1; // (-count) + *p++ = -cSequence; + cSequence = 0; + + // Start a repeat sequence. + // + cRepeats++; + } + } + } + else + { + if (CanSequence) + { + // Start a sequence phrase. + // + cSequence++; + } + else + { + // Start a repeat sequence. + // + cRepeats++; + } + } + iLastNode = BestNextNodes[i]; + } + if (cRepeats) + { + // Emit the repeat phrase. + // + CompressedSize += 2; + *p++ = cRepeats - 1; +#if 0 + iLastNode = iFrom + *pRoute; + if (iLastNode >= m_cNodes) iLastNode -= m_cNodes; + else if (iLastNode < 0) iLastNode += m_cNodes; +#endif + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + } + if (cSequence) + { + // Emit the Sequence phrase. + // + CompressedSize += 1; + *p++ = -cSequence; + } + + // Go find a place to store this thing and point to it. + // + int nRoute = p - pRoute; + if (m_pRouteInfo) + { + for (int i = 0; i < m_nRouteInfo - nRoute; i++) + { + if (memcmp(m_pRouteInfo + i, pRoute, nRoute) == 0) + { + break; + } + } + if (i < m_nRouteInfo - nRoute) + { + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = i; + } + else + { + char *Tmp = (char *)calloc(sizeof(char), (m_nRouteInfo + nRoute)); + memcpy(Tmp, m_pRouteInfo, m_nRouteInfo); + free(m_pRouteInfo); + m_pRouteInfo = Tmp; + memcpy(m_pRouteInfo + m_nRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = m_nRouteInfo; + m_nRouteInfo += nRoute; + nTotalCompressedSize += CompressedSize; + } + } + else + { + m_nRouteInfo = nRoute; + m_pRouteInfo = (char *)calloc(sizeof(char), nRoute); + memcpy(m_pRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = 0; + nTotalCompressedSize += CompressedSize; + } + } + } + } + ALERT( at_aiconsole, "Size of Routes = %d\n", nTotalCompressedSize); + } + if (Routes) delete Routes; + if (BestNextNodes) delete BestNextNodes; + if (pRoute) delete pRoute; + if (pMyPath) delete pMyPath; + Routes = 0; + BestNextNodes = 0; + pRoute = 0; + pMyPath = 0; + +#if 0 + TestRoutingTables(); +#endif + m_fRoutingComplete = TRUE; +} + +// Test those routing tables. Doesn't really work, yet. +// +void CGraph :: TestRoutingTables( void ) +{ + int *pMyPath = new int[m_cNodes]; + int *pMyPath2 = new int[m_cNodes]; + if (pMyPath && pMyPath2) + { + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + for (int iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + m_fRoutingComplete = FALSE; + int cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + int cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + + // Unless we can look at the entire path, we can verify that it's correct. + // + if (cPathSize2 == MAX_PATH_SIZE) continue; + + // Compare distances. + // +#if 1 + float flDistance1 = 0.0; + for (int i = 0; i < cPathSize1-1; i++) + { + // Find the link from pMyPath[i] to pMyPath[i+1] + // + if (pMyPath[i] == pMyPath[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath[i], iLink ); + if (iVisitNode == pMyPath[i+1]) + { + flDistance1 += m_pLinkPool[ m_pNodes[ pMyPath[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + + float flDistance2 = 0.0; + for (i = 0; i < cPathSize2-1; i++) + { + // Find the link from pMyPath2[i] to pMyPath2[i+1] + // + if (pMyPath2[i] == pMyPath2[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath2[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath2[i], iLink ); + if (iVisitNode == pMyPath2[i+1]) + { + flDistance2 += m_pLinkPool[ m_pNodes[ pMyPath2[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + if (fabs(flDistance1 - flDistance2) > 0.10) + { +#else + if (cPathSize1 != cPathSize2 || memcmp(pMyPath, pMyPath2, sizeof(int)*cPathSize1) != 0) + { +#endif + ALERT(at_aiconsole, "Routing is inconsistent!!!\n"); + ALERT(at_aiconsole, "(%d to %d |%d/%d)1:", iFrom, iTo, iHull, iCap); + for (int i = 0; i < cPathSize1; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath[i]); + } + ALERT(at_aiconsole, "\n(%d to %d |%d/%d)2:", iFrom, iTo, iHull, iCap); + for (i = 0; i < cPathSize2; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath2[i]); + } + ALERT(at_aiconsole, "\n"); + m_fRoutingComplete = FALSE; + cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + goto EnoughSaid; + } + } + } + } + } + } + +EnoughSaid: + + if (pMyPath) delete pMyPath; + if (pMyPath2) delete pMyPath2; + pMyPath = 0; + pMyPath2 = 0; +} + + + + + + + + + +//========================================================= +// CNodeViewer - Draws a graph of the shorted path from all nodes +// to current location (typically the player). It then draws +// as many connects as it can per frame, trying not to overflow the buffer +//========================================================= +class CNodeViewer : public CBaseEntity +{ +public: + void Spawn( void ); + + int m_iBaseNode; + int m_iDraw; + int m_nVisited; + int m_aFrom[128]; + int m_aTo[128]; + int m_iHull; + int m_afNodeType; + Vector m_vecColor; + + void FindNodeConnections( int iNode ); + void AddNode( int iFrom, int iTo ); + void EXPORT DrawThink( void ); + +}; +LINK_ENTITY_TO_CLASS( node_viewer, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_human, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_fly, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_large, CNodeViewer ); + +void CNodeViewer::Spawn( ) +{ + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_console, "Graph not ready!\n" ); + UTIL_Remove( this ); + return; + } + + + if (FClassnameIs( pev, "node_viewer_fly")) + { + m_iHull = NODE_FLY_HULL; + m_afNodeType = bits_NODE_AIR; + m_vecColor = Vector( 160, 100, 255 ); + } + else if (FClassnameIs( pev, "node_viewer_large")) + { + m_iHull = NODE_LARGE_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 100, 255, 160 ); + } + else + { + m_iHull = NODE_HUMAN_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 255, 160, 100 ); + } + + + m_iBaseNode = WorldGraph.FindNearestNode ( pev->origin, m_afNodeType ); + + if ( m_iBaseNode < 0 ) + { + ALERT( at_console, "No nearby node\n" ); + return; + } + + m_nVisited = 0; + + ALERT( at_aiconsole, "basenode %d\n", m_iBaseNode ); + + if (WorldGraph.m_cNodes < 128) + { + for (int i = 0; i < WorldGraph.m_cNodes; i++) + { + AddNode( i, WorldGraph.NextNodeInRoute( i, m_iBaseNode, m_iHull, 0 )); + } + } + else + { + // do a depth traversal + FindNodeConnections( m_iBaseNode ); + + int start = 0; + int end; + do { + end = m_nVisited; + // ALERT( at_console, "%d :", m_nVisited ); + for (end = m_nVisited; start < end; start++) + { + FindNodeConnections( m_aFrom[start] ); + FindNodeConnections( m_aTo[start] ); + } + } while (end != m_nVisited); + } + + ALERT( at_aiconsole, "%d nodes\n", m_nVisited ); + + m_iDraw = 0; + SetThink( DrawThink ); + pev->nextthink = gpGlobals->time; +} + + +void CNodeViewer :: FindNodeConnections ( int iNode ) +{ + AddNode( iNode, WorldGraph.NextNodeInRoute( iNode, m_iBaseNode, m_iHull, 0 )); + for ( int i = 0 ; i < WorldGraph.m_pNodes[ iNode ].m_cNumLinks ; i++ ) + { + CLink *pToLink = &WorldGraph.NodeLink( iNode, i); + AddNode( pToLink->m_iDestNode, WorldGraph.NextNodeInRoute( pToLink->m_iDestNode, m_iBaseNode, m_iHull, 0 )); + } +} + +void CNodeViewer::AddNode( int iFrom, int iTo ) +{ + if (m_nVisited >= 128) + { + return; + } + else + { + if (iFrom == iTo) + return; + + for (int i = 0; i < m_nVisited; i++) + { + if (m_aFrom[i] == iFrom && m_aTo[i] == iTo) + return; + if (m_aFrom[i] == iTo && m_aTo[i] == iFrom) + return; + } + m_aFrom[m_nVisited] = iFrom; + m_aTo[m_nVisited] = iTo; + m_nVisited++; + } +} + + +void CNodeViewer :: DrawThink( void ) +{ + pev->nextthink = gpGlobals->time; + + for (int i = 0; i < 10; i++) + { + if (m_iDraw == m_nVisited) + { + UTIL_Remove( this ); + return; + } + + extern short g_sModelIndexLaser; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 250 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( m_vecColor.x ); // r, g, b + WRITE_BYTE( m_vecColor.y ); // r, g, b + WRITE_BYTE( m_vecColor.z ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + m_iDraw++; + } +} + + diff --git a/dlls/nodes.h b/dlls/nodes.h index 4a18cb1..90325d2 100644 --- a/dlls/nodes.h +++ b/dlls/nodes.h @@ -6,49 +6,369 @@ * 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. +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. * ****/ //========================================================= // nodes.h //========================================================= -#ifndef NODES_H -#define NODES_H +//========================================================= +// DEFINE +//========================================================= +#define MAX_STACK_NODES 100 +#define NO_NODE -1 +#define MAX_NODE_HULLS 4 -#define bits_NODE_GROUP_REALM 1 +#define bits_NODE_LAND ( 1 << 0 ) // Land node, so nudge if necessary. +#define bits_NODE_AIR ( 1 << 1 ) // Air node, don't nudge. +#define bits_NODE_WATER ( 1 << 2 ) // Water node, don't nudge. +#define bits_NODE_GROUP_REALM (bits_NODE_LAND | bits_NODE_AIR | bits_NODE_WATER) + +//========================================================= +// Instance of a node. +//========================================================= +class CNode +{ +public: + Vector m_vecOrigin;// location of this node in space + Vector m_vecOriginPeek; // location of this node (LAND nodes are NODE_HEIGHT higher). + BYTE m_Region[3]; // Which of 256 regions do each of the coordinate belong? + int m_afNodeInfo;// bits that tell us more about this location + + int m_cNumLinks; // how many links this node has + int m_iFirstLink;// index of this node's first link in the link pool. + + // Where to start looking in the compressed routing table (offset into m_pRouteInfo). + // (4 hull sizes -- smallest to largest + fly/swim), and secondly, door capability. + // + int m_pNextBestNode[MAX_NODE_HULLS][2]; + + // Used in finding the shortest path. m_fClosestSoFar is -1 if not visited. + // Then it is the distance to the source. If another path uses this node + // and has a closer distance, then m_iPreviousNode is also updated. + // + float m_flClosestSoFar; // Used in finding the shortest path. + int m_iPreviousNode; + + short m_sHintType;// there is something interesting in the world at this node's position + short m_sHintActivity;// there is something interesting in the world at this node's position + float m_flHintYaw;// monster on this node should face this yaw to face the hint. +}; + +//========================================================= +// CLink - A link between 2 nodes +//========================================================= +#define bits_LINK_SMALL_HULL ( 1 << 0 )// headcrab box can fit through this connection +#define bits_LINK_HUMAN_HULL ( 1 << 1 )// player box can fit through this connection +#define bits_LINK_LARGE_HULL ( 1 << 2 )// big box can fit through this connection +#define bits_LINK_FLY_HULL ( 1 << 3 )// a flying big box can fit through this connection +#define bits_LINK_DISABLED ( 1 << 4 )// link is not valid when the set + +#define NODE_SMALL_HULL 0 +#define NODE_HUMAN_HULL 1 +#define NODE_LARGE_HULL 2 +#define NODE_FLY_HULL 3 class CLink { public: + int m_iSrcNode;// the node that 'owns' this link ( keeps us from having to make reverse lookups ) + int m_iDestNode;// the node on the other end of the link. + entvars_t *m_pLinkEnt;// the entity that blocks this connection (doors, etc) + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + char m_szLinkEntModelname[ 4 ];// the unique name of the brush model that blocks the connection (this is kept for save/restore) + + int m_afLinkInfo;// information about this link + float m_flWeight;// length of the link line segment }; +typedef struct +{ + int m_SortedBy[3]; + int m_CheckedEvent; +} DIST_INFO; + +typedef struct +{ + Vector v; + short n; // Nearest node or -1 if no node found. +} CACHE_ENTRY; + +//========================================================= +// CGraph +//========================================================= +#define GRAPH_VERSION (int)16// !!!increment this whever graph/node/link classes change, to obsolesce older disk files. class CGraph { public: + +// the graph has two flags, and should not be accessed unless both flags are TRUE! BOOL m_fGraphPresent;// is the graph in memory? BOOL m_fGraphPointersSet;// are the entity pointers for the graph all set? + BOOL m_fRoutingComplete; // are the optimal routes computed, yet? - int m_cLinks;// total number of links + CNode *m_pNodes;// pointer to the memory block that contains all node info CLink *m_pLinkPool;// big list of all node connections + char *m_pRouteInfo; // compressed routing information the nodes use. + int m_cNodes;// total number of nodes + int m_cLinks;// total number of links + int m_nRouteInfo; // size of m_pRouteInfo in bytes. + + // Tables for making nearest node lookup faster. SortedBy provided nodes in a + // order of a particular coordinate. Instead of doing a binary search, RangeStart + // and RangeEnd let you get to the part of SortedBy that you are interested in. + // + // Once you have a point of interest, the only way you'll find a closer point is + // if at least one of the coordinates is closer than the ones you have now. So we + // search each range. After the search is exhausted, we know we have the closest + // node. + // +#define CACHE_SIZE 128 +#define NUM_RANGES 256 + DIST_INFO *m_di; // This is m_cNodes long, but the entries don't correspond to CNode entries. + int m_RangeStart[3][NUM_RANGES]; + int m_RangeEnd[3][NUM_RANGES]; + float m_flShortest; + int m_iNearest; + int m_minX, m_minY, m_minZ, m_maxX, m_maxY, m_maxZ; + int m_minBoxX, m_minBoxY, m_minBoxZ, m_maxBoxX, m_maxBoxY, m_maxBoxZ; + int m_CheckedCounter; + float m_RegionMin[3], m_RegionMax[3]; // The range of nodes. + CACHE_ENTRY m_Cache[CACHE_SIZE]; + + + int m_HashPrimes[16]; + short *m_pHashLinks; + int m_nHashLinks; + + + // kinda sleazy. In order to allow variety in active idles for monster groups in a room with more than one node, + // we keep track of the last node we searched from and store it here. Subsequent searches by other monsters will pick + // up where the last search stopped. + int m_iLastActiveIdleSearch; + + // another such system used to track the search for cover nodes, helps greatly with two monsters trying to get to the same node. + int m_iLastCoverSearch; + + // functions to create the graph + int LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ); + int RejectInlineLinks ( CLink *pLinkPool, FILE *file ); + int FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask); + int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); + int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + //int FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ); + float PathLength( int iStart, int iDest, int iHull, int afCapMask ); + int NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ); + + enum NODEQUERY { NODEGRAPH_DYNAMIC, NODEGRAPH_STATIC }; + // A static query means we're asking about the possiblity of handling this entity at ANY time + // A dynamic query means we're asking about it RIGHT NOW. So we should query the current state + int HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ); + entvars_t* LinkEntForLink ( CLink *pLink, CNode *pNode ); + void ShowNodeConnections ( int iNode ); void InitGraph( void ); int AllocNodes ( void ); int CheckNODFile(char *szMapName); int FLoadGraph(char *szMapName); + int FSaveGraph(char *szMapName); int FSetGraphPointers(void); - void ShowNodeConnections ( int iNode ); - int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); - int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + void CheckNode(Vector vecOrigin, int iNode); + + void BuildRegionTables(void); + void ComputeStaticRoutingTables(void); + void TestRoutingTables(void); + + void HashInsert(int iSrcNode, int iDestNode, int iKey); + void HashSearch(int iSrcNode, int iDestNode, int &iKey); + void HashChoosePrimes(int TableSize); + void BuildLinkLookups(void); + + void SortNodes(void); + + int HullIndex( const CBaseEntity *pEntity ); // what hull the monster uses + int NodeType( const CBaseEntity *pEntity ); // what node type the monster uses + inline int CapIndex( int afCapMask ) + { + if (afCapMask & (bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE)) + return 1; + return 0; + } + + + inline CNode &Node( int i ) + { +#ifdef _DEBUG + if ( !m_pNodes || i < 0 || i > m_cNodes ) + ALERT( at_error, "Bad Node!\n" ); +#endif + return m_pNodes[i]; + } + + inline CLink &Link( int i ) + { +#ifdef _DEBUG + if ( !m_pLinkPool || i < 0 || i > m_cLinks ) + ALERT( at_error, "Bad link!\n" ); +#endif + return m_pLinkPool[i]; + } + + inline CLink &NodeLink( int iNode, int iLink ) + { + return Link( Node( iNode ).m_iFirstLink + iLink ); + } + + inline CLink &NodeLink( const CNode &node, int iLink ) + { + return Link( node.m_iFirstLink + iLink ); + } + + inline int INodeLink ( int iNode, int iLink ) + { + return NodeLink( iNode, iLink ).m_iDestNode; + } + +#if 0 + inline CNode &SourceNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iSrcNode ); + } + + inline CNode &DestNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iDestNode ); + } + + inline CNode *PNodeLink ( int iNode, int iLink ) + { + return &DestNode( iNode, iLink ); + } +#endif +}; + +//========================================================= +// Nodes start out as ents in the level. The node graph +// is built, then these ents are discarded. +//========================================================= +class CNodeEnt : public CBaseEntity +{ + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + short m_sHintType; + short m_sHintActivity; +}; + + +//========================================================= +// CStack - last in, first out. +//========================================================= +class CStack +{ +public: + CStack( void ); + void Push( int value ); + int Pop( void ); + int Top( void ); + int Empty( void ) { return m_level==0; } + int Size( void ) { return m_level; } + void CopyToArray ( int *piArray ); + +private: + int m_stack[ MAX_STACK_NODES ]; + int m_level; +}; + + +//========================================================= +// CQueue - first in, first out. +//========================================================= +class CQueue +{ +public: + + CQueue( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( void ) { return ( m_queue[ m_tail ] ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float & ); + +private: + int m_cSize; + struct tag_QUEUE_NODE + { + int Id; + float Priority; + } m_queue[ MAX_STACK_NODES ]; + int m_head; + int m_tail; +}; + +//========================================================= +// CQueuePriority - Priority queue (smallest item out first). +// +//========================================================= +class CQueuePriority +{ +public: + + CQueuePriority( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( float & ) { return ( m_queue[ m_tail ].Id ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float &); + +private: + int m_cSize; + struct tag_HEAP_NODE + { + int Id; + float Priority; + } m_heap[ MAX_STACK_NODES ]; + void Heap_SiftDown(int); + void Heap_SiftUp(void); }; -extern CGraph WorldGraph; +//========================================================= +// hints - these MUST coincide with the HINTS listed under +// info_node in the FGD file! +//========================================================= +enum +{ + HINT_NONE = 0, + HINT_WORLD_DOOR, + HINT_WORLD_WINDOW, + HINT_WORLD_BUTTON, + HINT_WORLD_MACHINERY, + HINT_WORLD_LEDGE, + HINT_WORLD_LIGHT_SOURCE, + HINT_WORLD_HEAT_SOURCE, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_BRIGHT_COLORS, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, -#endif // NODES_H \ No newline at end of file + HINT_TACTICAL_EXIT = 100, + HINT_TACTICAL_VANTAGE, + HINT_TACTICAL_AMBUSH, + + HINT_STUKA_PERCH = 300, + HINT_STUKA_LANDING, +}; + +extern CGraph WorldGraph; diff --git a/dlls/observer.cpp b/dlls/observer.cpp new file mode 100644 index 0000000..a4c0907 --- /dev/null +++ b/dlls/observer.cpp @@ -0,0 +1,287 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "pm_shared.h" + +extern int gmsgCurWeapon; +extern int gmsgSetFOV; +extern int gmsgTeamInfo; +extern int gmsgSpectator; + +// Player has become a spectator. Set it up. +// This was moved from player.cpp. +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) +{ + // clear any clientside entities attached to this player + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_KILLPLAYERATTACHMENTS ); + WRITE_BYTE( (BYTE)entindex() ); + MESSAGE_END(); + + // Holster weapon immediately, to allow it to cleanup + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + if ( m_pTank != NULL ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + // 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(); + + // reset FOV + m_iFOV = m_iClientFOV = 0; + pev->fov = m_iFOV; + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE(0); + MESSAGE_END(); + + // Setup flags + pev->classname = MAKE_STRING("observer"); + 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; + ClearBits( m_afPhysicsFlags, PFLAG_DUCKING ); + ClearBits( pev->flags, FL_DUCKING ); + pev->deadflag = DEAD_RESPAWNABLE; + pev->health = 1; + + // Clear out the status bar + m_fInitHUD = TRUE; + + // Update Team Status + pev->team = 0; + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_STRING( "" ); + MESSAGE_END(); + + // Remove all the player's stuff + RemoveAllItems(); + + // Move them to the new position + UTIL_SetOrigin( pev, vecPosition ); + + // Find a player to watch + m_flNextObserverInput = 0; + Observer_SetMode(OBS_CHASE_LOCKED); + + // Tell all clients this player is now a spectator + MESSAGE_BEGIN( MSG_ALL, gmsgSpectator ); + WRITE_BYTE( ENTINDEX( edict() ) ); + WRITE_BYTE( 1 ); + MESSAGE_END(); +} + +// Leave observer mode +void CBasePlayer::StopObserver( void ) +{ + // Turn off spectator + if ( pev->iuser1 || pev->iuser2 ) + { + // Tell all clients this player is not a spectator anymore + MESSAGE_BEGIN( MSG_ALL, gmsgSpectator ); + WRITE_BYTE( ENTINDEX( edict() ) ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + pev->iuser1 = pev->iuser2 = 0; + m_hObserverTarget = NULL; + } + + m_iHideHUD = 0; + m_afPhysicsFlags &= ~PFLAG_OBSERVER; + pev->effects = 0; + pev->fixangle = FALSE; + pev->solid = SOLID_SLIDEBOX; + pev->takedamage = DAMAGE_AIM; + pev->movetype = MOVETYPE_WALK; + pev->deadflag = DEAD_NO; +} + +// Find the next client in the game for this player to spectate +void CBasePlayer::Observer_FindNextPlayer( bool bReverse ) +{ + // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching + // only a subset of the players. e.g. Make it check the target's team. + + int iStart; + if ( m_hObserverTarget ) + iStart = ENTINDEX( m_hObserverTarget->edict() ); + else + iStart = ENTINDEX( edict() ); + int iCurrent = iStart; + m_hObserverTarget = NULL; + int iDir = bReverse ? -1 : 1; + + do + { + iCurrent += iDir; + + // Loop through the clients + if (iCurrent > gpGlobals->maxClients) + iCurrent = 1; + if (iCurrent < 1) + iCurrent = gpGlobals->maxClients; + + CBaseEntity *pEnt = UTIL_PlayerByIndex( iCurrent ); + if ( !pEnt ) + continue; + if ( pEnt == this ) + continue; + // Don't spec observers or invisible players + if ( ((CBasePlayer*)pEnt)->IsObserver() || (pEnt->pev->effects & EF_NODRAW) ) + continue; + + // MOD AUTHORS: Add checks on target here. + + m_hObserverTarget = pEnt; + break; + + } while ( iCurrent != iStart ); + + // Did we find a target? + if ( m_hObserverTarget ) + { + // Store the target in pev so the physics DLL can get to it + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + // Move to the target + UTIL_SetOrigin( pev, m_hObserverTarget->pev->origin ); + + ALERT( at_console, "Now Tracking %s\n", STRING( m_hObserverTarget->pev->netname ) ); + } + else + { + ALERT( at_console, "No observer targets.\n" ); + } +} + +// Handle buttons in observer mode +void CBasePlayer::Observer_HandleButtons() +{ + // Slow down mouse clicks + if ( m_flNextObserverInput > gpGlobals->time ) + return; + + // Jump changes from modes: Chase to Roaming + if ( m_afButtonPressed & IN_JUMP ) + { + if ( pev->iuser1 == OBS_ROAMING ) + Observer_SetMode( OBS_CHASE_LOCKED ); + else if ( pev->iuser1 == OBS_CHASE_LOCKED ) + Observer_SetMode( OBS_CHASE_FREE ); + else + Observer_SetMode( OBS_ROAMING ); + + m_flNextObserverInput = gpGlobals->time + 0.2; + } + + // Attack moves to the next player + if ( m_afButtonPressed & IN_ATTACK && pev->iuser1 != OBS_ROAMING ) + { + Observer_FindNextPlayer( false ); + + m_flNextObserverInput = gpGlobals->time + 0.2; + } + + // Attack2 moves to the prev player + if ( m_afButtonPressed & IN_ATTACK2 && pev->iuser1 != OBS_ROAMING ) + { + Observer_FindNextPlayer( true ); + + m_flNextObserverInput = gpGlobals->time + 0.2; + } +} + +// Attempt to change the observer mode +void CBasePlayer::Observer_SetMode( int iMode ) +{ + // Just abort if we're changing to the mode we're already in + if ( iMode == pev->iuser1 ) + return; + + // Changing to Roaming? + if ( iMode == OBS_ROAMING ) + { + // MOD AUTHORS: If you don't want to allow roaming observers at all in your mod, just abort here. + pev->iuser1 = OBS_ROAMING; + pev->iuser2 = 0; + + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_Mode3" ); + pev->maxspeed = 320; + return; + } + + // Changing to Chase Lock? + if ( iMode == OBS_CHASE_LOCKED ) + { + // If changing from Roaming, or starting observing, make sure there is a target + if ( m_hObserverTarget == NULL ) + Observer_FindNextPlayer( false ); + + if (m_hObserverTarget) + { + pev->iuser1 = OBS_CHASE_LOCKED; + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_Mode1" ); + pev->maxspeed = 0; + } + else + { + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_NoTarget" ); + Observer_SetMode(OBS_ROAMING); + } + + return; + } + + // Changing to Chase Freelook? + if ( iMode == OBS_CHASE_FREE ) + { + // If changing from Roaming, or starting observing, make sure there is a target + if ( m_hObserverTarget == NULL ) + Observer_FindNextPlayer( false ); + + if (m_hObserverTarget) + { + pev->iuser1 = OBS_CHASE_FREE; + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_Mode2" ); + pev->maxspeed = 0; + } + else + { + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_NoTarget" ); + Observer_SetMode(OBS_ROAMING); + } + + return; + } +} \ No newline at end of file diff --git a/dlls/plane.cpp b/dlls/plane.cpp index 7c2d3dc..bd03140 100644 --- a/dlls/plane.cpp +++ b/dlls/plane.cpp @@ -13,6 +13,7 @@ * ****/ #include "extdll.h" +#include "util.h" #include "plane.h" //========================================================= diff --git a/dlls/player.cpp b/dlls/player.cpp index b249286..92ae359 100644 --- a/dlls/player.cpp +++ b/dlls/player.cpp @@ -1,4 +1,4 @@ - /*** +/*** * * Copyright (c) 1996-2001, Valve LLC. All rights reserved. * @@ -34,16 +34,24 @@ #include "decals.h" #include "gamerules.h" #include "game.h" +#include "thewastes.h" +#include "tw_common.h" +#include "pm_shared.h" // #define DUCKFIX +//BLONDE - Different default movement speeds +#define NORMAL_SPEED 320 +#define AIMED_SPEED 190 +#define DYING_SPEED 240 +//&BLONDE + extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; extern DLL_GLOBAL BOOL g_fGameOver; extern DLL_GLOBAL BOOL g_fDrawLines; int gEvilImpulse101; extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle; - BOOL gInitHUD = TRUE; extern void CopyToBodyQue(entvars_t* pev); @@ -85,11 +93,6 @@ TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = 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 ), @@ -134,7 +137,6 @@ TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = //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 @@ -145,7 +147,6 @@ TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = }; - int giPrecacheGrunt = 0; int gmsgShake = 0; int gmsgFade = 0; @@ -158,9 +159,7 @@ 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; @@ -170,6 +169,7 @@ int gmsgTeamInfo = 0; int gmsgTeamScore = 0; int gmsgGameMode = 0; int gmsgMOTD = 0; +int gmsgBriefing = 0; int gmsgServerName = 0; int gmsgAmmoPickup = 0; int gmsgWeapPickup = 0; @@ -182,6 +182,10 @@ int gmsgSetFOV = 0; int gmsgShowMenu = 0; int gmsgGeigerRange = 0; int gmsgTeamNames = 0; +int gmsgSpectator = 0; +int gmsgLaserInfo = 0; +int gmsgPlayerState = 0; +int gmsgLmsStart = 0; void LinkUserMessages( void ) { @@ -191,6 +195,7 @@ void LinkUserMessages( void ) return; } + // Message identifiers must be less than 12 characters! - Stuart Crouch gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo)); gmsgCurWeapon = REG_USER_MSG("CurWeapon", 3); gmsgGeigerRange = REG_USER_MSG("Geiger", 1); @@ -198,7 +203,6 @@ void LinkUserMessages( void ) 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 ); @@ -213,6 +217,7 @@ void LinkUserMessages( void ) 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 ); + gmsgBriefing = REG_USER_MSG( "Briefing", 1 ); gmsgServerName = REG_USER_MSG( "ServerName", -1 ); gmsgAmmoPickup = REG_USER_MSG( "AmmoPickup", 2 ); gmsgWeapPickup = REG_USER_MSG( "WeapPickup", 1 ); @@ -224,12 +229,250 @@ void LinkUserMessages( void ) gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); gmsgAmmoX = REG_USER_MSG("AmmoX", 2); gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 ); + gmsgSpectator = REG_USER_MSG( "Spectator", 2 ); + gmsgLaserInfo = REG_USER_MSG( "LaserInfo", 2 ); + gmsgPlayerState = REG_USER_MSG( "PlayerState", 1 ); + gmsgLmsStart = REG_USER_MSG( "LmsStart", 2 ); } +/* + * + * CPlayerInventory + * + */ +CPlayerInventory::CPlayerInventory() +{ + memset( m_iItemIndices, -1, sizeof( m_iItemIndices ) ); + m_bRandom = FALSE; +} + +BOOL CPlayerInventory::SetInventory( int Slot, int Index ) +{ + if( m_bRandom ) + return FALSE; + + BOOL bOutOfBounds = FALSE; + playeritemlist_t *pList = GetListForSlot( Slot ); + + if( pList == NULL ) + return FALSE; + + if( Index < pList->size && Index >= 0 ) + { + if( g_pGameRules->AcceptInventory( &pList->array[Index] ) ) + { + m_iItemIndices[Slot] = Index; + return TRUE; + } + } + + m_iItemIndices[Slot] = -1; + return TRUE; +} + +void CPlayerInventory::SpawnInventory( CBasePlayer *pPlayer ) +{ + for( int i = 0;i < 5;i++ ) + { + playeritemlist_t *pList = GetListForSlot( i ); + int index; + + if( m_bRandom ) + { + // Randomize weapon + index = RANDOM_FLOAT( -1, pList->size-1 ); + + if( !g_pGameRules->AcceptInventory( &pList->array[index] ) ) + index = -1; + } + else + index = m_iItemIndices[i]; + + if( index < 0 ) + continue; + + char *weapon_classname = pList->array[index].weapon_classname; + char *ammo_classname = pList->array[index].ammo_classname; + int ammo_count = pList->array[index].num_ammospawns; + + // Spawn ammo for weapon + if( ammo_classname != NULL ) + { + for( int j = 0;j < ammo_count;j++ ) + pPlayer->GiveNamedItem( ammo_classname ); + } + + // Spawn weapon + pPlayer->GiveNamedItem( weapon_classname ); + } + + // Spawn with best weapon selected + CBasePlayerItem *pBest; + int iBestWeight = -1 ; + + for( i = 0;i < MAX_ITEM_TYPES;i++ ) + { + CBasePlayerItem *pWeap = pPlayer->m_rgpPlayerItems[ i ]; + + while( pWeap != NULL ) + { + if( pWeap->iWeight() > iBestWeight && pWeap != pPlayer->m_pActiveItem ) + { + if( pWeap->CanDeploy() ) + { + pBest = pWeap; + iBestWeight = pWeap->iWeight(); + } + } + + pWeap = pWeap->m_pNext; + } + } + + if( pBest != NULL ) + pPlayer->SwitchWeapon( pBest ); +} + +void CPlayerInventory::SetRandom( BOOL b ) +{ + m_bRandom = b; + if( m_bRandom == TRUE ) + memset( m_iItemIndices, -1, sizeof( m_iItemIndices ) ); +} + +playeritemlist_t *CPlayerInventory::GetListForSlot( int Slot ) +{ + playeritemlist_t *pList; + + switch( Slot ) + { + case 0: pList = &g_MeleeItems; break; + case 1: pList = &g_SidearmItems; break; + case 2: pList = &g_PrimaryItems; break; + case 3: pList = &g_UniqueItems; break; + case 4: pList = &g_OtherItems; break; + default: pList = NULL; break; + } + + return pList; +} + +#if 0 +/* + CPlayerInventory +*/ +BOOL CPlayerInventory::bSetInventory(char *pszClassname,int iSlot) +{ + // Get category + int iCat = iGetCategory(pszClassname); + int iSecondaryIndex = -1; + + // Make sure this item isnt already in the inventory more than it should be + for(int i = 0;i < 3;i++) + { + // This comes into play later on + if(iGetCategory(m_pszItemClasses[i]) == CAT_SECONDARY) + iSecondaryIndex = i; + } + + // If this is a secondary weapon, make sure we dont have one already in inventory + if(iSecondaryIndex != -1) + iSlot = iSecondaryIndex; + + // Now we just go ahead and fill in based on the slot the user wants to use + strcpy(m_pszItemClasses[iSlot],pszClassname); + return TRUE; +} + +void CPlayerInventory::SpawnInventory(CBasePlayer *pPlayer) +{ + if(m_bRandomItems) + { + int unique = 0; + int secondary = 0; + BOOL bUseSecondary = FALSE; + int count = 0; + + // First lets find out where the 0 number (unique) weapons start + for(playeritem_t *pCurItem = &g_PlayerItems[0];pCurItem->weapon_classname != NULL;pCurItem++) + { + if(pCurItem->category == CAT_UNIQUE) + break; + + unique++; + } + + // Then we find where the secondary weapons start + for(pCurItem = &g_PlayerItems[0];pCurItem->weapon_classname != NULL;pCurItem++) + { + if(pCurItem->category == CAT_SECONDARY) + break; + + secondary++; + } + + // Now that we have our constraints, we can start spawning items. + for(int i = 0;i < 3;i++) + { + int index = bUseSecondary ? RANDOM_LONG(0,secondary-1) : RANDOM_LONG(0,unique-1); + + pCurItem = &g_PlayerItems[index]; + + if(pCurItem->category == CAT_SECONDARY) + bUseSecondary = TRUE; + + count = 0; + + // See if we already tried to spawn this item. + for(int j = 0;j < 3;j++) + if(!strcmp(pCurItem->weapon_classname,m_pszItemClasses[i])) + count++; + + if(count) + continue; + + // Add it up :) + strcpy(m_pszItemClasses[i],pCurItem->weapon_classname); + } + } + + for(int i = 0;i < 3;i++) + { + // Spawn weapons + if(strcmp(m_pszItemClasses[i],"")) + { + pPlayer->GiveNamedItem(m_pszItemClasses[i]); + + // Spawn ammo + for(playeritem_t *pCurItem = &g_PlayerItems[0];pCurItem->weapon_classname != NULL;pCurItem++) + { + if( !strcmp( m_pszItemClasses[i], pCurItem->weapon_classname ) ) + { + // Now items! + for( int j = 0;j < pCurItem->num_ammospawns;j++ ) + pPlayer->GiveNamedItem(pCurItem->ammo_classname); + + break; + } + } + } + } +} + +int CPlayerInventory::iGetCategory(char *pszClassname) +{ + for(playeritem_t *pCurItem = &g_PlayerItems[0];pCurItem->weapon_classname != NULL;pCurItem++) + { + if(!strcmp(pCurItem->weapon_classname,pszClassname)) + return pCurItem->category; + } + + return CAT_NONE; +} +#endif + LINK_ENTITY_TO_CLASS( player, CBasePlayer ); - - void CBasePlayer :: Pain( void ) { float flRndSound;//sound randomizer @@ -353,9 +596,6 @@ void CBasePlayer :: DeathSound( void ) EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); break; } - - // play one of the suit death alarms - EMIT_GROUPNAME_SUIT(ENT(pev), "HEV_DEAD"); } // override takehealth @@ -381,10 +621,19 @@ Vector CBasePlayer :: GetGunPosition( ) //========================================================= // TraceAttack //========================================================= -void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue) { if ( pev->takedamage ) { + // If this bullet has gone through + // a wall, reduce bullet damage + if(PenetrationValue) + { + flDamage *= PenetrationValue; + if(flDamage < 0) + return; + } + m_LastHitGroup = ptr->iHitgroup; switch ( ptr->iHitgroup ) @@ -392,29 +641,74 @@ void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector case HITGROUP_GENERIC: break; case HITGROUP_HEAD: - flDamage *= gSkillData.plrHead; + switch(RANDOM_LONG(0,2)) + { + case 0: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/headshot1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/headshot2.wav", 1, ATTN_NORM); break; + case 2: + default: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/headshot3.wav", 1, ATTN_NORM); break; + } + + if(!(bitsDamageType & DMG_BUCKSHOT)) + { + pev->vuser2.y = 1; // Store off headshot value. + flDamage *= HEAD_DAMAGE; + } break; case HITGROUP_CHEST: - flDamage *= gSkillData.plrChest; + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/chest_hit1.wav", 1, ATTN_NORM); + + if(!(bitsDamageType & DMG_BUCKSHOT)) + flDamage *= CHEST_DAMAGE; break; case HITGROUP_STOMACH: - flDamage *= gSkillData.plrStomach; + if(!(bitsDamageType & DMG_BUCKSHOT)) + flDamage *= GUT_DAMAGE; break; case HITGROUP_LEFTARM: case HITGROUP_RIGHTARM: - flDamage *= gSkillData.plrArm; + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/arm_hit1.wav", 1, ATTN_NORM); + + if(!(bitsDamageType & DMG_BUCKSHOT)) + flDamage *= ARM_DAMAGE; break; case HITGROUP_LEFTLEG: case HITGROUP_RIGHTLEG: - flDamage *= gSkillData.plrLeg; + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/leg_hit1.wav", 1, ATTN_NORM); + + if(!(bitsDamageType & DMG_BUCKSHOT)) + flDamage *= LEG_DAMAGE; break; default: break; } + // FIX: Molotovs/Explosives shouldnt have blood streams + if(!(bitsDamageType & DMG_BURN) && !(bitsDamageType & DMG_BLAST)) + UTIL_BloodStream(pev->origin,vecDir*(flDamage/2),70,flDamage*5); + + if(flDamage && flDamage < pev->health) + { + // Flash blends for damage + if(bitsDamageType == DMG_BULLET_CONC) + { + UTIL_ScreenShake( pev->origin,(int)(flDamage/3),12,1.5,0.1); + UTIL_ScreenFade(this,RGB_CONC,TIME_CONC,HOLD_CONC,ALPHA_CONC,FLAGS_CONC); + } + else if( !(bitsDamageType & DMG_SHOCK) ) + UTIL_ScreenFade(this,RGB_SHOT,TIME_SHOT,HOLD_SHOT,ALPHA_SHOT,FLAGS_SHOT); + else + { + UTIL_ScreenFade(this,RGB_SHOCK,TIME_SHOCK,HOLD_SHOCK,ALPHA_SHOCK,FLAGS_SHOCK); + } + } + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); - AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage,bitsDamageType); } } @@ -430,7 +724,6 @@ void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { - // have suit diagnose the problem - ie: report damage type int bitsDamage = bitsDamageType; int ffound = TRUE; int fmajor; @@ -455,7 +748,6 @@ int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, return 0; // go take the damage first - CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); if ( !g_pGameRules->FPlayerCanTakeDamage( this, pAttacker ) ) @@ -503,13 +795,13 @@ int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, } // tell director about it - MESSAGE_BEGIN( MSG_SPEC, SVC_HLTV ); - WRITE_BYTE ( DRC_EVENT ); // take damage event - WRITE_SHORT( ENTINDEX(this->edict()) ); // index number of primary entity - WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity - WRITE_LONG( 5 ); // eventflags (priority and flags) - MESSAGE_END(); - + MESSAGE_BEGIN ( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); //command length in bytes + WRITE_BYTE ( DRC_EVENT ); + WRITE_SHORT( ENTINDEX(this->edict()) ); + WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); + WRITE_LONG ( 5 ); + MESSAGE_END(); // how bad is it, doc? @@ -518,16 +810,15 @@ int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, fcritical = (pev->health < 30); // handle all bits set in this damage message, - // let the suit give player the diagnosis // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash ) // UNDONE: still need to record damage and heal messages for the following types - // DMG_BURN - // DMG_FREEZE - // DMG_BLAST - // DMG_SHOCK + // DMG_BURN + // DMG_FREEZE + // DMG_BLAST + // DMG_SHOCK m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client m_bitsHUDDamage = -1; // make sure the damage bits get resent @@ -538,76 +829,56 @@ int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, if (bitsDamage & DMG_CLUB) { - if (fmajor) - SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC); // minor fracture bitsDamage &= ~DMG_CLUB; ffound = TRUE; } if (bitsDamage & (DMG_FALL | DMG_CRUSH)) { - if (fmajor) - SetSuitUpdate("!HEV_DMG5", FALSE, SUIT_NEXT_IN_30SEC); // major fracture - else - SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC); // minor fracture - bitsDamage &= ~(DMG_FALL | DMG_CRUSH); ffound = TRUE; } if (bitsDamage & DMG_BULLET) { - if (m_lastDamageAmount > 5) - SetSuitUpdate("!HEV_DMG6", FALSE, SUIT_NEXT_IN_30SEC); // blood loss detected - //else - // SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC); // minor laceration - bitsDamage &= ~DMG_BULLET; ffound = TRUE; } if (bitsDamage & DMG_SLASH) { - if (fmajor) - SetSuitUpdate("!HEV_DMG1", FALSE, SUIT_NEXT_IN_30SEC); // major laceration - else - SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC); // minor laceration - bitsDamage &= ~DMG_SLASH; ffound = TRUE; } if (bitsDamage & DMG_SONIC) { - if (fmajor) - SetSuitUpdate("!HEV_DMG2", FALSE, SUIT_NEXT_IN_1MIN); // internal bleeding bitsDamage &= ~DMG_SONIC; ffound = TRUE; } if (bitsDamage & (DMG_POISON | DMG_PARALYZE)) { - SetSuitUpdate("!HEV_DMG3", FALSE, SUIT_NEXT_IN_1MIN); // blood toxins detected bitsDamage &= ~(DMG_POISON | DMG_PARALYZE); ffound = TRUE; } if (bitsDamage & DMG_ACID) { - SetSuitUpdate("!HEV_DET1", FALSE, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected + bitsDamage &= ~DMG_ACID; ffound = TRUE; } if (bitsDamage & DMG_NERVEGAS) { - SetSuitUpdate("!HEV_DET0", FALSE, SUIT_NEXT_IN_1MIN); // biohazard detected + bitsDamage &= ~DMG_NERVEGAS; ffound = TRUE; } if (bitsDamage & DMG_RADIATION) { - SetSuitUpdate("!HEV_DET2", FALSE, SUIT_NEXT_IN_1MIN); // radiation detected + bitsDamage &= ~DMG_RADIATION; ffound = TRUE; } @@ -620,41 +891,8 @@ int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, pev->punchangle.x = -2; - if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75) - { - // first time we take major damage... - // turn automedic on if not on - SetSuitUpdate("!HEV_MED1", FALSE, SUIT_NEXT_IN_30MIN); // automedic on - - // give morphine shot if not given recently - SetSuitUpdate("!HEV_HEAL7", FALSE, SUIT_NEXT_IN_30MIN); // morphine shot - } - - if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75) - { - - // already took major damage, now it's critical... - if (pev->health < 6) - SetSuitUpdate("!HEV_HLTH3", FALSE, SUIT_NEXT_IN_10MIN); // near death - else if (pev->health < 20) - SetSuitUpdate("!HEV_HLTH2", FALSE, SUIT_NEXT_IN_10MIN); // health critical - - // give critical health warnings - if (!RANDOM_LONG(0,3) && flHealthPrev < 50) - SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention - } - - // if we're taking time based damage, warn about its continuing effects - if (fTookDamage && (bitsDamageType & DMG_TIMEBASED) && flHealthPrev < 75) - { - if (flHealthPrev < 50) - { - if (!RANDOM_LONG(0,3)) - SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention - } - else - SetSuitUpdate("!HEV_HLTH1", FALSE, SUIT_NEXT_IN_10MIN); // health dropping - } + // Store who it is (bleeding death credit) + m_pLastAttacker = pevAttacker; return fTookDamage; } @@ -686,7 +924,7 @@ void CBasePlayer::PackDeadPlayerItems( void ) if ( iWeaponRules == GR_PLR_DROP_GUN_NO && iAmmoRules == GR_PLR_DROP_AMMO_NO ) { // nothing to pack. Remove the weapons and return. Don't call create on the box! - RemoveAllItems( TRUE ); + RemoveAllItems(); return; } @@ -700,22 +938,31 @@ void CBasePlayer::PackDeadPlayerItems( void ) while ( pPlayerItem ) { - switch( iWeaponRules ) + CBasePlayerWeapon *pWeapon = (CBasePlayerWeapon*)pPlayerItem; + + if( pWeapon != NULL && pWeapon->iWeight() == WEIGHT_UNIQUE) { - case GR_PLR_DROP_GUN_ACTIVE: - if ( m_pActiveItem && pPlayerItem == m_pActiveItem ) - { - // this is the active item. Pack it. - rgpPackWeapons[ iPW++ ] = (CBasePlayerWeapon *)pPlayerItem; - } - break; - - case GR_PLR_DROP_GUN_ALL: rgpPackWeapons[ iPW++ ] = (CBasePlayerWeapon *)pPlayerItem; - break; + } + else + { + switch( iWeaponRules ) + { + case GR_PLR_DROP_GUN_ACTIVE: + if ( m_pActiveItem && pPlayerItem == m_pActiveItem ) + { + // this is the active item. Pack it. + rgpPackWeapons[ iPW++ ] = (CBasePlayerWeapon *)pPlayerItem; + } + break; - default: - break; + case GR_PLR_DROP_GUN_ALL: + rgpPackWeapons[ iPW++ ] = (CBasePlayerWeapon *)pPlayerItem; + break; + + default: + break; + } } pPlayerItem = pPlayerItem->m_pNext; @@ -757,6 +1004,7 @@ void CBasePlayer::PackDeadPlayerItems( void ) } } +#if 0 // create a box to pack the stuff into. CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin, pev->angles, edict() ); @@ -764,7 +1012,7 @@ void CBasePlayer::PackDeadPlayerItems( void ) pWeaponBox->pev->angles.z = 0; pWeaponBox->SetThink( CWeaponBox::Kill ); - pWeaponBox->pev->nextthink = gpGlobals->time + 120; + pWeaponBox->pev->nextthink = gpGlobals->time + 60.0f; // back these two lists up to their first elements iPA = 0; @@ -787,12 +1035,39 @@ void CBasePlayer::PackDeadPlayerItems( void ) } pWeaponBox->pev->velocity = pev->velocity * 1.2;// weaponbox has player's velocity, then some. +#endif - RemoveAllItems( TRUE );// now strip off everything that wasn't handled by the code above. +// back these two lists up to their first elements + iPA = 0; + iPW = 0; + + // Create a box for each weapon :) + while( rgpPackWeapons[ iPW ] ) + { + CWeaponBox *pWeaponBox = (CWeaponBox*)CBaseEntity::Create( "weaponbox", pev->origin, pev->angles, edict() ); + + pWeaponBox->pev->angles.x = 0 ;// don't let weaponbox tilt. + pWeaponBox->pev->angles.z = 0; + + pWeaponBox->SetThink( CWeaponBox::Kill ); + pWeaponBox->pev->nextthink = gpGlobals->time + 120; + + // pWeaponBox->pev->velocity = pev->velocity * 1.2;// weaponbox has player's velocity, then some. + pWeaponBox->pev->velocity = gpGlobals->v_forward * RANDOM_FLOAT(200,300) + gpGlobals->v_right * RANDOM_FLOAT(-400,400); + + pWeaponBox->PackWeapon( rgpPackWeapons[ iPW ] ); + + iPW++; + } + + RemoveAllItems();// now strip off everything that wasn't handled by the code above. } -void CBasePlayer::RemoveAllItems( BOOL removeSuit ) +void CBasePlayer::RemoveAllItems( void ) { + // This player isnt using a laser beam anymore :) + g_pGameRules->SetLaserBeam( ENTINDEX( edict() ), FALSE ); + if (m_pActiveItem) { ResetAutoaim( ); @@ -820,10 +1095,7 @@ void CBasePlayer::RemoveAllItems( BOOL removeSuit ) pev->viewmodel = 0; pev->weaponmodel = 0; - if ( removeSuit ) - pev->weapons = 0; - else - pev->weapons &= ~WEAPON_ALLWEAPONS; + pev->weapons = 0; for ( i = 0; i < MAX_AMMO_SLOTS;i++) m_rgAmmo[i] = 0; @@ -851,9 +1123,9 @@ void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) // Holster weapon immediately, to allow it to cleanup if ( m_pActiveItem ) - m_pActiveItem->Holster( ); + m_pActiveItem->Holster(); - g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor ); + g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor); if ( m_pTank != NULL ) { @@ -882,9 +1154,6 @@ void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) 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 ); @@ -905,10 +1174,6 @@ void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) WRITE_BYTE(0); 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 ); - if ( ( pev->health < -40 && iGib != GIB_NEVER ) || iGib == GIB_ALWAYS ) { pev->solid = SOLID_NOT; @@ -944,6 +1209,9 @@ void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) switch (playerAnim) { + case PLAYER_RELOAD: + m_IdealActivity = ACT_RELOAD; + break; case PLAYER_JUMP: m_IdealActivity = ACT_HOP; break; @@ -994,6 +1262,29 @@ void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) switch (m_IdealActivity) { + case ACT_WALK: + if (m_Activity != ACT_RANGE_ATTACK1 || m_fSequenceFinished) + { + if(FBitSet(pev->flags,FL_DUCKING)) + { + strcpy( szAnim, "crouch_aim_" ); + strcat( szAnim, m_szAnimExtention ); + } + else + { + strcpy( szAnim, "ref_aim_" ); + strcpy( szAnim, m_szAnimExtention ); + } + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + m_Activity = ACT_WALK; + } + else + { + animDesired = pev->sequence; + } + break; case ACT_HOVER: case ACT_LEAP: case ACT_SWIM: @@ -1014,17 +1305,41 @@ void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) pev->frame = 0; ResetSequenceInfo( ); return; - - case ACT_RANGE_ATTACK1: - if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching - strcpy( szAnim, "crouch_shoot_" ); + case ACT_RELOAD: + if(FBitSet(pev->flags,FL_DUCKING)) + strcpy( szAnim, "crouch_shoot_reload_"); else - strcpy( szAnim, "ref_shoot_" ); + strcpy( szAnim, "ref_shoot_reload_"); + 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_RANGE_ATTACK1: + if(FBitSet(pev->flags,FL_DUCKING)) + { + strcpy( szAnim, "crouch_shoot_" ); + strcat( szAnim, m_szAnimExtention ); + } + else + { + strcpy( szAnim, "ref_shoot_" ); + strcpy( szAnim, m_szAnimExtention ); + } + animDesired = LookupSequence( szAnim ); if (animDesired == -1) animDesired = 0; - if ( pev->sequence != animDesired || !m_fSequenceLoops ) { pev->frame = 0; @@ -1036,28 +1351,9 @@ void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) } 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 ) ) @@ -1086,7 +1382,6 @@ void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) pev->gaitsequence = LookupSequence( "deep_idle" ); } - // Already using the desired animation? if (pev->sequence == animDesired) return; @@ -1107,14 +1402,14 @@ all the ammo we have into the ammo vars. */ void CBasePlayer::TabulateAmmo() { - ammo_9mm = AmmoInventory( GetAmmoIndex( "9mm" ) ); - ammo_357 = AmmoInventory( GetAmmoIndex( "357" ) ); - ammo_argrens = AmmoInventory( GetAmmoIndex( "ARgrenades" ) ); - ammo_bolts = AmmoInventory( GetAmmoIndex( "bolts" ) ); - ammo_buckshot = AmmoInventory( GetAmmoIndex( "buckshot" ) ); - ammo_rockets = AmmoInventory( GetAmmoIndex( "rockets" ) ); - ammo_uranium = AmmoInventory( GetAmmoIndex( "uranium" ) ); - ammo_hornets = AmmoInventory( GetAmmoIndex( "Hornets" ) ); + ammo_9mm = AmmoInventory( GetAmmoIndex("9mm Parabellum")); + ammo_357 = AmmoInventory( GetAmmoIndex(".40 S&W")); + ammo_bolts = AmmoInventory( GetAmmoIndex(".50 AE")); + ammo_argrens = AmmoInventory( GetAmmoIndex(".454 Casull")); + ammo_rockets = AmmoInventory( GetAmmoIndex(".223 Handcannon")); + ammo_uranium = AmmoInventory( GetAmmoIndex("9mm SMG9 Ammo")); + ammo_buckshot = AmmoInventory( GetAmmoIndex("Buckshot")); +/* ammo_hornets = AmmoInventory( GetAmmoIndex( "Hornets" ) );*/ } @@ -1311,17 +1606,38 @@ void CBasePlayer::PlayerDeathThink(void) // 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 0 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() && forcerespawn.value > 0 && (gpGlobals->time > (m_fDeadTime + 5))) ) +#endif + +// If player has been dead for over 1 second, respond to key presses + if( gpGlobals->time > (m_fDeadTime + 1.0f) ) + { + if( !fAnyButtonDown ) + return; + } + else return; +// wait for any button down, or mp_forcerespawn is set and the respawn time is up +// if (!fAnyButtonDown +// && !( g_pGameRules->IsMultiplayer() && forcerespawn.value > 0 && (gpGlobals->time > (m_fDeadTime + 5))) ) +// return; + + // First, wait at least 5 seconds before respawning. if forcerespawn is set, respawn + // Otherwise, just go ahead and wait for a button press. +/* if(gpGlobals->time > (m_fDeadTime + 1)) + { + if(!fAnyButtonDown && !g_pGameRules->IsMultiplayer() ) + return; + } + else + return;*/ + pev->button = 0; m_iRespawnFrames = 0; @@ -1366,6 +1682,8 @@ void CBasePlayer::StartDeathCam( void ) } CopyToBodyQue( pev ); + + UTIL_ScreenFade(this,Vector(0,0,0),0,0,0,FFADE_OUT); StartObserver( pSpot->v.origin, pSpot->v.v_angle ); } else @@ -1374,25 +1692,13 @@ void CBasePlayer::StartDeathCam( void ) TraceResult tr; CopyToBodyQue( pev ); UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr ); + + UTIL_ScreenFade(this,Vector(0,0,0),0,0,0,FFADE_OUT); StartObserver( tr.vecEndPos, UTIL_VecToAngles( tr.vecEndPos - pev->origin ) ); return; } } -void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) -{ - m_afPhysicsFlags |= PFLAG_OBSERVER; - - 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; - pev->modelindex = 0; - UTIL_SetOrigin( pev, vecPosition ); -} - // // PlayerUse - handles USE keypress // @@ -1672,13 +1978,18 @@ void CBasePlayer::PreThink(void) 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 ( IsObserver() ) + { + Observer_HandleButtons(); + pev->impulse = 0; + return; + } if (pev->deadflag >= DEAD_DYING) { @@ -1769,11 +2080,6 @@ void CBasePlayer::PreThink(void) // 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: @@ -1806,7 +2112,6 @@ void CBasePlayer::PreThink(void) 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 @@ -1922,19 +2227,6 @@ void CBasePlayer::CheckTimeBasedDamage() 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) { @@ -1950,272 +2242,6 @@ void CBasePlayer::CheckTimeBasedDamage() } } -/* -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<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<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 @@ -2354,7 +2380,6 @@ void CBasePlayer :: UpdatePlayerSound ( void ) //ALERT ( at_console, "%d/%d\n", iVolume, m_iTargetVolume ); } - void CBasePlayer::PostThink() { if ( g_fGameOver ) @@ -2380,6 +2405,12 @@ void CBasePlayer::PostThink() // do weapon stuff ItemPostFrame( ); +// calculate weapon weight + pev->fuser4 = flCalculateInventoryWeight(); + +// can this person use willpower ? + pev->vuser3[0] = (m_bWillpower == TRUE) ? 1 : 0; + // 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 @@ -2451,9 +2482,92 @@ void CBasePlayer::PostThink() // Track button info so we can detect 'pressed' and 'released' buttons next frame m_afButtonLast = pev->button; + if(pev->health < 100 && IsAlive() && m_flNextHealthUpdate <= gpGlobals->time) + { + m_flNextHealthUpdate = gpGlobals->time + 2; + + if(pev->health >= PLAYER_COMPOSURE_LIMIT) + { + // Composure + pev->health++; + + if(pev->health > pev->max_health) + pev->health = pev->max_health; + + // Tell client about player state + MESSAGE_BEGIN(MSG_ONE,gmsgPlayerState,NULL,pev); + WRITE_BYTE(1); + MESSAGE_END(); + } + else if(pev->health <= PLAYER_BLEED_LIMIT) + { + // Bleeding + pev->vuser2.y = 2; + TakeDamage(m_pLastAttacker,m_pLastAttacker,1.0f,DMG_BLEEDING); // Last person to hurt us gets credit. + m_flLastAttack = gpGlobals->time; + + UTIL_ScreenFade(this,RGB_BLEED,TIME_BLEED,HOLD_BLEED,ALPHA_BLEED,FLAGS_BLEED); + + // blood drip + if(pev->health <= 9) + EMIT_SOUND(ENT(pev), CHAN_ITEM, "player/breathe2.wav", 0.5f, ATTN_NORM); + else if(pev->health >= 10) + EMIT_SOUND(ENT(pev), CHAN_ITEM, "player/breathe1.wav", 0.5f, ATTN_NORM); + + switch(RANDOM_LONG(0,2)) + { + case 0: + break; + EMIT_SOUND(ENT(pev), CHAN_BODY, "player/blood_drip1.wav", 1, ATTN_NORM); + case 1: + break; + EMIT_SOUND(ENT(pev), CHAN_BODY, "player/blood_drip2.wav", 1, ATTN_NORM); + case 2: + default: + EMIT_SOUND(ENT(pev), CHAN_BODY, "player/blood_drip3.wav", 1, ATTN_NORM); + break; + } + + // Tell client about player state + MESSAGE_BEGIN(MSG_ONE,gmsgPlayerState,NULL,pev); + WRITE_BYTE(2); + MESSAGE_END(); + } + else if(pev->health >= PLAYER_WILLPOWER_LIMIT_LO && pev->health <= PLAYER_WILLPOWER_LIMIT_HI && m_bWillpower) + { + // Willpower + pev->health++; + + if(pev->health > PLAYER_WILLPOWER_LIMIT_HI) + m_bWillpower = FALSE; + + // Tell client about player state + MESSAGE_BEGIN(MSG_ONE,gmsgPlayerState,NULL,pev); + WRITE_BYTE(3); + MESSAGE_END(); + } + else + { + // Tell client about player state + MESSAGE_BEGIN(MSG_ONE,gmsgPlayerState,NULL,pev); + WRITE_BYTE(0); + MESSAGE_END(); + } + } + + // Blood trails + if(pev->health <= PLAYER_BLEED_LIMIT && m_flNextBleedDrip <= gpGlobals->time) + { + m_flNextBleedDrip = gpGlobals->time + 0.3f; + + TraceResult tr; + UTIL_TraceLine(pev->origin,pev->origin + Vector(0,0,-64),ignore_monsters,ENT(pev),&tr); + UTIL_BloodDecalTrace(&tr,BLOOD_COLOR_RED); + } + pt_end: #if defined( CLIENT_WEAPONS ) - // Decay timers on weapons + // 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++ ) { @@ -2498,29 +2612,33 @@ pt_end: m_flNextAttack -= gpGlobals->frametime; if ( m_flNextAttack < -0.001 ) m_flNextAttack = -0.001; - - if ( m_flNextAmmoBurn != 1000 ) - { - m_flNextAmmoBurn -= gpGlobals->frametime; - - if ( m_flNextAmmoBurn < -0.001 ) - m_flNextAmmoBurn = -0.001; - } - - if ( m_flAmmoStartCharge != 1000 ) - { - m_flAmmoStartCharge -= gpGlobals->frametime; - - if ( m_flAmmoStartCharge < -0.001 ) - m_flAmmoStartCharge = -0.001; - } - #else return; #endif } +// as players gain more inventory items +// they will have a tendency to slow +// down. tell the game how much this player is carrying. +float CBasePlayer::flCalculateInventoryWeight() +{ + float flRet = 0; + + for(int i = 0;i < MAX_ITEM_TYPES;i++) + if(m_rgpPlayerItems[ i ] != NULL) + { + CWasteWeapon *pThisWeapon = (CWasteWeapon*)m_rgpPlayerItems[i]; + + if(pThisWeapon != NULL) + { + flRet += (m_pActiveItem == pThisWeapon) ? pThisWeapon->iGetWeight() : pThisWeapon->iGetWeight() * 0.85f; + } + } + + // slightly less than the full value + return flRet; +} // checks if the spot is clear of players BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ) @@ -2675,9 +2793,6 @@ void CBasePlayer::Spawn( void ) 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. @@ -2720,20 +2835,62 @@ void CBasePlayer::Spawn( void ) m_iClientHideHUD = -1; // force this to be recalculated m_fWeapon = FALSE; m_pClientActiveItem = NULL; - m_iClientBattery = -1; + + // Bleeding + m_pLastAttacker = NULL; + m_flNextBleedDrip = 0.0; + m_flLastAttack = 0.0; + + m_bWillpower = TRUE; + m_flNextHealthUpdate = 0.0; // reset all ammo values to 0 for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) { - m_rgAmmo[i] = 0; + 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; - g_pGameRules->PlayerSpawn( this ); -} + pev->vuser2[2] = 1; // Player can scream from falling + if(m_bCanSpawn) + { + m_iHideHUD &= ~( HIDEHUD_WEAPONS|HIDEHUD_FLASHLIGHT|HIDEHUD_HEALTH ); + pev->effects &= ~EF_NODRAW; + pev->iuser3 = 0; + pev->vuser3[1] = 0; // Run all physics + + // Remove spectator standing + MESSAGE_BEGIN(MSG_ALL,gmsgSpectator); + WRITE_BYTE(ENTINDEX(edict())); + WRITE_BYTE(0); + MESSAGE_END(); + + g_pGameRules->PlayerSpawn(this); // Get his weapons, etc + } + else + { + if(m_bObserver) + { + pev->vuser3[1] = 0; // Run all physics + StartObserver(pev->origin,pev->angles); + } + else + { + // Our player can just stick around + pev->effects |= EF_NODRAW; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + pev->iuser3 = 1; + pev->vuser3[1] = 1; // Dont run any player physics + } + + m_iHideHUD |= HIDEHUD_WEAPONS|HIDEHUD_FLASHLIGHT|HIDEHUD_HEALTH; + } +} void CBasePlayer :: Precache( void ) { @@ -2756,17 +2913,9 @@ void CBasePlayer :: Precache( void ) // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) // because they need to precache before any clients have connected - // 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 @@ -2778,7 +2927,6 @@ void CBasePlayer :: Precache( void ) m_fInitHUD = TRUE; } - int CBasePlayer::Save( CSave &save ) { if ( !CBaseMonster::Save(save) ) @@ -2867,6 +3015,9 @@ void CBasePlayer::SelectNextItem( int iItem ) CBasePlayerItem *pItem; pItem = m_rgpPlayerItems[ iItem ]; + + if( !g_pGameRules->FShouldSwitchWeapon( this, pItem ) ) + return; if (!pItem) return; @@ -3004,7 +3155,6 @@ void CBasePlayer::SelectPrevItem( int iItem ) { } - const char *CBasePlayer::TeamID( void ) { if ( pev == NULL ) // Not fully connected yet @@ -3098,13 +3248,13 @@ void CBloodSplat::Spray ( void ) { TraceResult tr; - if ( g_Language != LANGUAGE_GERMAN ) - { +// 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 ( SUB_Remove ); pev->nextthink = gpGlobals->time + 0.1; } @@ -3162,18 +3312,14 @@ void CBasePlayer :: FlashlightTurnOn( void ) return; } - if ( (pev->weapons & (1<effects, EF_DIMLIGHT); - MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); - WRITE_BYTE(1); - WRITE_BYTE(m_iFlashBattery); - MESSAGE_END(); + 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; - - } + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; } @@ -3202,7 +3348,6 @@ Reset stuff so that the state is transmitted. void CBasePlayer :: ForceClientDllUpdate( void ) { m_iClientHealth = -1; - m_iClientBattery = -1; m_iTrain |= TRAIN_NEW; // Force new train message. m_fWeapon = FALSE; // Force weapon send m_fKnownItem = FALSE; // Force weaponinit messages. @@ -3229,31 +3374,6 @@ void CBasePlayer::ImpulseCommands( ) int iImpulse = (int)pev->impulse; switch (iImpulse) { - 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() ) @@ -3301,7 +3421,6 @@ void CBasePlayer::ImpulseCommands( ) //========================================================= void CBasePlayer::CheatImpulseCommands( int iImpulse ) { -#if !defined( HLDEMO_BUILD ) if ( g_flWeaponCheat == 0.0 ) { return; @@ -3329,36 +3448,29 @@ void CBasePlayer::CheatImpulseCommands( int iImpulse ) case 101: + { gEvilImpulse101 = TRUE; - GiveNamedItem( "item_suit" ); - GiveNamedItem( "item_battery" ); - GiveNamedItem( "weapon_crowbar" ); - GiveNamedItem( "weapon_9mmhandgun" ); - GiveNamedItem( "ammo_9mmclip" ); - GiveNamedItem( "weapon_shotgun" ); - GiveNamedItem( "ammo_buckshot" ); - GiveNamedItem( "weapon_9mmAR" ); - GiveNamedItem( "ammo_9mmAR" ); - GiveNamedItem( "ammo_ARgrenades" ); - GiveNamedItem( "weapon_handgrenade" ); - GiveNamedItem( "weapon_tripmine" ); -#ifndef OEM_BUILD - GiveNamedItem( "weapon_357" ); - GiveNamedItem( "ammo_357" ); - GiveNamedItem( "weapon_crossbow" ); - GiveNamedItem( "ammo_crossbow" ); - GiveNamedItem( "weapon_egon" ); - GiveNamedItem( "weapon_gauss" ); - GiveNamedItem( "ammo_gaussclip" ); - GiveNamedItem( "weapon_rpg" ); - GiveNamedItem( "ammo_rpgclip" ); - GiveNamedItem( "weapon_satchel" ); - GiveNamedItem( "weapon_snark" ); - GiveNamedItem( "weapon_hornetgun" ); -#endif - gEvilImpulse101 = FALSE; - break; + int i; + + for( i = 0;i < g_MeleeItems.size;i++ ) + GiveNamedItem(g_MeleeItems.array[i].weapon_classname); + + for( i = 0;i < g_SidearmItems.size;i++ ) + GiveNamedItem(g_SidearmItems.array[i].weapon_classname); + + for( i = 0;i < g_PrimaryItems.size;i++ ) + GiveNamedItem(g_PrimaryItems.array[i].weapon_classname); + + for( i = 0;i < g_UniqueItems.size;i++ ) + GiveNamedItem(g_UniqueItems.array[i].weapon_classname); + + for( i = 0;i < g_OtherItems.size;i++ ) + GiveNamedItem(g_OtherItems.array[i].weapon_classname); + + gEvilImpulse101 = FALSE; + } + break; case 102: // Gibbage!!! CGib::SpawnRandomGibs( pev, 1, 1 ); @@ -3473,7 +3585,6 @@ void CBasePlayer::CheatImpulseCommands( int iImpulse ) } break; } -#endif // HLDEMO_BUILD } // @@ -3658,6 +3769,22 @@ void CBasePlayer::ItemPostFrame() { static int fInSelect = FALSE; + CWasteWeapon *pWeap = (CWasteWeapon*)(m_pActiveItem); + + if( pWeap != NULL ) + { + if( pWeap->bLaserActive() ) + { + g_pGameRules->SetLaserBeam( ENTINDEX( edict() ), TRUE ); + } + else + { + g_pGameRules->SetLaserBeam( ENTINDEX( edict() ), FALSE ); + } + } + else + g_pGameRules->SetLaserBeam( ENTINDEX( edict() ), FALSE ); + // check if the player is using a tank if ( m_pTank != NULL ) return; @@ -3806,18 +3933,6 @@ void CBasePlayer :: UpdateClientData( void ) m_iClientHealth = pev->health; } - - 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 @@ -3966,30 +4081,9 @@ void CBasePlayer :: UpdateClientData( void ) //========================================================= 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 @@ -4035,82 +4129,7 @@ void CBasePlayer :: EnableControl(BOOL fControl) //========================================================= 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 ( g_psv_aim->value != 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 ); + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); return gpGlobals->v_forward; } @@ -4329,46 +4348,40 @@ void CBasePlayer::DropPlayerItem ( char *pszItemName ) 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<m_iId);// take item off hud - - CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin + gpGlobals->v_forward * 10, pev->angles, edict() ); + 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; + pWeaponBox->pev->velocity = gpGlobals->v_forward * RANDOM_FLOAT(200,300) + gpGlobals->v_right * RANDOM_FLOAT(-400,400); - iAmmoIndex = GetAmmoIndex ( pWeapon->pszAmmo1() ); // ??? - - if ( iAmmoIndex != -1 ) + if( pWeapon->iFlags() & (ITEM_FLAG_EXHAUSTIBLE|ITEM_FLAG_LIMITINWORLD) ) { - // 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; + CBasePlayerWeapon *pLimitWeap = (CBasePlayerWeapon*)pWeapon; - } - else + if( pLimitWeap ) { - // pack half of the ammo - pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] / 2 ); - m_rgAmmo[ iAmmoIndex ] /= 2; + if( --m_rgAmmo[ pLimitWeap->m_iPrimaryAmmoType ] == 0 ) + { + g_pGameRules->GetNextBestWeapon( this, pWeapon ); + pev->weapons &= ~(1<m_iId);// take item off hud + } } + CBaseEntity *pEnt = CBaseEntity::Create( (char*)STRING( pWeapon->pev->classname ), pev->origin, pev->angles, NULL ); + pWeaponBox->PackWeapon( (CBasePlayerItem*)pEnt ); + } + else + { + g_pGameRules->GetNextBestWeapon( this, pWeapon ); + pev->weapons &= ~(1<m_iId);// take item off hud + + pWeaponBox->PackWeapon( pWeapon ); } return;// we're done, so stop searching with the FOR loop. @@ -4443,66 +4456,6 @@ BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) 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: @@ -4527,7 +4480,7 @@ void CStripWeapons :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TY } if ( pPlayer ) - pPlayer->RemoveAllItems( FALSE ); + pPlayer->RemoveAllItems(); } diff --git a/dlls/player.h b/dlls/player.h index 103b8f2..8cb21a2 100644 --- a/dlls/player.h +++ b/dlls/player.h @@ -17,13 +17,13 @@ #include "pm_materials.h" +#include "tw_common.h" - -#define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet -#define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet -#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_FATAL_FALL_SPEED 600// approx 35 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 507// approx 17 feet +#define DAMAGE_FOR_FALL_SPEED (float) 16 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. #define PLAYER_MIN_BOUNCE_SPEED 200 -#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. +#define PLAYER_FALL_PUNCH_THRESHHOLD (float)275 // won't punch player's screen/make scrape noise unless player falling at least this fast. // // Player PHYSICS FLAGS bits @@ -31,32 +31,61 @@ #define PFLAG_ONLADDER ( 1<<0 ) #define PFLAG_ONSWING ( 1<<0 ) #define PFLAG_ONTRAIN ( 1<<1 ) -#define PFLAG_ONBARNACLE ( 1<<2 ) #define PFLAG_DUCKING ( 1<<3 ) // In the process of ducking, but totally squatted yet #define PFLAG_USING ( 1<<4 ) // Using a continuous entity #define PFLAG_OBSERVER ( 1<<5 ) // player is locked in stationary cam mode. Spectators can move, observers can't. +// +// Screen Fade efx values +// +//#ifdef SHAKE_H + +#define RGB_BLEED Vector(255,0,0) +#define TIME_BLEED 2 +#define HOLD_BLEED 0 +#define ALPHA_BLEED 90 +#define FLAGS_BLEED FFADE_IN + +#define RGB_CONC Vector(255,255,255) +#define TIME_CONC flDamage/40.0f +#define HOLD_CONC flDamage/125.0f +#define ALPHA_CONC 160 +#define FLAGS_CONC FFADE_IN + +#define RGB_FADE Vector(0,0,0) +#define TIME_FADE 1.25 +#define HOLD_FADE 0 +#define ALPHA_FADE 255 +#define FLAGS_FADE FFADE_OUT|FFADE_STAYOUT + +#define RGB_FALL Vector(0,0,0) +#define TIME_FALL (pPlayer->m_flFallVelocity*DAMAGE_FOR_FALL_SPEED)/30 +#define HOLD_FALL 1.5 +#define ALPHA_FALL 128 +#define FLAGS_FALL FFADE_IN + +#define RGB_SHOT Vector(255,0,0) +#define TIME_SHOT flDamage/50.0f +#define HOLD_SHOT flDamage/150.0f +#define ALPHA_SHOT 128 +#define FLAGS_SHOT FFADE_IN + +#define RGB_SHOCK Vector(32,32,160) +#define TIME_SHOCK flDamage/10.0f +#define HOLD_SHOCK flDamage/50.0f +#define ALPHA_SHOCK 96 +#define FLAGS_SHOCK FFADE_IN + +#define FADE_BLEED Vector(255,0,0) + +//#endif + // // generic player // //----------------------------------------------------- //This is Half-Life player entity //----------------------------------------------------- -#define CSUITPLAYLIST 4 // max of 4 suit sentences queued up at any time - -#define SUIT_GROUP TRUE -#define SUIT_SENTENCE FALSE - -#define SUIT_REPEAT_OK 0 -#define SUIT_NEXT_IN_30SEC 30 -#define SUIT_NEXT_IN_1MIN 60 -#define SUIT_NEXT_IN_5MIN 300 -#define SUIT_NEXT_IN_10MIN 600 -#define SUIT_NEXT_IN_30MIN 1800 -#define SUIT_NEXT_IN_1HOUR 3600 - -#define CSUITNOREPEAT 32 - #define SOUND_FLASHLIGHT_ON "items/flashlight1.wav" #define SOUND_FLASHLIGHT_OFF "items/flashlight1.wav" @@ -70,8 +99,28 @@ typedef enum PLAYER_SUPERJUMP, PLAYER_DIE, PLAYER_ATTACK1, + PLAYER_RELOAD } PLAYER_ANIM; +// Manages player item selection +// Sets for all weapon categories +class CPlayerInventory +{ +public: + CPlayerInventory(); + + BOOL SetInventory( int Slot, int Index ); + void SpawnInventory( CBasePlayer *pPlayer ); + + void SetRandom( BOOL b ); +private: + playeritemlist_t *GetListForSlot( int Slot ); + + // Order: Melee, Sidearm, Primary, Unique, Item + int m_iItemIndices[5]; + BOOL m_bRandom; +}; + class CBasePlayer : public CBaseMonster { public: @@ -104,6 +153,22 @@ public: unsigned int m_afPhysicsFlags; // physics flags - set when 'normal' physics should be revisited or overriden float m_fNextSuicideTime; // the time after which the player can next use the suicide command + CPlayerInventory m_Inventory; // Spawn Inventory + + // Round Based Vars - Used by game modes + int m_iTempFrags; + int m_iLivesLeft; + + // Bleeding + entvars_t *m_pLastAttacker; + float m_flNextBleedDrip; // Spill blood on ground + float m_flLastAttack; // How long ago were we last attacked when bleeding? + + BOOL m_bWillpower; // True if this player can use willpower + float m_flNextHealthUpdate; + + BOOL m_bCanSpawn; // If false player will just spectate + BOOL m_bObserver; // HLTV and other cute features // these are time-sensitive things that we keep track of float m_flTimeStepSound; // when the last stepping sound was made @@ -112,17 +177,9 @@ public: float m_flDuckTime; // how long we've been ducking float m_flWallJumpTime; // how long until next walljump - float m_flSuitUpdate; // when to play next suit update - int m_rgSuitPlayList[CSUITPLAYLIST];// next sentencenum to play for suit update - int m_iSuitPlayNext; // next sentence slot for queue storage; - int m_rgiSuitNoRepeat[CSUITNOREPEAT]; // suit sentence no repeat list - float m_rgflSuitNoRepeatTime[CSUITNOREPEAT]; // how long to wait before allowing repeat int m_lastDamageAmount; // Last damage taken float m_tbdPrev; // Time-based damage timer - float m_flgeigerRange; // range to nearest radiation source - float m_flgeigerDelay; // delay per update of range msg to client - int m_igeigerRangePrev; int m_iStepLeft; // alternate left/right foot stepping sound char m_szTextureName[CBTEXTURENAMEMAX]; // current texture name we're standing on char m_chTextureType; // current texture type @@ -146,7 +203,6 @@ public: float m_tSneaking; int m_iUpdateTime; // stores the number of frame ticks before sending HUD update messages int m_iClientHealth; // the health currently known by the client. If this changes, send a new - int m_iClientBattery; // the Battery currently known by the client. If this changes, send a new int m_iHideHUD; // the players hud weapon info is to be hidden int m_iClientHideHUD; int m_iFOV; // field of view @@ -180,9 +236,10 @@ public: virtual void Duck( void ); virtual void PreThink( void ); virtual void PostThink( void ); + virtual float flCalculateInventoryWeight( void ); virtual Vector GetGunPosition( void ); virtual int TakeHealth( float flHealth, int bitsDamageType ); - virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue = 0); virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); virtual void Killed( entvars_t *pevAttacker, int iGib ); virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) + pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 ); }; // position to shoot at @@ -190,7 +247,7 @@ public: virtual void StopSneaking( void ) { m_tSneaking = gpGlobals->time + 30; } virtual BOOL IsSneaking( void ) { return m_tSneaking <= gpGlobals->time; } virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } - virtual BOOL ShouldFadeOnDeath( void ) { return FALSE; } + virtual BOOL ShouldFadeOnDeath( void ) { return TRUE; } virtual BOOL IsPlayer( void ) { return TRUE; } // Spectators should return FALSE for this, they aren't "players" as far as game logic is concerned virtual BOOL IsNetClient( void ) { return TRUE; } // Bots should return FALSE for this, they can't receive NET messages @@ -201,7 +258,7 @@ public: virtual int Restore( CRestore &restore ); void RenewItems(void); void PackDeadPlayerItems( void ); - void RemoveAllItems( BOOL removeSuit ); + void RemoveAllItems( void ); BOOL SwitchWeapon( CBasePlayerItem *pWeapon ); // JOHN: sends custom messages if player HUD data has changed (eg health, ammo) @@ -221,7 +278,7 @@ public: void DeathSound ( void ); int Classify ( void ); - void SetAnimation( PLAYER_ANIM playerAnim ); + void SetAnimation( PLAYER_ANIM playerAnim ); // extend this bad boy! void SetWeaponAnimType( const char *szExtention ); char m_szAnimExtention[32]; @@ -231,6 +288,14 @@ public: void StartDeathCam( void ); void StartObserver( Vector vecPosition, Vector vecViewAngle ); + void StopObserver( void ); + void Observer_FindNextPlayer( bool bReverse ); + void Observer_HandleButtons(); + void Observer_SetMode( int iMode ); + float m_flNextObserverInput; + int IsObserver() { return pev->iuser1; }; + + EHANDLE m_hObserverTarget; void AddPoints( int score, BOOL bAllowNegativeScore ); void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); @@ -240,6 +305,7 @@ public: BOOL HasPlayerItem( CBasePlayerItem *pCheckItem ); BOOL HasNamedPlayerItem( const char *pszItemName ); BOOL HasWeapons( void );// do I have ANY weapons? + void ClearLocalInventory(); void SelectPrevItem( int iItem ); void SelectNextItem( int iItem ); void SelectLastItem(void); @@ -256,14 +322,9 @@ public: void EXPORT PlayerDeathThink( void ); void PlayerUse( void ); - void CheckSuitUpdate(); - void SetSuitUpdate(char *name, int fgroup, int iNoRepeat); - void UpdateGeigerCounter( void ); void CheckTimeBasedDamage( void ); BOOL FBecomeProne ( void ); - void BarnacleVictimBitten ( entvars_t *pevBarnacle ); - void BarnacleVictimReleased ( void ); static int GetAmmoIndex(const char *psz); int AmmoInventory( int iAmmoIndex ); int Illumination( void ); @@ -273,18 +334,12 @@ public: Vector AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ); void ForceClientDllUpdate( void ); // Forces all client .dll specific data to be resent to client. - void DeathMessage( entvars_t *pevKiller ); void SetCustomDecalFrames( int nFrames ); int GetCustomDecalFrames( void ); void CBasePlayer::TabulateAmmo( void ); - - float m_flStartCharge; - float m_flAmmoStartCharge; - float m_flPlayAftershock; - float m_flNextAmmoBurn;// while charging, when to absorb another unit of player's ammo? }; #define AUTOAIM_2DEGREES 0.0348994967025 @@ -292,7 +347,6 @@ public: #define AUTOAIM_8DEGREES 0.1391731009601 #define AUTOAIM_10DEGREES 0.1736481776669 - extern int gmsgHudText; extern BOOL gInitHUD; diff --git a/dlls/schedule.cpp b/dlls/schedule.cpp new file mode 100644 index 0000000..76145e0 --- /dev/null +++ b/dlls/schedule.cpp @@ -0,0 +1,1289 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// schedule.cpp - functions and data pertaining to the +// monsters' AI scheduling system. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "scripted.h" +#include "nodes.h" +#include "soundent.h" + +extern CGraph WorldGraph; + +//========================================================= +// FHaveSchedule - Returns TRUE if monster's m_pSchedule +// is anything other than NULL. +//========================================================= +BOOL CBaseMonster :: FHaveSchedule( void ) +{ + if ( m_pSchedule == NULL ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// ClearSchedule - blanks out the caller's schedule pointer +// and index. +//========================================================= +void CBaseMonster :: ClearSchedule( void ) +{ + m_iTaskStatus = TASKSTATUS_NEW; + m_pSchedule = NULL; + m_iScheduleIndex = 0; +} + +//========================================================= +// FScheduleDone - Returns TRUE if the caller is on the +// last task in the schedule +//========================================================= +BOOL CBaseMonster :: FScheduleDone ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + if ( m_iScheduleIndex == m_pSchedule->cTasks ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// ChangeSchedule - replaces the monster's schedule pointer +// with the passed pointer, and sets the ScheduleIndex back +// to 0 +//========================================================= +void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule ) +{ + ASSERT( pNewSchedule != NULL ); + + m_pSchedule = pNewSchedule; + m_iScheduleIndex = 0; + m_iTaskStatus = TASKSTATUS_NEW; + m_afConditions = 0;// clear all of the conditions + m_failSchedule = SCHED_NONE; + + if ( m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND && !(m_pSchedule->iSoundMask) ) + { + ALERT ( at_aiconsole, "COND_HEAR_SOUND with no sound mask!\n" ); + } + else if ( m_pSchedule->iSoundMask && !(m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND) ) + { + ALERT ( at_aiconsole, "Sound mask without COND_HEAR_SOUND!\n" ); + } + +// this is very useful code if you can isolate a test case in a level with a single monster. It will notify +// you of every schedule selection the monster makes. +#if 0 + if ( FClassnameIs( pev, "monster_human_grunt" ) ) + { + Task_t *pTask = GetTask(); + + if ( pTask ) + { + const char *pName = NULL; + + if ( m_pSchedule ) + { + pName = m_pSchedule->pName; + } + else + { + pName = "No Schedule"; + } + + if ( !pName ) + { + pName = "Unknown"; + } + + ALERT( at_aiconsole, "%s: picked schedule %s\n", STRING( pev->classname ), pName ); + } + } +#endif// 0 + +} + +//========================================================= +// NextScheduledTask - increments the ScheduleIndex +//========================================================= +void CBaseMonster :: NextScheduledTask ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + m_iTaskStatus = TASKSTATUS_NEW; + m_iScheduleIndex++; + + if ( FScheduleDone() ) + { + // just completed last task in schedule, so make it invalid by clearing it. + SetConditions( bits_COND_SCHEDULE_DONE ); + //ClearSchedule(); + } +} + +//========================================================= +// IScheduleFlags - returns an integer with all Conditions +// bits that are currently set and also set in the current +// schedule's Interrupt mask. +//========================================================= +int CBaseMonster :: IScheduleFlags ( void ) +{ + if( !m_pSchedule ) + { + return 0; + } + + // strip off all bits excepts the ones capable of breaking this schedule. + return m_afConditions & m_pSchedule->iInterruptMask; +} + +//========================================================= +// FScheduleValid - returns TRUE as long as the current +// schedule is still the proper schedule to be executing, +// taking into account all conditions +//========================================================= +BOOL CBaseMonster :: FScheduleValid ( void ) +{ + if ( m_pSchedule == NULL ) + { + // schedule is empty, and therefore not valid. + return FALSE; + } + + if ( HasConditions( m_pSchedule->iInterruptMask | bits_COND_SCHEDULE_DONE | bits_COND_TASK_FAILED ) ) + { +#ifdef DEBUG + if ( HasConditions ( bits_COND_TASK_FAILED ) && m_failSchedule == SCHED_NONE ) + { + // fail! Send a visual indicator. + ALERT ( at_aiconsole, "Schedule: %s Failed\n", m_pSchedule->pName ); + + Vector tmp = pev->origin; + tmp.z = pev->absmax.z + 16; + UTIL_Sparks( tmp ); + } +#endif // DEBUG + + // some condition has interrupted the schedule, or the schedule is done + return FALSE; + } + + return TRUE; +} + +//========================================================= +// MaintainSchedule - does all the per-think schedule maintenance. +// ensures that the monster leaves this function with a valid +// schedule! +//========================================================= +void CBaseMonster :: MaintainSchedule ( void ) +{ + int i; + + // UNDONE: Tune/fix this 10... This is just here so infinite loops are impossible + for ( i = 0; i < 10; i++ ) + { + if ( m_pSchedule != NULL && TaskIsComplete() ) + { + NextScheduledTask(); + } + + // validate existing schedule + if ( !FScheduleValid() || m_MonsterState != m_IdealMonsterState ) + { + // if we come into this block of code, the schedule is going to have to be changed. + // if the previous schedule was interrupted by a condition, GetIdealState will be + // called. Else, a schedule finished normally. + + // Notify the monster that his schedule is changing + ScheduleChange(); + + if ( HasConditions( bits_COND_TASK_FAILED ) && m_MonsterState == m_IdealMonsterState ) + { + // schedule was invalid because the current task failed to start or complete + ALERT ( at_aiconsole, "Schedule Failed at %d!\n", m_iScheduleIndex ); + } + } + + if ( m_iTaskStatus == TASKSTATUS_NEW ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + TaskBegin(); + StartTask( pTask ); + } + + // UNDONE: Twice?!!! + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + + if ( !TaskIsComplete() && m_iTaskStatus != TASKSTATUS_NEW ) + break; + } + + if ( TaskIsRunning() ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + RunTask( pTask ); + } + + // UNDONE: We have to do this so that we have an animation set to blend to if RunTask changes the animation + // RunTask() will always change animations at the end of a script! + // Don't do this twice + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } +} + +//========================================================= +// RunTask +//========================================================= +void CBaseMonster :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + case TASK_TURN_LEFT: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + { + CBaseEntity *pTarget; + + if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET ) + pTarget = m_hTargetEnt; + else + pTarget = m_hEnemy; + if ( pTarget ) + { + pev->ideal_yaw = UTIL_VecToYaw( pTarget->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + if ( m_fSequenceFinished ) + TaskComplete(); + } + break; + + case TASK_PLAY_SEQUENCE: + case TASK_PLAY_ACTIVE_IDLE: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + + + case TASK_FACE_ENEMY: + { + MakeIdealYaw( m_vecEnemyLKP ); + + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_FACE_HINTNODE: + case TASK_FACE_LASTPOSITION: + case TASK_FACE_TARGET: + case TASK_FACE_IDEAL: + case TASK_FACE_ROUTE: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_PVS: + { + if ( !FNullEnt(FIND_CLIENT_IN_PVS(edict())) ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_RANDOM: + { + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK ) + m_movementActivity = ACT_WALK; + else if ( distance >= 270 && m_movementActivity != ACT_RUN ) + m_movementActivity = ACT_RUN; + } + + break; + } + case TASK_WAIT_FOR_MOVEMENT: + { + if (MovementIsComplete()) + { + TaskComplete(); + RouteClear(); // Stop moving + } + break; + } + case TASK_DIE: + { + if ( m_fSequenceFinished && pev->frame >= 255 ) + { + pev->deadflag = DEAD_DEAD; + + SetThink ( NULL ); + StopAnimation(); + + if ( !BBoxFlat() ) + { + // a bit of a hack. If a corpses' bbox is positioned such that being left solid so that it can be attacked will + // block the player on a slope or stairs, the corpse is made nonsolid. +// pev->solid = SOLID_NOT; + UTIL_SetSize ( pev, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) ); + } + else // !!!HACKHACK - put monster in a thin, wide bounding box until we fix the solid type/bounding volume problem + UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->mins.z ), Vector ( pev->maxs.x, pev->maxs.y, pev->mins.z + 1 ) ); + + if ( ShouldFadeOnDeath() ) + { + // this monster was created by a monstermaker... fade the corpse out. + SUB_StartFadeOut(); + } + else + { + // body is gonna be around for a while, so have it stink for a bit. + CSoundEnt::InsertSound ( bits_SOUND_CARCASS, pev->origin, 384, 30 ); + } + } + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RELOAD_NOTURN: + { + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_RANGE_ATTACK1: + case TASK_MELEE_ATTACK1: + case TASK_MELEE_ATTACK2: + case TASK_RANGE_ATTACK2: + case TASK_SPECIAL_ATTACK1: + case TASK_SPECIAL_ATTACK2: + case TASK_RELOAD: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_SMALL_FLINCH: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + } + break; + case TASK_WAIT_FOR_SCRIPT: + { + if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime ) + { + TaskComplete(); + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszPlay, TRUE ); + if ( m_fSequenceFinished ) + ClearSchedule(); + pev->framerate = 1.0; + //ALERT( at_aiconsole, "Script %s has begun for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + } + break; + } + case TASK_PLAY_SCRIPT: + { + if (m_fSequenceFinished) + { + m_pCine->SequenceDone( this ); + } + break; + } + } +} + +//========================================================= +// SetTurnActivity - measures the difference between the way +// the monster is facing and determines whether or not to +// select one of the 180 turn animations. +//========================================================= +void CBaseMonster :: SetTurnActivity ( void ) +{ + float flYD; + flYD = FlYawDiff(); + + if ( flYD <= -45 && LookupActivity ( ACT_TURN_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) + {// big right turn + m_IdealActivity = ACT_TURN_RIGHT; + } + else if ( flYD > 45 && LookupActivity ( ACT_TURN_LEFT ) != ACTIVITY_NOT_AVAILABLE ) + {// big left turn + m_IdealActivity = ACT_TURN_LEFT; + } +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CBaseMonster :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw - pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_TURN_LEFT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw + pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_REMEMBER: + { + Remember ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FORGET: + { + Forget ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FIND_HINTNODE: + { + m_iHintNode = FindHintNode(); + + if ( m_iHintNode != NO_NODE ) + { + TaskComplete(); + } + else + { + TaskFail(); + } + break; + } + case TASK_STORE_LASTPOSITION: + { + m_vecLastPosition = pev->origin; + TaskComplete(); + break; + } + case TASK_CLEAR_LASTPOSITION: + { + m_vecLastPosition = g_vecZero; + TaskComplete(); + break; + } + case TASK_CLEAR_HINTNODE: + { + m_iHintNode = NO_NODE; + TaskComplete(); + break; + } + case TASK_STOP_MOVING: + { + if ( m_IdealActivity == m_movementActivity ) + { + m_IdealActivity = GetStoppedActivity(); + } + + RouteClear(); + TaskComplete(); + break; + } + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + case TASK_PLAY_SEQUENCE: + { + m_IdealActivity = ( Activity )( int )pTask->flData; + break; + } + case TASK_PLAY_ACTIVE_IDLE: + { + // monsters verify that they have a sequence for the node's activity BEFORE + // moving towards the node, so it's ok to just set the activity without checking here. + m_IdealActivity = ( Activity )WorldGraph.m_pNodes[ m_iHintNode ].m_sHintActivity; + break; + } + case TASK_SET_SCHEDULE: + break; + case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, pTask->flData ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, pTask->flData, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ENEMY: + { + entvars_t *pevCover; + + if ( m_hEnemy == NULL ) + { + // Find cover from self if no enemy available + pevCover = pev; +// TaskFail(); +// return; + } + else + pevCover = m_hEnemy->pev; + + if ( FindLateralCover( pevCover->origin, pevCover->view_ofs ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else if ( FindCover( pevCover->origin, pevCover->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ORIGIN: + { + if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no cover! + TaskFail(); + } + } + break; + case TASK_FIND_COVER_FROM_BEST_SOUND: + { + CSound *pBestSound; + + pBestSound = PBestSound(); + + ASSERT( pBestSound != NULL ); + /* + if ( pBestSound && FindLateralCover( pBestSound->m_vecOrigin, g_vecZero ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + */ + + if ( pBestSound && FindCover( pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. or no sound in list + TaskFail(); + } + break; + } + case TASK_FACE_HINTNODE: + { + pev->ideal_yaw = WorldGraph.m_pNodes[ m_iHintNode ].m_flHintYaw; + SetTurnActivity(); + break; + } + + case TASK_FACE_LASTPOSITION: + MakeIdealYaw ( m_vecLastPosition ); + SetTurnActivity(); + break; + + case TASK_FACE_TARGET: + if ( m_hTargetEnt != NULL ) + { + MakeIdealYaw ( m_hTargetEnt->pev->origin ); + SetTurnActivity(); + } + else + TaskFail(); + break; + case TASK_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + SetTurnActivity(); + break; + } + case TASK_FACE_IDEAL: + { + SetTurnActivity(); + break; + } + case TASK_FACE_ROUTE: + { + if (FRouteClear()) + { + ALERT(at_aiconsole, "No route to face!\n"); + TaskFail(); + } + else + { + MakeIdealYaw(m_Route[m_iRouteIndex].vecLocation); + SetTurnActivity(); + } + break; + } + case TASK_WAIT_PVS: + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + } + case TASK_WAIT_RANDOM: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + RANDOM_FLOAT( 0.1, pTask->flData ); + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK, 2 ) ) + TaskFail(); + } + break; + } + case TASK_RUN_TO_TARGET: + case TASK_WALK_TO_TARGET: + { + Activity newActivity; + + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + if ( pTask->iTask == TASK_WALK_TO_TARGET ) + newActivity = ACT_WALK; + else + newActivity = ACT_RUN; + // This monster can't do this! + if ( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE ) + TaskComplete(); + else + { + if ( m_hTargetEnt == NULL || !MoveToTarget( newActivity, 2 ) ) + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to reach target!!!\n", STRING(pev->classname) ); + RouteClear(); + } + } + } + TaskComplete(); + break; + } + case TASK_CLEAR_MOVE_WAIT: + { + m_flMoveWaitFinished = gpGlobals->time; + TaskComplete(); + break; + } + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1: + { + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + } + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_MELEE_ATTACK2: + { + m_IdealActivity = ACT_MELEE_ATTACK2; + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2: + { + m_IdealActivity = ACT_RANGE_ATTACK2; + break; + } + case TASK_RELOAD_NOTURN: + case TASK_RELOAD: + { + m_IdealActivity = ACT_RELOAD; + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_SPECIAL_ATTACK2: + { + m_IdealActivity = ACT_SPECIAL_ATTACK2; + break; + } + case TASK_SET_ACTIVITY: + { + m_IdealActivity = (Activity)(int)pTask->flData; + TaskComplete(); + break; + } + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if ( BuildRoute ( m_vecEnemyLKP, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, 0, (m_vecEnemyLKP - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( BuildRoute ( pEnemy->pev->origin, bits_MF_TO_ENEMY, pEnemy ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, 0, (pEnemy->pev->origin - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 64, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + case TASK_GET_PATH_TO_SPOT: + { + CBaseEntity *pPlayer = CBaseEntity::Instance( FIND_ENTITY_BY_CLASSNAME( NULL, "player" ) ); + if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, pPlayer ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + + case TASK_GET_PATH_TO_TARGET: + { + RouteClear(); + if ( m_hTargetEnt != NULL && MoveToTarget( m_movementActivity, 1 ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_HINTNODE:// for active idles! + { + if ( MoveToLocation( m_movementActivity, 2, WorldGraph.m_pNodes[ m_iHintNode ].m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToHintNode failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_LASTPOSITION: + { + m_vecMoveGoal = m_vecLastPosition; + + if ( MoveToLocation( m_movementActivity, 2, m_vecMoveGoal ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToLastPosition failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_BESTSOUND: + { + CSound *pSound; + + pSound = PBestSound(); + + if ( pSound && MoveToLocation( m_movementActivity, 2, pSound->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestSound failed!!\n" ); + TaskFail(); + } + break; + } +case TASK_GET_PATH_TO_BESTSCENT: + { + CSound *pScent; + + pScent = PBestScent(); + + if ( pScent && MoveToLocation( m_movementActivity, 2, pScent->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestScent failed!!\n" ); + + TaskFail(); + } + break; + } + case TASK_RUN_PATH: + { + // UNDONE: This is in some default AI and some monsters can't run? -- walk instead? + if ( LookupActivity( ACT_RUN ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_RUN; + } + else + { + m_movementActivity = ACT_WALK; + } + TaskComplete(); + break; + } + case TASK_WALK_PATH: + { + if ( pev->movetype == MOVETYPE_FLY ) + { + m_movementActivity = ACT_FLY; + } + if ( LookupActivity( ACT_WALK ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_WALK; + } + else + { + m_movementActivity = ACT_RUN; + } + TaskComplete(); + break; + } + case TASK_STRAFE_PATH: + { + Vector2D vec2DirToPoint; + Vector2D vec2RightSide; + + // to start strafing, we have to first figure out if the target is on the left side or right side + UTIL_MakeVectors ( pev->angles ); + + vec2DirToPoint = ( m_Route[ 0 ].vecLocation - pev->origin ).Make2D().Normalize(); + vec2RightSide = gpGlobals->v_right.Make2D().Normalize(); + + if ( DotProduct ( vec2DirToPoint, vec2RightSide ) > 0 ) + { + // strafe right + m_movementActivity = ACT_STRAFE_RIGHT; + } + else + { + // strafe left + m_movementActivity = ACT_STRAFE_LEFT; + } + TaskComplete(); + break; + } + + + case TASK_WAIT_FOR_MOVEMENT: + { + if (FRouteClear()) + { + TaskComplete(); + } + break; + } + + case TASK_EAT: + { + Eat( pTask->flData ); + TaskComplete(); + break; + } + case TASK_SMALL_FLINCH: + { + m_IdealActivity = GetSmallFlinchActivity(); + break; + } + case TASK_DIE: + { + RouteClear(); + + m_IdealActivity = GetDeathActivity(); + + pev->deadflag = DEAD_DYING; + break; + } + case TASK_SOUND_WAKE: + { + AlertSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DIE: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_IDLE: + { + IdleSound(); + TaskComplete(); + break; + } + case TASK_SOUND_PAIN: + { + PainSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DEATH: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_ANGRY: + { + // sounds are complete as soon as we get here, cause we've already played them. + ALERT ( at_aiconsole, "SOUND\n" ); + TaskComplete(); + break; + } + case TASK_WAIT_FOR_SCRIPT: + { + if (m_pCine->m_iszIdle) + { + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszIdle, FALSE ); + if (FStrEq( STRING(m_pCine->m_iszIdle), STRING(m_pCine->m_iszPlay))) + { + pev->framerate = 0; + } + } + else + m_IdealActivity = ACT_IDLE; + + break; + } + case TASK_PLAY_SCRIPT: + { + pev->movetype = MOVETYPE_FLY; + ClearBits(pev->flags, FL_ONGROUND); + m_scriptState = SCRIPT_PLAYING; + break; + } + case TASK_ENABLE_SCRIPT: + { + m_pCine->DelayStart( 0 ); + TaskComplete(); + break; + } + case TASK_PLANT_ON_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->origin = m_hTargetEnt->pev->origin; // Plant on target + } + + TaskComplete(); + break; + } + case TASK_FACE_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->ideal_yaw = UTIL_AngleMod( m_hTargetEnt->pev->angles.y ); + } + + TaskComplete(); + m_IdealActivity = ACT_IDLE; + RouteClear(); + break; + } + + case TASK_SUGGEST_STATE: + { + m_IdealMonsterState = (MONSTERSTATE)(int)pTask->flData; + TaskComplete(); + break; + } + + case TASK_SET_FAIL_SCHEDULE: + m_failSchedule = (int)pTask->flData; + TaskComplete(); + break; + + case TASK_CLEAR_FAIL_SCHEDULE: + m_failSchedule = SCHED_NONE; + TaskComplete(); + break; + + default: + { + ALERT ( at_aiconsole, "No StartTask entry for %d\n", (SHARED_TASKS)pTask->iTask ); + break; + } + } +} + +//========================================================= +// GetTask - returns a pointer to the current +// scheduled task. NULL if there's a problem. +//========================================================= +Task_t *CBaseMonster :: GetTask ( void ) +{ + if ( m_iScheduleIndex < 0 || m_iScheduleIndex >= m_pSchedule->cTasks ) + { + // m_iScheduleIndex is not within valid range for the monster's current schedule. + return NULL; + } + else + { + return &m_pSchedule->pTasklist[ m_iScheduleIndex ]; + } +} + diff --git a/dlls/schedule.h b/dlls/schedule.h index 64fc479..54cd2fe 100644 --- a/dlls/schedule.h +++ b/dlls/schedule.h @@ -6,26 +6,283 @@ * 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. +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. * ****/ //========================================================= // Scheduling //========================================================= + #ifndef SCHEDULE_H #define SCHEDULE_H +#define TASKSTATUS_NEW 0 // Just started +#define TASKSTATUS_RUNNING 1 // Running task & movement +#define TASKSTATUS_RUNNING_MOVEMENT 2 // Just running movement +#define TASKSTATUS_RUNNING_TASK 3 // Just running task +#define TASKSTATUS_COMPLETE 4 // Completed, get next task + + +//========================================================= +// These are the schedule types +//========================================================= +typedef enum +{ + SCHED_NONE = 0, + SCHED_IDLE_STAND, + SCHED_IDLE_WALK, + SCHED_WAKE_ANGRY, + SCHED_WAKE_CALLED, + SCHED_ALERT_FACE, + SCHED_ALERT_SMALL_FLINCH, + SCHED_ALERT_BIG_FLINCH, + SCHED_ALERT_STAND, + SCHED_INVESTIGATE_SOUND, + SCHED_COMBAT_FACE, + SCHED_COMBAT_STAND, + SCHED_CHASE_ENEMY, + SCHED_CHASE_ENEMY_FAILED, + SCHED_VICTORY_DANCE, + SCHED_TARGET_FACE, + SCHED_TARGET_CHASE, + SCHED_SMALL_FLINCH, + SCHED_TAKE_COVER_FROM_ENEMY, + SCHED_TAKE_COVER_FROM_BEST_SOUND, + SCHED_TAKE_COVER_FROM_ORIGIN, + SCHED_COWER, // usually a last resort! + SCHED_MELEE_ATTACK1, + SCHED_MELEE_ATTACK2, + SCHED_RANGE_ATTACK1, + SCHED_RANGE_ATTACK2, + SCHED_SPECIAL_ATTACK1, + SCHED_SPECIAL_ATTACK2, + SCHED_STANDOFF, + SCHED_ARM_WEAPON, + SCHED_RELOAD, + SCHED_GUARD, + SCHED_AMBUSH, + SCHED_DIE, + SCHED_WAIT_TRIGGER, + SCHED_FOLLOW, + SCHED_SLEEP, + SCHED_WAKE, + SCHED_AISCRIPT, + SCHED_FAIL, + + LAST_COMMON_SCHEDULE // Leave this at the bottom +} SCHEDULE_TYPE; + +//========================================================= +// These are the shared tasks +//========================================================= +typedef enum +{ + TASK_INVALID = 0, + TASK_WAIT, + TASK_WAIT_FACE_ENEMY, + TASK_WAIT_PVS, + TASK_SUGGEST_STATE, + TASK_WALK_TO_TARGET, + TASK_RUN_TO_TARGET, + TASK_MOVE_TO_TARGET_RANGE, + TASK_GET_PATH_TO_ENEMY, + TASK_GET_PATH_TO_ENEMY_LKP, + TASK_GET_PATH_TO_ENEMY_CORPSE, + TASK_GET_PATH_TO_LEADER, + TASK_GET_PATH_TO_SPOT, + TASK_GET_PATH_TO_TARGET, + TASK_GET_PATH_TO_HINTNODE, + TASK_GET_PATH_TO_LASTPOSITION, + TASK_GET_PATH_TO_BESTSOUND, + TASK_GET_PATH_TO_BESTSCENT, + TASK_RUN_PATH, + TASK_WALK_PATH, + TASK_STRAFE_PATH, + TASK_CLEAR_MOVE_WAIT, + TASK_STORE_LASTPOSITION, + TASK_CLEAR_LASTPOSITION, + TASK_PLAY_ACTIVE_IDLE, + TASK_FIND_HINTNODE, + TASK_CLEAR_HINTNODE, + TASK_SMALL_FLINCH, + TASK_FACE_IDEAL, + TASK_FACE_ROUTE, + TASK_FACE_ENEMY, + TASK_FACE_HINTNODE, + TASK_FACE_TARGET, + TASK_FACE_LASTPOSITION, + TASK_RANGE_ATTACK1, + TASK_RANGE_ATTACK2, + TASK_MELEE_ATTACK1, + TASK_MELEE_ATTACK2, + TASK_RELOAD, + TASK_RANGE_ATTACK1_NOTURN, + TASK_RANGE_ATTACK2_NOTURN, + TASK_MELEE_ATTACK1_NOTURN, + TASK_MELEE_ATTACK2_NOTURN, + TASK_RELOAD_NOTURN, + TASK_SPECIAL_ATTACK1, + TASK_SPECIAL_ATTACK2, + TASK_CROUCH, + TASK_STAND, + TASK_GUARD, + TASK_STEP_LEFT, + TASK_STEP_RIGHT, + TASK_STEP_FORWARD, + TASK_STEP_BACK, + TASK_DODGE_LEFT, + TASK_DODGE_RIGHT, + TASK_SOUND_ANGRY, + TASK_SOUND_DEATH, + TASK_SET_ACTIVITY, + TASK_SET_SCHEDULE, + TASK_SET_FAIL_SCHEDULE, + TASK_CLEAR_FAIL_SCHEDULE, + TASK_PLAY_SEQUENCE, + TASK_PLAY_SEQUENCE_FACE_ENEMY, + TASK_PLAY_SEQUENCE_FACE_TARGET, + TASK_SOUND_IDLE, + TASK_SOUND_WAKE, + TASK_SOUND_PAIN, + TASK_SOUND_DIE, + TASK_FIND_COVER_FROM_BEST_SOUND,// tries lateral cover first, then node cover + TASK_FIND_COVER_FROM_ENEMY,// tries lateral cover first, then node cover + TASK_FIND_LATERAL_COVER_FROM_ENEMY, + TASK_FIND_NODE_COVER_FROM_ENEMY, + TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY,// data for this one is the MAXIMUM acceptable distance to the cover. + TASK_FIND_FAR_NODE_COVER_FROM_ENEMY,// data for this one is there MINIMUM aceptable distance to the cover. + TASK_FIND_COVER_FROM_ORIGIN, + TASK_EAT, + TASK_DIE, + TASK_WAIT_FOR_SCRIPT, + TASK_PLAY_SCRIPT, + TASK_ENABLE_SCRIPT, + TASK_PLANT_ON_SCRIPT, + TASK_FACE_SCRIPT, + TASK_WAIT_RANDOM, + TASK_WAIT_INDEFINITE, + TASK_STOP_MOVING, + TASK_TURN_LEFT, + TASK_TURN_RIGHT, + TASK_REMEMBER, + TASK_FORGET, + TASK_WAIT_FOR_MOVEMENT, // wait until MovementIsComplete() + LAST_COMMON_TASK, // LEAVE THIS AT THE BOTTOM!! (sjb) +} SHARED_TASKS; + + +// These go in the flData member of the TASK_WALK_TO_TARGET, TASK_RUN_TO_TARGET +enum +{ + TARGET_MOVE_NORMAL = 0, + TARGET_MOVE_SCRIPTED = 1, +}; + + +// A goal should be used for a task that requires several schedules to complete. +// The goal index should indicate which schedule (ordinally) the monster is running. +// That way, when tasks fail, the AI can make decisions based on the context of the +// current goal and sequence rather than just the current schedule. +enum +{ + GOAL_ATTACK_ENEMY, + GOAL_MOVE, + GOAL_TAKE_COVER, + GOAL_MOVE_TARGET, + GOAL_EAT, +}; + +// an array of tasks is a task list +// an array of schedules is a schedule list +struct Task_t +{ + + int iTask; + float flData; +}; + +struct Schedule_t +{ + + Task_t *pTasklist; + int cTasks; + int iInterruptMask;// a bit mask of conditions that can interrupt this schedule + + // a more specific mask that indicates which TYPES of sounds will interrupt the schedule in the + // event that the schedule is broken by COND_HEAR_SOUND + int iSoundMask; + const char *pName; +}; + +// an array of waypoints makes up the monster's route. +// !!!LATER- this declaration doesn't belong in this file. +struct WayPoint_t +{ + Vector vecLocation; + int iType; +}; + +// these MoveFlag values are assigned to a WayPoint's TYPE in order to demonstrate the +// type of movement the monster should use to get there. +#define bits_MF_TO_TARGETENT ( 1 << 0 ) // local move to targetent. +#define bits_MF_TO_ENEMY ( 1 << 1 ) // local move to enemy +#define bits_MF_TO_COVER ( 1 << 2 ) // local move to a hiding place +#define bits_MF_TO_DETOUR ( 1 << 3 ) // local move to detour point. +#define bits_MF_TO_PATHCORNER ( 1 << 4 ) // local move to a path corner +#define bits_MF_TO_NODE ( 1 << 5 ) // local move to a node +#define bits_MF_TO_LOCATION ( 1 << 6 ) // local move to an arbitrary point +#define bits_MF_IS_GOAL ( 1 << 7 ) // this waypoint is the goal of the whole move. +#define bits_MF_DONT_SIMPLIFY ( 1 << 8 ) // Don't let the route code simplify this waypoint + +// If you define any flags that aren't _TO_ flags, add them here so we can mask +// them off when doing compares. +#define bits_MF_NOT_TO_MASK (bits_MF_IS_GOAL | bits_MF_DONT_SIMPLIFY) + +#define MOVEGOAL_NONE (0) +#define MOVEGOAL_TARGETENT (bits_MF_TO_TARGETENT) +#define MOVEGOAL_ENEMY (bits_MF_TO_ENEMY) +#define MOVEGOAL_PATHCORNER (bits_MF_TO_PATHCORNER) +#define MOVEGOAL_LOCATION (bits_MF_TO_LOCATION) +#define MOVEGOAL_NODE (bits_MF_TO_NODE) + +// these bits represent conditions that may befall the monster, of which some are allowed +// to interrupt certain schedules. +#define bits_COND_NO_AMMO_LOADED ( 1 << 0 ) // weapon needs to be reloaded! #define bits_COND_SEE_HATE ( 1 << 1 ) // see something that you hate #define bits_COND_SEE_FEAR ( 1 << 2 ) // see something that you are afraid of #define bits_COND_SEE_DISLIKE ( 1 << 3 ) // see something that you dislike #define bits_COND_SEE_ENEMY ( 1 << 4 ) // target entity is in full view. +#define bits_COND_ENEMY_OCCLUDED ( 1 << 5 ) // target entity occluded by the world +#define bits_COND_SMELL_FOOD ( 1 << 6 ) +#define bits_COND_ENEMY_TOOFAR ( 1 << 7 ) #define bits_COND_LIGHT_DAMAGE ( 1 << 8 ) // hurt a little #define bits_COND_HEAVY_DAMAGE ( 1 << 9 ) // hurt a lot +#define bits_COND_CAN_RANGE_ATTACK1 ( 1 << 10) +#define bits_COND_CAN_MELEE_ATTACK1 ( 1 << 11) +#define bits_COND_CAN_RANGE_ATTACK2 ( 1 << 12) +#define bits_COND_CAN_MELEE_ATTACK2 ( 1 << 13) +// #define bits_COND_CAN_RANGE_ATTACK3 ( 1 << 14) +#define bits_COND_PROVOKED ( 1 << 15) +#define bits_COND_NEW_ENEMY ( 1 << 16) +#define bits_COND_HEAR_SOUND ( 1 << 17) // there is an interesting sound +#define bits_COND_SMELL ( 1 << 18) // there is an interesting scent +#define bits_COND_ENEMY_FACING_ME ( 1 << 19) // enemy is facing me +#define bits_COND_ENEMY_DEAD ( 1 << 20) // enemy was killed. If you get this in combat, try to find another enemy. If you get it in alert, victory dance. #define bits_COND_SEE_CLIENT ( 1 << 21) // see a client #define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis +#define bits_COND_SPECIAL1 ( 1 << 28) // Defined by individual monster +#define bits_COND_SPECIAL2 ( 1 << 29) // Defined by individual monster + +#define bits_COND_TASK_FAILED ( 1 << 30) +#define bits_COND_SCHEDULE_DONE ( 1 << 31) + + +#define bits_COND_ALL_SPECIAL (bits_COND_SPECIAL1 | bits_COND_SPECIAL2) + +#define bits_COND_CAN_ATTACK (bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK2) #endif // SCHEDULE_H diff --git a/dlls/scripted.cpp b/dlls/scripted.cpp new file mode 100644 index 0000000..bd2c86d --- /dev/null +++ b/dlls/scripted.cpp @@ -0,0 +1,1247 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + + +===== scripted.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SAVERESTORE_H +#include "saverestore.h" +#endif + +#include "schedule.h" +#include "scripted.h" + +/* +classname "scripted_sequence" +targetname "me" - there can be more than one with the same name, and they act in concert +target "the_entity_I_want_to_start_playing" or "class entity_classname" will pick the closest inactive scientist +play "name_of_sequence" +idle "name of idle sequence to play before starting" +donetrigger "whatever" - can be any other triggerable entity such as another sequence, train, door, or a special case like "die" or "remove" +moveto - if set the monster first moves to this nodes position +range # - only search this far to find the target +spawnflags - (stop if blocked, stop if player seen) +*/ + + +// +// Cache user-entity-field values until spawn is called. +// + +void CCineMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszIdle")) + { + m_iszIdle = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszPlay")) + { + m_iszPlay = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEntity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fMoveTo")) + { + m_fMoveTo = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRepeat")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRadius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iFinishSchedule")) + { + m_iFinishSchedule = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseMonster::KeyValue( pkvd ); + } +} + +TYPEDESCRIPTION CCineMonster::m_SaveData[] = +{ + DEFINE_FIELD( CCineMonster, m_iszIdle, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszPlay, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_fMoveTo, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CCineMonster, m_flRadius, FIELD_FLOAT ), + + DEFINE_FIELD( CCineMonster, m_iDelay, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_startTime, FIELD_TIME ), + + DEFINE_FIELD( CCineMonster, m_saved_movetype, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_solid, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_effects, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_iFinishSchedule, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_interruptable, FIELD_BOOLEAN ), +}; + + +IMPLEMENT_SAVERESTORE( CCineMonster, CBaseMonster ); + +LINK_ENTITY_TO_CLASS( scripted_sequence, CCineMonster ); +#define CLASSNAME "scripted_sequence" + +LINK_ENTITY_TO_CLASS( aiscripted_sequence, CCineAI ); + + +void CCineMonster :: Spawn( void ) +{ + // pev->solid = SOLID_TRIGGER; + // UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + pev->solid = SOLID_NOT; + + + // REMOVE: The old side-effect +#if 0 + if ( m_iszIdle ) + m_fMoveTo = 4; +#endif + + // if no targetname, start now + if ( FStringNull(pev->targetname) || !FStringNull( m_iszIdle ) ) + { + SetThink( CineThink ); + pev->nextthink = gpGlobals->time + 1.0; + // Wait to be used? + if ( pev->targetname ) + m_startTime = gpGlobals->time + 1E6; + } + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + m_interruptable = FALSE; + else + m_interruptable = TRUE; +} + +//========================================================= +// FCanOverrideState - returns FALSE, scripted sequences +// cannot possess entities regardless of state. +//========================================================= +BOOL CCineMonster :: FCanOverrideState( void ) +{ + if ( pev->spawnflags & SF_SCRIPT_OVERRIDESTATE ) + return TRUE; + return FALSE; +} + +//========================================================= +// FCanOverrideState - returns true because scripted AI can +// possess entities regardless of their state. +//========================================================= +BOOL CCineAI :: FCanOverrideState( void ) +{ + return TRUE; +} + + +// +// CineStart +// +void CCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // do I already know who I should use + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + // am I already playing the script? + if ( pTarget->m_scriptState == SCRIPT_PLAYING ) + return; + + m_startTime = gpGlobals->time + 0.05; + } + else + { + // if not, try finding them + SetThink( CineThink ); + pev->nextthink = gpGlobals->time; + } +} + + +// This doesn't really make sense since only MOVETYPE_PUSH get 'Blocked' events +void CCineMonster :: Blocked( CBaseEntity *pOther ) +{ + +} + +void CCineMonster :: Touch( CBaseEntity *pOther ) +{ +/* + ALERT( at_aiconsole, "Cine Touch\n" ); + if (m_pentTarget && OFFSET(pOther->pev) == OFFSET(m_pentTarget)) + { + CBaseMonster *pTarget = GetClassPtr((CBaseMonster *)VARS(m_pentTarget)); + pTarget->m_monsterState == MONSTERSTATE_SCRIPT; + } +*/ +} + + +/* + entvars_t *pevOther = VARS( gpGlobals->other ); + + if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevOther->origin.z += 1; + + if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevOther->flags -= FL_ONGROUND; + } + + // toss the monster! + pevOther->velocity = pev->movedir * pev->speed; + pevOther->velocity.z += m_flHeight; + + + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE +} +*/ + + +// +// ********** Cinematic DIE ********** +// +void CCineMonster :: Die( void ) +{ + SetThink( SUB_Remove ); +} + +// +// ********** Cinematic PAIN ********** +// +void CCineMonster :: Pain( void ) +{ + +} + +// +// ********** Cinematic Think ********** +// + +// find a viable entity +int CCineMonster :: FindEntity( void ) +{ + edict_t *pentTarget; + + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + m_hTargetEnt = NULL; + CBaseMonster *pTarget = NULL; + + while (!FNullEnt(pentTarget)) + { + if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER )) + { + pTarget = GetMonsterPointer( pentTarget ); + if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_BY_NAME ) ) + { + m_hTargetEnt = pTarget; + return TRUE; + } + ALERT( at_console, "Found %s, but can't play!\n", STRING(m_iszEntity) ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + pTarget = NULL; + } + + if ( !pTarget ) + { + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pTarget = pEntity->MyMonsterPointer( ); + if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_IDLE ) ) + { + m_hTargetEnt = pTarget; + return TRUE; + } + } + } + } + } + pTarget = NULL; + m_hTargetEnt = NULL; + return FALSE; +} + +// make the entity enter a scripted sequence +void CCineMonster :: PossessEntity( void ) +{ + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + + // FindEntity() just checked this! +#if 0 + if ( !pTarget->CanPlaySequence( FCanOverrideState() ) ) + { + ALERT( at_aiconsole, "Can't possess entity %s\n", STRING(pTarget->pev->classname) ); + return; + } +#endif + + pTarget->m_pGoalEnt = this; + pTarget->m_pCine = this; + pTarget->m_hTargetEnt = this; + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + + switch (m_fMoveTo) + { + case 0: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + + case 1: + pTarget->m_scriptState = SCRIPT_WALK_TO_MARK; + DelayStart( 1 ); + break; + + case 2: + pTarget->m_scriptState = SCRIPT_RUN_TO_MARK; + DelayStart( 1 ); + break; + + case 4: + UTIL_SetOrigin( pTarget->pev, pev->origin ); + pTarget->pev->ideal_yaw = pev->angles.y; + pTarget->pev->avelocity = Vector( 0, 0, 0 ); + pTarget->pev->velocity = Vector( 0, 0, 0 ); + pTarget->pev->effects |= EF_NOINTERP; + pTarget->pev->angles.y = pev->angles.y; + pTarget->m_scriptState = SCRIPT_WAIT; + m_startTime = gpGlobals->time + 1E6; + // UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters + // pTarget->pev->flags &= ~FL_ONGROUND; + break; + } +// ALERT( at_aiconsole, "\"%s\" found and used (INT: %s)\n", STRING( pTarget->pev->targetname ), FBitSet(pev->spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; + if (m_iszIdle) + { + StartSequence( pTarget, m_iszIdle, FALSE ); + if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) + { + pTarget->pev->framerate = 0; + } + } + } +} + +// make the entity carry out the scripted sequence instructions, but without +// destroying the monster's state. +void CCineAI :: PossessEntity( void ) +{ + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + if ( !pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_AI ) ) + { + ALERT( at_aiconsole, "(AI)Can't possess entity %s\n", STRING(pTarget->pev->classname) ); + return; + } + + pTarget->m_pGoalEnt = this; + pTarget->m_pCine = this; + pTarget->m_hTargetEnt = this; + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + + switch (m_fMoveTo) + { + case 0: + case 5: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + + case 1: + pTarget->m_scriptState = SCRIPT_WALK_TO_MARK; + break; + + case 2: + pTarget->m_scriptState = SCRIPT_RUN_TO_MARK; + break; + + case 4: + // zap the monster instantly to the site of the script entity. + UTIL_SetOrigin( pTarget->pev, pev->origin ); + pTarget->pev->ideal_yaw = pev->angles.y; + pTarget->pev->avelocity = Vector( 0, 0, 0 ); + pTarget->pev->velocity = Vector( 0, 0, 0 ); + pTarget->pev->effects |= EF_NOINTERP; + pTarget->pev->angles.y = pev->angles.y; + pTarget->m_scriptState = SCRIPT_WAIT; + m_startTime = gpGlobals->time + 1E6; + // UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters + pTarget->pev->flags &= ~FL_ONGROUND; + break; + default: + ALERT ( at_aiconsole, "aiscript: invalid Move To Position value!" ); + break; + } + + ALERT( at_aiconsole, "\"%s\" found and used\n", STRING( pTarget->pev->targetname ) ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; + +/* + if (m_iszIdle) + { + StartSequence( pTarget, m_iszIdle, FALSE ); + if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) + { + pTarget->pev->framerate = 0; + } + } +*/ + } +} + +void CCineMonster :: CineThink( void ) +{ + if (FindEntity()) + { + PossessEntity( ); + ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + } + else + { + CancelScript( ); + ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + pev->nextthink = gpGlobals->time + 1.0; + } +} + + +// lookup a sequence name and setup the target monster to play it +BOOL CCineMonster :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ + if ( !iszSeq && completeOnEmpty ) + { + SequenceDone( pTarget ); + return FALSE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown scripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + +#if 0 + char *s; + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + s = "No"; + else + s = "Yes"; + + ALERT( at_console, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->pev->targetname ), STRING( pTarget->pev->classname ), STRING( iszSeq), s ); +#endif + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +// lookup a sequence name and setup the target monster to play it +// overridden for CCineAI because it's ok for them to not have an animation sequence +// for the monster to play. For a regular Scripted Sequence, that situation is an error. +BOOL CCineAI :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ + if ( iszSeq == 0 && completeOnEmpty ) + { + // no sequence was provided. Just let the monster proceed, however, we still have to fire any Sequence target + // and remove any non-repeatable CineAI entities here ( because there is code elsewhere that handles those tasks, but + // not until the animation sequence is finished. We have to manually take care of these things where there is no sequence. + + SequenceDone ( pTarget ); + + return TRUE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown aiscripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +//========================================================= +// SequenceDone - called when a scripted sequence animation +// sequence is done playing ( or when an AI Scripted Sequence +// doesn't supply an animation sequence to play ). Expects +// the CBaseMonster pointer to the monster that the sequence +// possesses. +//========================================================= +void CCineMonster :: SequenceDone ( CBaseMonster *pMonster ) +{ + //ALERT( at_aiconsole, "Sequence %s finished\n", STRING( m_pCine->m_iszPlay ) ); + + if ( !( pev->spawnflags & SF_SCRIPT_REPEATABLE ) ) + { + SetThink( SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + } + + // This is done so that another sequence can take over the monster when triggered by the first + + pMonster->CineCleanup(); + + FixScriptMonsterSchedule( pMonster ); + + // This may cause a sequence to attempt to grab this guy NOW, so we have to clear him out + // of the existing sequence + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// Scripted sequences just dirty the Schedule and drop the +// monster in Idle State. +//========================================================= +void CCineMonster :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + if ( pMonster->m_IdealMonsterState != MONSTERSTATE_DEAD ) + pMonster->m_IdealMonsterState = MONSTERSTATE_IDLE; + pMonster->ClearSchedule(); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// AI Scripted sequences will, depending on what the level +// designer selects: +// +// -Dirty the monster's schedule and drop out of the +// sequence in their current state. +// +// -Select a specific AMBUSH schedule, regardless of state. +//========================================================= +void CCineAI :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + switch ( m_iFinishSchedule ) + { + case SCRIPT_FINISHSCHED_DEFAULT: + pMonster->ClearSchedule(); + break; + case SCRIPT_FINISHSCHED_AMBUSH: + break; + default: + ALERT ( at_aiconsole, "FixScriptMonsterSchedule - no case!\n" ); + pMonster->ClearSchedule(); + break; + } +} + +BOOL CBaseMonster :: ExitScriptedSequence( ) +{ + if ( pev->deadflag == DEAD_DYING ) + { + // is this legal? + // BUGBUG -- This doesn't call Killed() + m_IdealMonsterState = MONSTERSTATE_DEAD; + return FALSE; + } + + if (m_pCine) + { + m_pCine->CancelScript( ); + } + + return TRUE; +} + + +void CCineMonster::AllowInterrupt( BOOL fAllow ) +{ + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + return; + m_interruptable = fAllow; +} + + +BOOL CCineMonster::CanInterrupt( void ) +{ + if ( !m_interruptable ) + return FALSE; + + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL && pTarget->pev->deadflag == DEAD_NO ) + return TRUE; + + return FALSE; +} + + +int CCineMonster::IgnoreConditions( void ) +{ + if ( CanInterrupt() ) + return 0; + return SCRIPT_BREAK_CONDITIONS; +} + + +void ScriptEntityCancel( edict_t *pentCine ) +{ + // make sure they are a scripted_sequence + if (FClassnameIs( pentCine, CLASSNAME )) + { + CCineMonster *pCineTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + // make sure they have a monster in mind for the script + CBaseEntity *pEntity = pCineTarget->m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if (pTarget) + { + // make sure their monster is actually playing a script + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + // tell them do die + pTarget->m_scriptState = CCineMonster::SCRIPT_CLEANUP; + // do it now + pTarget->CineCleanup( ); + } + } + } +} + + +// find all the cinematic entities with my targetname and stop them from playing +void CCineMonster :: CancelScript( void ) +{ + ALERT( at_aiconsole, "Cancelling script: %s\n", STRING(m_iszPlay) ); + + if ( !pev->targetname ) + { + ScriptEntityCancel( edict() ); + return; + } + + edict_t *pentCineTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname)); + + while (!FNullEnt(pentCineTarget)) + { + ScriptEntityCancel( pentCineTarget ); + pentCineTarget = FIND_ENTITY_BY_TARGETNAME(pentCineTarget, STRING(pev->targetname)); + } +} + + +// find all the cinematic entities with my targetname and tell them to wait before starting +void CCineMonster :: DelayStart( int state ) +{ + edict_t *pentCine = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname)); + + while (!FNullEnt(pentCine)) + { + if (FClassnameIs( pentCine, "scripted_sequence" )) + { + CCineMonster *pTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + if (state) + { + pTarget->m_iDelay++; + } + else + { + pTarget->m_iDelay--; + if (pTarget->m_iDelay <= 0) + pTarget->m_startTime = gpGlobals->time + 0.05; + } + } + pentCine = FIND_ENTITY_BY_TARGETNAME(pentCine, STRING(pev->targetname)); + } +} + + + +// Find an entity that I'm interested in and precache the sounds he'll need in the sequence. +void CCineMonster :: Activate( void ) +{ + edict_t *pentTarget; + CBaseMonster *pTarget; + + // The entity name could be a target name or a classname + // Check the targetname + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + pTarget = NULL; + + while (!pTarget && !FNullEnt(pentTarget)) + { + if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER )) + { + pTarget = GetMonsterPointer( pentTarget ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + + // If no entity with that targetname, check the classname + if ( !pTarget ) + { + pentTarget = FIND_ENTITY_BY_CLASSNAME(NULL, STRING(m_iszEntity)); + while (!pTarget && !FNullEnt(pentTarget)) + { + pTarget = GetMonsterPointer( pentTarget ); + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + } + // Found a compatible entity + if ( pTarget ) + { + void *pmodel; + pmodel = GET_MODEL_PTR( pTarget->edict() ); + if ( pmodel ) + { + // Look through the event list for stuff to precache + SequencePrecache( pmodel, STRING( m_iszIdle ) ); + SequencePrecache( pmodel, STRING( m_iszPlay ) ); + } + } +} + + +BOOL CBaseMonster :: CineCleanup( ) +{ + CCineMonster *pOldCine = m_pCine; + + // am I linked to a cinematic? + if (m_pCine) + { + // okay, reset me to what it thought I was before + m_pCine->m_hTargetEnt = NULL; + pev->movetype = m_pCine->m_saved_movetype; + pev->solid = m_pCine->m_saved_solid; + pev->effects = m_pCine->m_saved_effects; + } + else + { + // arg, punt + pev->movetype = MOVETYPE_STEP;// this is evil + pev->solid = SOLID_SLIDEBOX; + } + m_pCine = NULL; + m_hTargetEnt = NULL; + m_pGoalEnt = NULL; + if (pev->deadflag == DEAD_DYING) + { + // last frame of death animation? + pev->health = 0; + pev->framerate = 0.0; + pev->solid = SOLID_NOT; + pev->deadflag = DEAD_DEAD; + UTIL_SetSize( pev, pev->mins, Vector(pev->maxs.x, pev->maxs.y, pev->mins.z + 2) ); + + if ( pOldCine && FBitSet( pOldCine->pev->spawnflags, SF_SCRIPT_LEAVECORPSE ) ) + { + SetUse( NULL ); // BUGBUG -- This doesn't call Killed() + SetThink( NULL ); // This will probably break some stuff + SetTouch( NULL ); + } + else + SUB_StartFadeOut(); // SetThink( SUB_DoNothing ); + // This turns off animation & physics in case their origin ends up stuck in the world or something + StopAnimation(); + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NOINTERP; // Don't interpolate either, assume the corpse is positioned in its final resting place + return FALSE; + } + + // If we actually played a sequence + if ( pOldCine && pOldCine->m_iszPlay ) + { + if ( !(pOldCine->pev->spawnflags & SF_SCRIPT_NOSCRIPTMOVEMENT) ) + { + // reset position + Vector new_origin, new_angle; + GetBonePosition( 0, new_origin, new_angle ); + + // Figure out how far they have moved + // We can't really solve this problem because we can't query the movement of the origin relative + // to the sequence. We can get the root bone's position as we do here, but there are + // cases where the root bone is in a different relative position to the entity's origin + // before/after the sequence plays. So we are stuck doing this: + + // !!!HACKHACK: Float the origin up and drop to floor because some sequences have + // irregular motion that can't be properly accounted for. + + // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. + Vector oldOrigin = pev->origin; + + // UNDONE: ugly hack. Don't move monster if they don't "seem" to move + // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly + // being set, so animations that really do move won't be caught. + if ((oldOrigin - new_origin).Length2D() < 8.0) + new_origin = oldOrigin; + + pev->origin.x = new_origin.x; + pev->origin.y = new_origin.y; + pev->origin.z += 1; + + pev->flags |= FL_ONGROUND; + int drop = DROP_TO_FLOOR( ENT(pev) ); + + // Origin in solid? Set to org at the end of the sequence + if ( drop < 0 ) + pev->origin = oldOrigin; + else if ( drop == 0 ) // Hanging in air? + { + pev->origin.z = new_origin.z; + pev->flags &= ~FL_ONGROUND; + } + // else entity hit floor, leave there + + // pEntity->pev->origin.z = new_origin.z + 5.0; // damn, got to fix this + + UTIL_SetOrigin( pev, pev->origin ); + pev->effects |= EF_NOINTERP; + } + + // We should have some animation to put these guys in, but for now it's idle. + // Due to NOINTERP above, there won't be any blending between this anim & the sequence + m_Activity = ACT_RESET; + } + // set them back into a normal state + pev->enemy = NULL; + if ( pev->health > 0 ) + m_IdealMonsterState = MONSTERSTATE_IDLE; // m_previousState; + else + { + // Dropping out because he got killed + // Can't call killed() no attacker and weirdness (late gibbing) may result + m_IdealMonsterState = MONSTERSTATE_DEAD; + SetConditions( bits_COND_LIGHT_DAMAGE ); + pev->deadflag = DEAD_DYING; + FCheckAITrigger(); + pev->deadflag = DEAD_NO; + } + + + // SetAnimation( m_MonsterState ); + ClearBits(pev->spawnflags, SF_MONSTER_WAIT_FOR_SCRIPT ); + + return TRUE; +} + + + + +class CScriptedSentence : public CBaseToggle +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FindThink( void ); + void EXPORT DelayThink( void ); + int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + CBaseMonster *FindEntity( void ); + BOOL AcceptableSpeaker( CBaseMonster *pMonster ); + BOOL StartSentence( CBaseMonster *pTarget ); + + +private: + int m_iszSentence; // string index for idle animation + int m_iszEntity; // entity that is wanted for this sentence + float m_flRadius; // range to search + float m_flDuration; // How long the sentence lasts + float m_flRepeat; // repeat rate + float m_flAttenuation; + float m_flVolume; + BOOL m_active; + int m_iszListener; // name of entity to look at while talking +}; + +#define SF_SENTENCE_ONCE 0x0001 +#define SF_SENTENCE_FOLLOWERS 0x0002 // only say if following player +#define SF_SENTENCE_INTERRUPT 0x0004 // force talking except when dead +#define SF_SENTENCE_CONCURRENT 0x0008 // allow other people to keep talking + +TYPEDESCRIPTION CScriptedSentence::m_SaveData[] = +{ + DEFINE_FIELD( CScriptedSentence, m_iszSentence, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flDuration, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_active, FIELD_BOOLEAN ), + DEFINE_FIELD( CScriptedSentence, m_iszListener, FIELD_STRING ), +}; + + +IMPLEMENT_SAVERESTORE( CScriptedSentence, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( scripted_sentence, CScriptedSentence ); + +void CScriptedSentence :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sentence")) + { + m_iszSentence = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "entity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + m_flDuration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "refire")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "attenuation")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = atof( pkvd->szValue ) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "listener")) + { + m_iszListener = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + + +void CScriptedSentence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !m_active ) + return; +// ALERT( at_console, "Firing sentence: %s\n", STRING(m_iszSentence) ); + SetThink( FindThink ); + pev->nextthink = gpGlobals->time; +} + + +void CScriptedSentence :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + + m_active = TRUE; + // if no targetname, start now + if ( !pev->targetname ) + { + SetThink( FindThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + + switch( pev->impulse ) + { + case 1: // Medium radius + m_flAttenuation = ATTN_STATIC; + break; + + case 2: // Large radius + m_flAttenuation = ATTN_NORM; + break; + + case 3: //EVERYWHERE + m_flAttenuation = ATTN_NONE; + break; + + default: + case 0: // Small radius + m_flAttenuation = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( m_flVolume <= 0 ) + m_flVolume = 1.0; +} + + +void CScriptedSentence :: FindThink( void ) +{ + CBaseMonster *pMonster = FindEntity(); + if ( pMonster ) + { + StartSentence( pMonster ); + if ( pev->spawnflags & SF_SENTENCE_ONCE ) + UTIL_Remove( this ); + SetThink( DelayThink ); + pev->nextthink = gpGlobals->time + m_flDuration + m_flRepeat; + m_active = FALSE; +// ALERT( at_console, "%s: found monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + } + else + { +// ALERT( at_console, "%s: can't find monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + pev->nextthink = gpGlobals->time + m_flRepeat + 0.5; + } +} + + +void CScriptedSentence :: DelayThink( void ) +{ + m_active = TRUE; + if ( !pev->targetname ) + pev->nextthink = gpGlobals->time + 0.1; + SetThink( FindThink ); +} + + +BOOL CScriptedSentence :: AcceptableSpeaker( CBaseMonster *pMonster ) +{ + if ( pMonster ) + { + if ( pev->spawnflags & SF_SENTENCE_FOLLOWERS ) + { + if ( pMonster->m_hTargetEnt == NULL || !FClassnameIs(pMonster->m_hTargetEnt->pev, "player") ) + return FALSE; + } + BOOL override; + if ( pev->spawnflags & SF_SENTENCE_INTERRUPT ) + override = TRUE; + else + override = FALSE; + if ( pMonster->CanPlaySentence( override ) ) + return TRUE; + } + return FALSE; +} + + +CBaseMonster *CScriptedSentence :: FindEntity( void ) +{ + edict_t *pentTarget; + CBaseMonster *pMonster; + + + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + pMonster = NULL; + + while (!FNullEnt(pentTarget)) + { + pMonster = GetMonsterPointer( pentTarget ); + if ( pMonster != NULL ) + { + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; +// ALERT( at_console, "%s (%s), not acceptable\n", STRING(pMonster->pev->classname), STRING(pMonster->pev->targetname) ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pMonster = pEntity->MyMonsterPointer( ); + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; + } + } + } + + return NULL; +} + + +BOOL CScriptedSentence :: StartSentence( CBaseMonster *pTarget ) +{ + if ( !pTarget ) + { + ALERT( at_aiconsole, "Not Playing sentence %s\n", STRING(m_iszSentence) ); + return NULL; + } + + BOOL bConcurrent = FALSE; + if ( !(pev->spawnflags & SF_SENTENCE_CONCURRENT) ) + bConcurrent = TRUE; + + CBaseEntity *pListener = NULL; + if (!FStringNull(m_iszListener)) + { + float radius = m_flRadius; + + if ( FStrEq( STRING(m_iszListener ), "player" ) ) + radius = 4096; // Always find the player + + pListener = UTIL_FindEntityGeneric( STRING( m_iszListener ), pTarget->pev->origin, radius ); + } + + pTarget->PlayScriptedSentence( STRING(m_iszSentence), m_flDuration, m_flVolume, m_flAttenuation, bConcurrent, pListener ); + ALERT( at_aiconsole, "Playing sentence %s (%.1f)\n", STRING(m_iszSentence), m_flDuration ); + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + return TRUE; +} + + + + + +/* + +*/ + + +//========================================================= +// Furniture - this is the cool comment I cut-and-pasted +//========================================================= +class CFurniture : public CBaseMonster +{ +public: + void Spawn ( void ); + void Die( void ); + int Classify ( void ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } +}; + + +LINK_ENTITY_TO_CLASS( monster_furniture, CFurniture ); + + +//========================================================= +// Furniture is killed +//========================================================= +void CFurniture :: Die ( void ) +{ + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// This used to have something to do with bees flying, but +// now it only initializes moving furniture in scripted sequences +//========================================================= +void CFurniture :: Spawn( ) +{ + PRECACHE_MODEL((char *)STRING(pev->model)); + SET_MODEL(ENT(pev), STRING(pev->model)); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->health = 80000; + pev->takedamage = DAMAGE_AIM; + pev->effects = 0; + pev->yaw_speed = 0; + pev->sequence = 0; + pev->frame = 0; + +// pev->nextthink += 1.0; +// SetThink (WalkMonsterDelay); + + ResetSequenceInfo( ); + pev->frame = 0; + MonsterInit(); +} + +//========================================================= +// ID's Furniture as neutral (noone will attack it) +//========================================================= +int CFurniture::Classify ( void ) +{ + return CLASS_NONE; +} + + diff --git a/dlls/scripted.h b/dlls/scripted.h new file mode 100644 index 0000000..db9d5dd --- /dev/null +++ b/dlls/scripted.h @@ -0,0 +1,107 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef SCRIPTED_H +#define SCRIPTED_H + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#define SF_SCRIPT_WAITTILLSEEN 1 +#define SF_SCRIPT_EXITAGITATED 2 +#define SF_SCRIPT_REPEATABLE 4 +#define SF_SCRIPT_LEAVECORPSE 8 +//#define SF_SCRIPT_INTERPOLATE 16 // don't use, old bug +#define SF_SCRIPT_NOINTERRUPT 32 +#define SF_SCRIPT_OVERRIDESTATE 64 +#define SF_SCRIPT_NOSCRIPTMOVEMENT 128 + +#define SCRIPT_BREAK_CONDITIONS (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) + +enum SS_INTERRUPT +{ + SS_INTERRUPT_IDLE = 0, + SS_INTERRUPT_BY_NAME, + SS_INTERRUPT_AI, +}; + +// when a monster finishes an AI scripted sequence, we can choose +// a schedule to place them in. These defines are the aliases to +// resolve worldcraft input to real schedules (sjb) +#define SCRIPT_FINISHSCHED_DEFAULT 0 +#define SCRIPT_FINISHSCHED_AMBUSH 1 + +class CCineMonster : public CBaseMonster +{ +public: + void Spawn( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + virtual void Touch( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual void Activate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // void EXPORT CineSpawnThink( void ); + void EXPORT CineThink( void ); + void Pain( void ); + void Die( void ); + void DelayStart( int state ); + BOOL FindEntity( void ); + virtual void PossessEntity( void ); + + void ReleaseEntity( CBaseMonster *pEntity ); + void CancelScript( void ); + virtual BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + virtual BOOL FCanOverrideState ( void ); + void SequenceDone ( CBaseMonster *pMonster ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); + BOOL CanInterrupt( void ); + void AllowInterrupt( BOOL fAllow ); + int IgnoreConditions( void ); + + int m_iszIdle; // string index for idle animation + int m_iszPlay; // string index for scripted animation + int m_iszEntity; // entity that is wanted for this script + int m_fMoveTo; + int m_iFinishSchedule; + float m_flRadius; // range to search + float m_flRepeat; // repeat rate + + int m_iDelay; + float m_startTime; + + int m_saved_movetype; + int m_saved_solid; + int m_saved_effects; +// Vector m_vecOrigOrigin; + BOOL m_interruptable; +}; + +class CCineAI : public CCineMonster +{ + BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + void PossessEntity( void ); + BOOL FCanOverrideState ( void ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); +}; + + +#endif //SCRIPTED_H diff --git a/dlls/skill.cpp b/dlls/skill.cpp index bb94a6e..4ea8e63 100644 --- a/dlls/skill.cpp +++ b/dlls/skill.cpp @@ -17,11 +17,6 @@ //========================================================= #include "extdll.h" #include "util.h" -#include "skill.h" - - -skilldata_t gSkillData; - //========================================================= // take the name of a cvar, tack a digit for the skill level @@ -29,19 +24,7 @@ skilldata_t gSkillData; //========================================================= float GetSkillCvar( char *pName ) { - int iCount; - float flValue; - char szBuffer[ 64 ]; - - iCount = sprintf( szBuffer, "%s%d",pName, gSkillData.iSkillLevel ); - - flValue = CVAR_GET_FLOAT ( szBuffer ); - - if ( flValue <= 0 ) - { - ALERT ( at_console, "\n\n** GetSkillCVar Got a zero for %s **\n\n", szBuffer ); - } - - return flValue; + // We dont use skill cvars + return 0; } diff --git a/dlls/sound.cpp b/dlls/sound.cpp index cce1af2..dce0010 100644 --- a/dlls/sound.cpp +++ b/dlls/sound.cpp @@ -21,7 +21,6 @@ #include "cbase.h" #include "weapons.h" #include "player.h" -#include "talkmonster.h" #include "gamerules.h" @@ -1420,51 +1419,6 @@ void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volu EMIT_SOUND_DYN2(entity, channel, sample, volume, attenuation, flags, pitch); } -// play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename - -void EMIT_SOUND_SUIT(edict_t *entity, const char *sample) -{ - float fvol; - int pitch = PITCH_NORM; - - fvol = CVAR_GET_FLOAT("suitvolume"); - if (RANDOM_LONG(0,1)) - pitch = RANDOM_LONG(0,6) + 98; - - if (fvol > 0.05) - EMIT_SOUND_DYN(entity, CHAN_STATIC, sample, fvol, ATTN_NORM, 0, pitch); -} - -// play a sentence, randomly selected from the passed in group id, over the HEV suit speaker - -void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg) -{ - float fvol; - int pitch = PITCH_NORM; - - fvol = CVAR_GET_FLOAT("suitvolume"); - if (RANDOM_LONG(0,1)) - pitch = RANDOM_LONG(0,6) + 98; - - if (fvol > 0.05) - SENTENCEG_PlayRndI(entity, isentenceg, fvol, ATTN_NORM, 0, pitch); -} - -// play a sentence, randomly selected from the passed in groupname - -void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname) -{ - float fvol; - int pitch = PITCH_NORM; - - fvol = CVAR_GET_FLOAT("suitvolume"); - if (RANDOM_LONG(0,1)) - pitch = RANDOM_LONG(0,6) + 98; - - if (fvol > 0.05) - SENTENCEG_PlayRndSz(entity, groupname, fvol, ATTN_NORM, 0, pitch); -} - // ===================== MATERIAL TYPE DETECTION, MAIN ROUTINES ======================== // // Used to detect the texture the player is standing on, map the @@ -1531,8 +1485,36 @@ static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer return NULL; } - void TEXTURETYPE_Init() +{ + gcTextures = 0; + + memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); + memset(grgchTextureType, 0, CTEXTURESMAX); +} + +void TEXTURETYPE_End() +{ + fTextureTypeInit = TRUE; +} + +BOOL TEXTURETYPE_FindSimilarTexture(char chTextureType,char *szTextureName) +{ + for(int i = 0;i < gcTextures;i++) + { + if(FStrEq(grgszTextureName[i],szTextureName)) + { + // Change the texture type if needed + grgchTextureType[i] = chTextureType; + + return TRUE; // Dont add this texture back into the global list + } + } + + return FALSE; +} + +void TEXTURETYPE_Process(const char *pszFilename) { char buffer[512]; int i, j; @@ -1542,13 +1524,9 @@ void TEXTURETYPE_Init() if (fTextureTypeInit) return; - memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); - memset(grgchTextureType, 0, CTEXTURESMAX); - - gcTextures = 0; memset(buffer, 0, 512); - pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/materials.txt", &fileSize ); + pMemFile = g_engfuncs.pfnLoadFileForMe((char*)pszFilename, &fileSize ); if ( !pMemFile ) return; @@ -1567,8 +1545,7 @@ void TEXTURETYPE_Init() if (buffer[i] == '/' || !isalpha(buffer[i])) continue; - // get texture type - grgchTextureType[gcTextures] = toupper(buffer[i++]); + char chTextureType = toupper(buffer[i++]); // skip whitespace while(buffer[i] && isspace(buffer[i])) @@ -1588,12 +1565,23 @@ void TEXTURETYPE_Init() // null-terminate name and save in sentences array j = min (j, CBTEXTURENAMEMAX-1+i); buffer[j] = 0; - strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + + char szTextureName[CBTEXTURENAMEMAX]; + + strcpy(&(szTextureName[0]),&(buffer[i])); + + // Try to see if this texture exists in the current + // material list, and replace if found + if(TEXTURETYPE_FindSimilarTexture(chTextureType,szTextureName)) + continue; + + // save in global array + grgchTextureType[gcTextures] = chTextureType; + + strcpy(&(grgszTextureName[gcTextures++][0]),&(buffer[i])); } g_engfuncs.pfnFreeFile( pMemFile ); - - fTextureTypeInit = TRUE; } // given texture name, find texture type @@ -1738,8 +1726,6 @@ float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int cnt = 3; break; case CHAR_TEX_FLESH: - if (iBulletType == BULLET_PLAYER_CROWBAR) - return 0.0; // crowbar already makes this sound fvol = 1.0; fvolbar = 0.2; rgsz[0] = "weapons/bullet_hit1.wav"; rgsz[1] = "weapons/bullet_hit2.wav"; @@ -1857,14 +1843,6 @@ void CSpeaker :: SpeakerThink( void ) float flattenuation = 0.3; int flags = 0; int pitch = 100; - - - // Wait for the talkmonster to finish first. - if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) - { - pev->nextthink = CTalkMonster::g_talkWaitTime + RANDOM_FLOAT( 5, 10 ); - return; - } if (m_preset) { @@ -1906,8 +1884,6 @@ void CSpeaker :: SpeakerThink( void ) // set next announcement time for random 5 to 10 minute delay pev->nextthink = gpGlobals->time + RANDOM_FLOAT(ANNOUNCE_MINUTES_MIN * 60.0, ANNOUNCE_MINUTES_MAX * 60.0); - - CTalkMonster::g_talkWaitTime = gpGlobals->time + 5; // time delay until it's ok to speak: used so that two NPCs don't talk at once } return; diff --git a/dlls/teamplay_gamerules.h b/dlls/teamplay_gamerules.h index ac11dc2..4e8c25d 100644 --- a/dlls/teamplay_gamerules.h +++ b/dlls/teamplay_gamerules.h @@ -36,9 +36,9 @@ public: virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); virtual void InitHUD( CBasePlayer *pl ); virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ); - virtual const char *GetGameDescription( void ) { return "HL Teamplay"; } // this is the game name that gets seen in the server browser + virtual const char *GetGameDescription( void ) { return "Turf War"; } // this is the game name that gets seen in the server browser virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode - virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor); virtual void Think ( void ); virtual int GetTeamIndex( const char *pTeamName ); virtual const char *GetIndexedTeamName( int teamIndex ); diff --git a/dlls/thewastes.cpp b/dlls/thewastes.cpp new file mode 100644 index 0000000..88b1202 --- /dev/null +++ b/dlls/thewastes.cpp @@ -0,0 +1,223 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "thewastes.h" + +// Player items people can store on their person +playeritem_t MeleeItems[] = { + { "weapon_combatknife", "", 0, CAT_MELEE }, + { "weapon_throwingknife", "weapon_throwingknife", 4, CAT_MELEE }, + { "weapon_baseballbat", "", 0, CAT_MELEE }, + { "weapon_sledgehammer", "", 0, CAT_MELEE }, + { "weapon_spear", "weapon_spear", 2, CAT_MELEE }, + { "weapon_cattleprod", "", 0, CAT_MELEE }, + { "weapon_katana", "", 0, CAT_MELEE }, +}; + +playeritem_t SidearmItems[] = { + { "weapon_beretta", "ammo_beretta", 1, CAT_SIDEARM }, + { "weapon_colt", "ammo_colt", 1, CAT_SIDEARM }, + { "weapon_deagle", "ammo_deagle", 1, CAT_SIDEARM }, + { "weapon_sawedoff", "ammo_buckshot", 1, CAT_SIDEARM }, + { "weapon_ruger", "ammo_ruger", 2, CAT_SIDEARM }, + { "weapon_handcannon", "ammo_handcannon", 2, CAT_SIDEARM }, +}; + +playeritem_t PrimaryItems[] = { + { "weapon_smg9", "ammo_smg9", 1, CAT_PRIMARY }, + { "weapon_sten", "ammo_sten", 1, CAT_PRIMARY }, + { "weapon_boltrifle", "ammo_boltrifle", 1, CAT_PRIMARY }, + { "weapon_mossberg", "ammo_buckshot", 1, CAT_PRIMARY }, + { "weapon_winchester", "ammo_winchester", 1, CAT_PRIMARY }, + { "weapon_akimboberettas", "ammo_beretta", 2, CAT_PRIMARY }, + { "weapon_akimbocolts", "ammo_colt", 1, CAT_PRIMARY }, + { "weapon_akimbodeagles", "ammo_deagle", 1, CAT_PRIMARY }, + { "weapon_akimbosawedoffs", "ammo_buckshot", 1, CAT_PRIMARY }, +}; + +playeritem_t UniqueItems[] = { + { "weapon_fnfal", "ammo_fnfal", 1, CAT_UNIQUE }, + { "weapon_tommygun", "ammo_tommygun", 1, CAT_UNIQUE }, + { "weapon_g11", "ammo_g11", 1, CAT_UNIQUE }, + { "weapon_jackhammer", "ammo_jackhammer", 1, CAT_UNIQUE }, +}; + +playeritem_t OtherItems[] = { +// TODO: Bring back in when fixed! +// { "weapon_molotov", "weapon_molotov", 1, CAT_ITEM }, + { "weapon_fraggrenade", "weapon_fraggrenade", 1, CAT_ITEM }, + { "weapon_pipebomb", "weapon_pipebomb", 0, CAT_ITEM }, + { "extra_ammo", NULL, 0, CAT_ITEM }, +}; + +playeritemlist_t g_MeleeItems = { + MeleeItems, + 7, +}; + +playeritemlist_t g_SidearmItems = { + SidearmItems, + 6, +}; + +playeritemlist_t g_PrimaryItems = { + PrimaryItems, + 9, +}; + +playeritemlist_t g_UniqueItems = { + UniqueItems, + 4, +}; + +playeritemlist_t g_OtherItems = { + OtherItems, +// 4, + 3, +}; + +#ifndef CLIENT_DLL +// Spawns people with extra ammo +class CExtraAmmo : public CBasePlayerItem +{ +public: + void Spawn() + { + pev->classname = MAKE_STRING( "extra_ammo" ); + + pev->solid = SOLID_TRIGGER; + UTIL_SetSize( pev, g_vecZero, g_vecZero ); + } + + void Touch( CBaseEntity *pOther ) + { + if( pOther != NULL && pOther->IsPlayer() ) + { + CBasePlayer *pPlayer = (CBasePlayer*)pOther; + + // Sidearms + pPlayer->GiveNamedItem( "ammo_beretta" ); + pPlayer->GiveNamedItem( "ammo_colt" ); + pPlayer->GiveNamedItem( "ammo_deagle" ); + pPlayer->GiveNamedItem( "ammo_ruger" ); + pPlayer->GiveNamedItem( "ammo_handcannon" ); + + // Primary + pPlayer->GiveNamedItem( "ammo_smg9" ); + pPlayer->GiveNamedItem( "ammo_sten" ); + pPlayer->GiveNamedItem( "ammo_boltrifle" ); + pPlayer->GiveNamedItem( "ammo_winchester" ); + + // Uniques + pPlayer->GiveNamedItem( "ammo_fnfal" ); + pPlayer->GiveNamedItem( "ammo_tommygun" ); + pPlayer->GiveNamedItem( "ammo_g11" ); + + pPlayer->GiveNamedItem( "ammo_buckshot" ); + } + + UTIL_Remove( this ); + } +}; +LINK_ENTITY_TO_CLASS( extra_ammo, CExtraAmmo ); + +#endif + +void CWasteWeapon::Spawn() +{ + CBasePlayerWeapon::Spawn(); +} + +void CWasteWeapon::ItemPostFrame() +{ + if ((m_pPlayer->pev->button & IN_SPECIAL) && m_flNextPrimaryAttack <= 0.0) + { + SpecialMode(); + m_pPlayer->pev->button &= ~IN_SPECIAL; + } + + CBasePlayerWeapon::ItemPostFrame(); +} + +BOOL CWasteWeapon::UseDecrement() +{ +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif +} + +void CWasteWeapon::SpecialMode() +{ +} + +// Simple solution +int CWasteWeapon::iGetWeight() +{ + ItemInfo II; + + GetItemInfo(&II); + + return II.iWeight; +} + +// Used for pistol whips (and melee weaps) +void FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ) +{ + int i, j, k; + float distance; + float *minmaxs[2] = {mins, maxs}; + TraceResult tmpTrace; + Vector vecHullEnd = tr.vecEndPos; + Vector vecEnd; + + distance = 1e6f; + + vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2); + UTIL_TraceLine( vecSrc, vecHullEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + tr = tmpTrace; + return; + } + + for ( i = 0; i < 2; i++ ) + { + for ( j = 0; j < 2; j++ ) + { + for ( k = 0; k < 2; k++ ) + { + vecEnd.x = vecHullEnd.x + minmaxs[i][0]; + vecEnd.y = vecHullEnd.y + minmaxs[j][1]; + vecEnd.z = vecHullEnd.z + minmaxs[k][2]; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + float thisDistance = (tmpTrace.vecEndPos - vecSrc).Length(); + if ( thisDistance < distance ) + { + tr = tmpTrace; + distance = thisDistance; + } + } + } + } + } +} \ No newline at end of file diff --git a/dlls/thewastes.def b/dlls/thewastes.def new file mode 100644 index 0000000..3c783e4 --- /dev/null +++ b/dlls/thewastes.def @@ -0,0 +1,5 @@ +LIBRARY thewastes +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/dlls/triggers.cpp b/dlls/triggers.cpp index 61becd7..a2282fe 100644 --- a/dlls/triggers.cpp +++ b/dlls/triggers.cpp @@ -879,13 +879,6 @@ void CTriggerHurt :: RadiationThink( void ) vecRange = vecSpot1 - vecSpot2; flRange = vecRange.Length(); - - // if player's current geiger counter range is larger - // than range to this trigger hurt, reset player's - // geiger counter range - - if (pPlayer->m_flgeigerRange >= flRange) - pPlayer->m_flgeigerRange = flRange; } pev->nextthink = gpGlobals->time + 0.25; @@ -1011,8 +1004,6 @@ void CBaseTrigger :: HurtTouch ( CBaseEntity *pOther ) // Apply damage every half second pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again - - if ( pev->target ) { diff --git a/dlls/turret.cpp b/dlls/turret.cpp new file mode 100644 index 0000000..878dd94 --- /dev/null +++ b/dlls/turret.cpp @@ -0,0 +1,1318 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== turret.cpp ======================================================== + +*/ + +// +// TODO: +// Take advantage of new monster fields like m_hEnemy and get rid of that OFFSET() stuff +// Revisit enemy validation stuff, maybe it's not necessary with the newest monster code +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "effects.h" + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +#define TURRET_SHOTS 2 +#define TURRET_RANGE (100 * 12) +#define TURRET_SPREAD Vector( 0, 0, 0 ) +#define TURRET_TURNRATE 30 //angles per 0.1 second +#define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target +#define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target +#define TURRET_MACHINE_VOLUME 0.5 + +typedef enum +{ + TURRET_ANIM_NONE = 0, + TURRET_ANIM_FIRE, + TURRET_ANIM_SPIN, + TURRET_ANIM_DEPLOY, + TURRET_ANIM_RETIRE, + TURRET_ANIM_DIE, +} TURRET_ANIM; + +class CBaseTurret : public CBaseMonster +{ +public: + void Spawn(void); + virtual void Precache(void); + void KeyValue( KeyValueData *pkvd ); + void EXPORT TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue = 0); + virtual int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Classify(void); + + int BloodColor( void ) { return DONT_BLEED; } + void GibMonster( void ) {} // UNDONE: Throw turret gibs? + + // Think functions + + void EXPORT ActiveThink(void); + void EXPORT SearchThink(void); + void EXPORT AutoSearchThink(void); + void EXPORT TurretDeath(void); + + virtual void EXPORT SpinDownCall(void) { m_iSpin = 0; } + virtual void EXPORT SpinUpCall(void) { m_iSpin = 1; } + + // void SpinDown(void); + // float EXPORT SpinDownCall( void ) { return SpinDown(); } + + // virtual float SpinDown(void) { return 0;} + // virtual float Retire(void) { return 0;} + + void EXPORT Deploy(void); + void EXPORT Retire(void); + + void EXPORT Initialize(void); + + virtual void Ping(void); + virtual void EyeOn(void); + virtual void EyeOff(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void SetTurretAnim(TURRET_ANIM anim); + int MoveTurret(void); + virtual void Shoot(Vector &vecSrc, Vector &vecDirToEnemy) { }; + + float m_flMaxSpin; // Max time to spin the barrel w/o a target + int m_iSpin; + + CSprite *m_pEyeGlow; + int m_eyeBrightness; + + int m_iDeployHeight; + int m_iRetractHeight; + int m_iMinPitch; + + int m_iBaseTurnRate; // angles per second + float m_fTurnRate; // actual turn rate + int m_iOrientation; // 0 = floor, 1 = Ceiling + int m_iOn; + int m_fBeserk; // Sometimes this bitch will just freak out + int m_iAutoStart; // true if the turret auto deploys when a target + // enters its range + + Vector m_vecLastSight; + float m_flLastSight; // Last time we saw a target + float m_flMaxWait; // Max time to seach w/o a target + int m_iSearchSpeed; // Not Used! + + // movement + float m_flStartYaw; + Vector m_vecCurAngles; + Vector m_vecGoalAngles; + + + float m_flPingTime; // Time until the next ping, used when searching + float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching +}; + + +TYPEDESCRIPTION CBaseTurret::m_SaveData[] = +{ + DEFINE_FIELD( CBaseTurret, m_flMaxSpin, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSpin, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_pEyeGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( CBaseTurret, m_eyeBrightness, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iDeployHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iRetractHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iMinPitch, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_iBaseTurnRate, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fTurnRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iOrientation, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iOn, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fBeserk, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iAutoStart, FIELD_INTEGER ), + + + DEFINE_FIELD( CBaseTurret, m_vecLastSight, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_flLastSight, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flMaxWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSearchSpeed, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_flStartYaw, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_vecCurAngles, FIELD_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_vecGoalAngles, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseTurret, m_flPingTime, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flSpinUpTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CBaseTurret, CBaseMonster ); + +class CTurret : public CBaseTurret +{ +public: + void Spawn(void); + void Precache(void); + // Think functions + void SpinUpCall(void); + void SpinDownCall(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + +private: + int m_iStartSpin; + +}; +TYPEDESCRIPTION CTurret::m_SaveData[] = +{ + DEFINE_FIELD( CTurret, m_iStartSpin, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CTurret, CBaseTurret ); + + +class CMiniTurret : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); +}; + + +LINK_ENTITY_TO_CLASS( monster_turret, CTurret ); +LINK_ENTITY_TO_CLASS( monster_miniturret, CMiniTurret ); + +void CBaseTurret::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "maxsleep")) + { + m_flMaxWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "orientation")) + { + m_iOrientation = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "searchspeed")) + { + m_iSearchSpeed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "turnrate")) + { + m_iBaseTurnRate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CBaseTurret::Spawn() +{ + Precache( ); + pev->nextthink = gpGlobals->time + 1; + pev->movetype = MOVETYPE_FLY; + pev->sequence = 0; + pev->frame = 0; + pev->solid = SOLID_SLIDEBOX; + pev->takedamage = DAMAGE_AIM; + + SetBits (pev->flags, FL_MONSTER); + SetUse( TurretUse ); + + if (( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + && !( pev->spawnflags & SF_MONSTER_TURRET_STARTINACTIVE )) + { + m_iAutoStart = TRUE; + } + + ResetSequenceInfo( ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + m_flFieldOfView = VIEW_FIELD_FULL; + // m_flSightRange = TURRET_RANGE; +} + + +void CBaseTurret::Precache( ) +{ + PRECACHE_SOUND ("turret/tu_fire1.wav"); + PRECACHE_SOUND ("turret/tu_ping.wav"); + PRECACHE_SOUND ("turret/tu_active2.wav"); + PRECACHE_SOUND ("turret/tu_die.wav"); + PRECACHE_SOUND ("turret/tu_die2.wav"); + PRECACHE_SOUND ("turret/tu_die3.wav"); + // PRECACHE_SOUND ("turret/tu_retract.wav"); // just use deploy sound to save memory + PRECACHE_SOUND ("turret/tu_deploy.wav"); + PRECACHE_SOUND ("turret/tu_spinup.wav"); + PRECACHE_SOUND ("turret/tu_spindown.wav"); + PRECACHE_SOUND ("turret/tu_search.wav"); + PRECACHE_SOUND ("turret/tu_alert.wav"); +} + +#define TURRET_GLOW_SPRITE "sprites/flare3.spr" + +void CTurret::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/turret.mdl"); + pev->health = 120; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = TURRET_MAXSPIN; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight)); + + SetThink(Initialize); + + m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, pev->origin, FALSE ); + m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( edict(), 2 ); + m_eyeBrightness = 0; + + pev->nextthink = gpGlobals->time + 0.3; +} + +void CTurret::Precache() +{ + CBaseTurret::Precache( ); + PRECACHE_MODEL ("models/turret.mdl"); + PRECACHE_MODEL (TURRET_GLOW_SPRITE); +} + +void CMiniTurret::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/miniturret.mdl"); + pev->health = 50; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = 0; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetThink(Initialize); + pev->nextthink = gpGlobals->time + 0.3; +} + + +void CMiniTurret::Precache() +{ + CBaseTurret::Precache( ); + PRECACHE_MODEL ("models/miniturret.mdl"); + PRECACHE_SOUND("weapons/hks1.wav"); + PRECACHE_SOUND("weapons/hks2.wav"); + PRECACHE_SOUND("weapons/hks3.wav"); +} + +void CBaseTurret::Initialize(void) +{ + m_iOn = 0; + m_fBeserk = 0; + m_iSpin = 0; + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE; + if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT; + m_flStartYaw = pev->angles.y; + if (m_iOrientation == 1) + { + pev->idealpitch = 180; + pev->angles.x = 180; + pev->view_ofs.z = -pev->view_ofs.z; + pev->effects |= EF_INVLIGHT; + pev->angles.y = pev->angles.y + 180; + if (pev->angles.y > 360) + pev->angles.y = pev->angles.y - 360; + } + + m_vecGoalAngles.x = 0; + + if (m_iAutoStart) + { + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(AutoSearchThink); + pev->nextthink = gpGlobals->time + .1; + } + else + SetThink(SUB_DoNothing); +} + +void CBaseTurret::TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_iOn ) ) + return; + + if (m_iOn) + { + m_hEnemy = NULL; + pev->nextthink = gpGlobals->time + 0.1; + m_iAutoStart = FALSE;// switching off a turret disables autostart + //!!!! this should spin down first!!BUGBUG + SetThink(Retire); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; // turn on delay + + // if the turret is flagged as an autoactivate turret, re-enable it's ability open self. + if ( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + { + m_iAutoStart = TRUE; + } + + SetThink(Deploy); + } +} + + +void CBaseTurret::Ping( void ) +{ + // make the pinging noise every second while searching + if (m_flPingTime == 0) + m_flPingTime = gpGlobals->time + 1; + else if (m_flPingTime <= gpGlobals->time) + { + m_flPingTime = gpGlobals->time + 1; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_ping.wav", 1, ATTN_NORM); + EyeOn( ); + } + else if (m_eyeBrightness > 0) + { + EyeOff( ); + } +} + + +void CBaseTurret::EyeOn( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness != 255) + { + m_eyeBrightness = 255; + } + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } +} + + +void CBaseTurret::EyeOff( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness > 0) + { + m_eyeBrightness = max( 0, m_eyeBrightness - 30 ); + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } + } +} + + +void CBaseTurret::ActiveThink(void) +{ + int fAttack = 0; + Vector vecDirToEnemy; + + pev->nextthink = gpGlobals->time + 0.1; + StudioFrameAdvance( ); + + if ((!m_iOn) || (m_hEnemy == NULL)) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(SearchThink); + return; + } + + // if it's dead, look for something new + if ( !m_hEnemy->IsAlive() ) + { + if (!m_flLastSight) + { + m_flLastSight = gpGlobals->time + 0.5; // continue-shooting timeout + } + else + { + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(SearchThink); + return; + } + } + } + + Vector vecMid = pev->origin + pev->view_ofs; + Vector vecMidEnemy = m_hEnemy->BodyTarget( vecMid ); + + // Look for our current enemy + int fEnemyVisible = FBoxVisible(pev, m_hEnemy->pev, vecMidEnemy ); + + vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy + float flDistToEnemy = vecDirToEnemy.Length(); + + Vector vec = UTIL_VecToAngles(vecMidEnemy - vecMid); + + // Current enmey is not visible. + if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE)) + { + if (!m_flLastSight) + m_flLastSight = gpGlobals->time + 0.5; + else + { + // Should we look for a new target? + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(SearchThink); + return; + } + } + fEnemyVisible = 0; + } + else + { + m_vecLastSight = vecMidEnemy; + } + + UTIL_MakeAimVectors(m_vecCurAngles); + + /* + ALERT( at_console, "%.0f %.0f : %.2f %.2f %.2f\n", + m_vecCurAngles.x, m_vecCurAngles.y, + gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_forward.z ); + */ + + Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight; + vecLOS = vecLOS.Normalize(); + + // Is the Gun looking at the target + if (DotProduct(vecLOS, gpGlobals->v_forward) <= 0.866) // 30 degree slop + fAttack = FALSE; + else + fAttack = TRUE; + + // fire the gun + if (m_iSpin && ((fAttack) || (m_fBeserk))) + { + Vector vecSrc, vecAng; + GetAttachment( 0, vecSrc, vecAng ); + SetTurretAnim(TURRET_ANIM_FIRE); + Shoot(vecSrc, gpGlobals->v_forward ); + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } + + //move the gun + if (m_fBeserk) + { + if (RANDOM_LONG(0,9) == 0) + { + m_vecGoalAngles.y = RANDOM_FLOAT(0,360); + m_vecGoalAngles.x = RANDOM_FLOAT(0,90) - 90 * m_iOrientation; + TakeDamage(pev,pev,1, DMG_GENERIC); // don't beserk forever + return; + } + } + else if (fEnemyVisible) + { + if (vec.y > 360) + vec.y -= 360; + + if (vec.y < 0) + vec.y += 360; + + //ALERT(at_console, "[%.2f]", vec.x); + + if (vec.x < -180) + vec.x += 360; + + if (vec.x > 180) + vec.x -= 360; + + // now all numbers should be in [1...360] + // pin to turret limitations to [-90...15] + + if (m_iOrientation == 0) + { + if (vec.x > 90) + vec.x = 90; + else if (vec.x < m_iMinPitch) + vec.x = m_iMinPitch; + } + else + { + if (vec.x < -90) + vec.x = -90; + else if (vec.x > -m_iMinPitch) + vec.x = -m_iMinPitch; + } + + // ALERT(at_console, "->[%.2f]\n", vec.x); + + m_vecGoalAngles.y = vec.y; + m_vecGoalAngles.x = vec.x; + + } + + SpinUpCall(); + MoveTurret(); +} + + +void CTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_762MM, 1 ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.6); + pev->effects = pev->effects | EF_MUZZLEFLASH; + +/* FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_12MM, 1 ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.6); + pev->effects = pev->effects | EF_MUZZLEFLASH;*/ +} + + +void CMiniTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_762MM, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +/* FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_9MM, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH;*/ +} + + +void CBaseTurret::Deploy(void) +{ + pev->nextthink = gpGlobals->time + 0.1; + StudioFrameAdvance( ); + + if (pev->sequence != TURRET_ANIM_DEPLOY) + { + m_iOn = 1; + SetTurretAnim(TURRET_ANIM_DEPLOY); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SUB_UseTargets( this, USE_ON, 0 ); + } + + if (m_fSequenceFinished) + { + pev->maxs.z = m_iDeployHeight; + pev->mins.z = -m_iDeployHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + + m_vecCurAngles.x = 0; + + if (m_iOrientation == 1) + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y + 180 ); + } + else + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y ); + } + + SetTurretAnim(TURRET_ANIM_SPIN); + pev->framerate = 0; + SetThink(SearchThink); + } + + m_flLastSight = gpGlobals->time + m_flMaxWait; +} + +void CBaseTurret::Retire(void) +{ + // make the turret level + m_vecGoalAngles.x = 0; + m_vecGoalAngles.y = m_flStartYaw; + + pev->nextthink = gpGlobals->time + 0.1; + + StudioFrameAdvance( ); + + EyeOff( ); + + if (!MoveTurret()) + { + if (m_iSpin) + { + SpinDownCall(); + } + else if (pev->sequence != TURRET_ANIM_RETIRE) + { + SetTurretAnim(TURRET_ANIM_RETIRE); + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120); + SUB_UseTargets( this, USE_OFF, 0 ); + } + else if (m_fSequenceFinished) + { + m_iOn = 0; + m_flLastSight = 0; + SetTurretAnim(TURRET_ANIM_NONE); + pev->maxs.z = m_iRetractHeight; + pev->mins.z = -m_iRetractHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + if (m_iAutoStart) + { + SetThink(AutoSearchThink); + pev->nextthink = gpGlobals->time + .1; + } + else + SetThink(SUB_DoNothing); + } + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } +} + + +void CTurret::SpinUpCall(void) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + // Are we already spun up? If not start the two stage process. + if (!m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + // for the first pass, spin up the the barrel + if (!m_iStartSpin) + { + pev->nextthink = gpGlobals->time + 1.0; // spinup delay + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_spinup.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + m_iStartSpin = 1; + pev->framerate = 0.1; + } + // after the barrel is spun up, turn on the hum + else if (pev->framerate >= 1.0) + { + pev->nextthink = gpGlobals->time + 0.1; // retarget delay + EMIT_SOUND(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SetThink(ActiveThink); + m_iStartSpin = 0; + m_iSpin = 1; + } + else + { + pev->framerate += 0.075; + } + } + + if (m_iSpin) + { + SetThink(ActiveThink); + } +} + + +void CTurret::SpinDownCall(void) +{ + if (m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + if (pev->framerate == 1.0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_spindown.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } + pev->framerate -= 0.02; + if (pev->framerate <= 0) + { + pev->framerate = 0; + m_iSpin = 0; + } + } +} + + +void CBaseTurret::SetTurretAnim(TURRET_ANIM anim) +{ + if (pev->sequence != anim) + { + switch(anim) + { + case TURRET_ANIM_FIRE: + case TURRET_ANIM_SPIN: + if (pev->sequence != TURRET_ANIM_FIRE && pev->sequence != TURRET_ANIM_SPIN) + { + pev->frame = 0; + } + break; + default: + pev->frame = 0; + break; + } + + pev->sequence = anim; + ResetSequenceInfo( ); + + switch(anim) + { + case TURRET_ANIM_RETIRE: + pev->frame = 255; + pev->framerate = -1.0; + break; + case TURRET_ANIM_DIE: + pev->framerate = 1.0; + break; + } + //ALERT(at_console, "Turret anim #%d\n", anim); + } +} + + +// +// This search function will sit with the turret deployed and look for a new target. +// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will +// retact. +// +void CBaseTurret::SearchThink(void) +{ + // ensure rethink + SetTurretAnim(TURRET_ANIM_SPIN); + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (m_flSpinUpTime == 0 && m_flMaxSpin) + m_flSpinUpTime = gpGlobals->time + m_flMaxSpin; + + Ping( ); + + // If we have a target and we're still healthy + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + + // Acquire Target + if (m_hEnemy == NULL) + { + Look(TURRET_RANGE); + m_hEnemy = BestVisibleEnemy(); + } + + // If we've found a target, spin up the barrel and start to attack + if (m_hEnemy != NULL) + { + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(ActiveThink); + } + else + { + // Are we out of time, do we need to retract? + if (gpGlobals->time > m_flLastSight) + { + //Before we retrace, make sure that we are spun down. + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(Retire); + } + // should we stop the spin? + else if ((m_flSpinUpTime) && (gpGlobals->time > m_flSpinUpTime)) + { + SpinDownCall(); + } + + // generic hunt for new victims + m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_fTurnRate); + if (m_vecGoalAngles.y >= 360) + m_vecGoalAngles.y -= 360; + MoveTurret(); + } +} + + +// +// This think function will deploy the turret when something comes into range. This is for +// automatically activated turrets. +// +void CBaseTurret::AutoSearchThink(void) +{ + // ensure rethink + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.3; + + // If we have a target and we're still healthy + + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + // Acquire Target + + if (m_hEnemy == NULL) + { + Look( TURRET_RANGE ); + m_hEnemy = BestVisibleEnemy(); + } + + if (m_hEnemy != NULL) + { + SetThink(Deploy); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_alert.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } +} + + +void CBaseTurret :: TurretDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + if (m_iOrientation == 0) + m_vecGoalAngles.x = -15; + else + m_vecGoalAngles.x = -90; + + SetTurretAnim(TURRET_ANIM_DIE); + + EyeOn( ); + } + + EyeOff( ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ) ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ) ); + WRITE_COORD( pev->origin.z - m_iOrientation * 64 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 25 ); // scale * 10 + WRITE_BYTE( 10 - m_iOrientation * 5); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 5 ) > gpGlobals->time) + { + Vector vecSrc = Vector( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ), RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ), 0 ); + if (m_iOrientation == 0) + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->origin.z, pev->absmax.z ) ); + else + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->absmin.z, pev->origin.z ) ); + + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && !MoveTurret( ) && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + + + +void CBaseTurret :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue) +{ + if ( ptr->iHitgroup == 10 ) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + + if ( !pev->takedamage ) + return; + + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + +// take damage. bitsDamageType indicates type of damage sustained, ie: DMG_BULLET + +int CBaseTurret::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + flDamage /= 10.0; + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(TurretDeath); + SUB_UseTargets( this, USE_ON, 0 ); // wake up others + pev->nextthink = gpGlobals->time + 0.1; + + return 0; + } + + if (pev->health <= 10) + { + if (m_iOn && (1 || RANDOM_LONG(0, 0x7FFF) > 800)) + { + m_fBeserk = 1; + SetThink(SearchThink); + } + } + + return 1; +} + +int CBaseTurret::MoveTurret(void) +{ + int state = 0; + // any x movement? + + if (m_vecCurAngles.x != m_vecGoalAngles.x) + { + float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ; + + m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir; + + // if we started below the goal, and now we're past, peg to goal + if (flDir == 1) + { + if (m_vecCurAngles.x > m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + else + { + if (m_vecCurAngles.x < m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + + if (m_iOrientation == 0) + SetBoneController(1, -m_vecCurAngles.x); + else + SetBoneController(1, m_vecCurAngles.x); + state = 1; + } + + if (m_vecCurAngles.y != m_vecGoalAngles.y) + { + float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ; + float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y); + + if (flDist > 180) + { + flDist = 360 - flDist; + flDir = -flDir; + } + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 10) + { + m_fTurnRate += m_iBaseTurnRate; + } + } + else if (m_fTurnRate > 45) + { + m_fTurnRate -= m_iBaseTurnRate; + } + else + { + m_fTurnRate += m_iBaseTurnRate; + } + + m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir; + + if (m_vecCurAngles.y < 0) + m_vecCurAngles.y += 360; + else if (m_vecCurAngles.y >= 360) + m_vecCurAngles.y -= 360; + + if (flDist < (0.05 * m_iBaseTurnRate)) + m_vecCurAngles.y = m_vecGoalAngles.y; + + //ALERT(at_console, "%.2f -> %.2f\n", m_vecCurAngles.y, y); + if (m_iOrientation == 0) + SetBoneController(0, m_vecCurAngles.y - pev->angles.y ); + else + SetBoneController(0, pev->angles.y - 180 - m_vecCurAngles.y ); + state = 1; + } + + if (!state) + m_fTurnRate = m_iBaseTurnRate; + + //ALERT(at_console, "(%.2f, %.2f)->(%.2f, %.2f)\n", m_vecCurAngles.x, + // m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y); + return state; +} + +// +// ID as a machine +// +int CBaseTurret::Classify ( void ) +{ + if (m_iOn || m_iAutoStart) + return CLASS_MACHINE; + return CLASS_NONE; +} + + + + +//========================================================= +// Sentry gun - smallest turret, placed near grunt entrenchments +//========================================================= +class CSentry : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void EXPORT SentryTouch( CBaseEntity *pOther ); + void EXPORT SentryDeath( void ); + +}; + +LINK_ENTITY_TO_CLASS( monster_sentry, CSentry ); + +void CSentry::Precache() +{ + CBaseTurret::Precache( ); + PRECACHE_MODEL ("models/sentry.mdl"); +} + +void CSentry::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/sentry.mdl"); + pev->health = 100; + m_HackedGunPos = Vector( 0, 0, 48 ); + pev->view_ofs.z = 48; + m_flMaxWait = 1E6; + m_flMaxSpin = 1E6; + + CBaseTurret::Spawn(); + m_iRetractHeight = 64; + m_iDeployHeight = 64; + m_iMinPitch = -60; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetTouch(SentryTouch); + SetThink(Initialize); + pev->nextthink = gpGlobals->time + 0.3; +} + +void CSentry::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ +/* FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_MP5, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH;*/ +} + +int CSentry::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + { + SetThink( Deploy ); + SetUse( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + } + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(SentryDeath); + SUB_UseTargets( this, USE_ON, 0 ); // wake up others + pev->nextthink = gpGlobals->time + 0.1; + + return 0; + } + + return 1; +} + + +void CSentry::SentryTouch( CBaseEntity *pOther ) +{ + if ( pOther && (pOther->IsPlayer() || (pOther->pev->flags & FL_MONSTER)) ) + { + TakeDamage(pOther->pev, pOther->pev, 0, 0 ); + } +} + + +void CSentry :: SentryDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + SetTurretAnim(TURRET_ANIM_DIE); + + pev->solid = SOLID_NOT; + pev->angles.y = UTIL_AngleMod( pev->angles.y + RANDOM_LONG( 0, 2 ) * 120 ); + + EyeOn( ); + } + + EyeOff( ); + + Vector vecSrc, vecAng; + GetAttachment( 1, vecSrc, vecAng ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.y + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 15 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 8 ) > gpGlobals->time) + { + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + diff --git a/dlls/util.cpp b/dlls/util.cpp index 328d846..87bd6b4 100644 --- a/dlls/util.cpp +++ b/dlls/util.cpp @@ -30,6 +30,7 @@ #include "player.h" #include "weapons.h" #include "gamerules.h" +#include "tw_common.h" float UTIL_WeaponTimeBase( void ) { @@ -1144,8 +1145,8 @@ void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, if ( !UTIL_ShouldShowBlood( color ) ) return; - if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) - color = 0; +// if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) +// color = 0; MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); @@ -1181,7 +1182,22 @@ void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, if ( amount > 255 ) amount = 255; +// #define TE_SPRITE 17 // additive sprite, plays 1 cycle +// coord, coord, coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (brightness) MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( origin.x ); + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_SHORT( g_sModelIndexBloodSpray ); + WRITE_BYTE ( 5 ); + WRITE_BYTE ( 64 ); + MESSAGE_END(); + +/* MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); WRITE_BYTE( TE_BLOODSPRITE ); WRITE_COORD( origin.x); // pos WRITE_COORD( origin.y); @@ -1190,7 +1206,7 @@ void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, WRITE_SHORT( g_sModelIndexBloodDrop ); // droplet sprite models WRITE_BYTE( color ); // color index into host_basepal WRITE_BYTE( min( max( 3, amount / 10 ), 16 ) ); // size - MESSAGE_END(); + MESSAGE_END();*/ } Vector UTIL_RandomBloodVector( void ) @@ -1636,6 +1652,22 @@ void UTIL_StripToken( const char *pKey, char *pDest ) pDest[i] = 0; } +void UTIL_SendDirectorMessage( CBaseEntity *ent1, CBaseEntity *ent2, int priority ) +{ + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); //command length in bytes + WRITE_BYTE ( DRC_EVENT ); + if( ent1 != NULL ) + WRITE_SHORT( ENTINDEX(ent1->edict()) ); + else + WRITE_SHORT( 0 ); + if( ent2 != NULL ) + WRITE_SHORT( ENTINDEX(ent2->edict()) ); + else + WRITE_SHORT( 0 ); + WRITE_LONG( priority ); + MESSAGE_END(); +} // -------------------------------------------------------------- // diff --git a/dlls/util.h b/dlls/util.h index e6541fb..db08cfc 100644 --- a/dlls/util.h +++ b/dlls/util.h @@ -422,13 +422,8 @@ extern DLL_GLOBAL int g_Language; #define SVC_CDTRACK 32 #define SVC_WEAPONANIM 35 #define SVC_ROOMTYPE 37 -#define SVC_HLTV 50 - -// prxoy director stuff -#define DRC_EVENT 3 // informs the dircetor about ann important game event - -#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) -#define DRC_FLAG_DRAMATIC (1<<5) +//#define SVC_HLTV 50 // Deprecated +#define SVC_DIRECTOR 51 // triggers #define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger @@ -481,6 +476,8 @@ int SENTENCEG_GetIndex(const char *szrootname); int SENTENCEG_Lookup(const char *sample, char *sentencenum); void TEXTURETYPE_Init(); +void TEXTURETYPE_Process(const char *pszFilename); +void TEXTURETYPE_End(); char TEXTURETYPE_Find(char *name); float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType); @@ -504,10 +501,6 @@ inline void STOP_SOUND(edict_t *entity, int channel, const char *sample) EMIT_SOUND_DYN(entity, channel, sample, 0, 0, SND_STOP, PITCH_NORM); } -void EMIT_SOUND_SUIT(edict_t *entity, const char *sample); -void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg); -void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname); - #define PRECACHE_SOUND_ARRAY( a ) \ { for (int i = 0; i < ARRAYSIZE( a ); i++ ) PRECACHE_SOUND((char *) a [i]); } @@ -542,3 +535,6 @@ int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); float UTIL_WeaponTimeBase( void ); +void UTIL_SendDirectorMessage( CBaseEntity *ent1, CBaseEntity *ent2, int priority ); + + diff --git a/dlls/weapons.cpp b/dlls/weapons.cpp index 9576e18..d48175f 100644 --- a/dlls/weapons.cpp +++ b/dlls/weapons.cpp @@ -114,6 +114,9 @@ void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) return; gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); + + // Reset vuser2 + gMultiDamage.pEntity->pev->vuser2.y = 0; } @@ -169,26 +172,10 @@ void DecalGunshot( TraceResult *pTrace, int iBulletType ) if ( !FNullEnt(pTrace->pHit) ) pEntity = CBaseEntity::Instance(pTrace->pHit); - switch( iBulletType ) + if( iBulletType != BULLET_NONE ) { - case BULLET_PLAYER_9MM: - case BULLET_MONSTER_9MM: - case BULLET_PLAYER_MP5: - case BULLET_MONSTER_MP5: - case BULLET_PLAYER_BUCKSHOT: - case BULLET_PLAYER_357: - default: // smoke and decal UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); - break; - case BULLET_MONSTER_12MM: - // smoke and decal - UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); - break; - case BULLET_PLAYER_CROWBAR: - // wall decal - UTIL_DecalTrace( pTrace, DamageDecal( pEntity, DMG_CLUB ) ); - break; } } } @@ -309,88 +296,61 @@ void W_Precache(void) memset( CBasePlayerItem::AmmoInfoArray, 0, sizeof(CBasePlayerItem::AmmoInfoArray) ); giAmmoIndex = 0; - // custom items... +// crippled dll's have no access to any weapons other +// than the beretta and the katana - // common world objects - UTIL_PrecacheOther( "item_suit" ); - UTIL_PrecacheOther( "item_battery" ); - UTIL_PrecacheOther( "item_antidote" ); - UTIL_PrecacheOther( "item_security" ); - UTIL_PrecacheOther( "item_longjump" ); + // custom items + UTIL_PrecacheOtherWeapon( "weapon_beretta" ); + UTIL_PrecacheOtherWeapon( "weapon_colt" ); + UTIL_PrecacheOtherWeapon( "weapon_deagle" ); + UTIL_PrecacheOtherWeapon( "weapon_ruger" ); + UTIL_PrecacheOtherWeapon( "weapon_handcannon" ); + UTIL_PrecacheOtherWeapon( "weapon_sawedoff" ); + UTIL_PrecacheOtherWeapon( "weapon_jackhammer" ); + UTIL_PrecacheOtherWeapon( "weapon_mossberg" ); + UTIL_PrecacheOtherWeapon( "weapon_winchester" ); + UTIL_PrecacheOtherWeapon( "weapon_smg9" ); + UTIL_PrecacheOtherWeapon( "weapon_fnfal" ); + UTIL_PrecacheOtherWeapon( "weapon_tommygun" ); + UTIL_PrecacheOtherWeapon( "weapon_g11" ); + UTIL_PrecacheOtherWeapon( "weapon_boltrifle" ); + UTIL_PrecacheOtherWeapon( "weapon_sten" ); + UTIL_PrecacheOtherWeapon( "weapon_molotov" ); + UTIL_PrecacheOtherWeapon( "weapon_fraggrenade" ); + UTIL_PrecacheOtherWeapon( "weapon_pipebomb" ); + UTIL_PrecacheOtherWeapon( "weapon_combatknife" ); + UTIL_PrecacheOtherWeapon( "weapon_throwingknife" ); + UTIL_PrecacheOtherWeapon( "weapon_baseballbat" ); + UTIL_PrecacheOtherWeapon( "weapon_sledgehammer" ); + UTIL_PrecacheOtherWeapon( "weapon_akimboberettas" ); + UTIL_PrecacheOtherWeapon( "weapon_akimbocolts" ); + UTIL_PrecacheOtherWeapon( "weapon_akimbodeagles" ); + UTIL_PrecacheOtherWeapon( "weapon_akimbosawedoffs" ); + UTIL_PrecacheOtherWeapon( "weapon_katana" ); + UTIL_PrecacheOtherWeapon( "weapon_spear" ); + UTIL_PrecacheOtherWeapon( "weapon_cattleprod" ); - // shotgun - UTIL_PrecacheOtherWeapon( "weapon_shotgun" ); + UTIL_PrecacheOther( "ammo_handcannon" ); + UTIL_PrecacheOther( "ammo_beretta" ); + UTIL_PrecacheOther( "ammo_colt" ); + UTIL_PrecacheOther( "ammo_deagle" ); + UTIL_PrecacheOther( "ammo_ruger" ); UTIL_PrecacheOther( "ammo_buckshot" ); + UTIL_PrecacheOther( "ammo_smg9" ); + UTIL_PrecacheOther( "ammo_fnfal" ); + UTIL_PrecacheOther( "ammo_tommygun" ); + UTIL_PrecacheOther( "ammo_g11" ); + UTIL_PrecacheOther( "ammo_boltrifle" ); + UTIL_PrecacheOther( "ammo_sten" ); + UTIL_PrecacheOther( "ammo_winchester" ); + UTIL_PrecacheOther( "ammo_jackhammer" ); - // crowbar - UTIL_PrecacheOtherWeapon( "weapon_crowbar" ); - - // glock - UTIL_PrecacheOtherWeapon( "weapon_9mmhandgun" ); - UTIL_PrecacheOther( "ammo_9mmclip" ); - - // mp5 - UTIL_PrecacheOtherWeapon( "weapon_9mmAR" ); - UTIL_PrecacheOther( "ammo_9mmAR" ); - UTIL_PrecacheOther( "ammo_ARgrenades" ); - -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) - // python - UTIL_PrecacheOtherWeapon( "weapon_357" ); - UTIL_PrecacheOther( "ammo_357" ); -#endif - -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) - // gauss - UTIL_PrecacheOtherWeapon( "weapon_gauss" ); - UTIL_PrecacheOther( "ammo_gaussclip" ); -#endif - -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) - // rpg - UTIL_PrecacheOtherWeapon( "weapon_rpg" ); - UTIL_PrecacheOther( "ammo_rpgclip" ); -#endif - -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) - // crossbow - UTIL_PrecacheOtherWeapon( "weapon_crossbow" ); - UTIL_PrecacheOther( "ammo_crossbow" ); -#endif - -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) - // egon - UTIL_PrecacheOtherWeapon( "weapon_egon" ); -#endif - - // tripmine - UTIL_PrecacheOtherWeapon( "weapon_tripmine" ); - -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) - // satchel charge - UTIL_PrecacheOtherWeapon( "weapon_satchel" ); -#endif - - // hand grenade - UTIL_PrecacheOtherWeapon("weapon_handgrenade"); - -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) - // squeak grenade - UTIL_PrecacheOtherWeapon( "weapon_snark" ); -#endif - -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) - // hornetgun - UTIL_PrecacheOtherWeapon( "weapon_hornetgun" ); -#endif - - -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) if ( g_pGameRules->IsDeathmatch() ) { UTIL_PrecacheOther( "weaponbox" );// container for dropped deathmatch weapons } -#endif + + PRECACHE_MODEL("sprites/fire.spr"); // Molotov flame g_sModelIndexFireball = PRECACHE_MODEL ("sprites/zerogxplode.spr");// fireball g_sModelIndexWExplosion = PRECACHE_MODEL ("sprites/WXplo1.spr");// underwater fireball @@ -400,7 +360,7 @@ void W_Precache(void) g_sModelIndexBloodDrop = PRECACHE_MODEL ("sprites/blood.spr"); // splattered blood g_sModelIndexLaser = PRECACHE_MODEL( (char *)g_pModelNameLaser ); - g_sModelIndexLaserDot = PRECACHE_MODEL("sprites/laserdot.spr"); +// g_sModelIndexLaserDot = PRECACHE_MODEL("sprites/laserdot.spr"); // used by explosions @@ -419,12 +379,8 @@ void W_Precache(void) PRECACHE_SOUND ("weapons/bullet_hit2.wav"); // hit by bullet PRECACHE_SOUND ("items/weapondrop1.wav");// weapon falls to the ground - } - - - TYPEDESCRIPTION CBasePlayerItem::m_SaveData[] = { DEFINE_FIELD( CBasePlayerItem, m_pPlayer, FIELD_CLASSPTR ), @@ -520,7 +476,6 @@ void CBasePlayerItem::Materialize( void ) if ( pev->effects & EF_NODRAW ) { // changing from invisible state to visible. - EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); pev->effects &= ~EF_NODRAW; pev->effects |= EF_MUZZLEFLASH; } @@ -667,7 +622,7 @@ void CBasePlayerWeapon::ItemPostFrame( void ) SecondaryAttack(); m_pPlayer->pev->button &= ~IN_ATTACK2; } - else if ((m_pPlayer->pev->button & IN_ATTACK) && CanAttack( m_flNextPrimaryAttack, gpGlobals->time, UseDecrement() ) ) + else if ((m_pPlayer->pev->button & IN_ATTACK) && CanAttack(m_flNextPrimaryAttack,gpGlobals->time,UseDecrement()) ) { if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) ) { @@ -688,25 +643,6 @@ void CBasePlayerWeapon::ItemPostFrame( void ) m_fFireOnEmpty = FALSE; - if ( !IsUseable() && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) ) - { - // weapon isn't useable, switch. - if ( !(iFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon( m_pPlayer, this ) ) - { - m_flNextPrimaryAttack = ( UseDecrement() ? 0.0 : gpGlobals->time ) + 0.3; - return; - } - } - else - { - // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing - if ( m_iClip == 0 && !(iFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) ) - { - Reload(); - return; - } - } - WeaponIdle( ); return; } @@ -763,7 +699,8 @@ void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) pev->aiment = pPlayer->edict(); pev->effects = EF_NODRAW; // ?? pev->modelindex = 0;// server won't send down to clients if modelindex == 0 - pev->model = iStringNull; +// pev->model = iStringNull; // This is so weaponboxes can take the weapon models inside them, so it appears you are dropping the weapon itself. + // I dont think this will cause any problems (the modelindex is 0) - gage pev->owner = pPlayer->edict(); pev->nextthink = gpGlobals->time + .1; SetTouch( NULL ); @@ -908,7 +845,7 @@ BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip { // play the "got ammo" sound only if we gave some ammo to a player that already had this gun. // if the player is just getting this gun for the first time, DefaultTouch will play the "picked up gun" sound for us. - EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); +// EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); } } @@ -927,7 +864,7 @@ BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) if (iIdAmmo > 0) { m_iSecondaryAmmoType = iIdAmmo; - EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); +// EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); } return iIdAmmo > 0 ? TRUE : FALSE; } @@ -954,31 +891,6 @@ BOOL CBasePlayerWeapon :: IsUseable( void ) BOOL CBasePlayerWeapon :: CanDeploy( void ) { - BOOL bHasAmmo = 0; - - if ( !pszAmmo1() ) - { - // this weapon doesn't use ammo, can always deploy. - return TRUE; - } - - if ( pszAmmo1() ) - { - bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); - } - if ( pszAmmo2() ) - { - bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); - } - if (m_iClip > 0) - { - bHasAmmo |= 1; - } - if (!bHasAmmo) - { - return FALSE; - } - return TRUE; } @@ -1086,7 +998,6 @@ void CBasePlayerAmmo::Materialize( void ) if ( pev->effects & EF_NODRAW ) { // changing from invisible state to visible. - EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); pev->effects &= ~EF_NODRAW; pev->effects |= EF_MUZZLEFLASH; } @@ -1180,6 +1091,7 @@ void CBasePlayerWeapon::RetireWeapon( void ) m_pPlayer->pev->weaponmodel = iStringNull; //m_pPlayer->pev->viewmodelindex = NULL; + // Dont automatically select a new weapon g_pGameRules->GetNextBestWeapon( m_pPlayer, this ); } @@ -1231,7 +1143,13 @@ void CWeaponBox::Spawn( void ) { Precache( ); - pev->movetype = MOVETYPE_TOSS; + pev->movetype = MOVETYPE_BOUNCE; + + pev->avelocity.x = RANDOM_FLOAT(-100,-500); + pev->avelocity.y = RANDOM_FLOAT(-100,-500); + pev->avelocity.z = RANDOM_FLOAT(-100,-500); + pev->friction = 1.0f; + pev->solid = SOLID_TRIGGER; UTIL_SetSize( pev, g_vecZero, g_vecZero ); @@ -1271,11 +1189,24 @@ void CWeaponBox::Kill( void ) //========================================================= void CWeaponBox::Touch( CBaseEntity *pOther ) { + BOOL bRemove = FALSE; + if ( !(pev->flags & FL_ONGROUND ) ) { return; } + // stop moving + pev->velocity = Vector(0,0,0); + + // Stop turning + pev->avelocity.x = 0.0f; + pev->avelocity.y = 0.0f; + pev->avelocity.z = 0.0f; + + pev->angles[0] = 0.0f; + pev->angles[2] = 0.0f; + if ( !pOther->IsPlayer() ) { // only players may touch a weaponbox. @@ -1285,6 +1216,7 @@ void CWeaponBox::Touch( CBaseEntity *pOther ) if ( !pOther->IsAlive() ) { // no dead guys. + // Oh come on, the dead cant say no - gage return; } @@ -1324,6 +1256,16 @@ void CWeaponBox::Touch( CBaseEntity *pOther ) pItem = m_rgpPlayerItems[ i ]; m_rgpPlayerItems[ i ] = m_rgpPlayerItems[ i ]->m_pNext;// unlink this weapon from the box + CBasePlayerWeapon *pCastWeap = (CBasePlayerWeapon*)pItem; + if( pCastWeap != NULL ) + { + if( pPlayer->m_rgAmmo[ pCastWeap->m_iPrimaryAmmoType ] < 1 && + pCastWeap->m_iClip == 0 && pCastWeap->iMaxClip() != WEAPON_NOCLIP ) + continue; + } + + bRemove = TRUE; + if ( pPlayer->AddPlayerItem( pItem ) ) { pItem->AttachToPlayer( pPlayer ); @@ -1332,9 +1274,12 @@ void CWeaponBox::Touch( CBaseEntity *pOther ) } } - EMIT_SOUND( pOther->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); - SetTouch(NULL); - UTIL_Remove(this); + if( bRemove ) + { + EMIT_SOUND( pOther->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + SetTouch(NULL); + UTIL_Remove(this); + } } //========================================================= @@ -1348,6 +1293,8 @@ BOOL CWeaponBox::PackWeapon( CBasePlayerItem *pWeapon ) return FALSE;// box can only hold one of each weapon type } + SET_MODEL(ENT(pev),STRING(pWeapon->pev->model)); + if ( pWeapon->m_pPlayer ) { if ( !pWeapon->m_pPlayer->RemovePlayerItem( pWeapon ) ) @@ -1377,7 +1324,7 @@ BOOL CWeaponBox::PackWeapon( CBasePlayerItem *pWeapon ) pWeapon->pev->solid = SOLID_NOT; pWeapon->pev->effects = EF_NODRAW; pWeapon->pev->modelindex = 0; - pWeapon->pev->model = iStringNull; +// pWeapon->pev->model = iStringNull; pWeapon->pev->owner = edict(); pWeapon->SetThink( NULL );// crowbar may be trying to swing again, etc. pWeapon->SetTouch( NULL ); @@ -1414,6 +1361,10 @@ BOOL CWeaponBox::PackAmmo( int iszName, int iCount ) return FALSE; } +void CWeaponBox::SetModel(char *pszModelname) +{ +} + //========================================================= // CWeaponBox - GiveAmmo //========================================================= @@ -1523,57 +1474,3 @@ void CBasePlayerWeapon::PrintState( void ) ALERT( at_console, "m_iclip: %i\n", m_iClip ); } - -TYPEDESCRIPTION CRpg::m_SaveData[] = -{ - DEFINE_FIELD( CRpg, m_fSpotActive, FIELD_INTEGER ), - DEFINE_FIELD( CRpg, m_cActiveRockets, FIELD_INTEGER ), -}; -IMPLEMENT_SAVERESTORE( CRpg, CBasePlayerWeapon ); - -TYPEDESCRIPTION CRpgRocket::m_SaveData[] = -{ - DEFINE_FIELD( CRpgRocket, m_flIgniteTime, FIELD_TIME ), - DEFINE_FIELD( CRpgRocket, m_pLauncher, FIELD_CLASSPTR ), -}; -IMPLEMENT_SAVERESTORE( CRpgRocket, CGrenade ); - -TYPEDESCRIPTION CShotgun::m_SaveData[] = -{ - DEFINE_FIELD( CShotgun, m_flNextReload, FIELD_TIME ), - DEFINE_FIELD( CShotgun, m_fInSpecialReload, FIELD_INTEGER ), - DEFINE_FIELD( CShotgun, m_flNextReload, FIELD_TIME ), - // DEFINE_FIELD( CShotgun, m_iShell, FIELD_INTEGER ), - DEFINE_FIELD( CShotgun, m_flPumpTime, FIELD_TIME ), -}; -IMPLEMENT_SAVERESTORE( CShotgun, CBasePlayerWeapon ); - -TYPEDESCRIPTION CGauss::m_SaveData[] = -{ - DEFINE_FIELD( CGauss, m_fInAttack, FIELD_INTEGER ), -// DEFINE_FIELD( CGauss, m_flStartCharge, FIELD_TIME ), -// DEFINE_FIELD( CGauss, m_flPlayAftershock, FIELD_TIME ), -// DEFINE_FIELD( CGauss, m_flNextAmmoBurn, FIELD_TIME ), - DEFINE_FIELD( CGauss, m_fPrimaryFire, FIELD_BOOLEAN ), -}; -IMPLEMENT_SAVERESTORE( CGauss, CBasePlayerWeapon ); - -TYPEDESCRIPTION CEgon::m_SaveData[] = -{ -// DEFINE_FIELD( CEgon, m_pBeam, FIELD_CLASSPTR ), -// DEFINE_FIELD( CEgon, m_pNoise, FIELD_CLASSPTR ), -// DEFINE_FIELD( CEgon, m_pSprite, FIELD_CLASSPTR ), - DEFINE_FIELD( CEgon, m_shootTime, FIELD_TIME ), - DEFINE_FIELD( CEgon, m_fireState, FIELD_INTEGER ), - DEFINE_FIELD( CEgon, m_fireMode, FIELD_INTEGER ), - DEFINE_FIELD( CEgon, m_shakeTime, FIELD_TIME ), - DEFINE_FIELD( CEgon, m_flAmmoUseTime, FIELD_TIME ), -}; -IMPLEMENT_SAVERESTORE( CEgon, CBasePlayerWeapon ); - -TYPEDESCRIPTION CSatchel::m_SaveData[] = -{ - DEFINE_FIELD( CSatchel, m_chargeReady, FIELD_INTEGER ), -}; -IMPLEMENT_SAVERESTORE( CSatchel, CBasePlayerWeapon ); - diff --git a/dlls/weapons.h b/dlls/weapons.h index f682767..cde360c 100644 --- a/dlls/weapons.h +++ b/dlls/weapons.h @@ -57,132 +57,32 @@ public: // constant items -#define ITEM_HEALTHKIT 1 -#define ITEM_ANTIDOTE 2 -#define ITEM_SECURITY 3 -#define ITEM_BATTERY 4 - #define WEAPON_NONE 0 -#define WEAPON_CROWBAR 1 -#define WEAPON_GLOCK 2 -#define WEAPON_PYTHON 3 -#define WEAPON_MP5 4 -#define WEAPON_CHAINGUN 5 -#define WEAPON_CROSSBOW 6 -#define WEAPON_SHOTGUN 7 -#define WEAPON_RPG 8 -#define WEAPON_GAUSS 9 -#define WEAPON_EGON 10 -#define WEAPON_HORNETGUN 11 -#define WEAPON_HANDGRENADE 12 -#define WEAPON_TRIPMINE 13 -#define WEAPON_SATCHEL 14 -#define WEAPON_SNARK 15 - -#define WEAPON_ALLWEAPONS (~(1<absmin = pev->origin + Vector(-16, -16, -5); - pev->absmax = pev->origin + Vector(16, 16, 28); - } - - void PrimaryAttack( void ); - BOOL Deploy( void ); - void Holster( int skiplocal = 0 ); - void WeaponIdle( void ); - - virtual BOOL UseDecrement( void ) - { -#if defined( CLIENT_WEAPONS ) - return TRUE; -#else - return FALSE; -#endif - } - -private: - unsigned short m_usTripFire; - -}; - -class CSqueak : public CBasePlayerWeapon -{ -public: - void Spawn( void ); - void Precache( void ); - int iItemSlot( void ) { return 5; } - int GetItemInfo(ItemInfo *p); - - void PrimaryAttack( void ); - void SecondaryAttack( void ); - BOOL Deploy( void ); - void Holster( int skiplocal = 0 ); - void WeaponIdle( void ); - int m_fJustThrown; - - virtual BOOL UseDecrement( void ) - { -#if defined( CLIENT_WEAPONS ) - return TRUE; -#else - return FALSE; -#endif - } - -private: - unsigned short m_usSnarkFire; -}; - - #endif // WEAPONS_H diff --git a/dlls/world.cpp b/dlls/world.cpp index 610b33b..1015a76 100644 --- a/dlls/world.cpp +++ b/dlls/world.cpp @@ -27,7 +27,6 @@ #include "soundent.h" #include "client.h" #include "decals.h" -#include "skill.h" #include "effects.h" #include "player.h" #include "weapons.h" @@ -44,6 +43,11 @@ extern DLL_GLOBAL int gDisplayTitle; extern void W_Precache(void); +extern "C" +{ + void PM_CheckMap(char *pszMapname); +} + // // This must match the list in util.h // @@ -519,8 +523,19 @@ void CWorld :: Precache( void ) // init texture type array from materials.txt +// now supports map based material files -> gage + char szMapMaterials[512]; + sprintf(szMapMaterials,"sound/%s.mat",STRING(gpGlobals->mapname)); + TEXTURETYPE_Init(); + TEXTURETYPE_Process("sound/materials.txt"); + TEXTURETYPE_Process(szMapMaterials); + + TEXTURETYPE_End(); + +// process for player state code + PM_CheckMap((char*)STRING(gpGlobals->mapname)); // the area based ambient sounds MUST be the first precache_sounds @@ -532,7 +547,6 @@ void CWorld :: Precache( void ) // sounds used from C physics code PRECACHE_SOUND("common/null.wav"); // clears sound channels - PRECACHE_SOUND( "items/suitchargeok1.wav" );//!!! temporary sound for respawning weapons. PRECACHE_SOUND( "items/gunpickup2.wav" );// player picks up a gun. PRECACHE_SOUND( "common/bodydrop3.wav" );// dead bodies hitting the ground (animation events) @@ -549,15 +563,9 @@ void CWorld :: Precache( void ) PRECACHE_MODEL( "models/agibs.mdl" ); } - PRECACHE_SOUND ("weapons/ric1.wav"); - PRECACHE_SOUND ("weapons/ric2.wav"); - PRECACHE_SOUND ("weapons/ric3.wav"); - PRECACHE_SOUND ("weapons/ric4.wav"); - PRECACHE_SOUND ("weapons/ric5.wav"); // // Setup light animation tables. 'a' is total darkness, 'z' is maxbright. // - // 0 normal LIGHT_STYLE(0, "m"); diff --git a/dlls/wpn_shared/tw_akimbos.cpp b/dlls/wpn_shared/tw_akimbos.cpp new file mode 100644 index 0000000..feb82aa --- /dev/null +++ b/dlls/wpn_shared/tw_akimbos.cpp @@ -0,0 +1,599 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "player.h" +#include "soundent.h" +#include "thewastes.h" +#include "game.h" +#include "gamerules.h" + +// Entity Linkage +LINK_ENTITY_TO_CLASS( weapon_akimboberettas, CAkimboBerettas ); +LINK_ENTITY_TO_CLASS( weapon_akimbocolts, CAkimboColts ); +LINK_ENTITY_TO_CLASS( weapon_akimbodeagles, CAkimboDeagles ); +LINK_ENTITY_TO_CLASS( weapon_akimbosawedoffs, CAkimboSawedOffs ); + +/************* + Akimbo weapon +*************/ +void CAkimboWeapon::Spawn() +{ + CWasteWeapon::Spawn(); + Precache(); + + m_bSlowFire = FALSE; + m_iCurrentHand = AH_LEFT; + + FallInit(); +} + +int CAkimboWeapon::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 5; + p->iFlags = 0; + + p->iPosition = iItemSlot() - 1; + + return 1; +} + +void CAkimboWeapon::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_iCurrentHand; +} + +void CAkimboWeapon::UnpackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_iCurrentHand = local_data->m_iWeaponState; +} + +void CAkimboWeapon::ItemPostFrame() +{ + if( ( m_fInReload ) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // complete the reload. + int j = min( iMaxClip(), m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ); + + // For animations sake, if we are putting an odd number of bullets in, + // swap the current hand to the right hand + if ( j % 2 != 0 ) + m_iCurrentHand = AH_RIGHT; + + m_iClip = 0; + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; + +#ifndef CLIENT_DLL + m_pPlayer->TabulateAmmo(); +#endif + m_fInReload = FALSE; + } + + CWasteWeapon::ItemPostFrame(); +} + +void CAkimboWeapon::PrimaryAttack() +{ + if( m_iClip <= 0 ) + { + if( m_fFireOnEmpty ) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.3f; + } + + SendWeaponAnim( (m_iCurrentHand == AH_RIGHT) ? AKIMBO_RSHOOTEMPTY : AKIMBO_LSHOOTEMPTY, UseDecrement() ? 1 : 0 ); + + ( m_iCurrentHand == AH_LEFT ) ? m_iCurrentHand = AH_RIGHT : m_iCurrentHand = AH_LEFT; + + return; + } + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition(); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; + + float spread = m_fAccuracy; + + if( m_bSlowFire ) + spread = m_fAccuracy * 0.50f; + + // Accuracy modifiers + if(m_pPlayer->pev->flags & FL_DUCKING) + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Vector(spread*SIDEARM_DUCKPENALTY,spread*SIDEARM_DUCKPENALTY,spread*SIDEARM_DUCKPENALTY), 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_HANDCANNON ); + else if(m_pPlayer->pev->flags & FL_ONGROUND) + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Vector(spread,spread,spread), 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_HANDCANNON ); + else + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Vector(spread*SIDEARM_JUMPPENALTY,spread*SIDEARM_JUMPPENALTY,spread*SIDEARM_JUMPPENALTY), 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_HANDCANNON ); + + int flags; + +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + // Fire off the event + PLAYBACK_EVENT_FULL( flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + dir.x, + dir.y, + m_iCurrentHand, + m_iClip, + 0, + 0 ); + + ( m_iCurrentHand == AH_LEFT ) ? m_iCurrentHand = AH_RIGHT : m_iCurrentHand = AH_LEFT; + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + m_fRateOfFire; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT( 10, 15 ); +} + +void CAkimboWeapon::SecondaryAttack() +{ + m_bSlowFire = TRUE; + PrimaryAttack(); + m_bSlowFire = FALSE; + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + ( m_fRateOfFire * 2.0f ); +} + +void CAkimboWeapon::Reload() +{ + if(m_flNextPrimaryAttack > UTIL_WeaponTimeBase() || m_flNextSecondaryAttack > UTIL_WeaponTimeBase()) + return; + + int iResult; + + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // player "reload" animation + m_pPlayer->SetAnimation( PLAYER_RELOAD ); + + if (m_iClip == 0) + iResult = DefaultReload( m_iClipSize, AKIMBO_RELOAD2_EMPTY, 4.78f ); + else + iResult = DefaultReload( m_iClipSize, AKIMBO_RELOAD, 1.0f ); + + if (iResult) + { + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); + + // Reset hand + m_iCurrentHand = AH_LEFT; + } +} + +BOOL CAkimboWeapon::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CAkimboWeapon::WeaponIdle() +{ + ResetEmptySound(); +} + +/************* + Dual Berettas - John Woo meets Mad Max +*************/ +void CAkimboBerettas::Spawn() +{ + pev->classname = MAKE_STRING( "weapon_akimboberettas" ); + + m_iBulletType = BULLET_9MMP; + m_iId = WEAPON_AKIMBOBERETTAS; + m_iDefaultAmmo = AMMO_GIVE_BERETTA * 2; + m_iClipSize = CLIP_BERETTA * 2; + + m_fAccuracy = 0.078; + m_fRateOfFire = 0.18; + + CAkimboWeapon::Spawn(); +} + +void CAkimboBerettas::Precache() +{ + PRECACHE_MODEL("models/v_akimbo_berettas.mdl"); + PRECACHE_MODEL("models/p_akimbo_berettas.mdl"); + + PRECACHE_MODEL("models/shells/shell_9x18mm_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_9x18mm_hi.mdl"); + PRECACHE_MODEL("models/shells/mag_beretta.mdl"); + + PRECACHE_SOUND("weapons/beretta_fire.wav"); + PRECACHE_SOUND("weapons/beretta_fire_empty.wav"); + + m_usFire1 = PRECACHE_EVENT(1, "events/akimboberettas.sc" ); +} + +int CAkimboBerettas::GetItemInfo( ItemInfo *p ) +{ + p->pszAmmo1 = "9mm Parabellum"; + p->iMaxAmmo1 = AMMO_MAX_BERETTA; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_BERETTA * 2; + + p->iId = m_iId = WEAPON_AKIMBOBERETTAS; + p->iWeight = WEIGHT_BERETTA * 2; + + return CAkimboWeapon::GetItemInfo( p ); +} + +float CAkimboBerettas::flGetTiltedDamage() +{ + return RANDOM_FLOAT( 38, 48 ); +} + +char *CAkimboBerettas::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_AKIMBOBERETTAS_1 : DEATH_AKIMBOBERETTAS_2; +} + +BOOL CAkimboBerettas::Deploy() +{ + if(!DefaultDeploy("models/v_akimbo_berettas.mdl","models/p_akimbo_berettas.mdl",AKIMBO_DRAW,"akimbo")) + return FALSE; + + return CWasteWeapon::Deploy(); +} + +/************* + Dual Colt Enforcers +*************/ +void CAkimboColts::Spawn() +{ + pev->classname = MAKE_STRING( "weapon_akimbocolts" ); + + m_iBulletType = BULLET_10MM; + m_iId = WEAPON_AKIMBOCOLTS; + m_iDefaultAmmo = AMMO_GIVE_COLT * 2; + m_iClipSize = CLIP_COLT * 2; + + m_fAccuracy = 0.080; + m_fRateOfFire = 0.240; + + CAkimboWeapon::Spawn(); +} + +void CAkimboColts::Precache() +{ + PRECACHE_MODEL("models/v_akimbo_colts.mdl"); + PRECACHE_MODEL("models/p_akimbo_colts.mdl"); + + PRECACHE_MODEL("models/shells/shell_10mm_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_10mm_hi.mdl"); + PRECACHE_MODEL("models/shells/mag_colt.mdl"); + + PRECACHE_SOUND("weapons/colt_fire.wav"); + PRECACHE_SOUND("weapons/colt_fire_empty.wav"); + + m_usFire1 = PRECACHE_EVENT(1, "events/akimbocolts.sc" ); +} + +int CAkimboColts::GetItemInfo( ItemInfo *p ) +{ + p->pszAmmo1 = ".40 S&W"; + p->iMaxAmmo1 = AMMO_MAX_COLT; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_COLT * 2; + + p->iId = m_iId = WEAPON_AKIMBOCOLTS; + p->iWeight = WEIGHT_COLT * 2; + + return CAkimboWeapon::GetItemInfo( p ); +} + +float CAkimboColts::flGetTiltedDamage() +{ + return RANDOM_FLOAT( 45, 55 ); +} + +char *CAkimboColts::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_AKIMBOCOLTS_1 : DEATH_AKIMBOCOLTS_2; +} + +BOOL CAkimboColts::Deploy() +{ + if(!DefaultDeploy("models/v_akimbo_colts.mdl","models/p_akimbo_colts.mdl",AKIMBO_DRAW,"akimbo")) + return FALSE; + + return CWasteWeapon::Deploy(); +} + +/************* + Dual Desert Eagles +*************/ +void CAkimboDeagles::Spawn() +{ + pev->classname = MAKE_STRING( "weapon_akimbodeagles" ); + + m_iBulletType = BULLET_50AE; + m_iId = WEAPON_AKIMBODEAGLES; + m_iDefaultAmmo = AMMO_GIVE_DEAGLE * 2; + m_iClipSize = CLIP_DEAGLE * 2; + + m_fAccuracy = 0.078; + m_fRateOfFire = 0.308; + + CAkimboWeapon::Spawn(); +} + +void CAkimboDeagles::Precache() +{ + PRECACHE_MODEL("models/v_akimbo_deagles.mdl"); + PRECACHE_MODEL("models/p_akimbo_deagles.mdl"); + + PRECACHE_MODEL("models/shells/shell_50AE_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_50AE_hi.mdl"); + PRECACHE_MODEL("models/shells/mag_deagle.mdl"); + + PRECACHE_SOUND("weapons/deagle_fire.wav"); + PRECACHE_SOUND("weapons/deagle_fire_empty.wav"); + + m_usFire1 = PRECACHE_EVENT(1, "events/akimbodeagles.sc" ); +} + +int CAkimboDeagles::GetItemInfo( ItemInfo *p ) +{ + p->pszAmmo1 = ".50 AE"; + p->iMaxAmmo1 = AMMO_MAX_DEAGLE; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_DEAGLE * 2; + + p->iId = m_iId = WEAPON_AKIMBODEAGLES; + p->iWeight = WEIGHT_DEAGLE * 2; + + return CAkimboWeapon::GetItemInfo( p ); +} + +float CAkimboDeagles::flGetTiltedDamage() +{ + return RANDOM_FLOAT( 50, 60 ); +} + +char *CAkimboDeagles::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_AKIMBODEAGLES_1 : DEATH_AKIMBODEAGLES_2; +} + +BOOL CAkimboDeagles::Deploy() +{ + if(!DefaultDeploy("models/v_akimbo_deagles.mdl","models/p_akimbo_deagles.mdl",AKIMBO_DRAW,"akimbo")) + return FALSE; + + return CWasteWeapon::Deploy(); +} + +/************* + Dual Sawed Off's +*************/ +void CAkimboSawedOffs::Spawn() +{ + pev->classname = MAKE_STRING( "weapon_akimbosawedoffs" ); + + m_iBulletType = BULLET_20GAUGE; + m_iId = WEAPON_AKIMBOSAWEDOFFS; + m_iDefaultAmmo = AMMO_GIVE_BUCKSHOT; + m_iClipSize = CLIP_SAWEDOFF * 2; + + CAkimboWeapon::Spawn(); +} + +void CAkimboSawedOffs::Precache() +{ + PRECACHE_MODEL("models/v_akimbo_sawedoffs.mdl"); + PRECACHE_MODEL("models/p_akimbo_sawedoffs.mdl"); + + PRECACHE_MODEL("models/shells/shell_buckshot_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_buckshot_hi.mdl"); + + PRECACHE_SOUND("weapons/sawedoff_fire1.wav"); + PRECACHE_SOUND("weapons/sawedoff_fire2.wav"); + PRECACHE_SOUND("weapons/sawedoff_fire_empty.wav"); + + m_usFire1 = PRECACHE_EVENT(1, "events/akimbosawedoffs.sc" ); +} + +int CAkimboSawedOffs::GetItemInfo( ItemInfo *p ) +{ + p->pszAmmo1 = "Buckshot"; + p->iMaxAmmo1 = AMMO_MAX_BUCKSHOT; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_SAWEDOFF * 2; + + p->iId = m_iId = WEAPON_AKIMBOSAWEDOFFS; + p->iWeight = WEIGHT_SAWEDOFF * 2; + + return CAkimboWeapon::GetItemInfo( p ); +} + +float CAkimboSawedOffs::flGetTiltedDamage() +{ + return RANDOM_FLOAT( 16, 19 ); +} + +char *CAkimboSawedOffs::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_SAWEDOFF_1 : DEATH_SAWEDOFF_2; +} + +void CAkimboSawedOffs::ItemPostFrame() +{ + // We call CWasteWeapon instead of CAkimboWeapon because + // akimboweapon has special reload code for magazine based weapons. + CWasteWeapon::ItemPostFrame(); + + if( !( m_pPlayer->pev->button & IN_ATTACK ) ) + m_iAllowFire = TRUE; +} + +void CAkimboSawedOffs::FireSawedOffs( int iShellsFired ) +{ + if( !m_iAllowFire ) + return; + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if( !m_iClip ) + { + // SendWeaponAnim( SAWED_SHOOT_EMPTY, UseDecrement() ? 1 : 0 ); + PlayEmptySound(); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.3f; + return; + } + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip -= iShellsFired; + + // OMG kickback! +#ifndef CLIENT_DLL + float flZVel = m_pPlayer->pev->velocity.z; + float kickback = 175 * iShellsFired; + + m_pPlayer->pev->velocity = m_pPlayer->pev->velocity - gpGlobals->v_forward * kickback; + + // clamp the z velocity so people + // cant sawnoff jump with the gun :) + m_pPlayer->pev->velocity.z = flZVel; +#endif + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition(); + Vector vecAiming = gpGlobals->v_forward; + Vector vecDir; + + int shotCount = 7 * iShellsFired; + float spread; + + if( iShellsFired == 1 ) + spread = 0.15; + else + spread = 0.25 * (iShellsFired / 2); + + Vector vecSpread = Vector( spread, spread, spread ); + + vecDir = m_pPlayer->FireBulletsPlayer( this, shotCount, vecSrc, vecAiming, vecSpread, 1280, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, + m_pPlayer->edict(), + m_usFire1, + 0.0f, + (float*)&g_vecZero, + (float*)&g_vecZero, + spread, + spread, + m_iCurrentHand, + 0, + (iShellsFired > 1) ? 1 : 0, // True if both sawed offs were fired at once + 0 ); + + m_iAllowFire = FALSE; + ( m_iCurrentHand == AH_LEFT ) ? m_iCurrentHand = AH_RIGHT : m_iCurrentHand = AH_LEFT; +} + +void CAkimboSawedOffs::PrimaryAttack() +{ + FireSawedOffs( 1 ); +} + +void CAkimboSawedOffs::SecondaryAttack() +{ + FireSawedOffs( m_iClip ); +} + +void CAkimboSawedOffs::Reload() +{ + if(m_flNextPrimaryAttack > 0.0 || m_flNextSecondaryAttack > 0.0) + return; + + int iResult; + + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // player "reload" animation + m_pPlayer->SetAnimation( PLAYER_RELOAD ); + + iResult = DefaultReload( m_iClipSize, AKIMBOSS_RELOAD, 3.0f ); + + if(iResult) + { +// m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.6f; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); + } +} + +BOOL CAkimboSawedOffs::Deploy() +{ + if(!DefaultDeploy("models/v_akimbo_sawedoffs.mdl","models/p_akimbo_sawedoffs.mdl",AKIMBOSS_DRAW,"akimbo")) + return FALSE; + + return CWasteWeapon::Deploy(); +} \ No newline at end of file diff --git a/dlls/wpn_shared/tw_automatics.cpp b/dlls/wpn_shared/tw_automatics.cpp new file mode 100644 index 0000000..ffe012d --- /dev/null +++ b/dlls/wpn_shared/tw_automatics.cpp @@ -0,0 +1,2225 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "player.h" +#include "soundent.h" +#include "thewastes.h" +#include "game.h" +#include "gamerules.h" + +// Entity Linkage +LINK_ENTITY_TO_CLASS(weapon_smg9,CSmg9); +LINK_ENTITY_TO_CLASS(ammo_smg9,CSmg9Ammo); + +LINK_ENTITY_TO_CLASS(weapon_fnfal,CFnFal); +LINK_ENTITY_TO_CLASS(ammo_fnfal,CFnFalAmmo); + +LINK_ENTITY_TO_CLASS(weapon_tommygun,CTommyGun); +LINK_ENTITY_TO_CLASS(ammo_tommygun,CTommyGunAmmo); + +LINK_ENTITY_TO_CLASS(weapon_g11,CG11); +LINK_ENTITY_TO_CLASS(ammo_g11,CG11Ammo); + +LINK_ENTITY_TO_CLASS(weapon_boltrifle,CBoltRifle); +LINK_ENTITY_TO_CLASS(ammo_boltrifle,CBoltRifleAmmo); + +LINK_ENTITY_TO_CLASS(weapon_sten,CSten); +LINK_ENTITY_TO_CLASS(ammo_sten,CStenAmmo); + +/************* + Automatic weapon +*************/ +enum automatic_state_e { + STATE_BURST, + STATE_FULLAUTO, +}; + +int CAutomatic::GetItemInfo(ItemInfo *p) +{ + p->iPosition = iItemSlot() - 1; + p->pszName = STRING(pev->classname); + p->iFlags = 0; + + return 1; +} + +void CAutomatic::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_iState; + local_data->iuser1 = m_iAllowFire; +} + +void CAutomatic::UnpackWeapon(void *weapon_data) +{ +#ifdef CLIENT_DLL + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_iState = local_data->m_iWeaponState; + m_iAllowFire = local_data->iuser1; +#endif +} + +void CAutomatic::ItemPostFrame() +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // complete the reload. + int j = min( iMaxClip(), m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // account for a bullet in the gun + if(m_iClip != 0) + m_iClip = 1; + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; +#ifndef CLIENT_DLL + m_pPlayer->TabulateAmmo(); +#endif + m_fInReload = FALSE; + } + + if(!(m_pPlayer->pev->button & IN_ATTACK)) + { + m_iAllowFire = TRUE; + } + + CWasteWeapon::ItemPostFrame(); +} + +void CAutomatic::Holster(int skiplocal) +{ + m_iState = STATE_BURST; + m_iAllowFire = TRUE; + CWasteWeapon::Holster(skiplocal); +} + +/************* + SMG9 - let the whoring begin! +*************/ +#define SMG9_JUMPPENALTY 2.4 // Accuracy infraction for jumping shooters. +#define SMG9_DUCKPENALTY 0.65 // Not really a penalty, increases accuracy if your duckin +#define SMG9_AIM_SECONDARY 0.3 + +#define SMG9_BURST_COUNT 3 + +void CSmg9::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_smg9"); + + m_iId = WEAPON_SMG9; + m_iDefaultAmmo = AMMO_GIVE_SMG9; + m_iClipSize = CLIP_SMG9; + m_iAllowFire = TRUE; + m_iState = STATE_BURST; + m_bBurstFire = FALSE; + + m_flAccuracy = 0.0385f; + m_flRateOfFire = 0.1; + + pev->fuser1 = UTIL_WeaponTimeBase(); + + SET_MODEL(ENT(pev),"models/w_smg9.mdl"); + + FallInit(); +} + +void CSmg9::Precache() +{ + PRECACHE_MODEL("models/v_smg9.mdl"); + PRECACHE_MODEL("models/p_smg9.mdl"); + PRECACHE_MODEL("models/w_smg9.mdl"); + + PRECACHE_MODEL("models/shells/shell_9x22mm_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_9x22mm_hi.mdl"); + + PRECACHE_SOUND("weapons/smg9_fire1.wav"); + PRECACHE_SOUND("weapons/smg9_fire2.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/smg9.sc"); +} + +int CSmg9::GetItemInfo(ItemInfo *p) +{ + p->iMaxClip = CLIP_SMG9; + p->iId = m_iId = WEAPON_SMG9; + p->iWeight = WEIGHT_SMG9; + p->iSlot = 2; + + p->pszAmmo1 = "9mm SMG9 Ammo"; + p->iMaxAmmo1 = AMMO_MAX_SMG9; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + + return CAutomatic::GetItemInfo(p); +} + +void CSmg9::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + local_data->iuser3 = m_bLaserVisible; + local_data->iuser4 = (m_pPlayer->m_pActiveItem == this) ? 1 : 0; + + CAutomatic::PackWeapon(weapon_data); +} + +void CSmg9::UnpackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + m_bLaserVisible = local_data->iuser3; + +#ifdef CLIENT_DLL + // Only do this check if we're active + if(local_data->iuser4) + g_iClientLasersEnabled[GetLocalPlayerIndex()] = m_bLaserVisible; +#endif + + CAutomatic::UnpackWeapon(weapon_data); +} + +float CSmg9::flGetTiltedDamage() +{ + float fRet = RANDOM_FLOAT(28,38); + + // 5% Damage increase + if(m_iState == STATE_BURST) + fRet *= 1.05f; + + return fRet; +} + +char *CSmg9::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_SMG9_1 : DEATH_SMG9_2; +} + +void CSmg9::PrimaryAttack() +{ + if(!m_iAllowFire) + return; + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + else if(m_iClip <= 0) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.4; + SendWeaponAnim((m_iState == STATE_BURST) ? SMG9_SHOOT_EMPTY : SMG9_SECONDARY_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + return; + } + + float spread = m_flAccuracy; + float rof = m_flRateOfFire; + BOOL startburst = FALSE; + + // Semi-Automatic control + if(m_iState == STATE_BURST) + { + if(pev->fuser1 <= UTIL_WeaponTimeBase()) + { + m_bBurstFire = TRUE; + pev->fuser1 = UTIL_WeaponTimeBase() + (SMG9_BURST_COUNT * rof); + + startburst = TRUE; + } + } + else if(m_iState == STATE_FULLAUTO) + // Modify gun logic if we're just shooting the gangsta style + spread /= SMG9_AIM_SECONDARY; + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; + + Vector Spread; + + // Accuracy modifiers + if(m_pPlayer->pev->flags & FL_DUCKING) + Spread = Vector(spread*SMG9_DUCKPENALTY,spread*SMG9_DUCKPENALTY,spread*SMG9_DUCKPENALTY); + else if(m_pPlayer->pev->flags & FL_ONGROUND) + Spread = Vector(spread,spread,spread); + else + Spread = Vector(spread*SMG9_JUMPPENALTY,spread*SMG9_JUMPPENALTY,spread*SMG9_JUMPPENALTY); + + dir = m_pPlayer->FireBulletsPlayer(this,1, vecSrc, vecAiming, Spread, 8192, BULLET_9MM, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_SMG9); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + if(startburst) + { + Vector accVector(m_flAccuracy,0,0); + + for(int i = 0;i < min(m_iClip,SMG9_BURST_COUNT);i++) + { + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + rof * i, + (float*)&g_vecZero, + (float*)&accVector, + dir.x, + dir.y + m_flAccuracy * i, + (m_iState == STATE_FULLAUTO) ? 1 : 0, + (m_iClip == 0) ? 1 : 0, + (m_pPlayer->pev->flags & FL_DUCKING) ? 1 : 0, // Less recoil + (i > 0) ? 1 : 0); + } + } + else if(!m_bBurstFire) + { + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + dir.x, + dir.y, + (m_iState == STATE_FULLAUTO) ? 1 : 0, + (m_iClip == 0) ? 1 : 0, + (m_pPlayer->pev->flags & FL_DUCKING) ? 1 : 0, // Less recoil + 0); + } + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + rof; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); +} + +void CSmg9::SecondaryAttack() +{ + switch(m_iState) + { + case STATE_BURST: + m_iState = STATE_FULLAUTO; + SendWeaponAnim(SMG9_GOTO_SECONDARY,UseDecrement() ? 1 : 0); + m_bLaserVisible = FALSE; +#ifdef CLIENT_DLL + CenterPrint("Full-automatic fire"); + g_iClientLasersEnabled[GetLocalPlayerIndex()] = 0; +#endif + break; + case STATE_FULLAUTO: + m_iState = STATE_BURST; + SendWeaponAnim(SMG9_END_SECONDARY,UseDecrement() ? 1 : 0); + m_bLaserVisible = TRUE; +#ifdef CLIENT_DLL + CenterPrint( "Burst fire" ); + g_iClientLasersEnabled[GetLocalPlayerIndex()] = 1; +#endif + break; + } + + m_iAllowFire = TRUE; + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.25; +} + +void CSmg9::ItemPostFrame() +{ + // Burst fire logic + if(m_iState == STATE_BURST && m_bBurstFire) + { + // Player can still fire the gun + if(pev->fuser1 >= UTIL_WeaponTimeBase()) + { + // Player will now fire the gun + if(m_flNextPrimaryAttack <= UTIL_WeaponTimeBase()) + PrimaryAttack(); + } + else + { + m_iAllowFire = FALSE; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.3f; + } + } + + CAutomatic::ItemPostFrame(); + + if(m_iAllowFire && pev->fuser1 <= UTIL_WeaponTimeBase()) + m_bBurstFire = FALSE; +} + +void CSmg9::Reload() +{ + int iResult; + + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // player "reload" animation + m_pPlayer->SetAnimation(PLAYER_RELOAD); + + // Why am i making this function unreadable as hell? + // BECAUSE I CAN! :D + iResult = DefaultReload(m_iClipSize, + (m_iClip == 0) + ? (m_iState == STATE_BURST) ? SMG9_RELOAD_EMPTY : SMG9_SECONDARY_RELOAD + : (m_iState == STATE_BURST) ? SMG9_RELOAD : SMG9_SECONDARY_RELOAD, + (m_iClip == 0) ? 3.0f : 2.0f); + if(iResult) + { + m_iAllowFire = TRUE; + } +} + +BOOL CSmg9::Deploy() +{ + if(!DefaultDeploy("models/v_smg9.mdl","models/p_smg9.mdl",(m_iState == STATE_BURST) ? SMG9_DRAW : SMG9_SECONDARY_DRAW,"smg9")) + return FALSE; + + m_iAllowFire = TRUE; + + int ret = CAutomatic::Deploy(); + + if(ret && m_iState == STATE_BURST) + { + m_bLaserVisible = TRUE; + +#ifdef CLIENT_DLL + g_iClientLasersEnabled[GetLocalPlayerIndex()] = 1; +#endif + } + + return ret; +} + +BOOL CSmg9::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CSmg9::Holster(int skiplocal) +{ + m_bLaserVisible = FALSE; + +#ifdef CLIENT_DLL + g_iClientLasersEnabled[GetLocalPlayerIndex()] = 0; +#endif + m_iAllowFire = TRUE; + CWasteWeapon::Holster(skiplocal); +} + +BOOL CSmg9::bLaserActive() +{ + if( m_pPlayer != NULL && m_pPlayer->m_pActiveItem == this ) + return m_bLaserVisible; + return FALSE; +} + +// +// Ammo Box +// +void CSmg9Ammo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_smg9.mdl"); + CWasteAmmo::Spawn(); +} + +void CSmg9Ammo::Precache() +{ + PRECACHE_MODEL("models/ammo_smg9.mdl"); +} + +BOOL CSmg9Ammo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo( AMMO_GIVE_SMG9, "9mm SMG9 Ammo", AMMO_MAX_SMG9) != -1) + return TRUE; + return FALSE; +} + +/************* + FN-FAL +*************/ +#define FNFAL_JUMPPENALTY 2.4 // Accuracy infraction for jumping shooters. +#define FNFAL_DUCKPENALTY 0.5 // Not really a penalty, increases accuracy if your duckin +#define FNFAL_AIM_SECONDARY 0.75 +#define FNFAL_AIM_PRIMARY 0.80 + +void CFnFal::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_fnfal"); + + m_iId = WEAPON_FNFAL; + m_iDefaultAmmo = AMMO_GIVE_FNFAL; + m_iClipSize = CLIP_FNFAL; + m_iAllowFire = TRUE; + m_iState = STATE_FULLAUTO; + m_iAim = 0; + + m_flAccuracy = 0.015; + m_flRateOfFire = 0.105; + + SET_MODEL(ENT(pev),"models/w_fnfal.mdl"); + + FallInit(); +} + +void CFnFal::Precache() +{ + PRECACHE_MODEL("models/v_fnfal.mdl"); + PRECACHE_MODEL("models/p_fnfal.mdl"); + PRECACHE_MODEL("models/w_fnfal.mdl"); + + PRECACHE_MODEL("models/shells/shell_762mm_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_762mm_hi.mdl"); + + PRECACHE_SOUND("weapons/fnfal_fire1.wav"); + PRECACHE_SOUND("weapons/fnfal_fire2.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/fnfal.sc"); +} + +void CFnFal::PackWeapon(void *weapon_data) +{ + CAutomatic::PackWeapon(weapon_data); + + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_iState; + local_data->iuser4 = m_iAim; +} + +void CFnFal::UnpackWeapon(void *weapon_data) +{ +#ifdef CLIENT_DLL + CAutomatic::UnpackWeapon(weapon_data); + + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_iAim = local_data->iuser4; + m_iState = local_data->m_iWeaponState; +#endif +} + +int CFnFal::GetItemInfo(ItemInfo *p) +{ + p->iMaxClip = CLIP_FNFAL; + p->iId = m_iId = WEAPON_FNFAL; + p->iWeight = WEIGHT_UNIQUE; + p->iSlot = 3; + + p->pszAmmo1 = "FNFAL Ammo"; + p->iMaxAmmo1 = AMMO_MAX_FNFAL; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + + return CAutomatic::GetItemInfo(p); +} + +float CFnFal::flGetTiltedDamage() +{ + float fRet = RANDOM_FLOAT(45,55); + + // 5% damage increase + if(m_iState == STATE_BURST) + fRet *= 1.05f; + + // 5% damage increase + if(m_iAim) + fRet *= 1.05f; + + return fRet; +} + +char *CFnFal::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_FNFAL_1 : DEATH_FNFAL_2; +} + +void CFnFal::PrimaryAttack() +{ + if(!m_iAllowFire) + return; + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if(m_iClip <= 0) + { + if(m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.4; + SendWeaponAnim((m_iAim) ? FNFAL_AIM_SHOOT_EMPTY : FNFAL_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + } + return; + } + + float spread = m_flAccuracy; + float rof = m_flRateOfFire; + + // Semi-Automatic control + if(m_iState == STATE_BURST) + { + if( m_iAim ) + spread *= FNFAL_AIM_SECONDARY; + else + spread *= FNFAL_AIM_PRIMARY; + + m_iAllowFire = FALSE; + rof += 0.15; // Add some time + } + else if(m_iState == STATE_FULLAUTO) + { + // Increase accuracy for full auto + if( m_iAim ) + spread *= FNFAL_AIM_SECONDARY; + } + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; + + Vector Spread; + + // Accuracy modifiers + if(m_pPlayer->pev->flags & FL_DUCKING) + Spread = Vector(spread*FNFAL_DUCKPENALTY,spread*FNFAL_DUCKPENALTY,spread*FNFAL_DUCKPENALTY); + else if(m_pPlayer->pev->flags & FL_ONGROUND) + Spread = Vector(spread,spread,spread); + else + Spread = Vector(spread*FNFAL_JUMPPENALTY,spread*FNFAL_JUMPPENALTY,spread*FNFAL_JUMPPENALTY); + + dir = m_pPlayer->FireBulletsPlayer(this,1, vecSrc, vecAiming, Spread, 8192,BULLET_762MM, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_FNFAL); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + dir.x, + dir.y, + 0, + (m_iState == STATE_BURST) ? 1 : 0, + (m_pPlayer->pev->flags & FL_DUCKING) ? 1 : 0, // Less recoil + (m_iAim == 1 ) ? 1 : 0 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + rof; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); +} + + +void CFnFal::SecondaryAttack() +{ + switch( m_iState ) + { + case STATE_FULLAUTO: + m_iState = STATE_BURST; +#ifdef CLIENT_DLL + CenterPrint( "Semi-automatic fire" ); +#endif + break; + case STATE_BURST: + m_iState = STATE_FULLAUTO; +#ifdef CLIENT_DLL + CenterPrint( "Full-automatic fire" ); +#endif + break; + } + + m_iAllowFire = TRUE; + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.2f; +} + +void CFnFal::SpecialMode() +{ + if( m_iAim == 1) + { + m_iAim = 0; + MODIFY_PLAYER_SPEED( m_pPlayer, 0 ); + SendWeaponAnim( FNFAL_END_AIM, UseDecrement() ? 1 : 0 ); + } + else + { + m_iAim = 1; + MODIFY_PLAYER_SPEED( m_pPlayer, 0.7 ); + SendWeaponAnim( FNFAL_GOTO_AIM, UseDecrement() ? 1 : 0 ); + } + + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0f; +} + +void CFnFal::ItemPostFrame() +{ + // Now we reload + // A bit of hacking around with state code, but it will do. + if( m_iAim < 0 && m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) + { + if( m_iAim == -1 ) + { + m_iAim = -2; + Reload(); + } + else if( m_iAim == -2 ) + { + m_iAim = 0; + SpecialMode(); + } + } + + CAutomatic::ItemPostFrame(); +} + +void CFnFal::Reload() +{ + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // Revert to normal state if needed + if( m_iAim == 1 ) + { + SpecialMode(); + m_iAim = -1; + return; + } + + // player "reload" animation + m_pPlayer->SetAnimation(PLAYER_RELOAD); + + if(DefaultReload(m_iClipSize,FNFAL_RELOAD,4.29f)) + { + m_iAllowFire = TRUE; + + if( m_iAim == -1 ) + m_iAim = -2; + } +} + +BOOL CFnFal::Deploy() +{ + if(!DefaultDeploy("models/v_fnfal.mdl","models/p_fnfal.mdl",FNFAL_DRAW,"fnfal")) + return FALSE; + + m_iAllowFire = TRUE; + m_iState = STATE_FULLAUTO; + m_iAim = FALSE; + + return CAutomatic::Deploy(); +} + +void CFnFal::Holster(int skiplocal) +{ + m_iState = STATE_FULLAUTO; + MODIFY_PLAYER_SPEED(m_pPlayer,0); + CAutomatic::Holster(skiplocal); +} + +BOOL CFnFal::PlayEmptySound() +{ + if(m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +// +// Ammo Box +// +void CFnFalAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_fnfal.mdl"); + CWasteAmmo::Spawn(); +} + +void CFnFalAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_fnfal.mdl"); +} + +BOOL CFnFalAmmo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo(AMMO_GIVE_FNFAL,"FNFAL Ammo",AMMO_MAX_FNFAL) != -1) + return TRUE; + return FALSE; +} + +/************* + Tommy Gun - Damn it feels good to be a gangsta +*************/ +#define TGUN_JUMPPENALTY 3.2 // Accuracy infraction for jumping shooters. +#define TGUN_DUCKPENALTY 0.75 // Not really a penalty, increases accuracy if your ducking + +void CTommyGun::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_tommygun"); + + m_iId = WEAPON_TOMMYGUN; + m_iDefaultAmmo = AMMO_GIVE_TOMMYGUN; + m_iClipSize = CLIP_TOMMYGUN; + m_iAllowFire = TRUE; + m_iState = STATE_FULLAUTO; + + m_flAccuracy = 0.0625; + m_flRateOfFire = 0.1075; + + SET_MODEL(ENT(pev),"models/w_tommygun.mdl"); + + FallInit(); +} + +void CTommyGun::Precache() +{ + PRECACHE_MODEL("models/v_tommygun.mdl"); + PRECACHE_MODEL("models/p_tommygun.mdl"); + PRECACHE_MODEL("models/w_tommygun.mdl"); + + PRECACHE_MODEL("models/shells/shell_45cal_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_45cal_hi.mdl"); + + PRECACHE_SOUND("weapons/tommygun_fire1.wav"); + PRECACHE_SOUND("weapons/tommygun_fire2.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/tommygun.sc"); +} + +int CTommyGun::GetItemInfo(ItemInfo *p) +{ + p->iMaxClip = CLIP_TOMMYGUN; + p->iId = m_iId = WEAPON_TOMMYGUN; + p->iWeight = WEIGHT_UNIQUE; + p->iSlot = 3; + + p->pszAmmo1 = "Tommygun Ammo"; + p->iMaxAmmo1 = AMMO_MAX_TOMMYGUN; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + + return CAutomatic::GetItemInfo(p); +} + +float CTommyGun::flGetTiltedDamage() +{ + return RANDOM_FLOAT(35,45); +} + +char *CTommyGun::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_TOMMYGUN_1 : DEATH_TOMMYGUN_2; +} + +void CTommyGun::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if(m_iClip <= 0) + { + if(m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.4; + SendWeaponAnim(TOMMYGUN_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + } + return; + } + + float spread = m_flAccuracy; + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; + + Vector Spread; + + // Accuracy modifiers + if(m_pPlayer->pev->flags & FL_DUCKING) + spread *= TGUN_DUCKPENALTY; + else if(!(m_pPlayer->pev->flags & FL_ONGROUND)) + spread *= TGUN_JUMPPENALTY; + + Spread = Vector(spread,spread,spread); + + dir = m_pPlayer->FireBulletsPlayer(this,1, vecSrc, vecAiming, Spread, 8192,BULLET_45ACP, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_TOMMYGUN); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + dir.x, + dir.y, + 0, + 0, + (m_pPlayer->pev->flags & FL_DUCKING) ? 1 : 0, // Less recoil + 0); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + m_flRateOfFire; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); +} + +void CTommyGun::Reload() +{ + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // player "reload" animation + m_pPlayer->SetAnimation(PLAYER_RELOAD); + + if(DefaultReload(m_iClipSize,TOMMYGUN_RELOAD,4.1f)) + m_iAllowFire = TRUE; +} + +BOOL CTommyGun::Deploy() +{ + if(!DefaultDeploy("models/v_tommygun.mdl","models/p_tommygun.mdl",TOMMYGUN_DRAW,"tommygun")) + return FALSE; +// m_iAllowFire = TRUE; + int retval = CAutomatic::Deploy(); + if(retval) + { + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.8f; + } + return retval; +} + +BOOL CTommyGun::PlayEmptySound() +{ + if(m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +// +// Ammo Box +// +void CTommyGunAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_tommygun.mdl"); + CWasteAmmo::Spawn(); +} + +void CTommyGunAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_tommygun.mdl"); +} + +BOOL CTommyGunAmmo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo(AMMO_GIVE_TOMMYGUN,"Tommygun Ammo",AMMO_MAX_TOMMYGUN) != -1) + return TRUE; + return FALSE; +} + +/************* + H&K G11 - New Weapons for a better tomorrow +*************/ +#define G11_JUMPPENALTY 3.2 // Accuracy infraction for jumping shooters. +#define G11_DUCKPENALTY 0.8 // Not really a penalty, increases accuracy if your duckin + +enum g11_state_e { + G11_BURSTFIRE = 0, + G11_FULLAUTO = 1, +}; + +void CG11::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_g11"); + + m_iId = WEAPON_G11; + m_iDefaultAmmo = AMMO_GIVE_G11; + m_iClipSize = CLIP_G11; + m_iAllowFire = TRUE; + m_iState = STATE_FULLAUTO; + + m_flAccuracy = 0.04; + m_flRateOfFire = 0.1; + m_flBurstAccuracy = 0.03; + m_bThermal = FALSE; + m_bActivateThermal = FALSE; + + m_iWeaponState = G11_FULLAUTO; + + SET_MODEL(ENT(pev),"models/w_g11.mdl"); + + FallInit(); +} + +void CG11::Precache() +{ + PRECACHE_MODEL("models/v_g11.mdl"); + PRECACHE_MODEL("models/p_g11.mdl"); + PRECACHE_MODEL("models/w_g11.mdl"); + + PRECACHE_SOUND("weapons/g11_ffire1.wav"); + PRECACHE_SOUND("weapons/g11_ffire2.wav"); + PRECACHE_SOUND("weapons/g11_burst.wav"); + PRECACHE_SOUND("weapons/g11_zoom1.wav"); + PRECACHE_SOUND("weapons/g11_zoom2.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/g11.sc"); +} + +int CG11::GetItemInfo(ItemInfo *p) +{ + p->iMaxClip = CLIP_G11; + p->iId = m_iId = WEAPON_G11; + p->iWeight = WEIGHT_UNIQUE; + p->iSlot = 3; + + p->pszAmmo1 = "4.7mm Caseless"; + p->iMaxAmmo1 = AMMO_MAX_G11; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + + return CAutomatic::GetItemInfo(p); +} + +float CG11::flGetTiltedDamage() +{ + return RANDOM_FLOAT(30,40); +} + +char *CG11::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_G11_1 : DEATH_G11_2; +} + +void CG11::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_iWeaponState; + local_data->iuser3 = m_bThermal; + local_data->iuser4 = m_bActivateThermal; +} + +void CG11::UnpackWeapon(void *weapon_data) +{ +#ifdef CLIENT_DLL + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_bActivateThermal = local_data->iuser4; + m_bThermal = local_data->iuser3; + m_iWeaponState = local_data->m_iWeaponState; +#endif +} + +void CG11::PrimaryAttack() +{ + if(!m_iAllowFire) + return; + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if(m_iClip <= 0) + { + if(m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.4; +// SendWeaponAnim(G11_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + } + return; + } + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; // Average of real_dir + Vector Spread; + + float spread; + float rof; + int bulletcount; + + if(m_iWeaponState == G11_FULLAUTO) + { + spread = m_flAccuracy; + bulletcount = 1; + m_iClip--; + rof = m_flRateOfFire; + } + else + { + spread = m_flBurstAccuracy; + bulletcount = min(m_iClip,3); + m_iClip -= min(m_iClip,3); + m_iAllowFire = FALSE; + rof = m_flRateOfFire + 0.2f; + } + + // Accuracy modifiers + if(m_pPlayer->pev->flags & FL_DUCKING) + spread *= G11_DUCKPENALTY; + else if(!(m_pPlayer->pev->flags & FL_ONGROUND)) + spread *= G11_JUMPPENALTY; + + Spread = Vector(spread,spread,spread); + + // 3 round burst isnt just a random spray, each bullet goes up alot but horizontal only a little, + // So we control it this way. + for(int i = 0;i < bulletcount;i++) + { + float xspread = Spread.x; + Spread.x += RANDOM_FLOAT(-0.015,0.015); + + dir = m_pPlayer->FireBulletsPlayer(this,1,vecSrc,vecAiming,Spread,8192,BULLET_47MM,0,0,m_pPlayer->pev,m_pPlayer->random_seed,P_G11); + + Spread.x = xspread; + Spread.y += 0.01f; + } + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + (m_iWeaponState == G11_FULLAUTO) ? dir.x : 0.0f, // send the generic spread on 3 rnd burst (simulate client side) + (m_iWeaponState == G11_FULLAUTO) ? dir.y : 0.008f, // hopup on each burst bullet + (m_iWeaponState == G11_BURSTFIRE) ? 1 : 0, + bulletcount, + 0, + (m_pPlayer->pev->flags & FL_DUCKING) ? 1 : 0); // Less recoil + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + rof; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT(10,15); +} + +void CG11::SecondaryAttack() +{ + switch(m_iWeaponState) + { + case G11_BURSTFIRE: + m_iWeaponState = G11_FULLAUTO; +#ifdef CLIENT_DLL + CenterPrint("Full-automatic fire"); +#endif + break; + case G11_FULLAUTO: + m_iWeaponState = G11_BURSTFIRE; +#ifdef CLIENT_DLL + CenterPrint("Burst fire"); +#endif + break; + } + + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.2f; +} + +void CG11::SpecialMode() +{ + if(m_pPlayer->m_flNextAttack > UTIL_WeaponTimeBase()) + return; + + m_bThermal = !m_bThermal; + + int flags; + + // Zoom + switch(m_bThermal) + { + case FALSE: +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0, + 0, + 0, + 0, + 1, // Zoom sound :) + 1); // Unzoom sound :) + + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; + SendWeaponAnim(G11_ACTIVATE_SCOPE,UseDecrement() ? 1 : 0); + break; + case TRUE: +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0, + 0, + 0, + 0, + 1, // Zoom sound :) + 0); + + SendWeaponAnim(G11_DEACTIVATE_SCOPE,UseDecrement() ? 1 : 0); + m_bActivateThermal = TRUE; + break; + } + + +#ifdef CLIENT_DLL + if(!m_bThermal) + { + ev_thermal = FALSE; + CenterPrint("Thermal vision deactivated"); + V_StopSway(); + V_DisableFade(); + } + else + { + CenterPrint("Thermal vision activated"); + } +#endif + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.5f; +} + +void CG11::ItemPostFrame() +{ + if(m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() && m_bActivateThermal) + { + m_bActivateThermal = FALSE; + + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 70; + +#ifdef CLIENT_DLL + ev_thermal = TRUE; + ClientSway(); +#endif + } + + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) && m_bThermal) + { + m_bThermal = !m_bThermal; + + SpecialMode(); + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase(); + } + + CAutomatic::ItemPostFrame(); +} + +void CG11::ClientSway() +{ +#ifdef CLIENT_DLL + // Ducking + if(m_pPlayer->pev->flags & FL_DUCKING) + V_SetSway(1.0); + // On Ground + else if(m_pPlayer->pev->flags & FL_ONGROUND) + V_SetSway(2.25); + // Jumping + else + V_SetSway(4.5); +#endif +} + +void CG11::Reload() +{ + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + if(m_bThermal) + { + SpecialMode(); + m_bThermal = TRUE; // reset to true so when reload is done we go back to zoom +#ifdef CLIENT_DLL + CenterPrint(""); // Hack so player wont see center print text +#endif + } + + // player "reload" animation + m_pPlayer->SetAnimation(PLAYER_RELOAD); + + if(DefaultReload(m_iClipSize,G11_RELOAD,5.23f)) + m_iAllowFire = TRUE; +} + +BOOL CG11::Deploy() +{ + if(!DefaultDeploy("models/v_g11.mdl","models/p_g11.mdl",G11_DRAW,"g11")) + return FALSE; + m_iAllowFire = TRUE; + + int retval = CAutomatic::Deploy(); + if(retval) + { + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.8f; + } + return retval; +} + +void CG11::Holster(int skiplocal) +{ +#ifdef CLIENT_DLL + V_StopSway(); + V_DisableFade(); +#endif + + m_bThermal = FALSE; + m_bActivateThermal = FALSE; + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; + + CWasteWeapon::Holster(skiplocal); +} + +BOOL CG11::PlayEmptySound() +{ + if(m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +// +// Ammo Box +// +void CG11Ammo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_g11.mdl"); + CWasteAmmo::Spawn(); +} + +void CG11Ammo::Precache() +{ + PRECACHE_MODEL("models/ammo_g11.mdl"); +} + +BOOL CG11Ammo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo(AMMO_GIVE_G11,"4.7mm Caseless",AMMO_MAX_G11) != -1) + return TRUE; + return FALSE; +} + +/************* + Bolt Rifle - Now u see him, now u dont! +*************/ +// I know this isnt an automatic, but its probably the best section to put it in for now. + +enum boltrifle_state_e { + BR_NOZOOM, + BR_ZOOM_2X, + BR_ZOOM_4X, + BR_ZOOM_6X, +}; + +#define BR_FOV_2X 45 +#define BR_FOV_4X 23 +#define BR_FOV_6X 11 + +void CBoltRifle::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_boltrifle"); + + m_iBulletType = BULLET_762MM; + m_iId = WEAPON_BOLTRIFLE; + m_iDefaultAmmo = AMMO_GIVE_BOLTRIFLE; + m_iClipSize = CLIP_BOLTRIFLE; + m_iAllowFire = TRUE; + + m_iState = BR_NOZOOM; + m_bReZoom = FALSE; + + SET_MODEL(ENT(pev),"models/w_boltrifle.mdl"); + + FallInit(); +} + +void CBoltRifle::Precache() +{ + PRECACHE_MODEL("models/v_boltrifle.mdl"); + PRECACHE_MODEL("models/p_boltrifle.mdl"); + PRECACHE_MODEL("models/w_boltrifle.mdl"); + + PRECACHE_MODEL("models/shells/shell_762mm_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_762mm_hi.mdl"); + + PRECACHE_SOUND("weapons/boltrifle_fire1.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/boltrifle.sc"); +} + +int CBoltRifle::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 2; + p->iFlags = 0; + + p->pszAmmo1 = "BoltRifle Ammo"; + p->iMaxAmmo1 = AMMO_MAX_BOLTRIFLE; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_BOLTRIFLE; + + p->iPosition = iItemSlot() - 1; + + p->iId = m_iId = WEAPON_BOLTRIFLE; + p->iWeight = WEIGHT_BOLTRIFLE; + + return 1; +} + +void CBoltRifle::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_iState; +} + +void CBoltRifle::UnpackWeapon(void *weapon_data) +{ +#ifdef CLIENT_DLL + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_iState = local_data->m_iWeaponState; +#endif +} + +float CBoltRifle::flGetTiltedDamage() +{ + float fRet = RANDOM_FLOAT(90,100); + + // 10% accuracy loss if unzoomed and/or moving + if(m_iState == BR_NOZOOM || m_pPlayer->pev->velocity != Vector(0,0,0)) + fRet *= 0.9f; + + return fRet; +} + +char *CBoltRifle::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_BOLTRIFLE_1 : DEATH_BOLTRIFLE_2; +} + +void CBoltRifle::ItemPostFrame() +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // complete the reload. + int j = min( iMaxClip(), m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + m_iClip = 0; + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; + +#ifndef CLIENT_DLL + m_pPlayer->TabulateAmmo(); +#endif + m_fInReload = FALSE; + } + + if(m_flNextPrimaryAttack <= UTIL_WeaponTimeBase() && m_bReZoom) + { +#ifdef CLIENT_DLL + ClientSway(); +#endif + + switch(m_iState) + { + // ZOOM ZOOM ZOOM! + case BR_ZOOM_2X: + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = BR_FOV_2X; + break; + case BR_ZOOM_4X: + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = BR_FOV_4X; + break; + case BR_ZOOM_6X: + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = BR_FOV_6X; + break; + } + + m_bReZoom = FALSE; + } + + CWasteWeapon::ItemPostFrame(); + + // Set allow fire mode + if(!(m_pPlayer->pev->button & IN_ATTACK)) + m_iAllowFire = TRUE; +} + +void CBoltRifle::PrimaryAttack() +{ + if(!m_iAllowFire) + return; + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if(m_iClip <= 0) + { + if(m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.4; + // SendWeaponAnim(BOLTRIFLE_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + } + return; + } + + float spread; + + // Accuracy modifier + if(m_iState == BR_NOZOOM || m_pPlayer->pev->velocity != Vector(0,0,0)) + spread = 0.08f; + else + spread = 0.0001f; + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + if(m_iState != BR_NOZOOM && m_iClip) + m_bReZoom = TRUE; + + if( m_iClip < 1 ) + m_iState = BR_NOZOOM; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; + + Vector Spread = Vector(spread,spread,spread); + + dir = m_pPlayer->FireBulletsPlayer(this,1, vecSrc, vecAiming, Spread, 8192,BULLET_762MM, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_BOLTRIFLE); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + dir.x, + dir.y, + (m_iClip == 0) ? 1 : 0, + 0, + 0, + 0); + + // get out of zoom + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; +#ifdef CLIENT_DLL + V_StopSway(); + V_DisableFade(); +#endif + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 2.4f; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); +} + +void CBoltRifle::SecondaryAttack() +{ + if(!m_iAllowFire || m_iState != BR_NOZOOM) + return; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + // Attack something + TraceResult tr; + + Vector vecSrc = m_pPlayer->GetGunPosition(); + Vector vecAiming = gpGlobals->v_forward; + Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } + + // hit + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + ClearMultiDamage( ); + + // first swing does full damage TODO FIX + pEntity->TraceAttack(m_pPlayer->pev, BOLTRIFLE_DMG_WHIP , gpGlobals->v_forward, &tr, DMG_CLUB|DMG_NEVERGIB ); + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + if (pEntity) + { + if ( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) + { + // play thwack or smack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod3.wav", 1, ATTN_NORM); break; + } + m_pPlayer->m_iWeaponVolume = 128; + } + + if( pEntity->IsPlayer() ) + { + // Kickback from rifle + float flZVel = pEntity->pev->velocity.z; + pEntity->pev->velocity = pEntity->pev->velocity + gpGlobals->v_forward * 225.0f; + pEntity->pev->velocity.z = flZVel; + } + } +#endif + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0, + 0.0, + 0, + 0, + 1, // Whip boolean + 0); + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 9 ); // how long till we do this again. + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.25; +} + +void CBoltRifle::SpecialMode() +{ + if(!m_iAllowFire || !m_iClip) + return; + + m_iState ++; + + if(m_iState > BR_ZOOM_6X) + { + m_iState = BR_NOZOOM; +#ifdef CLIENT_DLL + V_StopSway(); + V_DisableFade(); +#endif + } +#ifdef CLIENT_DLL + else + ClientSway(); +#endif + + switch(m_iState) + { + case BR_NOZOOM: + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; + break; + // ZOOM ZOOM ZOOM! + case BR_ZOOM_2X: + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = BR_FOV_2X; + break; + case BR_ZOOM_4X: + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = BR_FOV_4X; + break; + case BR_ZOOM_6X: + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = BR_FOV_6X; + break; + } + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.25f; + m_iAllowFire = FALSE; +} + +void CBoltRifle::ClientSway() +{ +#ifdef CLIENT_DLL + // Ducking + if(m_pPlayer->pev->flags & FL_DUCKING) + V_SetSway(1.0); + // On Ground + else if(m_pPlayer->pev->flags & FL_ONGROUND) + V_SetSway(2.25); + // Jumping + else + V_SetSway(4.5); +#endif +} + +void CBoltRifle::Reload() +{ + if(m_flNextPrimaryAttack > 0.0 || m_flNextSecondaryAttack > 0.0) + return; + + int iResult; + + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // player "reload" animation + m_pPlayer->SetAnimation( PLAYER_RELOAD ); + + if(m_iClip == 0) + iResult = DefaultReload(m_iClipSize,BOLTRIFLE_RELOAD_EMPTY,2.44f); + else + iResult = DefaultReload( m_iClipSize, BOLTRIFLE_RELOAD, 2.0f ); + + if(iResult) + { + if(m_iState != BR_NOZOOM) + { + m_flNextPrimaryAttack = m_flNextSecondaryAttack = m_pPlayer->m_flNextAttack; + m_bReZoom = TRUE; + } + +// m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.75f; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); + } +} + +BOOL CBoltRifle::Deploy() +{ + if(!DefaultDeploy("models/v_boltrifle.mdl","models/p_boltrifle.mdl",BOLTRIFLE_DRAW,"boltrifle")) + return FALSE; + + return CWasteWeapon::Deploy(); +} + +void CBoltRifle::Holster(int skiplocal) +{ + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; + m_iState = BR_NOZOOM; + +#ifdef CLIENT_DLL + V_DisableFade(); + V_StopSway(); +#endif + + CWasteWeapon::Holster(skiplocal); +} + +BOOL CBoltRifle::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +// +// Ammo Box +// +void CBoltRifleAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_boltrifle.mdl"); + CWasteAmmo::Spawn(); +} + +void CBoltRifleAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_boltrifle.mdl"); +} + +BOOL CBoltRifleAmmo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo(AMMO_GIVE_BOLTRIFLE,"BoltRifle Ammo",AMMO_MAX_BOLTRIFLE) != -1) + return TRUE; + return FALSE; +} + +/************* + Sten Gun - New meaning to Old Skewl +*************/ +enum sten_state_e { + STEN_SILENCER_ON = 0, + STEN_SILENCER_OFF, +}; + +#define STEN_CRITICAL_LEVEL 1.35 // Time in seconds a silenced sten can sustain full auto + +void CSten::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_sten"); + + m_iBulletType = BULLET_9MMP; + m_iId = WEAPON_STEN; + m_iDefaultAmmo = AMMO_GIVE_STEN; + m_iClipSize = CLIP_STEN; + + m_iState = STEN_SILENCER_OFF; + pev->fuser1 = 0.0f; + + m_iAllowFire = 1; + m_bOverheated = FALSE; + + SET_MODEL(ENT(pev),"models/w_sten.mdl"); + + FallInit(); +} + +void CSten::Precache() +{ + PRECACHE_MODEL("models/v_sten.mdl"); + PRECACHE_MODEL("models/p_sten.mdl"); + PRECACHE_MODEL("models/w_sten.mdl"); + + PRECACHE_MODEL("models/shells/shell_9x22mm_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_9x22mm_hi.mdl"); + + PRECACHE_SOUND("weapons/sten_nfire1.wav"); + PRECACHE_SOUND("weapons/sten_nfire2.wav"); + PRECACHE_SOUND("weapons/sten_sfire1.wav"); + PRECACHE_SOUND("weapons/sten_sfire2.wav"); + PRECACHE_SOUND("weapons/sten_overheat.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/sten.sc"); +} + +int CSten::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 2; +// p->iSlot = 4; + p->iFlags = 0; + + p->pszAmmo1 = "Sten Ammo"; + p->iMaxAmmo1 = AMMO_MAX_STEN; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_STEN; + + p->iPosition = iItemSlot() - 1; + + p->iId = m_iId = WEAPON_STEN; + p->iWeight = WEIGHT_STEN; + + return 1; +} + +void CSten::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_iState; + local_data->iuser3 = m_bOverheated; +} + +void CSten::UnpackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_iState = local_data->m_iWeaponState; + m_bOverheated = local_data->iuser3; +} + +float CSten::flGetTiltedDamage() +{ + float fRet = RANDOM_FLOAT(25,35); + + if( m_iState == STEN_SILENCER_ON ) + fRet *= 0.9f; + + return fRet; +} + +char *CSten::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_STEN_1 : DEATH_STEN_2; +} + +void CSten::ItemPostFrame() +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // complete the reload. + int j = min( iMaxClip(), m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + m_iClip = 0; + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; + +#ifndef CLIENT_DLL + m_pPlayer->TabulateAmmo(); +#endif + m_fInReload = FALSE; + } + + CWasteWeapon::ItemPostFrame(); + + if(pev->fuser1 <= UTIL_WeaponTimeBase()) + { + m_bOverheated = FALSE; + + // Set allow fire mode + if(!(m_pPlayer->pev->button & IN_ATTACK)) + m_iAllowFire = TRUE; + } +} + +void CSten::PrimaryAttack() +{ + if(!m_iAllowFire) + return; + + if(pev->fuser1 > UTIL_WeaponTimeBase() + STEN_CRITICAL_LEVEL && !m_bOverheated) + { + // TODO: perhaps a cool animation? :D + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = pev->fuser1 * 2; // Takes twice as long to cool off :) + m_bOverheated = TRUE; + m_iAllowFire = FALSE; + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0, + 0.0, + 0, + 0, + 0, + 1); // overheat sound + + return; + } + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if(m_iClip <= 0) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.4; + SendWeaponAnim((m_iState == STEN_SILENCER_ON) ? STEN_SHOOT_EMPTY_SILENCED : STEN_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + return; + } + + float spread = 0.075f; + float rof = 0.1f; + + // Silencer adds a very slight amount of accuracy + if(m_iState == STEN_SILENCER_ON) + spread *= 1.15; + + m_iClip--; + + if(m_iState == STEN_SILENCER_OFF) + { + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + pev->fuser1 += UTIL_WeaponTimeBase() + 0.09f; // unsilenced takes a bit longer + } + else + pev->fuser1 += UTIL_WeaponTimeBase() + 0.25f; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; + + Vector Spread; + + // Accuracy modifiers + if(m_pPlayer->pev->flags & FL_DUCKING) + Spread = Vector(spread*0.75,spread*0.75,spread*0.75); + else if(m_pPlayer->pev->flags & FL_ONGROUND) + Spread = Vector(spread,spread,spread); + else + Spread = Vector(spread*2.4,spread*2.4,spread*2.4); + + dir = m_pPlayer->FireBulletsPlayer(this,1, vecSrc, vecAiming, Spread, 8192, BULLET_9MMP, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_STEN); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + dir.x, + dir.y, + 0, + 0, + (m_iState == STEN_SILENCER_ON) ? 1 : 0, + 0); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + rof; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); +} + +void CSten::SecondaryAttack() +{ + m_iState = !m_iState; + + switch(m_iState) + { + case STEN_SILENCER_ON: +#ifdef CLIENT_DLL + CenterPrint("Adding Silencer"); +#endif + SendWeaponAnim(STEN_ADDSILENCER,UseDecrement() ? 1 : 0); + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 2.5f; + break; + case STEN_SILENCER_OFF: + if(!(pev->fuser1 > UTIL_WeaponTimeBase())) + { +#ifdef CLIENT_DLL + CenterPrint("Removing Silencer"); +#endif + SendWeaponAnim(STEN_REMSILENCER,UseDecrement() ? 1 : 0); + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 2.5f; + } + break; + } +} + +void CSten::Reload() +{ + if(m_flNextPrimaryAttack > 0.0 || m_flNextSecondaryAttack > 0.0) + return; + + int iResult; + + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // player "reload" animation + m_pPlayer->SetAnimation( PLAYER_RELOAD ); + + iResult = DefaultReload(m_iClipSize,(m_iState == STEN_SILENCER_ON) ? STEN_RELOAD_SILENCED : STEN_RELOAD, 3.15f ); + + if(iResult) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); +} + +BOOL CSten::Deploy() +{ + if(!DefaultDeploy("models/v_sten.mdl","models/p_sten.mdl",(m_iState == STEN_SILENCER_ON) ? STEN_DRAW_SILENCED : STEN_DRAW,"sten")) + return FALSE; + + return CWasteWeapon::Deploy(); +} + +BOOL CSten::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +// +// Ammo Box +// +void CStenAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_sten.mdl"); + CWasteAmmo::Spawn(); +} + +void CStenAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_sten.mdl"); +} + +BOOL CStenAmmo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo(AMMO_GIVE_STEN,"Sten Ammo",AMMO_MAX_STEN) != -1) + return TRUE; + return FALSE; +} diff --git a/dlls/wpn_shared/tw_explosives.cpp b/dlls/wpn_shared/tw_explosives.cpp new file mode 100644 index 0000000..4a39f9f --- /dev/null +++ b/dlls/wpn_shared/tw_explosives.cpp @@ -0,0 +1,970 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "player.h" +#include "soundent.h" +#include "thewastes.h" +#include "game.h" +#include "gamerules.h" + +// Entity Linkage +LINK_ENTITY_TO_CLASS(weapon_molotov,CMolotovCocktail); +LINK_ENTITY_TO_CLASS(weapon_fraggrenade,CFragGrenade); +LINK_ENTITY_TO_CLASS(weapon_pipebomb,CPipebomb); + +enum explosivetype_e { + ET_FRAG, + ET_PIPEBOMB, +}; + +#ifndef CLIENT_DLL + +// Baseline timed explosive class +class CThrownFrag : public CGrenade +{ +public: + void Spawn(); + + static CThrownFrag *ThrowFrag( unsigned int usEvent, entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time, char *pszExplosiveModel, float flDamage, float flResistance) + { + return ThrowExplosive( GetClassPtr((CThrownFrag*)NULL), usEvent, pevOwner, + vecStart, vecVelocity, time, + pszExplosiveModel, flDamage, flResistance ); + } + + static CThrownFrag *ThrowExplosive(CThrownFrag *PtrFrag, unsigned int usEvent, entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time, char *pszExplosiveModel, float flDamage, float flResistance); + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType,int PenetrationValue = 0) + { + CBaseEntity *pEnt = CBaseEntity::Instance( pevAttacker ); + + if( pEnt != NULL && pEnt->IsPlayer() ) + { + if( flDamage > 20 && m_pfnThink == ExpThink ) + { + SetThink( NULL ); + ExpDetonate(); + } + } + } + + // Callbacks + void EXPORT ExpThink( void ); + void EXPORT ExpDetonate( void ); + void EXPORT ExpUse(CBaseEntity *pActivator,CBaseEntity *pCaller,USE_TYPE useType,float value); +private: + unsigned int m_usFire1; +}; +LINK_ENTITY_TO_CLASS(frag_grenade,CThrownFrag); + +class CThrownPipeBomb : public CThrownFrag +{ +public: + void Spawn() + { + CThrownFrag::Spawn(); + pev->classname = MAKE_STRING("pipe_bomb"); + } + + static CThrownPipeBomb *ThrowPipeBomb( unsigned int usEvent, entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time, char *pszExplosiveModel, float flDamage, float flResistance) + { + return (CThrownPipeBomb*)ThrowExplosive( GetClassPtr((CThrownPipeBomb*)NULL), usEvent, pevOwner, + vecStart, vecVelocity, time, + pszExplosiveModel, flDamage, flResistance ); + } +}; +LINK_ENTITY_TO_CLASS(pipe_bomb,CThrownPipeBomb); + +#endif + +/************* + Posty's Liver Killer +*************/ +#ifndef CLIENT_DLL + +#define MOLOTOV_RADIUS 220 // Radius of effect +#define MOLOTOV_SMOKETIME 0.10f // Space of time between smoke puffs + +class CMolotov : public CGrenade +{ +public: + void Spawn(); + void Precache(); + + void EXPORT MolotovTouch(CBaseEntity *pOther); + void EXPORT DamageThink( void ); + void EXPORT SmokeThink( void ); + + static CMolotov *ShootMolotov(unsigned int usFireEvent,CBasePlayer *pPlayer,Vector vecStart,Vector vecVelocity); + + // Fire event + unsigned int m_usFire1; +private: + float m_flBurnTime; + + CBasePlayer *m_pPlayer; +}; +LINK_ENTITY_TO_CLASS(molotov,CMolotov); + +void CMolotov::Spawn() +{ + CGrenade::Spawn(); + + m_flBurnTime = 0.0f; + m_pPlayer = NULL; + + pev->classname = MAKE_STRING("molotov"); +} + +void CMolotov::Precache() +{ + PRECACHE_MODEL("models/chromegibs.mdl"); +} + +void CMolotov::MolotovTouch(CBaseEntity *pOther) +{ + TraceResult tr; + Vector vecSpot; // trace starts here! + + pev->enemy = pOther->edict(); + + vecSpot = pev->origin - pev->velocity.Normalize() * 32; + UTIL_TraceLine(vecSpot,vecSpot + pev->velocity.Normalize()*64,ignore_monsters,ENT(pev),&tr); + + // invisible + pev->model = iStringNull; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT; + + // Pull out of the wall a bit + if(tr.flFraction != 1.0) + pev->origin = tr.vecEndPos + (tr.vecPlaneNormal * (pev->dmg-24) * 0.6f); + + pev->owner = NULL; // can't traceline attack owner if set + pev->effects |= EF_NODRAW; + pev->velocity = g_vecZero; + pev->gravity = 0.0f; // Dont move + + SetTouch(NULL); + + int iContents = UTIL_PointContents(pev->origin); + + if(iContents != CONTENTS_WATER) + { + float flLifeTime = RANDOM_FLOAT(7.0f,10.0f); + + m_flBurnTime = gpGlobals->time + flLifeTime; + + // Initial damage (stronger) + ::RadiusDamage(pev->origin,pev,m_pPlayer->pev,110,MOLOTOV_RADIUS*1.25f,CLASS_NONE,DMG_BURN); + + // Radius Damage + SetThink(DamageThink); + pev->nextthink = gpGlobals->time + 0.25f; + + PLAYBACK_EVENT_FULL(FEV_GLOBAL, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&pev->origin, + (float*)&tr.vecPlaneNormal, + flLifeTime, + MOLOTOV_RADIUS, + 0, + 0, + 0, + 0); + } + else + { + // Molotovs dont do anything underwater. + UTIL_Remove(this); + } +} + +void CMolotov::SmokeThink() +{ + MESSAGE_BEGIN(MSG_PVS,SVC_TEMPENTITY,pev->origin); + WRITE_BYTE(TE_SMOKE); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z); + WRITE_SHORT(g_sModelIndexSmoke); + WRITE_BYTE((BYTE)0.20f); + WRITE_BYTE(30); // framerate + MESSAGE_END(); + + // run again + pev->nextthink = gpGlobals->time + MOLOTOV_SMOKETIME; +} + + +void CMolotov::DamageThink() +{ + // We have outlived our stay + if(gpGlobals->time >= m_flBurnTime) + UTIL_Remove(this); + + // Damage people as they come by + ::RadiusDamage(pev->origin,pev,m_pPlayer->pev,20,MOLOTOV_RADIUS,CLASS_NONE,DMG_BURN); + + // Do it again + pev->nextthink = gpGlobals->time + 0.45f; +} + +CMolotov *CMolotov::ShootMolotov(unsigned int usFireEvent,CBasePlayer *pPlayer,Vector vecStart,Vector vecVelocity) +{ + CMolotov *pRetMolotov = GetClassPtr((CMolotov*)NULL); + + pRetMolotov->m_usFire1 = usFireEvent; + + pRetMolotov->Spawn(); + // set the arc + pRetMolotov->pev->gravity = 0.7f; + + // Set our owner + pRetMolotov->m_pPlayer = pPlayer; + + UTIL_SetOrigin(pRetMolotov->pev,vecStart); + pRetMolotov->pev->velocity = vecVelocity; + pRetMolotov->pev->angles = UTIL_VecToAngles(pRetMolotov->pev->velocity); + pRetMolotov->pev->owner = ENT(pPlayer->pev); + + // Tumble + pRetMolotov->pev->avelocity.x = RANDOM_FLOAT(-100,-500); + + // Explode on contact + pRetMolotov->SetTouch(MolotovTouch); + pRetMolotov->pev->dmg = 50; + + // Smoke for us + pRetMolotov->SetThink(SmokeThink); + pRetMolotov->pev->nextthink = gpGlobals->time + MOLOTOV_SMOKETIME; + + SET_MODEL(ENT(pRetMolotov->pev),"models/w_molotov.mdl"); + + return pRetMolotov; +} + +#endif + +// Player molotov control +void CMolotovCocktail::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_molotov"); + m_iId = WEAPON_MOLOTOVCOCKTAIL; + + m_flStartAttack = -1.0f; + m_bJustThrown = FALSE; + + SET_MODEL(ENT(pev),"models/w_molotov.mdl"); + FallInit(); +} + +void CMolotovCocktail::Precache() +{ + PRECACHE_MODEL("models/v_molotov.mdl"); + PRECACHE_MODEL("models/p_molotov.mdl"); + PRECACHE_MODEL("models/w_molotov.mdl"); + + m_usFire1 = PRECACHE_EVENT(1,"events/molotovcocktail.sc"); + +#ifndef CLIENT_DLL + UTIL_PrecacheOther("molotov"); +#endif +} + +int CMolotovCocktail::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 4; + p->iPosition = iItemSlot() - 1; + p->iFlags = ITEM_FLAG_EXHAUSTIBLE|ITEM_FLAG_LIMITINWORLD; + + p->pszAmmo1 = "Molotov Cocktail"; + p->iMaxAmmo1 = AMMO_MAX_MOLOTOVCOCKTAIL; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + + p->iId = m_iId = WEAPON_MOLOTOVCOCKTAIL; + p->iWeight = WEIGHT_MOLOTOVCOCKTAIL; + + return 1; +} + +void CMolotovCocktail::AttachToPlayer(CBasePlayer *pPlayer) +{ + pPlayer->GiveAmmo(1,"Molotov Cocktail",AMMO_MAX_MOLOTOVCOCKTAIL); + + CWasteWeapon::AttachToPlayer(pPlayer); +} + +int CMolotovCocktail::AddDuplicate(CBasePlayerItem *pItem) +{ + CBasePlayerWeapon *pWeapon = (CBasePlayerWeapon*)pItem; + + if(pWeapon != NULL) + pWeapon->m_pPlayer->GiveAmmo(1,"Molotov Cocktail",AMMO_MAX_MOLOTOVCOCKTAIL); + + return 1; // We just wanted to add ammo +} + +// Not used +float CMolotovCocktail::flGetTiltedDamage() +{ + return 0; +} + +char *CMolotovCocktail::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_MOLOTOVCOCKTAIL_1 : DEATH_MOLOTOVCOCKTAIL_2; +} + +void CMolotovCocktail::ItemPostFrame() +{ + CWasteWeapon::ItemPostFrame(); + + if( m_flNextPrimaryAttack <= UTIL_WeaponTimeBase() && m_bJustThrown ) + { + m_bJustThrown = FALSE; + SendWeaponAnim( MOLOTOV_DRAW, UseDecrement() ? 1 : 0 ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1.0f; + } + + if(!(m_pPlayer->pev->button & IN_ATTACK)) + if(m_flStartAttack != -1.0f && m_flNextPrimaryAttack <= UTIL_WeaponTimeBase()) + ThrowMolotov(); +} + +void CMolotovCocktail::PrimaryAttack() +{ + if(m_flStartAttack != -1.0f) + return; + + SendWeaponAnim(MOLOTOV_LIGHT,UseDecrement() ? 1 : 0); + m_flStartAttack = gpGlobals->time; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1.0f; +} + +void CMolotovCocktail::ThrowMolotov() +{ + SendWeaponAnim(MOLOTOV_THROW,UseDecrement() ? 1 : 0); + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.50f; + m_flStartAttack = -1.0f; + m_bJustThrown = TRUE; + +#ifndef CLIENT_DLL + float flVelocity = min(900,(gpGlobals->time - m_flStartAttack) * 350); + + CMolotov::ShootMolotov(m_usFire1,m_pPlayer, + m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16, + gpGlobals->v_forward * flVelocity); + + if(m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + g_pGameRules->GetNextBestWeapon(m_pPlayer,this); + } +#endif +} + +BOOL CMolotovCocktail::Deploy() +{ + return DefaultDeploy("models/v_molotov.mdl","models/p_molotov.mdl",MOLOTOV_DRAW,"molotov"); +} + +BOOL CMolotovCocktail::CanHolster() +{ + // Can only holster if not ready to attack + return (m_flStartAttack == -1.0f); +} + +void CMolotovCocktail::Holster(int skiplocal) +{ + if( m_flStartAttack != -1.0f ) + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_flStartAttack = -1.0f; + +#ifndef CLIENT_DLL + CMolotov::ShootMolotov(m_usFire1,m_pPlayer,m_pPlayer->pev->origin,Vector(0,0,0)); +#endif + } + + if( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < 1 ) + { + // Remove this item + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1f; + } + + CWasteWeapon::Holster(skiplocal); +} + +/************* + Base Timed Explosive Weapon +*************/ +void CTimedExplosiveWeapon::Spawn() +{ + Precache(); + + m_bFusePrimed = FALSE; + m_bJustThrown = FALSE; +} + +void CTimedExplosiveWeapon::Precache() +{ +#ifndef CLIENT_DLL + UTIL_PrecacheOther( "timed_explosive" ); +#endif +} + +int CTimedExplosiveWeapon::GetItemInfo( ItemInfo *p ) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 4; + p->iPosition = iItemSlot() - 1; + p->iFlags = ITEM_FLAG_EXHAUSTIBLE|ITEM_FLAG_LIMITINWORLD; + + return 1; +} + +// Not used +float CTimedExplosiveWeapon::flGetTiltedDamage() +{ + return 0; +} + +void CTimedExplosiveWeapon::ItemPostFrame() +{ + // We held the grenade too long! time to blow up in our hand +#ifndef CLIENT_DLL + if(m_bFusePrimed && gpGlobals->time >= m_flStartAttack + m_flFuseTimer) + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_bFusePrimed = FALSE; + m_flStartAttack = -1.0f; + + if( m_iExplosiveType == ET_PIPEBOMB ) + { + CThrownPipeBomb::ThrowPipeBomb( m_usExplosive, m_pPlayer->pev,m_pPlayer->pev->origin,Vector(0,0,0),0.0f,m_pszExplosiveModel,m_flDamage,m_flResistance); + } + else if( m_iExplosiveType == ET_FRAG ) + { + CThrownFrag::ThrowFrag( m_usExplosive, m_pPlayer->pev,m_pPlayer->pev->origin,Vector(0,0,0),0.0f,m_pszExplosiveModel,m_flDamage,m_flResistance); + } + + if(m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + g_pGameRules->GetNextBestWeapon(m_pPlayer,this); + } + } +#endif + + if(!(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2))) + { + if(m_flStartAttack != -1.0f && m_flNextPrimaryAttack <= UTIL_WeaponTimeBase()) + { + if(!m_bFusePrimed) + ThrowExplosive(m_flFuseTimer); + else + ThrowExplosive(m_flFuseTimer - (gpGlobals->time - m_flStartAttack)); + } + } + + CWasteWeapon::ItemPostFrame(); +} + +void CTimedExplosiveWeapon::PrimaryAttack() +{ + if(m_flStartAttack != -1.0f) + return; + + m_flStartAttack = gpGlobals->time; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75f; +} + +void CTimedExplosiveWeapon::SecondaryAttack() +{ + if(m_flStartAttack != -1.0f) + return; + + m_bFusePrimed = TRUE; + + m_flStartAttack = gpGlobals->time; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0f; +} + +void CTimedExplosiveWeapon::ThrowExplosive( float flFuse ) +{ + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_bJustThrown = TRUE; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.75f; + m_bFusePrimed = FALSE; + m_flStartAttack = -1.0f; + +#ifndef CLIENT_DLL + float flVelocity = min(700,(gpGlobals->time - m_flStartAttack) * 250); + + if( m_iExplosiveType == ET_FRAG ) + { + CThrownFrag::ThrowFrag( m_usExplosive, m_pPlayer->pev, + m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16, + gpGlobals->v_forward * flVelocity, + flFuse,m_pszExplosiveModel,m_flDamage,m_flResistance ); + } + else if( m_iExplosiveType == ET_PIPEBOMB ) + { + CThrownPipeBomb::ThrowPipeBomb( m_usExplosive, m_pPlayer->pev, + m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16, + gpGlobals->v_forward * flVelocity, + flFuse,m_pszExplosiveModel,m_flDamage,m_flResistance ); + } + + if(m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + g_pGameRules->GetNextBestWeapon(m_pPlayer,this); + } +#endif +} + +BOOL CTimedExplosiveWeapon::CanHolster() +{ + // Can only holster if not ready to attack + return (m_flStartAttack == -1.0f); +} + +void CTimedExplosiveWeapon::Holster( int skiplocal ) +{ + if( m_flStartAttack != -1.0f ) + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + +#ifndef CLIENT_DLL + float flTime = (m_bFusePrimed == TRUE) ? m_flFuseTimer - (gpGlobals->time - m_flStartAttack) : m_flFuseTimer; + + if( m_iExplosiveType == ET_FRAG ) + { + CThrownFrag::ThrowFrag(m_usExplosive, m_pPlayer->pev, + m_pPlayer->pev->origin, + Vector(0,0,0), + flTime, + m_pszExplosiveModel,m_flDamage,m_flResistance); + } + else if( m_iExplosiveType == ET_PIPEBOMB ) + { + CThrownPipeBomb::ThrowPipeBomb(m_usExplosive, m_pPlayer->pev, + m_pPlayer->pev->origin, + Vector(0,0,0), + flTime, + m_pszExplosiveModel,m_flDamage,m_flResistance); + } +#endif + + m_flStartAttack = -1.0f; + } + + if( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < 1 ) + { + // Remove this item + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1f; + } + + CWasteWeapon::Holster(skiplocal); +} + +/************* + Frag Grenade +*************/ +#define GRENADE_FUSE 3.0f + +void CFragGrenade::Spawn() +{ + CTimedExplosiveWeapon::Spawn(); + + pev->classname = MAKE_STRING("weapon_fraggrenade"); + m_iId = WEAPON_FRAGGRENADE; + + m_iExplosiveType = ET_FRAG; + m_flFuseTimer = GRENADE_FUSE; + m_flDamage = 150.0f; + m_flResistance = 0.5f; + m_pszExplosiveModel = "models/w_grenade.mdl"; + m_flStartAttack = -1.0f; + + SET_MODEL(ENT(pev),"models/w_grenade.mdl"); + FallInit(); +} + +void CFragGrenade::Precache() +{ + PRECACHE_MODEL("models/v_grenade.mdl"); + PRECACHE_MODEL("models/p_grenade.mdl"); + PRECACHE_MODEL("models/w_grenade.mdl"); + + m_usExplosive = PRECACHE_EVENT(1,"events/fraggrenade.sc"); + + CTimedExplosiveWeapon::Precache(); +} + +int CFragGrenade::GetItemInfo(ItemInfo *p) +{ + p->pszAmmo1 = "FragGrenade"; + p->iMaxAmmo1 = AMMO_MAX_FRAGGRENADE; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + + p->iId = m_iId = WEAPON_FRAGGRENADE; + p->iWeight = WEIGHT_FRAGGRENADE; + + return CTimedExplosiveWeapon::GetItemInfo(p); +} + +void CFragGrenade::AttachToPlayer(CBasePlayer *pPlayer) +{ + pPlayer->GiveAmmo(1,"FragGrenade",AMMO_MAX_FRAGGRENADE); + + CWasteWeapon::AttachToPlayer(pPlayer); +} + +int CFragGrenade::AddDuplicate(CBasePlayerItem *pItem) +{ + CBasePlayerWeapon *pWeapon = (CBasePlayerWeapon*)pItem; + + if(pWeapon != NULL) + pWeapon->m_pPlayer->GiveAmmo(1,"FragGrenade",AMMO_MAX_FRAGGRENADE); + + return 1; // We just wanted to add ammo +} + +char *CFragGrenade::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_FRAGGRENADE_1 : DEATH_FRAGGRENADE_2; +} + +void CFragGrenade::PrimaryAttack() +{ + if( m_flStartAttack == -1.0f ) + SendWeaponAnim(FRAG_GOTOTHROW2,UseDecrement() ? 1 : 0); + + CTimedExplosiveWeapon::PrimaryAttack(); +} + +void CFragGrenade::SecondaryAttack() +{ + if( m_flStartAttack == -1.0f ) + SendWeaponAnim(FRAG_GOTOTHROW,UseDecrement() ? 1 : 0); + + CTimedExplosiveWeapon::SecondaryAttack(); +} + +void CFragGrenade::ItemPostFrame() +{ + if( m_flStartAttack == -1.0f && m_bJustThrown && + m_flNextPrimaryAttack <= UTIL_WeaponTimeBase() && + m_flNextSecondaryAttack <= UTIL_WeaponTimeBase() ) + { + SendWeaponAnim( FRAG_DRAW, UseDecrement() ? 1 : 0 ); + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0f; + m_bJustThrown = FALSE; + } + + CTimedExplosiveWeapon::ItemPostFrame(); +} + +void CFragGrenade::ThrowExplosive(float flFuse) +{ + CTimedExplosiveWeapon::ThrowExplosive( flFuse ); + SendWeaponAnim(FRAG_THROW,UseDecrement() ? 1 : 0); +} + +BOOL CFragGrenade::Deploy() +{ + return DefaultDeploy("models/v_grenade.mdl","models/p_grenade.mdl",FRAG_DRAW,"frag"); +} + +/************* + Pipebomb +*************/ +#define PIPEBOMB_FUSE 4.0f + +void CPipebomb::Spawn() +{ + CTimedExplosiveWeapon::Spawn(); + + pev->classname = MAKE_STRING("weapon_pipebomb"); + m_iId = WEAPON_PIPEBOMB; + + m_iExplosiveType = ET_PIPEBOMB; + m_flFuseTimer = PIPEBOMB_FUSE; + m_flDamage = 225.0f; + m_flResistance = 0.75f; + m_pszExplosiveModel = "models/w_pipebomb.mdl"; + m_flStartAttack = -1.0f; + + SET_MODEL(ENT(pev),"models/w_pipebomb.mdl"); + FallInit(); +} + +void CPipebomb::Precache() +{ + PRECACHE_MODEL("models/v_pipebomb.mdl"); + PRECACHE_MODEL("models/p_pipebomb.mdl"); + PRECACHE_MODEL("models/w_pipebomb.mdl"); + + m_usExplosive = PRECACHE_EVENT(1,"events/pipebomb.sc"); + + CTimedExplosiveWeapon::Precache(); +} + +int CPipebomb::GetItemInfo(ItemInfo *p) +{ + p->pszAmmo1 = "Pipebomb"; + p->iMaxAmmo1 = AMMO_MAX_PIPEBOMB; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + + p->iId = m_iId = WEAPON_PIPEBOMB; + p->iWeight = WEIGHT_PIPEBOMB; + + return CTimedExplosiveWeapon::GetItemInfo(p); +} + +void CPipebomb::AttachToPlayer(CBasePlayer *pPlayer) +{ + pPlayer->GiveAmmo(1,"Pipebomb",AMMO_MAX_PIPEBOMB); + + CWasteWeapon::AttachToPlayer(pPlayer); +} + +int CPipebomb::AddDuplicate(CBasePlayerItem *pItem) +{ + CBasePlayerWeapon *pWeapon = (CBasePlayerWeapon*)pItem; + + if(pWeapon != NULL) + pWeapon->m_pPlayer->GiveAmmo(1,"Pipebomb",AMMO_MAX_PIPEBOMB); + + return 1; // We just wanted to add ammo +} + +char *CPipebomb::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_PIPEBOMB_1 : DEATH_PIPEBOMB_2; +} + +void CPipebomb::PrimaryAttack() +{ + if( m_flStartAttack == -1.0f ) + SendWeaponAnim(PIPEBOMB_GOTOTHROW,UseDecrement() ? 1 : 0); + + CTimedExplosiveWeapon::PrimaryAttack(); +} + +void CPipebomb::SecondaryAttack() +{ + if( m_flStartAttack == -1.0f ) + SendWeaponAnim(PIPEBOMB_GOTOTHROW,UseDecrement() ? 1 : 0); + + CTimedExplosiveWeapon::SecondaryAttack(); +} + +void CPipebomb::ItemPostFrame() +{ + if( m_flStartAttack == -1.0f && m_bJustThrown && + m_flNextPrimaryAttack <= UTIL_WeaponTimeBase() && + m_flNextSecondaryAttack <= UTIL_WeaponTimeBase() ) + { + SendWeaponAnim( PIPEBOMB_DRAW, UseDecrement() ? 1 : 0 ); + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0f; + m_bJustThrown = FALSE; + } + + CTimedExplosiveWeapon::ItemPostFrame(); +} + +void CPipebomb::ThrowExplosive( float flFuse ) +{ + CTimedExplosiveWeapon::ThrowExplosive( flFuse ); + SendWeaponAnim(PIPEBOMB_THROW,UseDecrement() ? 1 : 0); +} + +BOOL CPipebomb::Deploy() +{ + return DefaultDeploy("models/v_pipebomb.mdl","models/p_pipebomb.mdl",PIPEBOMB_DRAW,"pipebomb"); +} + +/************* + Explosive Timed Objects + Used by frag grenade and pipebomb :) +*************/ +#ifndef CLIENT_DLL + +void CThrownFrag::Spawn() +{ + pev->classname = MAKE_STRING("frag_grenade"); + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); +} + +CThrownFrag *CThrownFrag::ThrowExplosive(CThrownFrag *PtrFrag, unsigned int usEvent,entvars_t *pevOwner,Vector vecStart,Vector vecVelocity,float time,char *pszExplosiveModel,float flDamage, float flResistance) +{ + CThrownFrag *pRetExplosive = PtrFrag; + + pRetExplosive->Spawn(); + + pRetExplosive->pev->dmg = flDamage; + pRetExplosive->m_usFire1 = usEvent; + + UTIL_SetOrigin(pRetExplosive->pev,vecStart); + + pRetExplosive->pev->velocity = vecVelocity; + pRetExplosive->pev->angles = UTIL_VecToAngles(pRetExplosive->pev->velocity); + pRetExplosive->pev->owner = ENT(pevOwner); + + pRetExplosive->SetTouch(BounceTouch); // Bounce if touched + pRetExplosive->SetUse(ExpUse); + + // Take one second off of the desired detonation time and set the think to PreDetonate. PreDetonate + // will insert a DANGER sound into the world sound list and delay detonation for one second so that + // the grenade explodes after the exact amount of time specified in the call to ShootTimed(). + + pRetExplosive->pev->dmgtime = gpGlobals->time + time; + + pRetExplosive->SetThink(ExpThink); + pRetExplosive->pev->nextthink = gpGlobals->time + 0.1; + + if(time < 0.1) + { + pRetExplosive->pev->nextthink = gpGlobals->time; + pRetExplosive->pev->velocity = Vector( 0, 0, 0 ); + } + + // Tumble through the air + pRetExplosive->pev->avelocity.x = -400; + + pRetExplosive->pev->gravity = flResistance; + pRetExplosive->pev->friction = 0.8; + + SET_MODEL(ENT(pRetExplosive->pev),pszExplosiveModel); + + return pRetExplosive; +} + +void CThrownFrag::ExpThink() +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->dmgtime - 1 < gpGlobals->time) + { + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1 ); + } + + if (pev->dmgtime <= gpGlobals->time) + { + SetThink( ExpDetonate ); + } + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + } +} + +void CThrownFrag::ExpDetonate() +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( tr.flFraction != 1.0 ) + { + pev->origin = tr.vecEndPos + (tr.vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + if(iContents == CONTENTS_WATER) + pev->dmg /= 2; // only half as effective underwater + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + entvars_t *pevOwner; + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + RadiusDamage ( pev, pevOwner, pev->dmg, CLASS_NONE, DMG_BLAST ); + + pev->effects |= EF_NODRAW; + SetThink( SUB_Remove ); + pev->velocity = g_vecZero; + pev->nextthink = gpGlobals->time + 0.3; + + PLAYBACK_EVENT_FULL(0, // no longer FEV_GLOBAL (doesnt matter if its outside PVS/PAS, unlike the molotovs) + ENT(pevOwner), + m_usFire1, + 0.0, + (float*)&pev->origin, + (float*)&tr.vecPlaneNormal, + 0, + 0, + (iContents == CONTENTS_WATER), + 0, + 0, + 0); +} + +void CThrownFrag::ExpUse(CBaseEntity *pActivator,CBaseEntity *pCaller,USE_TYPE useType,float value) +{ + if(pActivator->IsPlayer()) + { + // TODO: The player should pick up this grenade :) + } +} + +#endif \ No newline at end of file diff --git a/dlls/wpn_shared/tw_melee.cpp b/dlls/wpn_shared/tw_melee.cpp new file mode 100644 index 0000000..d6632c9 --- /dev/null +++ b/dlls/wpn_shared/tw_melee.cpp @@ -0,0 +1,1312 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "player.h" +#include "soundent.h" +#include "thewastes.h" +#include "game.h" +#include "gamerules.h" + +#ifdef CLIENT_DLL +void HUD_PlaySound(char *sound,float volume); +#endif + +// Entity Linkage +LINK_ENTITY_TO_CLASS(weapon_combatknife,CCombatKnife); +LINK_ENTITY_TO_CLASS(weapon_throwingknife,CThrowingKnife); +LINK_ENTITY_TO_CLASS(weapon_baseballbat,CBaseballBat); +LINK_ENTITY_TO_CLASS(weapon_sledgehammer,CSledgeHammer); +LINK_ENTITY_TO_CLASS(weapon_spear,CSpear); +LINK_ENTITY_TO_CLASS(weapon_cattleprod,CCattleProd); +LINK_ENTITY_TO_CLASS(weapon_katana,CKatana); + +// Used by throwing knives and the spears! +#ifndef CLIENT_DLL +class CThrownMelee : public CBaseMonster +{ +public: + void Spawn(); + void Precache(); + + static CThrownMelee *ThrowObject(CBasePlayer *pPlayer,Vector vecOrigin,Vector vecVelocity,float flDmg,char *pszModel,char *pszClassname); + + // Thinks + void EXPORT ThrownTouch(CBaseEntity *pOther); + void EXPORT ThrownThink(); +private: + CBasePlayer *m_pPlayer; + + char *m_pszGiveClassname; + float m_flDieTime; +}; +LINK_ENTITY_TO_CLASS(thrown_object,CThrownMelee); +#endif + +/************* + Melee weapons - Generic melee code +*************/ +void CMeleeWeapon::Spawn() +{ + Precache(); + + SET_MODEL(ENT(pev),pszGetWorldModel()); + + FallInit(); +} + +void CMeleeWeapon::Precache() +{ + PRECACHE_MODEL(pszGetViewModel()); + PRECACHE_MODEL(pszGetPlayerModel()); + PRECACHE_MODEL(pszGetWorldModel()); +} + +int CMeleeWeapon::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 0; + p->iPosition = iItemSlot() - 1; + p->iFlags = 0; + + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + + return 1; +} + +float CMeleeWeapon::flGetAngleDamage(float flDamage,CBaseEntity *pEntity) +{ +#ifndef CLIENT_DLL + // Dont calculate if we're not against a player + if(!pEntity->IsPlayer()) + return flDamage; + + // We calculate if the knife stab was from behind + // if so we double the initial damage. + float flDot = DotProduct(m_pPlayer->pev->angles,pEntity->pev->angles); + + // Make sure we're reasonably behind the other guy + if(flDot >= 0.707f) + flDamage *= 2; +#endif + + return flDamage; +} + +CBaseEntity *CMeleeWeapon::SwingWeapon( float flKickback ) +{ + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + // Attack something + TraceResult tr; + + Vector vecSrc = m_pPlayer->GetGunPosition(); + Vector vecAiming = gpGlobals->v_forward; + Vector vecEnd = vecSrc + gpGlobals->v_forward * flGetRange(); + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } + + // hit + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + ClearMultiDamage( ); + + pEntity->TraceAttack(m_pPlayer->pev, flGetTiltedDamage() , gpGlobals->v_forward, &tr, iGetDamageType() ); + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + int HitWorld = TRUE; + + if (pEntity) + { + if ( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) + { + PlayHitSound(); + HitWorld = FALSE; + } + + // Kickback from weapon + if( pEntity->IsPlayer() ) + { + float flZVel = pEntity->pev->velocity.z; + pEntity->pev->velocity = pEntity->pev->velocity + gpGlobals->v_forward * flKickback; + pEntity->pev->velocity.z = flZVel; + } + } + + if(HitWorld) + { + float fvol = TEXTURETYPE_PlaySound(&tr,vecSrc,vecSrc + (vecEnd-vecSrc)*2,0); + + EMIT_SOUND_DYN(ENT(m_pPlayer->pev),CHAN_ITEM,"weapons/knife_hitwall.wav",fvol,ATTN_NORM,0,98 + RANDOM_LONG(0,3)); + } + + return pEntity; +#else + return NULL; +#endif +} + +void CMeleeWeapon::PlayHitSound() +{ + // play thwack or smack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod3.wav", 1, ATTN_NORM); break; + } +} + +BOOL CMeleeWeapon::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +/************* + Thrown Object base code +*************/ +void CThrowingWeapon::Precache() +{ +#ifndef CLIENT_DLL + UTIL_PrecacheOther("thrown_object"); +#endif +} + +int CThrowingWeapon::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 0; + p->iPosition = iItemSlot() - 1; + p->iFlags = ITEM_FLAG_EXHAUSTIBLE; + + p->pszAmmo1 = pszGetAmmoName(); + p->iMaxAmmo1 = iGetMaxAmmo(); + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + + return 1; +} + +void CThrowingWeapon::AttachToPlayer(CBasePlayer *pPlayer) +{ + CWasteWeapon::AttachToPlayer(pPlayer); + + pPlayer->GiveAmmo(1,pszGetAmmoName(),iGetMaxAmmo()); +} + +int CThrowingWeapon::AddDuplicate(CBasePlayerItem *pItem) +{ + CBasePlayerWeapon *pWeapon = (CBasePlayerWeapon*)pItem; + + if(pWeapon != NULL) + pWeapon->m_pPlayer->GiveAmmo(1,pszGetAmmoName(),iGetMaxAmmo()); + + return 1; // We just wanted to add ammo +} + +void CThrowingWeapon::ItemPostFrame() +{ + CWasteWeapon::ItemPostFrame(); + + if(!(m_pPlayer->pev->button & IN_ATTACK)) + if(m_flStartAttack != -1.0f && m_flNextPrimaryAttack <= UTIL_WeaponTimeBase()) + ThrowObject(); +} + +BOOL CThrowingWeapon::CanHolster() +{ + // Can only holster if not ready to attack + return (m_flStartAttack == -1.0f); +} + +void CThrowingWeapon::Holster( int skiplocal ) +{ + if( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < 1 ) + { + // Remove this item + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1f; + } +} + +/************* + Combat Knife - Everyone's got a little rambo in them +*************/ +void CCombatKnife::Spawn() +{ + pev->classname = MAKE_STRING("weapon_combatknife"); + + m_iId = WEAPON_COMBATKNIFE; + m_bSecondaryAttack = FALSE; + + CMeleeWeapon::Spawn(); +} + +void CCombatKnife::Precache() +{ + CMeleeWeapon::Precache(); + + PRECACHE_SOUND("weapons/combatknife_slash1.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/combatknife.sc"); +} + +int CCombatKnife::GetItemInfo(ItemInfo *p) +{ + p->iId = m_iId = WEAPON_COMBATKNIFE; + p->iWeight = WEIGHT_COMBATKNIFE; + + return CMeleeWeapon::GetItemInfo(p); +} + +float CCombatKnife::flGetTiltedDamage() +{ + if(m_bSecondaryAttack) + return RANDOM_FLOAT(55,65); + else + return RANDOM_FLOAT(35,45); +} + +char *CCombatKnife::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_COMBATKNIFE_1 : DEATH_COMBATKNIFE_2; +} + +void CCombatKnife::PrimaryAttack() +{ + SwingWeapon(); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0,0.0, + 1, // Primary Attack + 0,0,0); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.65f; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); // how long till we do this again. +} + +void CCombatKnife::SecondaryAttack() +{ + m_bSecondaryAttack = TRUE; + + SwingWeapon(); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0,0.0, + 0, // Secondary Attack + 0,0,0); + + m_bSecondaryAttack = FALSE; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); // how long till we do this again. + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.15f; +} + +BOOL CCombatKnife::Deploy() +{ + int bRet = DefaultDeploy("models/v_combatknife.mdl","models/p_combatknife.mdl",COMBATKNIFE_DRAW,"combatknife"); + if(bRet) + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); + return bRet; +} + +void CCombatKnife::WeaponIdle() +{ + if(m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + switch(RANDOM_LONG(0,2)) + { + case 0: + SendWeaponAnim(COMBATKNIFE_IDLE1,UseDecrement() ? 1 : 0); + break; + case 1: + SendWeaponAnim(COMBATKNIFE_IDLE2,UseDecrement() ? 1 : 0); + break; + case 2: + SendWeaponAnim(COMBATKNIFE_IDLE3,UseDecrement() ? 1 : 0); + break; + } + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 9 ); // how long till we do this again. + ResetEmptySound(); +} + +/************* + Throwing Knife +*************/ +void CThrowingKnife::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_throwingknife"); + + m_iId = WEAPON_THROWINGKNIFE; + m_flStartAttack = -1.0f; + m_bJustThrown = FALSE; + + SET_MODEL(ENT(pev),"models/w_throwingknife.mdl"); + + FallInit(); +} + +void CThrowingKnife::Precache() +{ + PRECACHE_MODEL("models/v_throwingknife.mdl"); + PRECACHE_MODEL("models/p_throwingknife.mdl"); + PRECACHE_MODEL("models/w_throwingknife.mdl"); + + PRECACHE_SOUND("weapons/knife_throw.wav"); + + CThrowingWeapon::Precache(); +} + +int CThrowingKnife::GetItemInfo(ItemInfo *p) +{ + p->iId = WEAPON_THROWINGKNIFE; + p->iWeight = WEIGHT_THROWINGKNIFE; + + return CThrowingWeapon::GetItemInfo(p); +} + +float CThrowingKnife::flGetTiltedDamage() +{ + return RANDOM_FLOAT(55,65); +} + +char *CThrowingKnife::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_THROWINGKNIFE_1 : DEATH_THROWINGKNIFE_2; +} + +void CThrowingKnife::PrimaryAttack() +{ + if(m_flStartAttack != -1.0f) + return; + + SendWeaponAnim(TKNIFE_GOTOTHROW,UseDecrement() ? 1 : 0); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.25f; + +#ifndef CLIENT_DLL + m_flStartAttack = gpGlobals->time; +#else + m_flStartAttack = 1.0f; // Just so we dont twitch the animation +#endif +} + +void CThrowingKnife::ItemPostFrame() +{ + if( m_bJustThrown && m_flStartAttack == -1.0f && m_flNextPrimaryAttack <= UTIL_WeaponTimeBase() ) + { + SendWeaponAnim( TKNIFE_DRAW, UseDecrement() ? 1 : 0 ); + m_bJustThrown = FALSE; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.25f; + } + + CThrowingWeapon::ItemPostFrame(); +} + +void CThrowingKnife::ThrowObject() +{ + SendWeaponAnim(TKNIFE_THROW,UseDecrement() ? 1 : 0); + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.50f; + m_flStartAttack = -1.0f; + m_bJustThrown = TRUE; + +#ifndef CLIENT_DLL + float flVelocity = min(2300,(gpGlobals->time - m_flStartAttack) * 1800); + + CThrownMelee::ThrowObject(m_pPlayer,m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16, + gpGlobals->v_forward * flVelocity, + flGetTiltedDamage(),"models/w_throwingknife.mdl","weapon_throwingknife"); + + if(m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + g_pGameRules->GetNextBestWeapon(m_pPlayer,this); + } +#endif + +#ifdef CLIENT_DLL + HUD_PlaySound("weapons/knife_throw.wav",1.0f); +#endif +} + +BOOL CThrowingKnife::Deploy() +{ + int bRet = DefaultDeploy("models/v_throwingknife.mdl","models/p_throwingknife.mdl",TKNIFE_DRAW,"tknife"); + if(bRet) + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); + return bRet; +} + +void CThrowingKnife::WeaponIdle() +{ + if(m_flTimeWeaponIdle >= UTIL_WeaponTimeBase()) + return; + + switch(RANDOM_LONG(0,1)) + { + case 0: SendWeaponAnim(TKNIFE_IDLE1,UseDecrement() ? 1 : 0); break; + case 1: SendWeaponAnim(TKNIFE_IDLE2,UseDecrement() ? 1 : 0); break; + } + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); + ResetEmptySound(); +} + +/************* + Baseball bat +*************/ +void CBaseballBat::Spawn() +{ + pev->classname = MAKE_STRING("weapon_baseballbat"); + + m_iId = WEAPON_BASEBALLBAT; + + CMeleeWeapon::Spawn(); +} + +void CBaseballBat::Precache() +{ + CMeleeWeapon::Precache(); + + PRECACHE_SOUND("weapons/baseballbat_slash1.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/baseballbat.sc"); +} + +int CBaseballBat::GetItemInfo(ItemInfo *p) +{ + p->iId = m_iId = WEAPON_BASEBALLBAT; + p->iWeight = WEIGHT_BASEBALLBAT; + + return CMeleeWeapon::GetItemInfo(p); +} + +float CBaseballBat::flGetTiltedDamage() +{ + return RANDOM_FLOAT(55,65); +} + +char *CBaseballBat::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_BASEBALLBAT_1 : DEATH_BASEBALLBAT_2; +} + +void CBaseballBat::PrimaryAttack() +{ + SwingWeapon( 250.0f ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0,0.0, + 0, + 0,0,0); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.85f; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); // how long till we do this again. +} + +BOOL CBaseballBat::Deploy() +{ + int bRet = DefaultDeploy("models/v_baseballbat.mdl","models/p_baseballbat.mdl",BAT_DRAW,"baseballbat"); + if(bRet) + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); + return bRet; +} + +void CBaseballBat::WeaponIdle() +{ + if(m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 9 ); // how long till we do this again. + ResetEmptySound(); +} + +/************* + Sledge Hammer +*************/ +void CSledgeHammer::Spawn() +{ + pev->classname = MAKE_STRING("weapon_sledgehammer"); + + m_iId = WEAPON_SLEDGEHAMMER; + + CMeleeWeapon::Spawn(); +} + +void CSledgeHammer::Precache() +{ + CMeleeWeapon::Precache(); + + PRECACHE_SOUND("weapons/sledgehammer_slash1.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/sledgehammer.sc"); +} + +int CSledgeHammer::GetItemInfo(ItemInfo *p) +{ + p->iId = m_iId = WEAPON_SLEDGEHAMMER; + p->iWeight = WEIGHT_SLEDGEHAMMER; + + return CMeleeWeapon::GetItemInfo(p); +} + +float CSledgeHammer::flGetTiltedDamage() +{ + return RANDOM_FLOAT(65,75); +} + +char *CSledgeHammer::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_SLEDGEHAMMER_1 : DEATH_SLEDGEHAMMER_2; +} + +void CSledgeHammer::PrimaryAttack() +{ + SwingWeapon( 350.0f ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0,0.0, + 0, + 0,0,0); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0f; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); // how long till we do this again. +} + +BOOL CSledgeHammer::Deploy() +{ + int bRet = DefaultDeploy("models/v_sledge.mdl","models/p_sledge.mdl",SLEDGE_DRAW,"sledge"); + if(bRet) + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); + return bRet; +} + +void CSledgeHammer::WeaponIdle() +{ + if(m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 9 ); // how long till we do this again. + ResetEmptySound(); +} + +/************* + Katana +*************/ +#define KATANA_STATE_FAST 0 +#define KATANA_STATE_STRONG 1 + +void CKatana::Spawn() +{ + pev->classname = MAKE_STRING("weapon_katana"); + + m_iId = WEAPON_KATANA; + m_iState = KATANA_STATE_FAST; + + CMeleeWeapon::Spawn(); +} + +void CKatana::Precache() +{ + CMeleeWeapon::Precache(); + + PRECACHE_SOUND("weapons/katana_slash1.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/katana.sc"); +} + +int CKatana::GetItemInfo(ItemInfo *p) +{ + p->iId = m_iId = WEAPON_KATANA; + p->iWeight = WEIGHT_KATANA; + + return CMeleeWeapon::GetItemInfo(p); +} + +float CKatana::flGetTiltedDamage() +{ + if(m_iState == KATANA_STATE_FAST) + return RANDOM_FLOAT(55,65); + else + return RANDOM_FLOAT(65,75); +} + +void CKatana::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_iState; +} + +void CKatana::UnpackWeapon(void *weapon_data) +{ +#ifdef CLIENT_DLL + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_iState = local_data->m_iWeaponState; +#endif +} + +char *CKatana::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_KATANA_1 : DEATH_KATANA_2; +} + +void CKatana::PrimaryAttack() +{ + SwingWeapon(); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0,0.0, + (m_iState == KATANA_STATE_FAST) ? 1 : 0, + 0,0,0); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + (m_iState == KATANA_STATE_FAST) ? 0.75f : 1.15f; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); // how long till we do this again. +} + +void CKatana::SecondaryAttack() +{ + if(m_iState == KATANA_STATE_FAST) + SendWeaponAnim(KATANA_ST2_GOTO_ST1,UseDecrement() ? 1 : 0); + else + SendWeaponAnim(KATANA_ST1_GOTO_ST2,UseDecrement() ? 1 : 0); + + m_iState = !m_iState; + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.5f; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); // how long till we do this again. +} + +BOOL CKatana::Deploy() +{ + int bRet = DefaultDeploy("models/v_katana.mdl","models/p_katana.mdl",KATANA_DRAW,"katana"); + if(bRet) + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); + return bRet; +} + +void CKatana::Holster(int skiplocal) +{ + m_iState = KATANA_STATE_FAST; + + CMeleeWeapon::Holster(skiplocal); +} + +void CKatana::WeaponIdle() +{ + if(m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 9 ); // how long till we do this again. + ResetEmptySound(); +} + +float CKatana::flGetRange() +{ + if(m_iState == KATANA_STATE_FAST) + return 48.0f; + else + return 54.0f; +} + +/************* + Cattle Prod +*************/ +void CCattleProd::Spawn() +{ + pev->classname = MAKE_STRING("weapon_cattleprod"); + + m_iId = WEAPON_CATTLEPROD; + + CMeleeWeapon::Spawn(); +} + +void CCattleProd::Precache() +{ + CMeleeWeapon::Precache(); + + PRECACHE_SOUND("weapons/prod_hit.wav"); + PRECACHE_SOUND("weapons/prod_hitbod.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/cattleprod.sc"); +} + +int CCattleProd::GetItemInfo(ItemInfo *p) +{ + p->iId = m_iId = WEAPON_CATTLEPROD; + p->iWeight = WEIGHT_CATTLEPROD; + + return CMeleeWeapon::GetItemInfo(p); +} + +float CCattleProd::flGetTiltedDamage() +{ + return RANDOM_FLOAT(30,40); +} + +char *CCattleProd::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_CATTLEPROD_1 : DEATH_CATTLEPROD_2; +} + +void CCattleProd::PrimaryAttack() +{ + CBaseEntity *pEntity = SwingWeapon(); + + if(m_pPlayer->pev->waterlevel > 1) + { + // Quake 1 Lightning gun type effect if you use the prod underwater + RadiusDamage(m_pPlayer->pev->origin,pev,pev,5000,512,0,DMG_SHOCK|DMG_NEVERGIB); + } + + // Slow down the victim, making him easier to hit + if(pEntity != NULL && pEntity->IsPlayer()) + { + HALT_PLAYER(pEntity,15,-0.10f); + } + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0,0.0, + 0, + 0,0,0); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.15f; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); // how long till we do this again. +} + +BOOL CCattleProd::Deploy() +{ + int bRet = DefaultDeploy("models/v_cattleprod.mdl","models/p_cattleprod.mdl",PROD_DRAW,"cattleprod"); + if(bRet) + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); + return bRet; +} + +void CCattleProd::WeaponIdle() +{ + if(m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 9 ); // how long till we do this again. + ResetEmptySound(); +} + +float CCattleProd::flGetRange() +{ + return 54.0f; +} + +void CCattleProd::PlayHitSound() +{ + EMIT_SOUND(ENT(m_pPlayer->pev),CHAN_ITEM,"weapons/prod_hitbod.wav",1,ATTN_NORM); +} + +/************* + Spear +*************/ +void CSpear::Spawn() +{ + Precache(); + + SET_MODEL(ENT(pev),"models/w_spear.mdl"); + + FallInit(); + + pev->classname = MAKE_STRING("weapon_spear"); + + m_flStartAttack = -1.0f; + m_iId = WEAPON_SPEAR; + m_bJustThrown = FALSE; +} + +void CSpear::Precache() +{ + PRECACHE_MODEL("models/v_spear.mdl"); + PRECACHE_MODEL("models/p_spear.mdl"); + PRECACHE_MODEL("models/w_spear.mdl"); + + PRECACHE_SOUND("weapons/spear_slash1.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/spear.sc"); +} + +int CSpear::GetItemInfo(ItemInfo *p) +{ + p->iId = m_iId = WEAPON_SPEAR; + p->iWeight = WEIGHT_SPEAR; + + return CThrowingWeapon::GetItemInfo(p); +} + +float CSpear::flGetTiltedDamage() +{ + if( m_bJustThrown ) + return RANDOM_FLOAT( 80,90 ); + return RANDOM_FLOAT(55,65); +} + +char *CSpear::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_SPEAR_1 : DEATH_SPEAR_2; +} + +void CSpear::SwingWeapon() +{ + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_bJustThrown = FALSE; + +#ifndef CLIENT_DLL + // Attack something + TraceResult tr; + + Vector vecSrc = m_pPlayer->GetGunPosition(); + Vector vecAiming = gpGlobals->v_forward; + Vector vecEnd = vecSrc + gpGlobals->v_forward * 48.0f; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } + + // hit + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + ClearMultiDamage( ); + + pEntity->TraceAttack(m_pPlayer->pev, flGetTiltedDamage() , gpGlobals->v_forward, &tr, DMG_CLUB|DMG_NEVERGIB ); + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + int HitWorld = TRUE; + + if (pEntity) + { + if ( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) + { + // play thwack or smack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod3.wav", 1, ATTN_NORM); break; + } + + HitWorld = FALSE; + } + } + + if(HitWorld) + { + float fvol = TEXTURETYPE_PlaySound(&tr,vecSrc,vecSrc + (vecEnd-vecSrc)*2,0); + + EMIT_SOUND_DYN(ENT(m_pPlayer->pev),CHAN_ITEM,"weapons/knife_hitwall.wav",fvol,ATTN_NORM,0,98 + RANDOM_LONG(0,3)); + } +#endif +} + +void CSpear::ThrowObject() +{ + SendWeaponAnim(SPEAR_THROW,UseDecrement() ? 1 : 0); + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_bJustThrown = TRUE; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.50f; + m_flStartAttack = -1.0f; + +#ifdef CLIENT_DLL + HUD_PlaySound("weapons/spear_throw.wav",1.0f); +#endif + +#ifndef CLIENT_DLL + float flVelocity = min(1500,(gpGlobals->time - m_flStartAttack) * 900); + + CThrownMelee *pMelee = CThrownMelee::ThrowObject(m_pPlayer,m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16, + gpGlobals->v_forward * flVelocity, + flGetTiltedDamage(),"models/w_spear.mdl","weapon_spear"); + + pMelee->pev->avelocity.x = 0.0f; + pMelee->pev->gravity = 0.7f; + + if(m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + RetireWeapon(); + } +#endif +} + +void CSpear::PrimaryAttack() +{ + if(m_flStartAttack != -1.0f) + return; + + SwingWeapon(); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0,0.0, + 0, + 0,0,0); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.85f; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); // how long till we do this again. +} + +void CSpear::SecondaryAttack() +{ + if(m_flStartAttack != -1.0f) + return; + + SendWeaponAnim(SPEAR_GOTO_THROW,UseDecrement() ? 1 : 0); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5f; + m_flStartAttack = gpGlobals->time; +} + +void CSpear::ItemPostFrame() +{ + if( m_bJustThrown && m_flStartAttack == -1.0f && + m_flNextPrimaryAttack <= UTIL_WeaponTimeBase() && + m_flNextSecondaryAttack <= UTIL_WeaponTimeBase() ) + { + SendWeaponAnim( SPEAR_DRAW, UseDecrement() ? 1 : 0 ); + m_bJustThrown = FALSE; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0f; + } + + if(!(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2))) + if(m_flStartAttack != -1.0f && m_flNextPrimaryAttack <= UTIL_WeaponTimeBase()) + ThrowObject(); + + CWasteWeapon::ItemPostFrame(); +} + +BOOL CSpear::Deploy() +{ + int bRet = DefaultDeploy("models/v_spear.mdl","models/p_spear.mdl",SPEAR_DRAW,"spear"); + if(bRet) + m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed,5,9); + return bRet; +} + +void CSpear::WeaponIdle() +{ + if(m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + switch(RANDOM_LONG(0,1)) + { + case 0: SendWeaponAnim(SPEAR_IDLE1,UseDecrement() ? 1 : 0); break; + case 1: SendWeaponAnim(SPEAR_IDLE2,UseDecrement() ? 1 : 0); break; + } + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 9 ); // how long till we do this again. + ResetEmptySound(); +} + +// +// Thrown Object code +// +#ifndef CLIENT_DLL + +void CThrownMelee::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("thrown_object"); + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_BBOX; + + UTIL_SetSize(pev,Vector(0,0,0),Vector(0,0,0)); +} + +void CThrownMelee::Precache() +{ + PRECACHE_SOUND("weapons/knife_hitwall.wav"); + PRECACHE_SOUND("weapons/knife_hitbod1.wav"); + PRECACHE_SOUND("weapons/knife_hitbod2.wav"); +} + +CThrownMelee *CThrownMelee::ThrowObject(CBasePlayer *pPlayer,Vector vecOrigin,Vector vecVelocity,float flDmg,char *pszModel,char *pszGiveClass) +{ + CThrownMelee *pRetObject = GetClassPtr((CThrownMelee*)NULL); + + pRetObject->Spawn(); + + // aerodynamic + pRetObject->pev->gravity = 0.3f; + + pRetObject->m_pPlayer = pPlayer; + UTIL_SetOrigin(pRetObject->pev,vecOrigin); + + pRetObject->pev->velocity = vecVelocity; + pRetObject->pev->angles = UTIL_VecToAngles(pRetObject->pev->velocity); + pRetObject->pev->owner = ENT(pPlayer->pev); + + // Tumble - OMG IM GETTING DIZZY + pRetObject->pev->avelocity.x = RANDOM_FLOAT(-1800,-2000); + + pRetObject->SetTouch(ThrownTouch); + pRetObject->pev->dmg = flDmg; + + SET_MODEL(ENT(pRetObject->pev),pszModel); + + pRetObject->m_pszGiveClassname = pszGiveClass; + + return pRetObject; +} + +void CThrownMelee::ThrownTouch(CBaseEntity *pOther) +{ + // If we hit another throwing knife, dont run + // touch code, or else we will levitate in air! + // Instead lets just fall to the ground + if(FClassnameIs(pOther->pev,STRING(pev->classname))) + { + SetTouch(NULL); + SetThink(ThrownThink); + + pev->gravity = 1.0f; + pev->avelocity.x = 0.0f; + pev->nextthink = gpGlobals->time + 0.15f; + + m_flDieTime = gpGlobals->time + 30.0f; + + EMIT_SOUND(ENT(m_pPlayer->pev),CHAN_AUTO,"weapons/knife_hitwall.wav",1,ATTN_NORM); + + // Sparks against the other knife + MESSAGE_BEGIN(MSG_PAS,SVC_TEMPENTITY,pev->origin); + WRITE_BYTE(TE_SPARKS); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z); + MESSAGE_END(); + + // If the other knife has his touch function set, run it... + // This should cause an action movie style feature of knifes + // hitting each other :D If the knife is on a wall, it wont work + // since its touch function is disabled. + pOther->Touch(this); + +#if 0 + ClientPrint(m_pPlayer->pev,HUD_PRINTCONSOLE,"Projectile Hit another object of the same class\n"); +#endif + return; + } + + // do damage + pOther->TakeDamage(pev,VARS(pev->owner),pev->dmg,DMG_SLASH|DMG_NEVERGIB); + + // If we hit a player add ourselves to the + // Inventory, otherwise just chill. + if(pOther->IsPlayer()) + { + // Make sure if we somehow hit our owner he doesnt scream like a girl :) + if(pOther != m_pPlayer) + EMIT_SOUND(ENT(pOther->pev),CHAN_AUTO,RANDOM_LONG(0,1) ? "weapons/knife_hitbod1.wav" : "weapons/knife_hitbod2.wav",1,ATTN_NORM); + + ((CBasePlayer*)pOther)->GiveNamedItem(m_pszGiveClassname); + UTIL_Remove(this); + } + else + { + SetTouch(NULL); + SetThink(ThrownThink); + + pev->gravity = 1.0f; + pev->nextthink = gpGlobals->time + 0.15f; + pev->avelocity.x = 0.0f; + + m_flDieTime = gpGlobals->time + 30.0f; + + EMIT_SOUND(ENT(pev),CHAN_AUTO,"weapons/knife_hitwall.wav",1,ATTN_NORM); + + if(!FStrEq(STRING(pOther->pev->classname),"func_breakable")) + { + pev->angles = UTIL_VecToAngles(pev->velocity.Normalize()); + pev->movetype = MOVETYPE_NONE; + pev->velocity = g_vecZero; + + // Sparks on wall from hit location + MESSAGE_BEGIN(MSG_PAS,SVC_TEMPENTITY,pev->origin); + WRITE_BYTE(TE_SPARKS); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z); + MESSAGE_END(); + } + else + pev->velocity = g_vecZero; + } +} + +void CThrownMelee::ThrownThink() +{ + CBaseEntity *pEntity = NULL; + + // See if someone is able to pick us up + while((pEntity = UTIL_FindEntityInSphere(pEntity,pev->origin,16)) != NULL) + { + if(pEntity->IsPlayer()) + { + ((CBasePlayer*)pEntity)->GiveNamedItem(m_pszGiveClassname); + UTIL_Remove(this); + } + } + + // Think again + pev->nextthink = gpGlobals->time + 0.15f; + + // We shouldnt exist anymore + if(gpGlobals->time >= m_flDieTime) + UTIL_Remove(this); +} + +#endif \ No newline at end of file diff --git a/dlls/wpn_shared/tw_shotguns.cpp b/dlls/wpn_shared/tw_shotguns.cpp new file mode 100644 index 0000000..0911a84 --- /dev/null +++ b/dlls/wpn_shared/tw_shotguns.cpp @@ -0,0 +1,1066 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "player.h" +#include "soundent.h" +#include "thewastes.h" +#include "game.h" +#include "gamerules.h" + +// Entity Linkage +LINK_ENTITY_TO_CLASS(ammo_buckshot,CShotgunAmmo); +LINK_ENTITY_TO_CLASS(ammo_winchester,CWinchesterAmmo); +LINK_ENTITY_TO_CLASS(ammo_jackhammer,CJackHammerAmmo); + +LINK_ENTITY_TO_CLASS(weapon_sawedoff,CSawedOff); +LINK_ENTITY_TO_CLASS(weapon_mossberg,CMossberg); +LINK_ENTITY_TO_CLASS(weapon_winchester,CWinchester); +LINK_ENTITY_TO_CLASS(weapon_jackhammer,CJackHammer); + +#define SHOTGUN_SHOOT_STATE 1 +#define SHOTGUN_RELOAD_STATE 2 + +enum reload_state_e +{ + RELOAD_NONE, + RELOAD_START, + RELOAD_END, +}; + +/************* + Shotgun +*************/ +int CShotgun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 2; + p->iFlags = 0; + + p->pszAmmo1 = "Buckshot"; + p->iMaxAmmo1 = AMMO_MAX_BUCKSHOT; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + + return 1; +} + +void CShotgun::ItemPostFrame() +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // complete the reload. + int j = min( 1, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; +#ifndef CLIENT_DLL + m_pPlayer->TabulateAmmo(); +#endif + m_fInReload = FALSE; + } + + CWasteWeapon::ItemPostFrame(); + + // Set allow fire mode + if(!(m_pPlayer->pev->button & IN_ATTACK)) + m_iAllowFire = TRUE; + + // Handle reloads if needed +//#ifndef CLIENT_DLL + if(m_iState == SHOTGUN_RELOAD_STATE && m_flNextPrimaryAttack <= UTIL_WeaponTimeBase()) + { + switch(m_iReloadState) + { + case RELOAD_START: + Reload(); + + // User must hold down button to reload. + if(!(m_pPlayer->pev->button & IN_ATTACK)) + m_iReloadState = RELOAD_END; + break; + case RELOAD_END: + Reload(); + break; + } + } +//#endif +} + +void CShotgun::WeaponIdle() +{ + ResetEmptySound(); +} + +void CShotgun::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_iState; +} + +void CShotgun::UnpackWeapon(void *weapon_data) +{ +#ifdef CLIENT_DLL + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_iState = local_data->m_iWeaponState; +#endif +} + +// +// Ammo Box +// +void CShotgunAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_buckshot.mdl"); + CWasteAmmo::Spawn(); +} + +void CShotgunAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_buckshot.mdl"); +} + +BOOL CShotgunAmmo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo( AMMO_GIVE_BUCKSHOT, "Buckshot", AMMO_MAX_BUCKSHOT) != -1) + return TRUE; + return FALSE; +} + +/************* + Mossberg 12 Gauge +*************/ +void CMossberg::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_mossberg"); + + m_iBulletType = BULLET_12GAUGE; + m_iId = WEAPON_MOSSBERG; + m_iDefaultAmmo = AMMO_GIVE_BUCKSHOT; + m_iClipSize = CLIP_MOSSBERG; + + m_iAllowFire = TRUE; + m_iState = SHOTGUN_SHOOT_STATE; + m_iReloadState = RELOAD_NONE; + + SET_MODEL(ENT(pev),"models/w_mossberg.mdl"); + + FallInit(); +} + +void CMossberg::Precache() +{ + PRECACHE_MODEL("models/v_mossberg.mdl"); + PRECACHE_MODEL("models/p_mossberg.mdl"); + PRECACHE_MODEL("models/w_mossberg.mdl"); + + PRECACHE_MODEL("models/shells/shell_buckshot_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_buckshot_hi.mdl"); + + PRECACHE_SOUND("weapons/mossberg_draw.wav"); + PRECACHE_SOUND("weapons/mossberg_fire_empty.wav"); + PRECACHE_SOUND("weapons/mossberg_fire1.wav"); + PRECACHE_SOUND("weapons/mossberg_fire2.wav"); + PRECACHE_SOUND("weapons/mossberg_holster.wav"); + PRECACHE_SOUND("weapons/mossberg_pump.wav"); + PRECACHE_SOUND("weapons/mossberg_reload.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/mossberg.sc"); +} + +int CMossberg::GetItemInfo(ItemInfo *p) +{ + p->iMaxClip = CLIP_MOSSBERG; + + p->iPosition = 0; + + p->iId = m_iId = WEAPON_MOSSBERG; + p->iWeight = WEIGHT_MOSSBERG; + + return CShotgun::GetItemInfo(p); +} + +float CMossberg::flGetTiltedDamage() +{ + return RANDOM_FLOAT(14,19); +} + +char *CMossberg::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_MOSSBERG_1 : DEATH_MOSSBERG_2; +} + +void CMossberg::PrimaryAttack() +{ + if(!m_iAllowFire || m_iState == SHOTGUN_RELOAD_STATE) + return; + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if(!m_iClip) + { + PlayEmptySound(); + SendWeaponAnim(MOSSBERG_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.3; + return; + } + + int random_long = UTIL_SharedRandomLong(m_pPlayer->random_seed,0,1); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecDir; + + Vector vecSpread = Vector(MOSSBERG_SPREAD,MOSSBERG_SPREAD,MOSSBERG_SPREAD); + + vecDir = m_pPlayer->FireBulletsPlayer(this,MOSSBERG_SHOTCOUNT, vecSrc, vecAiming, vecSpread, 1280, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + MOSSBERG_SPREAD, + MOSSBERG_SPREAD, + MOSSBERG_SHOTCOUNT*2, + 1, // Shotgun blast + 0, + random_long); + + m_iAllowFire = FALSE; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 2, 4 ); // how long till we do this again. + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75f; +} + +void CMossberg::SecondaryAttack() +{ + if(!m_iAllowFire || m_iState == SHOTGUN_RELOAD_STATE) + return; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + // Attack something + TraceResult tr; + + Vector vecSrc = m_pPlayer->GetGunPosition(); + Vector vecAiming = gpGlobals->v_forward; + Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } + + // hit + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + ClearMultiDamage( ); + + // first swing does full damage TODO FIX + pEntity->TraceAttack(m_pPlayer->pev, MOSSBERG_DMG_WHIP , gpGlobals->v_forward, &tr, DMG_CLUB|DMG_NEVERGIB ); + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + if (pEntity) + { + if ( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) + { + // play thwack or smack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod3.wav", 1, ATTN_NORM); break; + } + m_pPlayer->m_iWeaponVolume = 128; + } + + // Kickback from stock + if( pEntity->IsPlayer() ) + { + float flZVel = pEntity->pev->velocity.z; + pEntity->pev->velocity = pEntity->pev->velocity + gpGlobals->v_forward * 225.0f; + pEntity->pev->velocity.z = flZVel; + } + } + + +#endif + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0, + 0.0, + 0, + 0, // Stock whip + 0, + 0); + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 9 ); // how long till we do this again. + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.25; +} + +void CMossberg::Reload() +{ + if(m_flNextPrimaryAttack > UTIL_WeaponTimeBase() || m_flNextSecondaryAttack > UTIL_WeaponTimeBase()) + return; + + if(m_iState != SHOTGUN_RELOAD_STATE && (m_iClip == m_iClipSize || m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0)) + return; + + if(m_iState != SHOTGUN_RELOAD_STATE) + { + m_iState = SHOTGUN_RELOAD_STATE; + m_iReloadState = RELOAD_NONE; + } + + // if we're full, just go to the end. + if(m_iClipSize == m_iClip) + m_iReloadState = RELOAD_END; + + switch(m_iReloadState) + { + case RELOAD_NONE: + SendWeaponAnim( MOSSBERG_GOTO_RELOAD, UseDecrement() ? 1 : 0 ); + + m_iReloadState = RELOAD_START; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.6; + + break; + case RELOAD_START: + if(DefaultReload( m_iClipSize,MOSSBERG_RELOAD, 0.45 )) + { + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.565; + EMIT_SOUND(ENT(m_pPlayer->pev),CHAN_WEAPON,"weapons/mossberg_reload.wav",0.8,ATTN_NORM); + + break; + } + case RELOAD_END: + m_iState = SHOTGUN_SHOOT_STATE; + m_iReloadState = RELOAD_NONE; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.825; + SendWeaponAnim( MOSSBERG_END_RELOAD, UseDecrement() ? 1 : 0 ); + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 4,5 ); // how long till we do this again.break; + + break; + } +} + +BOOL CMossberg::Deploy() +{ + if(!DefaultDeploy("models/v_mossberg.mdl","models/p_mossberg.mdl",MOSSBERG_DRAW,"mossberg")) + return FALSE; + + return CShotgun::Deploy(); +} + +BOOL CMossberg::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CMossberg::WeaponIdle() +{ + CShotgun::WeaponIdle(); + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() || m_iState != SHOTGUN_SHOOT_STATE) + return; + + SendWeaponAnim( (RANDOM_LONG(0,1) == 0)? MOSSBERG_IDLE2 : MOSSBERG_IDLE3, UseDecrement() ? 1 : 0 ); + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 9 ); // how long till we do this again. +} + +void CMossberg::Holster(int skiplocal) +{ + CShotgun::Holster(skiplocal); +} + +/************* + Winchester +*************/ +void CWinchester::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_winchester"); + + m_iBulletType = BULLET_SLUG; + m_iId = WEAPON_WINCHESTER; + m_iDefaultAmmo = AMMO_GIVE_BUCKSHOT; + m_iClipSize = CLIP_WINCHESTER; + + m_iState = SHOTGUN_SHOOT_STATE; + m_iReloadState = RELOAD_NONE; + + SET_MODEL(ENT(pev),"models/w_winchester.mdl"); + + FallInit(); +} + +void CWinchester::Precache() +{ + PRECACHE_MODEL("models/v_winchester.mdl"); + PRECACHE_MODEL("models/p_winchester.mdl"); + PRECACHE_MODEL("models/w_winchester.mdl"); + + PRECACHE_MODEL("models/shells/shell_slug_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_slug_hi.mdl"); + + PRECACHE_SOUND("weapons/winchester_draw.wav"); + PRECACHE_SOUND("weapons/winchester_fire_empty.wav"); + PRECACHE_SOUND("weapons/winchester_fire1.wav"); + PRECACHE_SOUND("weapons/winchester_fire2.wav"); + PRECACHE_SOUND("weapons/winchester_pump.wav"); + PRECACHE_SOUND("weapons/winchester_reload.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/winchester.sc"); +} + +int CWinchester::GetItemInfo(ItemInfo *p) +{ + p->iMaxClip = CLIP_WINCHESTER; + + p->iPosition = 1; + + p->iId = m_iId = WEAPON_WINCHESTER; + p->iWeight = WEIGHT_WINCHESTER; + + p->pszName = STRING(pev->classname); + p->iSlot = 2; + p->iFlags = 0; + + p->pszAmmo1 = "WinchesterAmmo"; + p->iMaxAmmo1 = AMMO_MAX_WINCHESTER; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + + return 1; +} + +float CWinchester::flGetTiltedDamage() +{ + return RANDOM_FLOAT(75,85); +} + +char *CWinchester::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_WINCHESTER_1 : DEATH_WINCHESTER_2; +} + + +void CWinchester::PrimaryAttack() +{ + if(!m_iAllowFire || m_iState == SHOTGUN_RELOAD_STATE) + return; + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if(!m_iClip) + { + PlayEmptySound(); +// SendWeaponAnim(WINNY_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.3; + return; + } + + int iAnimations = UTIL_SharedRandomLong(m_pPlayer->random_seed,0,1); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1.0f; + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecDir; + + Vector vecSpread = Vector(WINCHESTER_SPREAD,WINCHESTER_SPREAD,WINCHESTER_SPREAD); + + vecDir = m_pPlayer->FireBulletsPlayer(this,1, vecSrc, vecAiming, vecSpread, 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_WINCHESTER ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + vecDir.x, + vecDir.y, + iAnimations, + 0, + 0, + 0); + + m_iAllowFire = FALSE; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 2, 4 ); // how long till we do this again. +} + +void CWinchester::SecondaryAttack() +{ +} + +void CWinchester::Reload() +{ + if(m_flNextPrimaryAttack > UTIL_WeaponTimeBase() || m_flNextSecondaryAttack > UTIL_WeaponTimeBase()) + return; + + if(m_iState != SHOTGUN_RELOAD_STATE && (m_iClip == m_iClipSize || m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0)) + return; + + if(m_iState != SHOTGUN_RELOAD_STATE) + { + m_iState = SHOTGUN_RELOAD_STATE; + m_iReloadState = RELOAD_NONE; + } + + // if we're full, just go to the end. + if(m_iClipSize == m_iClip) + m_iReloadState = RELOAD_END; + + switch(m_iReloadState) + { + case RELOAD_NONE: + SendWeaponAnim( WINNY_RELOAD_START, UseDecrement() ? 1 : 0 ); + m_iReloadState = RELOAD_START; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.7; + break; + case RELOAD_START: + if(DefaultReload( m_iClipSize,WINNY_RELOAD_MIDDLE, 0.525 )) + { + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.525; + EMIT_SOUND(ENT(m_pPlayer->pev),CHAN_WEAPON,"weapons/winchester_reload.wav",0.8,ATTN_NORM); + break; + } + case RELOAD_END: + m_iState = SHOTGUN_SHOOT_STATE; + m_iReloadState = RELOAD_NONE; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.825; + SendWeaponAnim( WINNY_RELOAD_END, UseDecrement() ? 1 : 0 ); + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 4,5 ); // how long till we do this again.break; + + break; + } +} + +BOOL CWinchester::Deploy() +{ + if(!DefaultDeploy("models/v_winchester.mdl","models/p_winchester.mdl",WINNY_DRAW,"winchester")) + return FALSE; + + return CShotgun::Deploy(); +} + +BOOL CWinchester::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +// +// Ammo Box +// +void CWinchesterAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_winchester.mdl"); + CWasteAmmo::Spawn(); +} + +void CWinchesterAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_winchester.mdl"); +} + +BOOL CWinchesterAmmo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo( AMMO_GIVE_WINCHESTER, "WinchesterAmmo", AMMO_MAX_WINCHESTER) != -1) + return TRUE; + return FALSE; +} + +/************* + Sawed Off Shotgun (Mad Max Babey!) +*************/ +void CSawedOff::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_sawedoff"); + + m_iBulletType = BULLET_20GAUGE; + m_iId = WEAPON_SAWEDOFF; + m_iDefaultAmmo = AMMO_GIVE_BUCKSHOT; + m_iClipSize = CLIP_SAWEDOFF; + m_iAllowFire = TRUE; + + SET_MODEL(ENT(pev),"models/w_sawedoff.mdl"); + + FallInit(); +} + +void CSawedOff::Precache() +{ + PRECACHE_MODEL("models/v_sawedoff.mdl"); + PRECACHE_MODEL("models/p_sawedoff.mdl"); + PRECACHE_MODEL("models/w_sawedoff.mdl"); + + PRECACHE_MODEL("models/shells/shell_buckshot_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_buckshot_hi.mdl"); + + PRECACHE_SOUND("weapons/sawedoff_draw.wav"); + PRECACHE_SOUND("weapons/sawedoff_fire1.wav"); + PRECACHE_SOUND("weapons/sawedoff_fire2.wav"); + PRECACHE_SOUND("weapons/sawedoff_fire_empty.wav"); + PRECACHE_SOUND("weapons/sawedoff_reload1.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/sawedoff.sc"); +} + +int CSawedOff::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 1; + p->iFlags = 0; + + p->pszAmmo1 = "Buckshot"; + p->iMaxAmmo1 = AMMO_MAX_BUCKSHOT; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_SAWEDOFF; + + p->iPosition = 5; + + p->iId = m_iId = WEAPON_SAWEDOFF; + p->iWeight = WEIGHT_SAWEDOFF; + + return 1; +} + +float CSawedOff::flGetTiltedDamage() +{ + return RANDOM_FLOAT( 16, 19 ); +} + +char *CSawedOff::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_SAWEDOFF_1 : DEATH_SAWEDOFF_2; +} + +void CSawedOff::ItemPostFrame() +{ + CWasteWeapon::ItemPostFrame(); + + // Set allow fire mode + if(!(m_pPlayer->pev->button & IN_ATTACK)) + m_iAllowFire = TRUE; +} + +void CSawedOff::FireShotgun(int barrels) +{ + if(!m_iAllowFire) + return; + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if(!m_iClip) + { + SendWeaponAnim(SAWED_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + PlayEmptySound(); + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.3; + return; + } + else if(barrels == 2 && m_iClip == 1) + { + // If we dont have enough to go all out, + // settle for second best. :) + FireShotgun(1); + return; + } + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + if(barrels == 2) + m_iClip -= 2; + else + m_iClip--; + + // OMG kickback! +#ifndef CLIENT_DLL + float flZVel = m_pPlayer->pev->velocity.z; + float kickback = (barrels == 1) ? 175 : 262.5; + + m_pPlayer->pev->velocity = m_pPlayer->pev->velocity - gpGlobals->v_forward * kickback; + + // clamp the z velocity so people + // cant sawnoff jump with the gun :) + m_pPlayer->pev->velocity.z = flZVel; +#endif + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecDir; + + int shotCount = (barrels == 1) ? 7 : 12; + float spread = (barrels == 1) ? 0.13 : 0.18; + + Vector vecSpread = Vector(spread,spread,spread); + + vecDir = m_pPlayer->FireBulletsPlayer(this, shotCount, vecSrc, vecAiming, vecSpread, 1280, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + spread, + spread, + barrels, + 0, + 0, + 0); + + m_iAllowFire = FALSE; +} + +void CSawedOff::PrimaryAttack() +{ + FireShotgun(1); + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.20; +} + +void CSawedOff::SecondaryAttack() +{ + FireShotgun(2); + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.50; +} + +void CSawedOff::Reload() +{ + if(m_flNextPrimaryAttack > 0.0 || m_flNextSecondaryAttack > 0.0) + return; + + int iResult; + + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // player "reload" animation + m_pPlayer->SetAnimation( PLAYER_RELOAD ); + + iResult = DefaultReload( m_iClipSize, SAWED_RELOAD, 1.6f ); + + if(iResult) + { + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.6f; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); + } +} + +BOOL CSawedOff::Deploy() +{ + if(!DefaultDeploy("models/v_sawedoff.mdl","models/p_sawedoff.mdl",SAWED_DRAW,"sawedoff")) + return FALSE; + + return CWasteWeapon::Deploy(); +} + +BOOL CSawedOff::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +/************* + Pancor Jackhammer +*************/ +void CJackHammer::Spawn() +{ + Precache(); + + pev->classname = MAKE_STRING("weapon_jackhammer"); + + m_iId = WEAPON_JACKHAMMER; + m_iDefaultAmmo = AMMO_GIVE_JACKHAMMER; + m_iClipSize = CLIP_JACKHAMMER; + + SET_MODEL(ENT(pev),"models/w_jackhammer.mdl"); + + FallInit(); +} + +void CJackHammer::Precache() +{ + PRECACHE_MODEL("models/v_jackhammer.mdl"); + PRECACHE_MODEL("models/p_jackhammer.mdl"); + PRECACHE_MODEL("models/w_jackhammer.mdl"); + + PRECACHE_MODEL("models/shells/shell_buckshot_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_buckshot_hi.mdl"); + + PRECACHE_SOUND("weapons/jackhammer_fire1.wav"); + PRECACHE_SOUND("weapons/jackhammer_fire2.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/jackhammer.sc"); +} + +int CJackHammer::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 3; + p->iFlags = 0; + + p->pszAmmo1 = "JackhammerAmmo"; + p->iMaxAmmo1 = AMMO_MAX_JACKHAMMER; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_JACKHAMMER; + + p->iPosition = iItemSlot() - 1; + + p->iId = m_iId = WEAPON_JACKHAMMER; + p->iWeight = WEIGHT_UNIQUE; + + return 1; +} + +float CJackHammer::flGetTiltedDamage() +{ + return RANDOM_LONG(14,19); +} + +char *CJackHammer::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_JACKHAMMER_1 : DEATH_JACKHAMMER_2; +} + +void CJackHammer::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if(!m_iClip) + { +// SendWeaponAnim(SAWED_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + PlayEmptySound(); + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.10; + return; + } + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + m_iClip--; + + HALT_PLAYER(m_pPlayer,25,-1); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecDir; + + float spread = MOSSBERG_SPREAD; + + Vector vecSpread = Vector(spread,spread,spread); + + vecDir = m_pPlayer->FireBulletsPlayer(this,MOSSBERG_SHOTCOUNT, vecSrc, vecAiming, vecSpread, 1280, BULLET_10GAUGE, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + spread, + spread, + MOSSBERG_SHOTCOUNT, + (m_pPlayer->pev->flags & FL_DUCKING), // is the player ducking or not + 0, + 0); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.13; +} + +void CJackHammer::SecondaryAttack() +{ +} + +void CJackHammer::Reload() +{ + if(m_flNextPrimaryAttack > 0.0 || m_flNextSecondaryAttack > 0.0) + return; + + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // player "reload" animation + m_pPlayer->SetAnimation( PLAYER_RELOAD ); + + int iResult = DefaultReload( m_iClipSize, JACKHAMMER_RELOAD, 3.34f ); +} + +BOOL CJackHammer::Deploy() +{ + if(!DefaultDeploy("models/v_jackhammer.mdl","models/p_jackhammer.mdl",JACKHAMMER_DRAW,"jackhammer")) + return FALSE; + return CWasteWeapon::Deploy(); +} + +BOOL CJackHammer::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +// +// Ammo Box +// +void CJackHammerAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_jackhammer.mdl"); + CWasteAmmo::Spawn(); +} + +void CJackHammerAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_jackhammer.mdl"); +} + +BOOL CJackHammerAmmo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo( AMMO_GIVE_JACKHAMMER, "JackhammerAmmo", AMMO_MAX_JACKHAMMER) != -1) + return TRUE; + return FALSE; +} diff --git a/dlls/wpn_shared/tw_sidearms.cpp b/dlls/wpn_shared/tw_sidearms.cpp new file mode 100644 index 0000000..1bf2ab9 --- /dev/null +++ b/dlls/wpn_shared/tw_sidearms.cpp @@ -0,0 +1,1559 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "player.h" +#include "soundent.h" +#include "thewastes.h" +#include "game.h" +#include "gamerules.h" + +// Entity Linkage +LINK_ENTITY_TO_CLASS(weapon_beretta,CBeretta); +LINK_ENTITY_TO_CLASS(ammo_beretta,CBerettaAmmo); +LINK_ENTITY_TO_CLASS(weapon_colt,CColt); +LINK_ENTITY_TO_CLASS(ammo_colt,CColtAmmo); + +LINK_ENTITY_TO_CLASS(weapon_deagle,CDesertEagle); +LINK_ENTITY_TO_CLASS(ammo_deagle,CDesertEagleAmmo); + +LINK_ENTITY_TO_CLASS(weapon_ruger,CRuger); +LINK_ENTITY_TO_CLASS(ammo_ruger,CRugerAmmo); + +LINK_ENTITY_TO_CLASS(weapon_handcannon,CHandcannon); +LINK_ENTITY_TO_CLASS(ammo_handcannon,CHandcannonAmmo); + +/************* + Sidearm +*************/ + +void CSidearm::Spawn() +{ + CWasteWeapon::Spawn(); + + Precache(); + + SET_MODEL(ENT(pev),GetModelW()); + m_iState = SIDEARM_SHOOT; + m_iOldState = -1; + m_iAllowFire = TRUE; + m_iExtraRound = TRUE; + + m_bAimReload = FALSE; + + FallInit(); +} + +void CSidearm::Precache() +{ + PRECACHE_MODEL(GetModelV()); + PRECACHE_MODEL(GetModelP()); + PRECACHE_MODEL(GetModelW()); + + PRECACHE_SOUND("weapons/whip.wav"); + PRECACHE_SOUND("weapons/whip2.wav"); + PRECACHE_SOUND("weapons/whip3.wav"); + + // For pistol whipping + PRECACHE_SOUND("weapons/cbar_hitbod1.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod3.wav"); +} + +int CSidearm::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 1; + p->iFlags = 0; + + return 1; +} + +void CSidearm::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_iState; + local_data->iuser4 = m_iAllowFire; +} + +void CSidearm::UnpackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_iState = local_data->m_iWeaponState; + m_iAllowFire = local_data->iuser4; +} + +void CSidearm::ItemPostFrame() +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + int j; + + // complete the reload. + if(m_iId == WEAPON_RUGER) + j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + else + j = min( iMaxClip(), m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // remove all ammo from gun. + if(m_iId != WEAPON_RUGER) + m_iClip = 0; + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; +#ifndef CLIENT_DLL + m_pPlayer->TabulateAmmo(); +#endif + m_fInReload = FALSE; + + // Go back to aim, we're done here. + if(m_bAimReload) + { + SetState(SIDEARM_AIM); + m_bAimReload = FALSE; + } + + // If we're a ruger, check to see + // if we need to zoom in again. + if(m_iId == WEAPON_RUGER) + { + CRuger *pRuger = (CRuger*)this; + + if(pRuger->m_bInZoom) + { + pRuger->m_bInZoom = FALSE; + pRuger->SecondaryAttack(); + } + } + } + + // client is trying to reload + if(m_bAimReload && m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase()) + { + Reload(); + return; + } + + CWasteWeapon::ItemPostFrame(); + + // Set allow fire mode + if(!(m_pPlayer->pev->button & IN_ATTACK)) + m_iAllowFire = TRUE; +// else +// m_iAllowFire = FALSE; +} + +void CSidearm::PrimaryAttack() +{ + if((m_iState == SIDEARM_SHOOT || m_iState == SIDEARM_AIM) && m_iAllowFire) + PistolFire(m_fAccuracy,m_fRateOfFire); +} + +void CSidearm::SecondaryAttack() +{ + // Cant whip from aim mode + if(m_iState == SIDEARM_AIM) + return; + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0, + 0.0, + (m_iClip == 0), + 0, // Whip + 0, + 0); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // Attack something + TraceResult tr; + + Vector vecSrc = m_pPlayer->GetGunPosition(); + Vector vecAiming = gpGlobals->v_forward; + Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + +#ifndef CLIENT_DLL + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } + + // hit + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + ClearMultiDamage( ); + + // first swing does full damage TODO FIX + pEntity->TraceAttack(m_pPlayer->pev, GetWhipDmg() , gpGlobals->v_forward, &tr, DMG_CLUB|DMG_NEVERGIB ); + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + if (pEntity) + { + if ( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) + { + // play thwack or smack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod3.wav", 1, ATTN_NORM); break; + } + m_pPlayer->m_iWeaponVolume = 128; + } + } +#endif + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5f; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.917f; +} + +void CSidearm::SpecialMode() +{ + if(m_pPlayer->m_flNextAttack > UTIL_WeaponTimeBase()) + return; + + if(m_iState == SIDEARM_AIM) + SetState(SIDEARM_SHOOT); + else + SetState(SIDEARM_AIM); +} + +void CSidearm::SetState(int statenum) +{ + m_iOldState = m_iState; + + switch(statenum) + { + case SIDEARM_SHOOT: + if(m_iState == SIDEARM_AIM) + { + SendWeaponAnim((m_iClip == 0) ? SPISTOL_END_AIM_EMPTY : SPISTOL_END_AIM,UseDecrement() ? 1 : 0); + } + else + SendWeaponAnim(SPISTOL_IDLE,UseDecrement() ? 1 : 0); + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75; + m_iState = SIDEARM_SHOOT; + break; + case SIDEARM_AIM: + m_iState = SIDEARM_AIM; + SendWeaponAnim((m_iClip == 0) ? SPISTOL_GOTO_AIM_EMPTY : SPISTOL_GOTO_AIM,UseDecrement() ? 1 : 0); + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75; + break; + } +} + +void CSidearm::PistolFire(float spread,float rof) +{ + if(m_iClip <= 0) + { + if(m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.3; + } + + SendWeaponAnim((m_iState == SIDEARM_SHOOT) ? SPISTOL_SHOOT_EMPTY : SPISTOL_AIM_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + return; + } + + // Modify gun logic if we're aiming + if(m_iState == SIDEARM_AIM) + { + spread /= SIDEARM_AIMPENALTY; + rof *= SIDEARM_AIMPENALTY; + + // If we are moving, incur an ADDITIONAL rof penalty + if(m_pPlayer->pev->velocity[0] != 0 || m_pPlayer->pev->velocity[1] != 0 || m_pPlayer->pev->velocity[2] != 0) + rof *= MOVING_AIMPENALTY; + } + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; + + Vector Spread; + + // Accuracy modifiers + if(m_pPlayer->pev->flags & FL_DUCKING) + Spread = Vector(spread*SIDEARM_DUCKPENALTY,spread*SIDEARM_DUCKPENALTY,spread*SIDEARM_DUCKPENALTY); + else if(m_pPlayer->pev->flags & FL_ONGROUND) + Spread = Vector(spread,spread,spread); + else + Spread = Vector(spread*SIDEARM_JUMPPENALTY,spread*SIDEARM_JUMPPENALTY,spread*SIDEARM_JUMPPENALTY); + + int penetration_type = 0; + + switch(m_iId) + { + case WEAPON_DEAGLE: + penetration_type = P_DEAGLE; break; + case WEAPON_COLT: + penetration_type = P_COLT; break; + case WEAPON_BERETTA: + penetration_type = P_BERETTA; break; + } + + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Spread, 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, penetration_type); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + dir.x, + dir.y, + 0, + 1, // Pistol + (m_iClip == 0) ? 1 : 0, + (m_iState == SIDEARM_AIM) ? 1 : 0 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + rof; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); + + m_iAllowFire = FALSE; +} + +void CSidearm::SwingWeapon() +{ +} + +// again, here some more ruger specific hack code. +void CSidearm::Reload() +{ + int iResult; + + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // If we are in aim mode, HACK our way into oblivion! + if(m_iState == SIDEARM_AIM) + { + m_bAimReload = TRUE; + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + SetState(SIDEARM_SHOOT); + return; + } + + // player "reload" animation + m_pPlayer->SetAnimation( PLAYER_RELOAD ); + + if (m_iClip == 0) + { + // ruger check - dont hold back on gun capacity like the semi's + if(m_iId == WEAPON_RUGER) + iResult = DefaultReload( m_iClipSize,RUGER_RELOAD, 4.0f); + else + { + int anim; + float timing; + + switch((int)UTIL_SharedRandomFloat(m_pPlayer->random_seed,0,1)) + { + case 0: anim = SPISTOL_RELOAD_EMPTY; timing = 2.955f; break; + case 1: anim = SPISTOL_RELOAD2_EMPTY; timing = 3.864f; break; + } + + iResult = DefaultReload(m_iClipSize, anim, timing ); + } + } + else + { + // See if we are a ruger - if not no big deal. + if(m_iId == WEAPON_RUGER) + iResult = DefaultReload( m_iClipSize, RUGER_RELOAD, 4.0f ); + else + { + int anim; + int timing; + + switch((int)UTIL_SharedRandomFloat(m_pPlayer->random_seed,0,1)) + { + case 0: anim = SPISTOL_RELOAD; timing = 2.273f; break; + case 1: anim = SPISTOL_RELOAD2; timing = 3.41f; break; + } + + iResult = DefaultReload(m_iClipSize, anim, timing ); + } + } + + if (iResult) + { + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); + } + else + m_bAimReload = FALSE; +} + +void CSidearm::Holster(int skiplocal) +{ + // In case the gun was zoomed in, or in + // aim mode, go ahead and give the + // player his movement back. + MODIFY_PLAYER_SPEED(m_pPlayer,0); + + if(m_iState != SIDEARM_SHOOT) + m_iState = SIDEARM_SHOOT; + CWasteWeapon::Holster(skiplocal); +} + +// Once again, ruger hacks abound! +BOOL CSidearm::Deploy() +{ + if(m_iId == WEAPON_RUGER) + { + if(!DefaultDeploy(GetModelV(),GetModelP(),RUGER_DRAW,GetAnimPlayer())) + return FALSE; + } + else + { + if(m_iClip) + { + if(!DefaultDeploy(GetModelV(),GetModelP(),SPISTOL_DRAW,GetAnimPlayer())) + return FALSE; + } + else + { + if(!DefaultDeploy(GetModelV(),GetModelP(),SPISTOL_DRAW_EMPTY,GetAnimPlayer())) + return FALSE; + } + } + + m_iState = SIDEARM_SHOOT; + m_iOldState = -1; + + return TRUE; +} + +BOOL CSidearm::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CSidearm::WeaponIdle() +{ + ResetEmptySound(); +} + +void CSidearm::Drop() +{ + if(m_iState != SIDEARM_SHOOT) + m_iState = SIDEARM_SHOOT; // Let the next person who picks this up start in a shoot mode. + CWasteWeapon::Drop(); +} + +/************* + Beretta +*************/ + +void CBeretta::Spawn() +{ + pev->classname = MAKE_STRING("weapon_beretta"); + + m_iBulletType = BULLET_9MMP; + m_iId = WEAPON_BERETTA; + m_iDefaultAmmo = AMMO_GIVE_BERETTA; + m_iClipSize = CLIP_BERETTA; + + m_fAccuracy = 0.05; + m_fRateOfFire = 0.225; + + CSidearm::Spawn(); +} + +void CBeretta::Precache() +{ + PRECACHE_SOUND("weapons/beretta_fire.wav"); + + PRECACHE_MODEL("models/shells/shell_9x18mm_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_9x18mm_hi.mdl"); + + PRECACHE_MODEL("models/shells/mag_beretta.mdl"); + + m_usFire1 = PRECACHE_EVENT(1,"events/beretta.sc"); + + CSidearm::Precache(); +} + +int CBeretta::GetItemInfo(ItemInfo *p) +{ + p->pszAmmo1 = "9mm Parabellum"; + p->iMaxAmmo1 = AMMO_MAX_BERETTA; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_BERETTA; + + p->iPosition = 0; + + p->iId = m_iId = WEAPON_BERETTA; + p->iWeight = WEIGHT_BERETTA; + + return CSidearm::GetItemInfo(p); +} + +float CBeretta::flGetTiltedDamage() +{ + float fRet = RANDOM_FLOAT(38,48); + + // 10% accuracy bonus when aiming + if(m_iState == SIDEARM_AIM) + fRet *= 1.10f; + + return fRet; +} + +char *CBeretta::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_BERETTA_1 : DEATH_BERETTA_2; +} + +void CBeretta::AttachToPlayer( CBasePlayer *pPlayer ) +{ +//#ifndef CLIENT_DLL +#if 0 + // TODO: Implement this code, and also implement removing akimbos + // when you drop your other guns + // See if this player already has this weapon + CBaseEntity *ent = NULL; + int iBerettaCount = 0; + + while( (ent = UTIL_FindEntityInSphere( ent, pPlayer->pev->origin, 16 )) != NULL ) + { + if( FStrEq( STRING(ent->pev->classname), "weapon_beretta" ) ) + { + // if( ent->pev->owner == pPlayer->edict() ) + iBerettaCount++; + } + } + + if( iBerettaCount > 1 ) + pPlayer->GiveNamedItem( "weapon_akimboberettas" ); +#endif + + CSidearm::AttachToPlayer( pPlayer ); +} + +// +// Ammo Box +// + +void CBerettaAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_beretta.mdl"); + CWasteAmmo::Spawn(); +} + +void CBerettaAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_beretta.mdl"); +} + +BOOL CBerettaAmmo::AddAmmo(CBaseEntity *pOther) +{ + if (pOther->GiveAmmo( AMMO_GIVE_BERETTA,"9mm Parabellum",AMMO_MAX_BERETTA ) != -1) + return TRUE; + return FALSE; +} + +/************* + Colt Enforcer +*************/ + +void CColt::Spawn() +{ + pev->classname = MAKE_STRING("weapon_colt"); + + m_iBulletType = BULLET_10MM; + m_iId = WEAPON_COLT; + m_iDefaultAmmo = AMMO_GIVE_COLT; + m_iClipSize = CLIP_COLT; + + m_fAccuracy = 0.06; + m_fRateOfFire = 0.300; + + CSidearm::Spawn(); +} + +void CColt::Precache() +{ + PRECACHE_SOUND("weapons/colt_fire.wav"); + PRECACHE_SOUND("weapons/colt_fire_empty.wav"); + + PRECACHE_MODEL("models/shells/shell_10mm_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_10mm_hi.mdl"); + + PRECACHE_MODEL("models/shells/mag_colt.mdl"); + + m_usFire1 = PRECACHE_EVENT(1,"events/colt.sc"); + + CSidearm::Precache(); +} + +int CColt::GetItemInfo(ItemInfo *p) +{ + + p->pszAmmo1 = ".40 S&W"; + p->iMaxAmmo1 = AMMO_MAX_COLT; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_COLT; + + p->iPosition = 1; + + p->iId = m_iId = WEAPON_COLT; + p->iWeight = WEIGHT_COLT; + + return CSidearm::GetItemInfo(p); +} + +float CColt::flGetTiltedDamage() +{ + float fRet = RANDOM_FLOAT(45,55); + + // 10% accuracy bonus when aiming + if(m_iState == SIDEARM_AIM) + fRet *= 1.10f; + + return fRet; +} + +char *CColt::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_COLT_1 : DEATH_COLT_2; +} + +// +// Ammo Box +// +void CColtAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_colt.mdl"); + CWasteAmmo::Spawn(); +} + +void CColtAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_colt.mdl"); +} + +BOOL CColtAmmo::AddAmmo(CBaseEntity *pOther) +{ + if (pOther->GiveAmmo( AMMO_GIVE_COLT,".40 S&W",AMMO_MAX_COLT ) != -1) + return TRUE; + return FALSE; +} + +/************* + Desert Eagle .50 +*************/ + +void CDesertEagle::Spawn() +{ + pev->classname = MAKE_STRING("weapon_deagle"); + + m_iBulletType = BULLET_50AE; + m_iId = WEAPON_DEAGLE; + m_iDefaultAmmo = AMMO_GIVE_DEAGLE; + m_iClipSize = CLIP_DEAGLE; + + CSidearm::Spawn(); + + m_fAccuracy = 0.0625; + m_fRateOfFire = 0.385; +} + +void CDesertEagle::Precache() +{ + PRECACHE_SOUND("weapons/deagle_fire.wav"); + PRECACHE_SOUND("weapons/deagle_fire_empty.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/deagle.sc"); + + CSidearm::Precache(); + + PRECACHE_MODEL("models/shells/shell_50AE_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_50AE_hi.mdl"); + + PRECACHE_MODEL("models/shells/mag_deagle.mdl"); +} + +int CDesertEagle::GetItemInfo(ItemInfo *p) +{ + + p->pszAmmo1 = ".50 AE"; + p->iMaxAmmo1 = AMMO_MAX_DEAGLE; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_DEAGLE; + + p->iPosition = 2; + + p->iId = m_iId = WEAPON_DEAGLE; + p->iWeight = WEIGHT_DEAGLE; + + return CSidearm::GetItemInfo(p); +} + +float CDesertEagle::flGetTiltedDamage() +{ + float fRet = RANDOM_FLOAT(50,60); + + // 10% accuracy bonus when aiming + if(m_iState == SIDEARM_AIM) + fRet *= 1.10f; + + return fRet; +} + +char *CDesertEagle::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_DEAGLE_1 : DEATH_DEAGLE_2; +} + +void CDesertEagle::SwingWeapon() +{ + CSidearm::SwingWeapon(); + + // Desert Eagle pistol whipping takes longer to recover from + m_flNextPrimaryAttack = m_flNextSecondaryAttack += 0.5; +} + +// +// Ammo Box +// + +void CDesertEagleAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_deagle.mdl"); + CWasteAmmo::Spawn(); +} + +void CDesertEagleAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_deagle.mdl"); +} + +BOOL CDesertEagleAmmo::AddAmmo(CBaseEntity *pOther) +{ + if (pOther->GiveAmmo( AMMO_GIVE_DEAGLE,".50 AE",AMMO_MAX_DEAGLE ) != -1) + return TRUE; + return FALSE; +} + +/************* + Ruger .454 +*************/ + +void CRuger::Spawn() +{ + pev->classname = MAKE_STRING("weapon_ruger"); + + m_iBulletType = BULLET_454CASULL; + m_iId = WEAPON_RUGER; + m_iDefaultAmmo = AMMO_GIVE_RUGER; + m_iClipSize = CLIP_RUGER; + + CSidearm::Spawn(); + + m_fAccuracy = 0.055; + m_fRateOfFire = 0.400; + m_iExtraRound = FALSE; + m_bInZoom = FALSE; + m_bStartZoom = FALSE; + +// m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + +void CRuger::Precache() +{ + PRECACHE_SOUND("weapons/ruger_fire.wav"); + + PRECACHE_MODEL("models/shells/shell_454_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_454_hi.mdl"); + + m_usFire1 = PRECACHE_EVENT(1,"events/ruger.sc"); + + CSidearm::Precache(); +} + +int CRuger::GetItemInfo(ItemInfo *p) +{ + p->pszAmmo1 = ".454 Casull"; + p->iMaxAmmo1 = AMMO_MAX_RUGER; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_RUGER; + + p->iPosition = 3; + + p->iId = m_iId = WEAPON_RUGER; + p->iWeight = WEIGHT_RUGER; + + return CSidearm::GetItemInfo(p); +} + +float CRuger::flGetTiltedDamage() +{ + float fRet = RANDOM_FLOAT(50,60); + + // 10% accuracy bonus when aiming or sniping + if(m_iState == SIDEARM_AIM || m_bInZoom) + fRet *= 1.10f; + + return fRet; +} + +char *CRuger::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_RUGER_1 : DEATH_RUGER_2; +} + +void CRuger::PackWeapon(void *weapon_data) +{ + CSidearm::PackWeapon(weapon_data); + + // Save zoom + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + local_data->m_fInZoom = m_bInZoom; +} + +void CRuger::UnpackWeapon(void *weapon_data) +{ + CSidearm::UnpackWeapon(weapon_data); + + // Load zoom + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + if(m_bInZoom != local_data->m_fInZoom) + { + m_bInZoom = local_data->m_fInZoom; + + // somehow client was mixed up on zoom status, so fix + // the situation :( + if(m_bInZoom == TRUE) + { + MODIFY_PLAYER_SPEED(m_pPlayer,0.75); + + ClientSway(); + + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 45; + } + else + { + MODIFY_PLAYER_SPEED(m_pPlayer,0); + +#ifdef CLIENT_DLL + V_StopSway(); +#endif + + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; + m_bInZoom = FALSE; + + m_iState = SIDEARM_SHOOT; + } + } +} + +void CRuger::SwingWeapon() +{ +} + +void CRuger::PistolFire(float spread,float rof) +{ + if(m_iClip <= 0) + { + // if(m_fFireOnEmpty) + // { + SendWeaponAnim(RUGER_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.3; + // } + return; + } + + // Modify gun logic if we're aiming + if(m_iState == SIDEARM_AIM || m_bInZoom) + { + spread /= RUGER_AIMACC; + rof *= RUGER_AIMROF; + + // If we are moving, incur an ADDITIONAL rof penalty + if(m_iId == WEAPON_DEAGLE) + { + // slooow + if(m_pPlayer->pev->velocity[0] != 0 || m_pPlayer->pev->velocity[1] != 0 || m_pPlayer->pev->velocity[2] != 0) + rof *= MOVING_AIMPENALTY * 4; + } + else + { + if(m_pPlayer->pev->velocity[0] != 0 || m_pPlayer->pev->velocity[1] != 0 || m_pPlayer->pev->velocity[2] != 0) + rof *= MOVING_AIMPENALTY; + } + } + +#ifdef CLIENT_DLL + if(m_bInZoom) + { + // Refresh zoom sway + ClientSway(); + } +#endif + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; + + // Accuracy modifiers + if(m_pPlayer->pev->flags & FL_DUCKING) + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Vector(spread*SIDEARM_DUCKPENALTY,spread*SIDEARM_DUCKPENALTY,spread*SIDEARM_DUCKPENALTY), 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_RUGER ); + else if(m_pPlayer->pev->flags & FL_ONGROUND) + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Vector(spread,spread,spread), 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_RUGER ); + else + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Vector(spread*SIDEARM_JUMPPENALTY,spread*SIDEARM_JUMPPENALTY,spread*SIDEARM_JUMPPENALTY), 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_RUGER ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + dir.x, + dir.y, + 0, + (m_bInZoom == 1) ? 1 : 0, + ( m_iClip == 0 ) ? 1 : 0, + (m_iState == SIDEARM_AIM) ? 1 : 0 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + rof; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); + m_iAllowFire = FALSE; +} + +void CRuger::SpecialMode() +{ + if(m_pPlayer->m_flNextAttack > 0.0 ) + return; + + if( m_bInZoom ) + { + MODIFY_PLAYER_SPEED(m_pPlayer,0); + +#ifdef CLIENT_DLL + V_StopSway(); +#endif + + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; + m_bInZoom = FALSE; + + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75; + } + else + { + if(m_iState == SIDEARM_SHOOT) + SetState(SIDEARM_AIM); + else if(m_iState == SIDEARM_AIM) + SetState(SIDEARM_SHOOT); + } +} + +void CRuger::ItemPostFrame() +{ + CSidearm::ItemPostFrame(); + + if(m_bStartZoom && m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase()) + { + m_bStartZoom = FALSE; + SecondaryAttack(); + } +} + +void CRuger::SecondaryAttack() +{ + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.45; + + if(m_iState == SIDEARM_SHOOT) + { + SetState(SIDEARM_AIM); + m_bStartZoom = TRUE; + return; + } + + if(m_pPlayer->pev->fov != 0) + { + MODIFY_PLAYER_SPEED(m_pPlayer,0); + +#ifdef CLIENT_DLL + V_StopSway(); +#endif + + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; + m_bInZoom = FALSE; + + SetState(SIDEARM_SHOOT); + } + else if(m_pPlayer->pev->fov != 45) + { + MODIFY_PLAYER_SPEED(m_pPlayer,0.5); + +#ifdef CLIENT_DLL + ClientSway(); +#endif + + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 45; + m_bInZoom = TRUE; + } +} + +void CRuger::SetState(int statenum) +{ + m_iOldState = m_iState; + + switch(statenum) + { + case SIDEARM_SHOOT: + if(m_iState == SIDEARM_AIM) + SendWeaponAnim(RUGER_END_AIM,UseDecrement() ? 1 : 0); + else + SendWeaponAnim(RUGER_IDLE,UseDecrement() ? 1 : 0); + m_iState = SIDEARM_SHOOT; + break; + case SIDEARM_AIM: + m_iState = SIDEARM_AIM; + SendWeaponAnim(RUGER_GOTO_AIM,UseDecrement() ? 1 : 0); + break; + } + + m_pPlayer->m_flNextAttack = m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75; +} + +void CRuger::Reload() +{ + if(m_bInZoom && m_iClip != m_iClipSize) + { + MODIFY_PLAYER_SPEED(m_pPlayer,0); + + // Disable zoom for now. + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; +#ifdef CLIENT_DLL + V_StopSway(); +#endif +// m_bInZoom = FALSE; + } + + CSidearm::Reload(); +} + +BOOL CRuger::Deploy() +{ + int ret = CSidearm::Deploy(); + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 7 ); + return ret; +} + +void CRuger::WeaponIdle() +{ + CSidearm::WeaponIdle(); + + if (m_flTimeWeaponIdle >= UTIL_WeaponTimeBase()) + return; + + if(!m_bInZoom && m_iState != SIDEARM_AIM) + SendWeaponAnim( RUGER_IDLE2, UseDecrement() ? 1 : 0 ); + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 5, 15 ); // how long till we do this again. +} + +void CRuger::Holster(int skiplocal) +{ + if(m_bInZoom) + { + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; +#ifdef CLIENT_DLL + V_StopSway(); +#endif + } + + m_bInZoom = FALSE; + CSidearm::Holster(skiplocal); +} + +void CRuger::ClientSway() +{ +#ifdef CLIENT_DLL + // Ducking + if(m_pPlayer->pev->flags & FL_DUCKING) + V_SetSway(1.75); + // On Ground + else if(m_pPlayer->pev->flags & FL_ONGROUND) + V_SetSway(3.25); + // Jumping + else + V_SetSway(6); +#endif +} + +// +// Ammo Box +// +void CRugerAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_ruger.mdl"); + CWasteAmmo::Spawn(); +} + +void CRugerAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_ruger.mdl"); +} + +BOOL CRugerAmmo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo( AMMO_GIVE_RUGER,".454 Casull",AMMO_MAX_RUGER ) != -1) + return TRUE; + return FALSE; +} + +/************* + Handcannon +*************/ + +void CHandcannon::Spawn() +{ + CWasteWeapon::Spawn(); + Precache(); + + // Now work on the HC itself + pev->classname = MAKE_STRING("weapon_handcannon"); + + m_iBulletType = BULLET_556MM; + m_iId = WEAPON_HANDCANNON; + m_iDefaultAmmo = AMMO_GIVE_HANDCANNON; + m_iClipSize = CLIP_HANDCANNON; + + m_fAccuracy = 0.0585; + m_fRateOfFire = 0.420; + + m_bLaserVisible = FALSE; + + SET_MODEL(ENT(pev),"models/w_handcannon.mdl"); + + FallInit(); +} + +void CHandcannon::Precache() +{ + PRECACHE_MODEL("models/v_handcannon.mdl"); + PRECACHE_MODEL("models/p_handcannon.mdl"); + PRECACHE_MODEL("models/w_handcannon.mdl"); + + PRECACHE_MODEL("models/shells/shell_556mm_lo.mdl"); + PRECACHE_MODEL("models/shells/shell_556mm_hi.mdl"); + PRECACHE_MODEL("models/shells/mag_handcannon.mdl"); + + PRECACHE_MODEL("sprites/hand_laser_dot.spr"); + + PRECACHE_SOUND("weapons/handcannon_fire.wav"); + PRECACHE_SOUND("weapons/handcannon_fire_empty.wav"); + + PRECACHE_SOUND("weapons/handcannon_laser_on.wav"); + PRECACHE_SOUND("weapons/handcannon_laser_off.wav"); + + m_usFire1 = PRECACHE_EVENT(1,"events/handcannon.sc"); +} + +int CHandcannon::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->iSlot = 1; + p->iFlags = 0; + + p->pszAmmo1 = ".223 Handcannon"; + p->iMaxAmmo1 = AMMO_MAX_HANDCANNON; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CLIP_HANDCANNON; + + p->iPosition = 4; + + p->iId = m_iId = WEAPON_HANDCANNON; + p->iWeight = WEIGHT_HANDCANNON; + + return 1; +} + +float CHandcannon::flGetTiltedDamage() +{ + float fRet = RANDOM_FLOAT(65,75); + + // 5% damage increase + if(m_bLaserVisible) + fRet *= 1.05f; + + return fRet; +} + +char *CHandcannon::szGetDeathNoticeString() +{ + return RANDOM_LONG(0,1) ? DEATH_HANDCANNON_1 : DEATH_HANDCANNON_2; +} + +void CHandcannon::PackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + local_data->m_iWeaponState = m_bLaserVisible; + local_data->fuser4 = m_iAllowFire; + local_data->iuser4 = (m_pPlayer->m_pActiveItem == this) ? 1 : 0; // Check for client predicted lasersight. +} + +void CHandcannon::UnpackWeapon(void *weapon_data) +{ + weapon_data_t *local_data = (weapon_data_t*)weapon_data; + + m_bLaserVisible = local_data->m_iWeaponState; + m_iAllowFire = local_data->fuser4; + +#ifdef CLIENT_DLL + if(local_data->iuser4) + g_iClientLasersEnabled[GetLocalPlayerIndex()] = m_bLaserVisible; + else + g_iClientLasersEnabled[GetLocalPlayerIndex()] = 0; +#endif +} + +void CHandcannon::PrimaryAttack() +{ + if(!m_iAllowFire) + return; + m_iAllowFire = FALSE; + + if(m_iClip <= 0) + { + if(m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.3; + } + SendWeaponAnim(HC_SHOOT_EMPTY,UseDecrement() ? 1 : 0); + + return; + } + + // OMG kickback! +#ifndef CLIENT_DLL + float flZVel = m_pPlayer->pev->velocity.z; + + m_pPlayer->pev->velocity = m_pPlayer->pev->velocity - gpGlobals->v_forward * 125; + + // clamp the z velocity so people + // cant HC jump with the gun :) + m_pPlayer->pev->velocity.z = flZVel; +#endif + + m_iClip--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + Vector dir; + + float spread = m_fAccuracy; + float rof = m_fRateOfFire; + + if(m_bLaserVisible) + { + spread /= 2; + rof = 1.5f; + } + + // Accuracy modifiers + if(m_pPlayer->pev->flags & FL_DUCKING) + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Vector(spread*HANDCANNON_DUCKPENALTY,spread*HANDCANNON_DUCKPENALTY,spread*HANDCANNON_DUCKPENALTY), 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_HANDCANNON ); + else if(m_pPlayer->pev->flags & FL_ONGROUND) + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Vector(spread,spread,spread), 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_HANDCANNON ); + else + dir = m_pPlayer->FireBulletsPlayer(this, 1, vecSrc, vecAiming, Vector(spread*HANDCANNON_JUMPPENALTY,spread*HANDCANNON_JUMPPENALTY,spread*HANDCANNON_JUMPPENALTY), 8192, m_iBulletType, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed, P_HANDCANNON ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + dir.x, + dir.y, + 0, + 0, + (m_iClip == 0) ? 1 : 0, + m_bLaserVisible ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + rof; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); +} + +void CHandcannon::SecondaryAttack() +{ + m_bLaserVisible = !m_bLaserVisible; + + if(m_bLaserVisible) + { +// MODIFY_PLAYER_SPEED(m_pPlayer,0.85); +#ifdef CLIENT_DLL + g_iClientLasersEnabled[GetLocalPlayerIndex()] = 1; + CenterPrint("Lasersight on"); +#endif + } + else + { +// MODIFY_PLAYER_SPEED(m_pPlayer,1); +#ifdef CLIENT_DLL + g_iClientLasersEnabled[GetLocalPlayerIndex()] = 0; + CenterPrint("Lasersight off"); +#endif + } + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL(flags, + m_pPlayer->edict(), + m_usFire1, + 0.0, + (float*)&g_vecZero, + (float*)&g_vecZero, + 0.0, + 0.0, + 1, // laser activation sound + 0, + 0, + m_bLaserVisible ); // Which sound to play + + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.45f; +} + +void CHandcannon::ItemPostFrame() +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // complete the reload. + int j = min( iMaxClip(), m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + m_iClip = 0; + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; + +#ifndef CLIENT_DLL + m_pPlayer->TabulateAmmo(); +#endif + m_fInReload = FALSE; + } + + CWasteWeapon::ItemPostFrame(); + + // Set allow fire mode + if(!(m_pPlayer->pev->button & IN_ATTACK)) + m_iAllowFire = TRUE; +// else +// m_iAllowFire = FALSE; +} + +void CHandcannon::Reload() +{ + if(m_flNextPrimaryAttack > UTIL_WeaponTimeBase() || m_flNextSecondaryAttack > UTIL_WeaponTimeBase()) + return; + + int iResult; + + // See if we need to reload + if(m_iClip == m_iClipSize) + return; + + // player "reload" animation + m_pPlayer->SetAnimation( PLAYER_RELOAD ); + + if (m_iClip == 0) + iResult = DefaultReload( m_iClipSize, HC_RELOAD_EMPTY, 3.0f ); + else + iResult = DefaultReload( m_iClipSize, HC_RELOAD, 2.75f ); + + if (iResult) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); +} + +BOOL CHandcannon::Deploy() +{ + if(!m_iClip) + { + if(!DefaultDeploy("models/v_handcannon.mdl","models/p_handcannon.mdl",HC_DRAW_EMPTY,"HC")) + return FALSE; + } + else + { + if(!DefaultDeploy("models/v_handcannon.mdl","models/p_handcannon.mdl",HC_DRAW,"HC")) + return FALSE; + } + +#ifdef CLIENT_DLL + g_iClientLasersEnabled[GetLocalPlayerIndex()] = m_bLaserVisible; +#endif + + return CWasteWeapon::Deploy(); +} + +BOOL CHandcannon::PlayEmptySound() +{ + if (m_iPlayEmptySound) + { + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CHandcannon::WeaponIdle() +{ + ResetEmptySound(); +} + +void CHandcannon::Holster(int skiplocal) +{ +// m_bLaserVisible = FALSE; + +#ifdef CLIENT_DLL + g_iClientLasersEnabled[GetLocalPlayerIndex()] = 0; +#endif + CWasteWeapon::Holster(skiplocal); +} + +BOOL CHandcannon::bLaserActive() +{ + if( m_pPlayer != NULL && m_pPlayer->m_pActiveItem == this ) + return m_bLaserVisible; + return FALSE; +} + +// +// Ammo Box +// +void CHandcannonAmmo::Spawn() +{ + Precache(); + SET_MODEL(ENT(pev),"models/ammo_handcannon.mdl"); + CWasteAmmo::Spawn(); +} + +void CHandcannonAmmo::Precache() +{ + PRECACHE_MODEL("models/ammo_handcannon.mdl"); +} + +BOOL CHandcannonAmmo::AddAmmo(CBaseEntity *pOther) +{ + if(pOther->GiveAmmo( AMMO_GIVE_HANDCANNON, ".223 Handcannon", AMMO_MAX_HANDCANNON) != -1) + return TRUE; + return FALSE; +} diff --git a/dlls/wxdebug.cpp b/dlls/wxdebug.cpp new file mode 100644 index 0000000..d3902d6 --- /dev/null +++ b/dlls/wxdebug.cpp @@ -0,0 +1,395 @@ +//==========================================================================; +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR +// PURPOSE. +// +// Copyright (c) 1992 - 1997 Microsoft Corporation. All Rights Reserved. +// +//--------------------------------------------------------------------------; + + +// For every module and executable we store a debugging level and flags +// for the types of output that are desired. Constants for the types are +// defined in WXDEBUG.H and more can be added. +// The keys are stored in the registry under the +// HKEY_LOCAL_MACHINE\SOFTWARE\Debug\\Type and +// HKEY_LOCAL_MACHINE\SOFTWARE\Debug\\Level key values +// +// There are also global values under SOFTWARE\Debug\Global which are loaded +// after the module-specific values. The Types specified there are OR'ed with +// the module specific types and m_dwLevel is set to the greater of the global +// and the module specific settings. + +#include +#include + +#include "extdll.h" +#include "util.h" +#include "wxdebug.h" + +#include + +#ifdef _DEBUG + +void WINAPI DbgInitModuleName(void); +void WINAPI DbgInitModuleSettings(void); +void WINAPI DbgInitGlobalSettings(void); +void WINAPI DbgInitLogTo(HKEY hKey); +void WINAPI DbgInitKeyLevels(HKEY hKey, DWORD *pdwTypes, DWORD *pdwLevel); + + + +const INT iDEBUGINFO = 512; // Used to format strings + +HINSTANCE m_hInst; // Module instance handle +TCHAR m_ModuleName[iDEBUGINFO]; // Cut down module name +//CRITICAL_SECTION m_CSDebug; // Controls access to list +BOOL m_bInit = FALSE; // Have we been initialised +HANDLE m_hOutput = INVALID_HANDLE_VALUE; // Optional output written here +DWORD m_dwTypes = 0; +DWORD m_dwLevel = 0; + +const TCHAR *m_pBaseKey = TEXT("SOFTWARE\\Debug"); +const TCHAR *m_pGlobalKey = TEXT("GLOBAL"); +TCHAR *pKeyNames[] = +{ + TEXT("Types"), + TEXT("Level") +}; + + +// DbgInitialize +// This sets the instance handle that the debug library uses to find +// the module's file name from the Win32 GetModuleFileName function +void WINAPI DbgInitialise(HINSTANCE hInst) +{ + if (!m_bInit) + { + //InitializeCriticalSection(&m_CSDebug); + m_bInit = TRUE; + m_hInst = hInst; + DbgInitModuleName(); + DbgInitModuleSettings(); + DbgInitGlobalSettings(); + } +} + + +// DbgTerminate +// This is called to clear up any resources the debug library uses - at the +// moment we delete our critical section and the handle of the output file. +void WINAPI DbgTerminate() +{ + if (m_bInit) + { + if (m_hOutput != INVALID_HANDLE_VALUE) + { + DBGASSERTEXECUTE(CloseHandle(m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + //DeleteCriticalSection(&m_CSDebug); + m_bInit = FALSE; + } +} + + +// DbgInitModuleName +// Initialise the module file name +void WINAPI DbgInitModuleName() +{ + TCHAR FullName[iDEBUGINFO]; // Load the full path and module name + TCHAR *pName; // Searches from the end for a backslash + + GetModuleFileName(m_hInst,FullName,iDEBUGINFO); + pName = _tcsrchr(FullName,'\\'); + if (pName == NULL) + { + pName = FullName; + } + else + { + pName++; + } + lstrcpy(m_ModuleName,pName); +} + + +// DbgInitModuleSettings +// Retrieve the module-specific settings +void WINAPI DbgInitModuleSettings() +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hModuleKey; // Module key handle + + // Construct the base key name + wsprintf(szInfo,TEXT("%s\\%s"),m_pBaseKey,m_ModuleName); + + // Create or open the key for this module + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD)0, // Reserved value + NULL, // Address of class name + (DWORD)0, // Special options flags + KEY_ALL_ACCESS, // Desired security access + NULL, // Key security descriptor + &hModuleKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not access module key")); + return; + } + + DbgInitLogTo(hModuleKey); + DbgInitKeyLevels(hModuleKey, &m_dwTypes, &m_dwLevel); + RegCloseKey(hModuleKey); +} + + +// DbgInitGlobalSettings +// This is called by DbgInitialize to read the global debug settings for +// Level and Type from the registry. The Types are OR'ed together and m_dwLevel +// is set to the greater of the global and module-specific values. +void WINAPI DbgInitGlobalSettings() +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hGlobalKey; // Global override key + DWORD dwTypes = 0; + DWORD dwLevel = 0; + + // Construct the global base key name + wsprintf(szInfo,TEXT("%s\\%s"),m_pBaseKey,m_pGlobalKey); + + // Create or open the key for this module + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD)0, // Reserved value + NULL, // Address of class name + (DWORD)0, // Special options flags + KEY_ALL_ACCESS, // Desired security access + NULL, // Key security descriptor + &hGlobalKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not access GLOBAL module key")); + return; + } + + DbgInitKeyLevels(hGlobalKey, &dwTypes, &dwLevel); + RegCloseKey(hGlobalKey); + + m_dwTypes |= dwTypes; + if (dwLevel > m_dwLevel) + m_dwLevel = dwLevel; +} + + +// DbgInitLogTo +// Called by DbgInitModuleSettings to setup alternate logging destinations +void WINAPI DbgInitLogTo(HKEY hKey) +{ + LONG lReturn; + DWORD dwKeyType; + DWORD dwKeySize; + TCHAR szFile[MAX_PATH] = {0}; + static const TCHAR cszKey[] = TEXT("LogToFile"); + + dwKeySize = MAX_PATH; + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + cszKey, // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) szFile, // Returns the field's value + &dwKeySize); // Number of bytes transferred + + // create an empty key if it does not already exist + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_SZ) + { + dwKeySize = 1; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + cszKey, // Address of subkey name + (DWORD) 0, // Reserved field + REG_SZ, // Type of the key field + (PBYTE)szFile, // Value for the field + dwKeySize); // Size of the field buffer + } + + // if an output-to was specified. try to open it. + if (m_hOutput != INVALID_HANDLE_VALUE) + { + DBGASSERTEXECUTE(CloseHandle(m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + if (szFile[0] != 0) + { + if (!lstrcmpi(szFile, TEXT("Console"))) + { + m_hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + if (m_hOutput == INVALID_HANDLE_VALUE) + { + AllocConsole(); + m_hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + } + SetConsoleTitle (TEXT("Valve Debug Output")); + } else if (szFile[0] && + lstrcmpi(szFile, TEXT("Debug")) && + lstrcmpi(szFile, TEXT("Debugger")) && + lstrcmpi(szFile, TEXT("Deb"))) + { + m_hOutput = CreateFile(szFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE != m_hOutput) + { + static const TCHAR cszBar[] = TEXT("\r\n\r\n=====DbgInitialize()=====\r\n\r\n"); + SetFilePointer (m_hOutput, 0, NULL, FILE_END); + DbgOutString (cszBar); + } + } + } +} + + +// DbgInitKeyLevels +// This is called by DbgInitModuleSettings and DbgInitGlobalSettings to read +// settings for Types and Level from the registry +void WINAPI DbgInitKeyLevels(HKEY hKey, DWORD *pdwTypes, DWORD *pdwLevel) +{ + LONG lReturn; // Create key return value + DWORD dwKeySize; // Size of the key value + DWORD dwKeyType; // Receives it's type + + // Get the Types value + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + pKeyNames[0], // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE)pdwTypes, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + // If either the key was not available or it was not a DWORD value + // then we ensure only the high priority debug logging is output + // but we try and update the field to a zero filled DWORD value + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) + { + *pdwTypes = 0; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + pKeyNames[0], // Address of subkey name + (DWORD)0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE)pdwTypes, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not create subkey %s"),pKeyNames[0]); + *pdwTypes = 0; + } + } + + // Get the Level value + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + pKeyNames[1], // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE)pdwLevel, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + // If either the key was not available or it was not a DWORD value + // then we ensure only the high priority debug logging is output + // but we try and update the field to a zero filled DWORD value + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) + { + *pdwLevel = 0; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + pKeyNames[1], // Address of subkey name + (DWORD)0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE)pdwLevel, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not create subkey %s"),pKeyNames[1]); + *pdwLevel = 0; + } + } +} + + +// DbgOutString +void WINAPI DbgOutString(LPCTSTR psz) +{ + if (!m_bInit) + return; + if (m_hOutput != INVALID_HANDLE_VALUE) { + UINT cb = lstrlen(psz); + DWORD dw; + WriteFile (m_hOutput, psz, cb, &dw, NULL); + } else { + OutputDebugString (psz); + } +} + + +// DbgLogInfo +// Print a formatted string to the debugger prefixed with this module's name +// Because the debug code is linked statically every module loaded will +// have its own copy of this code. It therefore helps if the module name is +// included on the output so that the offending code can be easily found +void WINAPI DbgLogInfo(DWORD Type, DWORD Level, const TCHAR *pFormat,...) +{ + if (!m_bInit) + return; + // Check the current level for this type combination */ + if (((Type & m_dwTypes) == 0) || (m_dwLevel < Level)) + return; + + TCHAR szInfo[2000]; + + // Format the variable length parameter list + + va_list va; + va_start(va, pFormat); + + //lstrcpy(szInfo, m_ModuleName); + //lstrcat(szInfo, TEXT(": ")); + wvsprintf(szInfo /* + lstrlen(szInfo) */, pFormat, va); + //lstrcat(szInfo, TEXT("\r\n")); + DbgOutString(szInfo); + + va_end(va); +} + + +// DbgKernelAssert +// If we are executing as a pure kernel filter we cannot display message +// boxes to the user, this provides an alternative which puts the error +// condition on the debugger output with a suitable eye catching message +void WINAPI DbgKernelAssert(const TCHAR *pCondition, const TCHAR *pFileName, INT iLine) +{ + if (!m_bInit) + return; + DbgLogInfo(LOG_ERROR, 0, TEXT(m_ModuleName)); + DbgLogInfo(LOG_ERROR, 0, TEXT(": Assertion FAILED (%s) at line %d in file %s\r\n"), pCondition, iLine, pFileName); + DebugBreak(); +} + +#endif // _DEBUG + + diff --git a/dlls/wxdebug.h b/dlls/wxdebug.h new file mode 100644 index 0000000..321b1bc --- /dev/null +++ b/dlls/wxdebug.h @@ -0,0 +1,137 @@ +#ifndef __WXDEBUG__ +#define __WXDEBUG__ + +// This library provides fairly straight forward debugging functionality, this +// is split into two main sections. The first is assertion handling, there are +// three types of assertions provided here. The most commonly used one is the +// ASSERT(condition) macro which will pop up a message box including the file +// and line number if the condition evaluates to FALSE. Then there is the +// EXECUTE_ASSERT macro which is the same as ASSERT except the condition will +// still be executed in NON debug builds. The final type of assertion is the +// KASSERT macro which is more suitable for pure (perhaps kernel) filters as +// the condition is printed onto the debugger rather than in a message box. +// +// The other part of the debug module facilties is general purpose logging. +// This is accessed by calling DbgLog(). The function takes a type and level +// field which define the type of informational string you are presenting and +// it's relative importance. The type field can be a combination (one or more) +// of LOG_TIMING, LOG_TRACE, LOG_MEMORY, LOG_LOCKING and LOG_ERROR. The level +// is a DWORD value where zero defines highest important. Use of zero as the +// debug logging level is to be encouraged ONLY for major errors or events as +// they will ALWAYS be displayed on the debugger. Other debug output has it's +// level matched against the current debug output level stored in the registry +// for this module and if less than the current setting it will be displayed. +// +// Each module or executable has it's own debug output level for each of the +// five types. These are read in when the DbgInitialise function is called +// for DLLs linking to STRMBASE.LIB this is done automatically when the DLL +// is loaded, executables must call it explicitely with the module instance +// handle given to them through the WINMAIN entry point. An executable must +// also call DbgTerminate when they have finished to clean up the resources +// the debug library uses, once again this is done automatically for DLLs + +// These are the five different categories of logging information + +#ifdef _DEBUG + + +enum +{ + LOG_TRACE = 0x00000001, // General tracing + LOG_ENTRY = 0x00000002, // Function entry logging + LOG_EXIT = 0x00000004, // Function exit logging + LOG_MEMORY = 0x00000008, // Memory alloc/free debugging + LOG_ERROR = 0x00000010, // Error notification + LOG_UNUSED0 = 0x00000020, // reserved + LOG_UNUSED1 = 0x00000040, // reserved + LOG_UNUSED2 = 0x00000080, // reserved + LOG_CHUM = 0x00000100, // Chumtoad debugging + LOG_LEECH = 0x00000200, // Leech debugging + LOG_ICHTHYOSAUR = 0x00000400, // Ichthyosaur debugging +}; + + +// These are public but should be called only by the DLLMain function +void WINAPI DbgInitialise(HINSTANCE hInst); +void WINAPI DbgTerminate(); +// These are public but should be called by macro only +void WINAPI DbgKernelAssert(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine); +void WINAPI DbgLogInfo(DWORD Type,DWORD Level,const TCHAR *pFormat,...); +void WINAPI DbgOutString(LPCTSTR psz); + + +// These are the macros that should be used in code. + +#define DBGASSERT(_x_) \ + if (!(_x_)) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + +#define DBGBREAK(_x_) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + +#define DBGASSERTEXECUTE(_x_) DBGASSERT(_x_) + +#define DBGLOG(_x_) DbgLogInfo _x_ + +#define DBGOUT(_x_) DbgOutString(_x_) + +#define ValidateReadPtr(p,cb) \ + {if(IsBadReadPtr((PVOID)p,cb) == TRUE) \ + DBGBREAK("Invalid read pointer");} + +#define ValidateWritePtr(p,cb) \ + {if(IsBadWritePtr((PVOID)p,cb) == TRUE) \ + DBGBREAK("Invalid write pointer");} + +#define ValidateReadWritePtr(p,cb) \ + {ValidateReadPtr(p,cb) ValidateWritePtr(p,cb)} + +#define ValidateStringPtr(p) \ + {if(IsBadStringPtr((LPCTSTR)p,INFINITE) == TRUE) \ + DBGBREAK("Invalid string pointer");} + +#define ValidateStringPtrA(p) \ + {if(IsBadStringPtrA((LPCSTR)p,INFINITE) == TRUE) \ + DBGBREAK("Invalid ANSII string pointer");} + +#define ValidateStringPtrW(p) \ + {if(IsBadStringPtrW((LPCWSTR)p,INFINITE) == TRUE) \ + DBGBREAK("Invalid UNICODE string pointer");} + +#else // !_DEBUG + +// Retail builds make public debug functions inert - WARNING the source +// files do not define or build any of the entry points in debug builds +// (public entry points compile to nothing) so if you go trying to call +// any of the private entry points in your source they won't compile + +#define DBGASSERT(_x_) +#define DBGBREAK(_x_) +#define DBGASSERTEXECUTE(_x_) _x_ +#define DBGLOG(_x_) +#define DBGOUT(_x_) +#define ValidateReadPtr(p,cb) +#define ValidateWritePtr(p,cb) +#define ValidateReadWritePtr(p,cb) +#define ValidateStringPtr(p) +#define ValidateStringPtrA(p) +#define ValidateStringPtrW(p) + +#endif // !_DEBUG + + +#ifndef REMIND + // REMIND macro - generates warning as reminder to complete coding + // (eg) usage: + // + // #pragma message (REMIND("Add automation support")) + + + #define REMINDQUOTE(x) #x + #define REMINDQQUOTE(y) REMINDQUOTE(y) + #define REMIND(str) __FILE__ "(" REMINDQQUOTE(__LINE__) ") : " str +#endif + +#endif // __WXDEBUG__ + + diff --git a/engine/eiface.h b/engine/eiface.h index dc5ff1f..50439ba 100644 --- a/engine/eiface.h +++ b/engine/eiface.h @@ -15,11 +15,7 @@ #ifndef EIFACE_H #define EIFACE_H -#ifdef HLDEMO_BUILD -#define INTERFACE_VERSION 001 -#else // !HLDEMO_BUILD, i.e., regular version of HL #define INTERFACE_VERSION 140 -#endif // !HLDEMO_BUILD #include #include "custom.h" @@ -33,10 +29,11 @@ // This is conveniently done for them in extdll.h // +/* Set up Linux Compile! */ #ifdef _WIN32 #define DLLEXPORT __stdcall #else -#define DLLEXPORT /* */ +#define DLLEXPORT __attribute__((stdcall)) /* */ #endif typedef enum @@ -258,6 +255,8 @@ typedef struct enginefuncs_s // NOTE: these functions take player entity indices (starting at 1). qboolean (*pfnVoice_GetClientListening)(int iReceiver, int iSender); qboolean (*pfnVoice_SetClientListening)(int iReceiver, int iSender, qboolean bListen); + + const char *(*pfnGetPlayerAuthId) ( edict_t *e ); } enginefuncs_t; // ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 diff --git a/engine/progdefs.h b/engine/progdefs.h index 506825a..56a84ac 100644 --- a/engine/progdefs.h +++ b/engine/progdefs.h @@ -27,7 +27,7 @@ typedef struct string_t startspot; float deathmatch; float coop; - float teamplay; + float gametype; float serverflags; float found_secrets; vec3_t v_forward; diff --git a/game_shared/twm.cpp b/game_shared/twm.cpp new file mode 100644 index 0000000..f6417ec --- /dev/null +++ b/game_shared/twm.cpp @@ -0,0 +1,223 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// twm.cpp -> .TWM loading code +// +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "ref_params.h" +#include "event_api.h" +#include "r_efx.h" +#include "twm.h" +#include "twmmanager.h" + +CTwmManager g_TwmManager; + +/* + CTwmModel methods +*/ +CTwmModel::CTwmModel() +{ + twminfo.num_vertices = 0; + twminfo.vertex_lump = NULL; + + twminfo.num_tris = 0; + twminfo.triangle_lump = NULL; + + twminfo.num_materialgroups = 0; + twminfo.materialgroup_lump = NULL; +} + +CTwmModel::~CTwmModel() +{ + // free all our data used + if(twminfo.num_vertices) + delete[] twminfo.vertex_lump; + + if(twminfo.num_tris) + delete[] twminfo.triangle_lump; + + if(twminfo.num_materialgroups) + { + for(int i = 0;i < twminfo.num_materialgroups;i++) + delete[] twminfo.materialgroup_lump[i].tris_indices; + + delete[] twminfo.materialgroup_lump; + } +} + +/* + CTwmManager methods +*/ +CTwmManager::CTwmManager() +{ +} + +CTwmManager::~CTwmManager() +{ + // Remove all precached models + for(int i = 0;i < vecModels.size();i++) + delete vecModels[i]; + + vecModels.clear(); +} + +void CTwmManager::ParseVertexLump(FILE *pFile,CTwmModel *pTwmModel) +{ + // Vertex count + fread((char*)&pTwmModel->twminfo.num_vertices,sizeof(short),1,pFile); + + // Allocate and store verts + pTwmModel->twminfo.vertex_lump = new twm_vert_t[pTwmModel->twminfo.num_vertices]; + fread((char*)pTwmModel->twminfo.vertex_lump,sizeof(twm_vert_t)*pTwmModel->twminfo.num_vertices,1,pFile); +} + +void CTwmManager::ParseTriangleLump(FILE *pFile,CTwmModel *pTwmModel) +{ + // Triangle count + fread((char*)&pTwmModel->twminfo.num_tris,sizeof(short),1,pFile); + + // allocate and store triangle info + pTwmModel->twminfo.triangle_lump = new twm_triangle_t[pTwmModel->twminfo.num_tris]; + + for(int i = 0;i < pTwmModel->twminfo.num_tris;i++) + { + twm_triangle_t *cur_tri = &pTwmModel->twminfo.triangle_lump[i]; + + fread((char*)cur_tri->vert_indices,sizeof(short)*3,1,pFile); + fread((char*)cur_tri->u,sizeof(float)*3,1,pFile); + fread((char*)cur_tri->v,sizeof(float)*3,1,pFile); + } +} + +void CTwmManager::ParseMaterialLump(FILE *pFile,CTwmModel *pTwmModel) +{ + // Material count + fread((char*)&pTwmModel->twminfo.num_materialgroups,sizeof(short),1,pFile); + + // allocate and store material info + pTwmModel->twminfo.materialgroup_lump = new twm_materialgroup_t[pTwmModel->twminfo.num_materialgroups]; + + for(int i = 0;i < pTwmModel->twminfo.num_materialgroups;i++) + { + twm_materialgroup_t *cur_mat = &pTwmModel->twminfo.materialgroup_lump[i]; + + fread(cur_mat->texturename,sizeof(char)*64,1,pFile); + + // allocate triangle list + fread((char*)&cur_mat->num_triangles,sizeof(short),1,pFile); + cur_mat->tris_indices = new short[cur_mat->num_triangles]; + fread((char*)cur_mat->tris_indices,sizeof(short)*cur_mat->num_triangles,1,pFile); + } +} + +// just use C style i/o +int CTwmManager::PrecacheModel(string filename) +{ + char path[256]; + CTwmModel *TwmModel = new CTwmModel; + FILE *pFile; + + // store full path + sprintf(path,"%s/%s",gEngfuncs.pfnGetGameDirectory(),filename.c_str()); + gEngfuncs.Con_DPrintf("TWM: Loading Model %s\n",filename.c_str()); + pFile = fopen(path,"rb"); + + if(pFile == NULL) + { + gEngfuncs.Con_DPrintf("TWM ERROR: Invalid file %s\n",filename.c_str()); + return 0; + } + + // Put basic information into twm model + TwmModel->filename = filename; + + fread((char*)&TwmModel->twminfo.header_id,sizeof(int),1,pFile); + fread((char*)&TwmModel->twminfo.major_version,sizeof(short),1,pFile); + fread((char*)&TwmModel->twminfo.minor_version,sizeof(short),1,pFile); + + if(TwmModel->twminfo.header_id == TWM_ID) + { + if(TwmModel->twminfo.major_version == TWM_MAJOR_VERSION) + { + // Only warning if minor versions differ + if(TwmModel->twminfo.minor_version != TWM_MINOR_VERSION) + gEngfuncs.Con_DPrintf("TWM WARNING: Different minor version for %s, expected %i got %i\n",filename.c_str(),TWM_MINOR_VERSION,TwmModel->twminfo.minor_version); + + // Start parsing! + ParseVertexLump(pFile,TwmModel); + ParseTriangleLump(pFile,TwmModel); + ParseMaterialLump(pFile,TwmModel); + + // push onto vector for storage + vecModels.push_back(TwmModel); + + goto precache_noerror; + } + else + gEngfuncs.Con_DPrintf("TWM ERROR: Invalid version for %s, expected %i got %i\n",filename.c_str(),TWM_MAJOR_VERSION,TwmModel->twminfo.major_version); + } + else + gEngfuncs.Con_DPrintf("TWM ERROR: Invalid header for %s\n",filename.c_str()); + + fclose(pFile); + return 0; +precache_noerror: + fclose(pFile); + return 1; +} + +void CTwmManager::BeginPrecache() +{ + // Remove all precached models + for(int i = 0;i < vecModels.size();i++) + delete vecModels[i]; + + vecModels.clear(); + + // Start precaching! + GetModelByName("models/muz_test.twm"); +} + +CTwmModel *CTwmManager::GetModelByName(string filename) +{ + for(int i = 0;i < vecModels.size();i++) + if(vecModels[i]->filename == filename) + return vecModels[i]; + + // Oops! we couldnt find the model, precache and return that + if(PrecacheModel(filename)) + return vecModels[vecModels.size()-1]; + + return NULL; +} + +// Update a specific twm object +void CTwmManager::TwmUpdate(twm_clientinfo_t *clientinfo, double frametime) +{ + bool bDie = false; + + // fade out model + clientinfo->color[3] -= clientinfo->fadetime * frametime; + if(clientinfo->color[3] <= 0.0f) + bDie = true; + + // set dead var + clientinfo->dead = bDie; +} + diff --git a/game_shared/vgui_loadtga.cpp b/game_shared/vgui_loadtga.cpp index fd90571..262f988 100644 --- a/game_shared/vgui_loadtga.cpp +++ b/game_shared/vgui_loadtga.cpp @@ -67,9 +67,10 @@ vgui::BitmapTGA* vgui_LoadTGA(char const *pFilename) MemoryInputStream stream; stream.m_pData = gEngfuncs.COM_LoadFile((char*)pFilename, 5, &stream.m_DataLen); + if(!stream.m_pData) return NULL; - + stream.m_ReadPos = 0; vgui::BitmapTGA *pRet = new vgui::BitmapTGA(&stream, true); gEngfuncs.COM_FreeFile(stream.m_pData); diff --git a/game_shared/vgui_loadtga.h b/game_shared/vgui_loadtga.h index 2a9c0b2..a4ef95c 100644 --- a/game_shared/vgui_loadtga.h +++ b/game_shared/vgui_loadtga.h @@ -11,12 +11,9 @@ #pragma once #endif - #include "vgui_bitmaptga.h" - vgui::BitmapTGA* vgui_LoadTGA(char const *pFilename); vgui::BitmapTGA* vgui_LoadTGANoInvertAlpha(char const *pFilename); - #endif // VGUI_LOADTGA_H diff --git a/game_shared/vgui_scrollbar2.cpp b/game_shared/vgui_scrollbar2.cpp index acfbb0d..9338322 100644 --- a/game_shared/vgui_scrollbar2.cpp +++ b/game_shared/vgui_scrollbar2.cpp @@ -8,12 +8,15 @@ #include "VGUI_ScrollBar2.h" #include "VGUI_Slider2.h" -#include "vgui_loadtga.h" #include #include #include #include +#include + +#include "../cl_dll/tw_vgui.h" +#include "vgui_loadtga.h" using namespace vgui; diff --git a/game_shared/voice_banmgr.cpp b/game_shared/voice_banmgr.cpp index af29510..9e2dabc 100644 --- a/game_shared/voice_banmgr.cpp +++ b/game_shared/voice_banmgr.cpp @@ -26,8 +26,6 @@ unsigned char HashPlayerID(char const playerID[16]) return curHash; } - - CVoiceBanMgr::CVoiceBanMgr() { Clear(); diff --git a/game_shared/voice_gamemgr.cpp b/game_shared/voice_gamemgr.cpp index 28c35f7..bbec3f4 100644 --- a/game_shared/voice_gamemgr.cpp +++ b/game_shared/voice_gamemgr.cpp @@ -13,8 +13,6 @@ #include "cbase.h" #include "player.h" - - #define UPDATE_INTERVAL 0.3 diff --git a/game_shared/voice_status.cpp b/game_shared/voice_status.cpp index 2452ccc..41248a6 100644 --- a/game_shared/voice_status.cpp +++ b/game_shared/voice_status.cpp @@ -5,28 +5,16 @@ // $NoKeywords: $ //============================================================================= -// There are hud.h's coming out of the woodwork so this ensures that we get the right one. -#if defined( DMC_BUILD ) - #include "../dmc/cl_dll/hud.h" - #include "../dmc/cl_dll/cl_util.h" -#else - #include "../cl_dll/hud.h" - #include "../cl_dll/cl_util.h" -#endif +#include "../cl_dll/hud.h" +#include "../cl_dll/cl_util.h" #include #include #include -#if defined( DMC_BUILD ) - #include "../dmc/cl_dll/parsemsg.h" - #include "../dmc/cl_dll/hud_servers.h" - #include "../dmc/cl_dll/demo.h" -#else - #include "../cl_dll/parsemsg.h" - #include "../cl_dll/hud_servers.h" - #include "../cl_dll/demo.h" -#endif +#include "../cl_dll/parsemsg.h" +#include "../cl_dll/hud_servers.h" +#include "../cl_dll/demo.h" #include "demo_api.h" #include "voice_status.h" @@ -39,6 +27,8 @@ #include "vgui_helpers.h" #include "vgui_mousecode.h" +#include "../cl_dll/tw_vgui.h" + using namespace vgui; extern int cam_thirdperson; @@ -373,7 +363,6 @@ void CVoiceStatus::CreateEntities() } } - void CVoiceStatus::UpdateSpeakerStatus(int entindex, qboolean bTalking) { if(!*m_pParentPanel) @@ -430,14 +419,14 @@ void CVoiceStatus::UpdateSpeakerStatus(int entindex, qboolean bTalking) if( pLabel->m_pBackground ) { - pLabel->m_pBackground->setBgColor( color[0], color[1], color[2], 100 ); + pLabel->m_pBackground->setBgColor( TW_PANEL_BG_RGBA ); pLabel->m_pBackground->setParent( *m_pParentPanel ); pLabel->m_pBackground->setVisible( m_pHelper->CanShowSpeakerLabels() ); } if( pLabel->m_pLabel ) { - pLabel->m_pLabel->setFgColor( 255, 255, 255, 0 ); + pLabel->m_pLabel->setFgColor( color[0],color[1],color[2], 0 ); pLabel->m_pLabel->setBgColor( 0, 0, 0, 255 ); pLabel->m_pLabel->setText( paddedName ); } diff --git a/game_shared/voice_vgui_tweakdlg.cpp b/game_shared/voice_vgui_tweakdlg.cpp index 2812b76..814ab3b 100644 --- a/game_shared/voice_vgui_tweakdlg.cpp +++ b/game_shared/voice_vgui_tweakdlg.cpp @@ -7,7 +7,8 @@ #include "../cl_dll/hud.h" #include "../cl_dll/cl_util.h" -#include "../cl_dll/vgui_teamfortressviewport.h" +#include "../cl_dll/tw_vgui.h" +#include "../cl_dll/vgui_TheWastesViewport.h" #include "vgui_actionsignal.h" diff --git a/pm_shared/pm_materials.h b/pm_shared/pm_materials.h index 88bba3b..eef026b 100644 --- a/pm_shared/pm_materials.h +++ b/pm_shared/pm_materials.h @@ -29,5 +29,11 @@ #define CHAR_TEX_COMPUTER 'P' #define CHAR_TEX_GLASS 'Y' #define CHAR_TEX_FLESH 'F' +#define CHAR_TEX_BARREL 'B' +#define CHAR_TEX_TIN 'N' +#define CHAR_TEX_DRYWALL 'U' +#define CHAR_TEX_RUST 'R' +#define CHAR_TEX_SAND 'I' +#define CHAR_TEX_SNOW 'E' #endif // !PM_MATERIALSH diff --git a/pm_shared/pm_math.c b/pm_shared/pm_math.c index b1b7625..e7f3c44 100644 --- a/pm_shared/pm_math.c +++ b/pm_shared/pm_math.c @@ -12,7 +12,7 @@ * without written permission from Valve LLC. * ****/ -// pm_math.c -- math primitives +// pm_math.cpp -- math primitives #include "mathlib.h" #include "const.h" diff --git a/pm_shared/pm_shared.c b/pm_shared/pm_shared.c index d31a942..5e61833 100644 --- a/pm_shared/pm_shared.c +++ b/pm_shared/pm_shared.c @@ -104,6 +104,12 @@ typedef struct hull_s #define CHAR_TEX_COMPUTER 'P' #define CHAR_TEX_GLASS 'Y' #define CHAR_TEX_FLESH 'F' +#define CHAR_TEX_BARREL 'B' +#define CHAR_TEX_TIN 'N' +#define CHAR_TEX_DRYWALL 'U' +#define CHAR_TEX_RUST 'R' +#define CHAR_TEX_SAND 'I' +#define CHAR_TEX_SNOW 'E' #define STEP_CONCRETE 0 // default step sound #define STEP_METAL 1 // metal floor @@ -114,6 +120,10 @@ typedef struct hull_s #define STEP_SLOSH 6 // shallow liquid puddle #define STEP_WADE 7 // wading in liquid #define STEP_LADDER 8 // climbing ladder +#define STEP_RUST 9 +#define STEP_SAND 10 +#define STEP_WOOD 11 +#define STEP_SNOW 12 #define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet #define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet @@ -150,11 +160,14 @@ static int rgStuckLast[MAX_CLIENTS][2]; // Texture names static int gcTextures = 0; +static qboolean gbTextureTypeInit = false; static char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; static char grgchTextureType[CTEXTURESMAX]; int g_onladder = 0; +int permanent_maxspeed; // since we're dicking around with this value, keep a backup! + void PM_SwapTextures( int i, int j ) { char chTemp; @@ -190,25 +203,55 @@ void PM_SortTextures( void ) } } -void PM_InitTextureTypes() +void PM_InitMaterials() +{ + gcTextures = 0; + gbTextureTypeInit = false; + + memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); + memset(grgchTextureType, 0, CTEXTURESMAX); +} + +void PM_FinishMaterials() +{ + PM_SortTextures(); + + gbTextureTypeInit = true; +} + +// modified from sound.cpp +int PM_FindSimilarTexture(char chTextureType,char *pszTextureName) +{ + int i; + + for(i = 0;i < gcTextures;i++) + { + if(strncmp(grgszTextureName[i],pszTextureName,CBTEXTURENAMEMAX) == 0) + { + // Change the texture type if needed + grgchTextureType[i] = chTextureType; + + return 1; // Dont add this texture back into the global list + } + } + + return 0; +} + +void PM_ProcessMaterials(char *pszFilename) { char buffer[512]; int i, j; byte *pMemFile; int fileSize, filePos; - static qboolean bTextureTypeInit = false; - if ( bTextureTypeInit ) + if ( gbTextureTypeInit ) return; - memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); - memset(grgchTextureType, 0, CTEXTURESMAX); - - gcTextures = 0; memset(buffer, 0, 512); - fileSize = pmove->COM_FileSize( "sound/materials.txt" ); - pMemFile = pmove->COM_LoadFile( "sound/materials.txt", 5, NULL ); + fileSize = pmove->COM_FileSize( pszFilename ); + pMemFile = pmove->COM_LoadFile( pszFilename, 5, NULL ); if ( !pMemFile ) return; @@ -216,6 +259,9 @@ void PM_InitTextureTypes() // for each line in the file... while ( pmove->memfgets( pMemFile, fileSize, &filePos, buffer, 511 ) != NULL && (gcTextures < CTEXTURESMAX) ) { + char chTextureType; + char szTextureName[CBTEXTURENAMEMAX]; + // skip whitespace i = 0; while(buffer[i] && isspace(buffer[i])) @@ -228,8 +274,7 @@ void PM_InitTextureTypes() if (buffer[i] == '/' || !isalpha(buffer[i])) continue; - // get texture type - grgchTextureType[gcTextures] = toupper(buffer[i++]); + chTextureType = toupper(buffer[i++]); // skip whitespace while(buffer[i] && isspace(buffer[i])) @@ -249,15 +294,20 @@ void PM_InitTextureTypes() // null-terminate name and save in sentences array j = min (j, CBTEXTURENAMEMAX-1+i); buffer[j] = 0; + + strcpy(&(szTextureName[0]),&(buffer[i])); + + if(PM_FindSimilarTexture(chTextureType,szTextureName)) + continue; + + // put values into global array + grgchTextureType[gcTextures] = chTextureType; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); } // Must use engine to free since we are in a .dll pmove->COM_FreeFile ( pMemFile ); - - PM_SortTextures(); - - bTextureTypeInit = true; } char PM_FindTextureType( char *name ) @@ -308,14 +358,14 @@ void PM_PlayStepSound( int step, float fvol ) irand = pmove->RandomLong(0,1) + ( pmove->iStepLeft * 2 ); // FIXME mp_footsteps needs to be a movevar - if ( pmove->multiplayer && !pmove->movevars->footsteps ) - return; +// if ( pmove->multiplayer && !pmove->movevars->footsteps ) +// return; VectorCopy( pmove->velocity, hvel ); hvel[2] = 0.0; - if ( pmove->multiplayer && ( !g_onladder && Length( hvel ) <= 220 ) ) - return; +// if ( pmove->multiplayer && ( !g_onladder && Length( hvel ) <= 220 ) ) +// return; // irand - 0,1 for right foot, 2,3 for left foot // used to alternate left and right foot @@ -437,6 +487,55 @@ void PM_PlayStepSound( int step, float fvol ) case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; } break; + + case STEP_RUST: + switch(irand) + { + // right footpmove->PM_PlaySound( CHAN_BODY, "player/pl_rust1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_rust1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_rust3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_rust2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_rust4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + + case STEP_SAND: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_sand1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_sand3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_sand2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_sand4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + + case STEP_SNOW: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_snow1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_snow3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_snow2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_snow4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + + case STEP_WOOD: + switch(irand) + { + // right foot + + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wood1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wood3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wood2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wood4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; } } @@ -444,14 +543,20 @@ int PM_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; + default: + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_TIN: + case CHAR_TEX_BARREL: + 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; + case CHAR_TEX_RUST: return STEP_RUST; + case CHAR_TEX_SAND: return STEP_SAND; + case CHAR_TEX_SNOW: return STEP_SNOW; + case CHAR_TEX_WOOD: return STEP_WOOD; } } @@ -498,15 +603,11 @@ void PM_CatagorizeTextureType( void ) void PM_UpdateStepSound( void ) { - int fWalking; float fvol; vec3_t knee; vec3_t feet; vec3_t center; float height; - float speed; - float velrun; - float velwalk; float flduck; int fLadder; int step; @@ -519,33 +620,25 @@ void PM_UpdateStepSound( void ) PM_CatagorizeTextureType(); - speed = Length( pmove->velocity ); - // determine if we are on a ladder fLadder = ( pmove->movetype == MOVETYPE_FLY );// IsOnLadder(); - // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + // We now compare against permanent_maxspeed, so players who have loads of equipment + // still make noise - gage if ( ( pmove->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 = 100; - } else - { - velwalk = 120; - velrun = 210; - flduck = 0; - } + flduck = 0; // If we're on a ladder or on the ground, and we're moving fast enough, // play step sound. Also, if pmove->flTimeStepSound is zero, get the new // sound right away - we just started moving in new level. if ( (fLadder || ( pmove->onground != -1 ) ) && ( Length( pmove->velocity ) > 0.0 ) && - ( speed >= velwalk || !pmove->flTimeStepSound ) ) + ( !pmove->flTimeStepSound ) ) { - fWalking = speed < velrun; + int iWalking = (pmove->maxspeed > Length(pmove->velocity) * 2); VectorCopy( pmove->origin, center ); VectorCopy( pmove->origin, knee ); @@ -560,20 +653,20 @@ void PM_UpdateStepSound( void ) if (fLadder) { step = STEP_LADDER; - fvol = 0.35; + fvol = 0.30; pmove->flTimeStepSound = 350; } else if ( pmove->PM_PointContents ( knee, NULL ) == CONTENTS_WATER ) { step = STEP_WADE; - fvol = 0.65; + fvol = 0.4; pmove->flTimeStepSound = 600; } else if ( pmove->PM_PointContents ( feet, NULL ) == CONTENTS_WATER ) { step = STEP_SLOSH; - fvol = fWalking ? 0.2 : 0.5; - pmove->flTimeStepSound = fWalking ? 400 : 300; + fvol = 0.4; + pmove->flTimeStepSound = 300; } else { @@ -585,38 +678,38 @@ void PM_UpdateStepSound( void ) { default: case CHAR_TEX_CONCRETE: - fvol = fWalking ? 0.2 : 0.5; - pmove->flTimeStepSound = fWalking ? 400 : 300; + fvol = 0.4; + pmove->flTimeStepSound = iWalking ? 400 : 300; break; case CHAR_TEX_METAL: - fvol = fWalking ? 0.2 : 0.5; - pmove->flTimeStepSound = fWalking ? 400 : 300; + fvol = 0.5; + pmove->flTimeStepSound = iWalking ? 400 : 300; break; case CHAR_TEX_DIRT: - fvol = fWalking ? 0.25 : 0.55; - pmove->flTimeStepSound = fWalking ? 400 : 300; + fvol = 0.4; + pmove->flTimeStepSound = iWalking ? 400 : 300; break; case CHAR_TEX_VENT: - fvol = fWalking ? 0.4 : 0.7; - pmove->flTimeStepSound = fWalking ? 400 : 300; + fvol = 0.5; + pmove->flTimeStepSound = iWalking ? 400 : 300; break; case CHAR_TEX_GRATE: - fvol = fWalking ? 0.2 : 0.5; - pmove->flTimeStepSound = fWalking ? 400 : 300; + fvol = 0.4; + pmove->flTimeStepSound = iWalking ? 400 : 300; break; case CHAR_TEX_TILE: - fvol = fWalking ? 0.2 : 0.5; - pmove->flTimeStepSound = fWalking ? 400 : 300; + fvol = 0.4; + pmove->flTimeStepSound = iWalking ? 400 : 300; break; case CHAR_TEX_SLOSH: - fvol = fWalking ? 0.2 : 0.5; - pmove->flTimeStepSound = fWalking ? 400 : 300; + fvol = 0.4; + pmove->flTimeStepSound = iWalking ? 400 : 300; break; } } @@ -627,8 +720,14 @@ void PM_UpdateStepSound( void ) // 35% volume if ducking if ( pmove->flags & FL_DUCKING ) { - fvol *= 0.35; + fvol *= 0.20; } + else if( iWalking ) + { + fvol *= 0.30f; + } + else + fvol *= 0.75f; PM_PlayStepSound( step, fvol ); } @@ -1010,7 +1109,7 @@ void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel) // Determine amount of accleration. accelspeed = accel * pmove->frametime * wishspeed * pmove->friction; - + // Cap at addspeed if (accelspeed > addspeed) accelspeed = addspeed; @@ -1187,7 +1286,6 @@ usedown: VectorCopy (downvel, pmove->velocity); } else // copy z value from slide move pmove->velocity[2] = downvel[2]; - } /* @@ -1808,6 +1906,7 @@ void PM_SpectatorMove (void) if ( pmove->runfuncs ) { + // Set spectator flag iIsSpectator = SPEC_IS_SPECTATOR; } @@ -2615,8 +2714,7 @@ PM_Jump void PM_Jump (void) { int i; - qboolean tfc = false; - + qboolean cansuperjump = false; if (pmove->dead) @@ -2625,15 +2723,6 @@ void PM_Jump (void) return; } - tfc = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "tfc" ) ) == 1 ? true : false; - - // Spy that's feigning death cannot jump - if ( tfc && - ( pmove->deadflag == ( DEAD_DISCARDBODY + 1 ) ) ) - { - return; - } - // See if we are waterjumping. If so, decrement count and return. if ( pmove->waterjumptime ) { @@ -2700,14 +2789,7 @@ void PM_Jump (void) PM_PreventMegaBunnyJumping(); - if ( tfc ) - { - pmove->PM_PlaySound( CHAN_BODY, "player/plyrjmp8.wav", 0.5, ATTN_NORM, 0, PITCH_NORM ); - } - else - { - PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chtexturetype ), 1.0 ); - } + PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chtexturetype ), 1.0 ); // See if user can super long jump? cansuperjump = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "slj" ) ) == 1 ? true : false; @@ -2847,14 +2929,6 @@ void PM_CheckFalling( void ) } else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) { - qboolean tfc = false; - tfc = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "tfc" ) ) == 1 ? true : false; - - if ( tfc ) - { - pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); - } - fvol = 0.85; } else if ( pmove->flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) @@ -2885,6 +2959,8 @@ void PM_CheckFalling( void ) if ( pmove->onground != -1 ) { pmove->flFallVelocity = 0; + + pmove->vuser2[2] = 1; // This player can scream again } } @@ -3086,7 +3162,7 @@ void PM_PlayerMove ( qboolean server ) physent_t *pLadder = NULL; // Are we running server code? - pmove->server = server; + pmove->server = server; // Adjust speeds etc. PM_CheckParamters(); @@ -3140,6 +3216,16 @@ void PM_PlayerMove ( qboolean server ) if ( pmove->onground == -1 ) { pmove->flFallVelocity = -pmove->velocity[2]; + + // See if this player should scream + // TODO: Finalize this code +/* if(pmove->vuser2[2] && pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED) + { + pmove->PM_PlaySound( CHAN_VOICE, "player/aaaggghhh.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + pmove->Con_Printf("O GAB SCREAM FOR ME\n"); + + pmove->vuser2[2] = 0; // Dont scream until we are on the ground + }*/ } g_onladder = 0; @@ -3453,8 +3539,6 @@ void PM_CreateStuckTable( void ) } } - - /* This modume implements the shared player physics code between any particular game and the engine. The same PM_Move routine is built into the game .dll and the client .dll and is @@ -3467,8 +3551,32 @@ void PM_Move ( struct playermove_s *ppmove, int server ) assert( pm_shared_initialized ); pmove = ppmove; + + permanent_maxspeed = pmove->maxspeed; + + // Dont run physics + if(ppmove->vuser3[1]) + return; + + // Weight system: subtract maxspeed by + // our submitted weight value for + // the clients weapon (set in think) + permanent_maxspeed -= pmove->fuser4; + + // Set mod defined speed variations. + if(pmove->fuser1 != 0.0) + pmove->clientmaxspeed = pmove->maxspeed = permanent_maxspeed * pmove->fuser1; + else + pmove->clientmaxspeed = pmove->maxspeed = permanent_maxspeed; + + // Crippled player? + if(pmove->fuser2 > 0.0) + { + pmove->fuser2 += pmove->fuser3; + pmove->clientmaxspeed /= pmove->fuser2 / 4; + } - PM_PlayerMove( ( server != 0 ) ? true : false ); + PM_PlayerMove((server != 0 ) ? true : false ); if ( pmove->onground != -1 ) { @@ -3495,6 +3603,9 @@ int PM_GetInfo( int ent ) return -1; } + +char gszCurrentLevel[512]; + void PM_Init( struct playermove_s *ppmove ) { assert( !pm_shared_initialized ); @@ -3502,7 +3613,44 @@ void PM_Init( struct playermove_s *ppmove ) pmove = ppmove; PM_CreateStuckTable(); - PM_InitTextureTypes(); + + memset(gszCurrentLevel,0,sizeof(gszCurrentLevel)); pm_shared_initialized = 1; } + +void PM_CheckMap(char *pszMapname) +{ + assert( pm_shared_initialized ); + + if(strcmp(pszMapname,gszCurrentLevel)) + strcpy(gszCurrentLevel,pszMapname); + else + return; // We already have this map info loaded + + PM_InitMaterials(); + + PM_ProcessMaterials("sound/materials.txt"); + + if(pszMapname != NULL) + { + char szResolvedMapname[512]; + +#ifdef CLIENT_DLL + // Client dll has the full mapname, so lets strip the extension + char *pszExtension; + + strcpy(szResolvedMapname,pszMapname); + + // Replace extension + pszExtension = strstr(szResolvedMapname,".bsp"); + strcpy(pszExtension,".mat"); +#else + sprintf(szResolvedMapname,"maps/%s.mat",pszMapname); +#endif + + PM_ProcessMaterials(szResolvedMapname); + } + + PM_FinishMaterials(); +} diff --git a/pm_shared/pm_shared.h b/pm_shared/pm_shared.h index 74e2a19..fcce70e 100644 --- a/pm_shared/pm_shared.h +++ b/pm_shared/pm_shared.h @@ -21,6 +21,7 @@ #pragma once void PM_Init( struct playermove_s *ppmove ); +void PM_LoadMaterials(char *pszMapname); void PM_Move ( struct playermove_s *ppmove, int server ); char PM_FindTextureType( char *name ); diff --git a/utils/WastedFX/Resource.aps b/utils/WastedFX/Resource.aps new file mode 100644 index 0000000..92ee808 Binary files /dev/null and b/utils/WastedFX/Resource.aps differ diff --git a/utils/WastedFX/Resource.rc b/utils/WastedFX/Resource.rc new file mode 100644 index 0000000..f169784 --- /dev/null +++ b/utils/WastedFX/Resource.rc @@ -0,0 +1,110 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON DISCARDABLE "wfx_icon.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_MENUMAIN MENU DISCARDABLE +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&New...", ID_FILE_NEW1 + MENUITEM "&Open...", ID_FILE_OPEN1 + MENUITEM "&Save", ID_FILE_SAVE1 + MENUITEM "S&ave As...", ID_FILE_SAVEAS + MENUITEM SEPARATOR + MENUITEM "E&xit", ID_FILE_QUIT + END + POPUP "&Edit" + BEGIN + MENUITEM "Cu&t", ID_EDIT_CUT1 + MENUITEM "&Copy", ID_EDIT_COPY1 + MENUITEM "&Paste", ID_EDIT_PASTE1 + MENUITEM SEPARATOR + MENUITEM "&Options...", ID_EDIT_OPTIONS + END + POPUP "&Preview" + BEGIN + MENUITEM "Emi&tter Preview", ID_PREVIEW_EMITTERPREVIEW + MENUITEM "&Map Preview...", ID_PREVIEW_MAPPREVIEW + MENUITEM "M&odel Preview...", ID_PREVIEW_MODELPREVIEW + END + POPUP "&Help" + BEGIN + MENUITEM "&Online Help...", ID_HELP_ONLINEHELP + MENUITEM "&About...", ID_HELP_ABOUT + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/WastedFX/WastedFX.dsp b/utils/WastedFX/WastedFX.dsp new file mode 100644 index 0000000..6240620 --- /dev/null +++ b/utils/WastedFX/WastedFX.dsp @@ -0,0 +1,167 @@ +# Microsoft Developer Studio Project File - Name="WastedFX" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=WastedFX - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "WastedFX.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "WastedFX.mak" CFG="WastedFX - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "WastedFX - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "WastedFX - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "WastedFX - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../../obj/WastedFX/Release" +# PROP Intermediate_Dir "../../obj/WastedFX/Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "inc/" /I "../../common/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"../../../WastedFX.exe" + +!ELSEIF "$(CFG)" == "WastedFX - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "../../obj/WastedFX/Debug" +# PROP Intermediate_Dir "../../obj/WastedFX/Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "inc/" /I "../../common/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"../../../WastedFX_Debug.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "WastedFX - Win32 Release" +# Name "WastedFX - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\src\Camera.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\Entry.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\GLWindow.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\MainWindow.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\Timer.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\util.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\Window.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\inc\Camera.h +# End Source File +# Begin Source File + +SOURCE=.\inc\GLWindow.h +# End Source File +# Begin Source File + +SOURCE=.\inc\MainWindow.h +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# Begin Source File + +SOURCE=.\inc\Timer.h +# End Source File +# Begin Source File + +SOURCE=.\inc\WastedFX.h +# End Source File +# Begin Source File + +SOURCE=.\inc\Window.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\Resource.rc +# End Source File +# Begin Source File + +SOURCE=.\wfx_icon.ico +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/WastedFX/WastedFX.dsw b/utils/WastedFX/WastedFX.dsw new file mode 100644 index 0000000..6c87cb4 --- /dev/null +++ b/utils/WastedFX/WastedFX.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "WastedFX"=.\WastedFX.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/utils/WastedFX/inc/Camera.h b/utils/WastedFX/inc/Camera.h new file mode 100644 index 0000000..b6c36a7 --- /dev/null +++ b/utils/WastedFX/inc/Camera.h @@ -0,0 +1,36 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#if !defined( __CAMERA_H_ ) +#define __CAMERA_H_ + +typedef enum mouse_mode_e { + MOUSE_NONE, + MOUSE_LEFT, + MOUSE_RIGHT, + MOUSE_BOTH, +}; + +// Camera for GL Preview +class CCamera +{ +public: + CCamera(); + CCamera( psvec3_t Origin, psvec3_t Angles ); + + void ReadInput( float flFrametime ); + void ResetInput(); + + psvec3_t m_Origin; + psvec3_t m_Angles; + + POINT m_LastPos; + int m_iMouseMode; +}; + +#endif \ No newline at end of file diff --git a/utils/WastedFX/inc/GLWindow.h b/utils/WastedFX/inc/GLWindow.h new file mode 100644 index 0000000..1ca215c --- /dev/null +++ b/utils/WastedFX/inc/GLWindow.h @@ -0,0 +1,38 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#if !defined( __GLWINDOW_H_ ) +#define __GLWINDOW_H_ + +class CCamera; +class CTimer; + +class CGLWindow : public CWindow +{ +public: + CGLWindow( char *pszClassname, HINSTANCE hInstance ); + ~CGLWindow(); + + virtual void Update(); + + void OnLButtonDown(); + void OnLButtonUp(); + void OnRButtonDown(); + void OnRButtonUp(); + + void InitGL( int iWidth, int iHeight ); + BOOL SetPixelFormat(); + void SetSize( int iWidth, int iHeight ); + + CCamera *m_pCamera; + CTimer *m_pTimer; + HDC m_hDC; + HGLRC m_hRC; +}; + +#endif \ No newline at end of file diff --git a/utils/WastedFX/inc/MainWindow.h b/utils/WastedFX/inc/MainWindow.h new file mode 100644 index 0000000..f2178df --- /dev/null +++ b/utils/WastedFX/inc/MainWindow.h @@ -0,0 +1,37 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#if !defined( __MAINWINDOW_H_ ) +#define __MAINWINDOW_H_ + +// Main background window +class CMainWindow : public CWindow +{ +public: + CMainWindow( char *pszClassname, HINSTANCE hInstance ); + ~CMainWindow(); + + virtual void Update(); + + void OnFileNew(); + void OnFileOpen(); + void OnFileSave(); + void OnFileSaveas(); + void OnFileQuit(); + void OnEditCut(); + void OnEditCopy(); + void OnEditPaste(); + void OnEditOptions(); + void OnPreviewEmitter(); + void OnPreviewMap(); + void OnPreviewModel(); + void OnHelpOnlineHelp(); + void OnHelpAbout(); +}; + +#endif \ No newline at end of file diff --git a/utils/WastedFX/inc/Timer.h b/utils/WastedFX/inc/Timer.h new file mode 100644 index 0000000..074dac0 --- /dev/null +++ b/utils/WastedFX/inc/Timer.h @@ -0,0 +1,34 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#if !defined( __TIMER_H_ ) +#define __TIMER_H_ + +class CTimer +{ +public: + CTimer(); + + void StartTimer(); + void EndTimer(); + + const float GetTime() const { return m_Time; } +private: + float m_Time; + + // QPC + BOOL m_UsePerfCounter; + LARGE_INTEGER m_PerfFrequency; + LARGE_INTEGER m_PerfStartCount; + LARGE_INTEGER m_PerfEndCount; + + // GetTickCount + float m_LastTime; +}; + +#endif \ No newline at end of file diff --git a/utils/WastedFX/inc/WastedFX.h b/utils/WastedFX/inc/WastedFX.h new file mode 100644 index 0000000..a90349a --- /dev/null +++ b/utils/WastedFX/inc/WastedFX.h @@ -0,0 +1,36 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#if !defined( __WASTEDFX_H_ ) +#define __WASTEDFX_H_ + +#include +#include +#include +#include +#include "../resource.h" +#include "mathlib.h" + +#pragma warning(disable:4244) // conversion from 'double' to 'float', possible loss of data + +// TODO: Put this in project settings ? +#pragma comment(lib, "opengl32.lib") +#pragma comment(lib, "glu32.lib") +#include +#include + +#define PROGRAM_NAME "WastedFX" + +#define WINCLASS_MAIN "WFXMainWindow" +#define WINCLASS_GL "WFXGLPreview" + +// TODO: Get rid of this when needed +//typedef float vec_t; +typedef vec_t psvec3_t[3]; + +#endif \ No newline at end of file diff --git a/utils/WastedFX/inc/Window.h b/utils/WastedFX/inc/Window.h new file mode 100644 index 0000000..9d5ef63 --- /dev/null +++ b/utils/WastedFX/inc/Window.h @@ -0,0 +1,57 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#if !defined( __WINDOW_H_ ) +#define __WINDOW_H_ + +// Error codes +enum error_codes_e { + ERR_ALERT, + ERR_INFO, + ERR_FATAL, +}; + +// Base window class +class CWindow +{ +public: + CWindow( char *pszClassname, HINSTANCE hInstance ); + virtual ~CWindow(); + + virtual void Update(); + + BOOL RegisterWindow( int style, void *WndProc, + HICON hIcon, HICON hIconSm, + HCURSOR hCursor, + HBRUSH hBrush, + HMENU hMenu ); + + BOOL InstanceWindow( char *pszTitle, int style, HWND hWndParent, + int x, int y, int w, int h ); + + static void Error( CWindow *pWindow, char *pszCaption, char *pszMsg, int iErrMode ); + + char *m_pszClassname; + HWND m_hWnd; + HWND m_hWndParent; + HMENU m_hMenu; + HINSTANCE m_hInstance; +}; + +// Support functions +template T *GetClassFromUserData( HWND hWnd ) +{ + T *pRet = (T*)(GetWindowLong( hWnd, GWL_USERDATA )); + +// if( pRet == NULL ) +// CWindow::Error( NULL, "GetClassFromUserData", "Unable to resolve class pointer", ERR_ALERT ); + + return pRet; +} + +#endif \ No newline at end of file diff --git a/utils/WastedFX/resource.h b/utils/WastedFX/resource.h new file mode 100644 index 0000000..6bccebf --- /dev/null +++ b/utils/WastedFX/resource.h @@ -0,0 +1,31 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by Resource.rc +// +#define IDI_ICON1 102 +#define IDR_MENUMAIN 105 +#define ID_FILE_QUIT 40007 +#define ID_EDIT_OPTIONS 40011 +#define ID_PREVIEW_EMITTERPREVIEW 40012 +#define ID_PREVIEW_MAPPREVIEW 40013 +#define ID_PREVIEW_MODELPREVIEW 40014 +#define ID_HELP_ONLINEHELP 40015 +#define ID_HELP_ABOUT 40016 +#define ID_FILE_SAVEAS 40017 +#define ID_EDIT_CUT1 40018 +#define ID_EDIT_COPY1 40019 +#define ID_EDIT_PASTE1 40020 +#define ID_FILE_NEW1 40021 +#define ID_FILE_OPEN1 40022 +#define ID_FILE_SAVE1 40023 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_COMMAND_VALUE 40024 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/WastedFX/src/Camera.cpp b/utils/WastedFX/src/Camera.cpp new file mode 100644 index 0000000..8200648 --- /dev/null +++ b/utils/WastedFX/src/Camera.cpp @@ -0,0 +1,136 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#include "WastedFX.h" +#include "Camera.h" + +#define CLAMP_VALUE( variable, lower, upper ) \ + if( variable > upper ) \ + variable = upper; \ + else if( variable < lower ) \ + variable = lower; + +float g_flSpeed = 0.1f; +float g_flMouseSpeed = 1000.0f; + +CCamera::CCamera() +{ + memset( &m_LastPos, 0, sizeof( POINT ) ); + m_iMouseMode = MOUSE_NONE; +} + +CCamera::CCamera( psvec3_t Origin, psvec3_t Angles ) +{ + CCamera(); + + memcpy( m_Origin, Origin, sizeof( psvec3_t ) ); + memcpy( m_Angles, Angles, sizeof( psvec3_t ) ); +} + +// Read mouse input +void CCamera::ReadInput( float flFrametime ) +{ + POINT mousePos; + + GetCursorPos( &mousePos ); + + // We havent moved + if( m_LastPos.x == mousePos.x && m_LastPos.y == mousePos.y ) + return; + + switch( m_iMouseMode ) + { + case MOUSE_NONE: + memcpy( &m_LastPos, &mousePos, sizeof( POINT ) ); + return; + case MOUSE_LEFT: + // Zoom +/* { + float MoveDist = m_LastPos.y - mousePos.y; + + // Move left to right + m_Angles[1] += -(m_LastPos.x - mousePos.x) * 0.25f; + + m_Origin[0] += (float)sin( m_Angles[1]*M_PI/180.0f ) * MoveDist; + m_Origin[2] += (float)cos( m_Angles[1]*M_PI/180.0f ) * MoveDist; + }*/ +/* { + float AnglePitch, AngleYaw; + psvec3_t forward; + + m_Angles[1] -= m_LastPos.x - mousePos.x; + + // Get forward vector + AnglePitch = m_Angles[1] * ( M_PI*2 / 360.0f ); + AngleYaw = m_Angles[0] * ( M_PI*2 / 360.0f ); + + forward[1] = cos( AnglePitch ) * cos( AngleYaw ); + forward[0] = cos( AnglePitch ) * sin( AngleYaw ); + forward[2] = -(sin( AnglePitch )); + + // Move along forward vector + VectorMA( forward, (m_LastPos.y - mousePos.y) * 0.25f, m_Origin, forward ); + VectorAdd( m_Origin, forward, m_Origin ); + }*/ + break; + case MOUSE_RIGHT: + // Rotate + m_Angles[1] += -(m_LastPos.x - mousePos.x) * 0.25f; + m_Angles[0] += -(m_LastPos.y - mousePos.y) * 0.25f; + break; + case MOUSE_BOTH: + // Move +/* { + float MoveX = m_LastPos.x - mousePos.x; + float MoveY = m_LastPos.y - mousePos.y; + + m_ + m_Origin[1] += -MoveY; + }*/ +// m_Origin[0] += m_LastPos.x - mousePos.x; +// m_Origin[1] += -(m_LastPos.y - mousePos.y); +/* { + psvec3_t forward,right,up; + float forwardscale,sidescale; + + AngleVectors( m_Angles, forward, right, up ); + + forwardscale = m_LastPos.x - mousePos.x; + sidescale = m_LastPos.y - mousePos.y; + + VectorMA( m_Origin, forwardscale, forward, m_Origin ); + VectorMA( m_Origin, sidescale, right, m_Origin ); + }*/ + break; + } + + // Clamp values + CLAMP_VALUE( m_Angles[0], -90, 90 ); + + if( m_Angles[1] < 0.0f ) + m_Angles[1] += 360.0f; + else if( m_Angles[1] > 360.0f ) + m_Angles[1] -= 360.0f; + + if( m_Angles[2] < 0.0f ) + m_Angles[2] += 360.0f; + else if( m_Angles[2] > 360.0f ) + m_Angles[2] -= 360.0f; + + SetCursorPos( m_LastPos.x, m_LastPos.y ); +} + +// Reset mouse input vars +void CCamera::ResetInput() +{ + POINT mousePos; + + m_iMouseMode = MOUSE_NONE; + GetCursorPos( &mousePos ); + memcpy( &m_LastPos, &mousePos, sizeof( POINT ) ); +} diff --git a/utils/WastedFX/src/Entry.cpp b/utils/WastedFX/src/Entry.cpp new file mode 100644 index 0000000..89b5669 --- /dev/null +++ b/utils/WastedFX/src/Entry.cpp @@ -0,0 +1,67 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#include "WastedFX.h" +#include "Window.h" +#include "MainWindow.h" +#include "GLWindow.h" +#include +using namespace std; + +/* +========================= + WinMain + + Program entry point +========================= +*/ +INT WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, + PSTR szCmdLine, int iCmdShow ) +{ + MSG msg; + int iQuit = 0; + list WindowList; + + WindowList.push_back( new CMainWindow( WINCLASS_MAIN, hInstance ) ); + WindowList.push_back( new CGLWindow( WINCLASS_GL, hInstance ) ); + + // Message pump + while( !iQuit ) + { + if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) + { + if( msg.message == WM_QUIT ) + iQuit = 1; + + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + else + { + // Update windows + if( WindowList.size() ) + { + list::iterator iter; + + for( iter = WindowList.begin(); iter != WindowList.end(); iter++ ) + (*iter)->Update(); + } + } + } + + // Clean up + if( WindowList.size() ) + { + list::iterator iter; + + for( iter = WindowList.begin(); iter != WindowList.end(); iter++ ) + delete (*iter); + } + + return msg.wParam; +} \ No newline at end of file diff --git a/utils/WastedFX/src/GLWindow.cpp b/utils/WastedFX/src/GLWindow.cpp new file mode 100644 index 0000000..f9e3431 --- /dev/null +++ b/utils/WastedFX/src/GLWindow.cpp @@ -0,0 +1,284 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#include "WastedFX.h" +#include "Window.h" +#include "GLWindow.h" +#include "Camera.h" +#include "Timer.h" + +#define WINDOW_TITLE "Preview" + +/* +========================= + GLWndProc + + Callback procedures for GL Preview +========================= +*/ +LRESULT CALLBACK GLWndProc( HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam ) +{ + switch( iMsg ) + { + case WM_SIZE: + GetClassFromUserData( hWnd )->SetSize( LOWORD( lParam ), HIWORD( lParam ) ); + break; + case WM_CLOSE: + return 0; + case WM_LBUTTONDOWN: + GetClassFromUserData( hWnd )->OnLButtonDown(); + return 0; + case WM_LBUTTONUP: + GetClassFromUserData( hWnd )->OnLButtonUp(); + return 0; + case WM_RBUTTONDOWN: + GetClassFromUserData( hWnd )->OnRButtonDown(); + ShowCursor( FALSE ); + return 0; + case WM_RBUTTONUP: + GetClassFromUserData( hWnd )->OnRButtonUp(); + return 0; + } + + return DefWindowProc( hWnd, iMsg, wParam, lParam ); +} + +CGLWindow::CGLWindow( char *pszClassname, HINSTANCE hInstance ) : CWindow( pszClassname, hInstance ) +{ + m_pCamera = new CCamera; + m_pTimer = new CTimer; + + if( RegisterWindow( CS_NOCLOSE|CS_OWNDC|CS_HREDRAW|CS_VREDRAW, + GLWndProc, + LoadIcon( NULL, IDI_WINLOGO ), + LoadIcon( NULL, IDI_WINLOGO ), + LoadCursor( NULL, IDC_ARROW ), + (HBRUSH)GetStockObject( BLACK_BRUSH ), + NULL ) ) + { + HWND hParent = FindWindow( WINCLASS_MAIN, NULL ); + + InstanceWindow( WINDOW_TITLE, + WS_CHILDWINDOW|WS_OVERLAPPEDWINDOW, + hParent, + 10, 10, 512, 384 ); // TODO: Preferences! + + SetWindowLong( m_hWnd, GWL_USERDATA, (LONG)this ); + + RECT rect; + GetClientRect( m_hWnd, &rect ); + InitGL( rect.right, rect.bottom ); + + ShowWindow( m_hWnd, SW_SHOW ); + UpdateWindow( m_hWnd ); + } +} + +CGLWindow::~CGLWindow() +{ + delete m_pCamera; + delete m_pTimer; + + if( m_hRC ) + { + wglMakeCurrent( NULL, NULL ); + wglDeleteContext( m_hRC ); + } + + if( m_hDC ) + ReleaseDC( m_hWnd, m_hDC ); +} + +void CGLWindow::Update() +{ + m_pTimer->StartTimer(); + + glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT ); + glLoadIdentity(); + + // Set camera + glRotatef( m_pCamera->m_Angles[0], 1, 0, 0 ); + glRotatef( m_pCamera->m_Angles[1], 0, 1, 0 ); + glRotatef( m_pCamera->m_Angles[2], 0, 0, 1 ); + glTranslatef( m_pCamera->m_Origin[0], m_pCamera->m_Origin[1], m_pCamera->m_Origin[2] ); + + float x = 0.0f; + float y = -5.0f; + float height = 10.0f; + float z = -50.0f; + float width = 10.0f; + + glBegin( GL_QUADS ); + glColor4f( 0, 0, 0.5f, 1.0f ); + glVertex3f( -4096, -1500, -4096 ); + glVertex3f( 4096, -1500, -4096 ); + glVertex3f( 4096, -1500, 4096 ); + glVertex3f( -4096 , -1500, 4096 ); + glEnd(); + + glBegin(GL_TRIANGLES); + + // These vertices create the Back Side + glColor3ub(255, 0, 0); glVertex3f(x, y + height, z); // Top point + glColor3ub(0, 255, 255); glVertex3f(x - width, y - height, z - width); // Bottom left point + glColor3ub(255, 0, 255); glVertex3f(x + width, y - height, z - width); // Bottom right point + + // These vertices create the Front Side + glColor3ub(255, 0, 0); glVertex3f(x, y + height, z); // Top point + glColor3ub(0, 255, 255); glVertex3f(x + width, y - height, z + width); // Bottom right point + glColor3ub(255, 0, 255); glVertex3f(x - width, y - height, z + width); // Bottom left point + + // These vertices create the Front Left Side + glColor3ub(255, 0, 0); glVertex3f(x, y + height, z); // Top point + glColor3ub(255, 0, 255); glVertex3f(x - width, y - height, z + width); // Front bottom point + glColor3ub(0, 255, 255); glVertex3f(x - width, y - height, z - width); // Bottom back point + + // These vertices create the Front right Side + glColor3ub(255, 0, 0); glVertex3f(x, y + height, z); // Top point + glColor3ub(255, 0, 255); glVertex3f(x + width, y - height, z - width); // Bottom back point + glColor3ub(0, 255, 255); glVertex3f(x + width, y - height, z + width); // Front bottom point + + glEnd(); + + // Now render the bottom of our pyramid + + glBegin(GL_QUADS); + + // These vertices create the bottom of the pyramid + glColor3ub(0, 0, 255); glVertex3f(x - width, y - height, z + width); // Front left point + glColor3ub(0, 0, 255); glVertex3f(x + width, y - height, z + width); // Front right point + glColor3ub(0, 0, 255); glVertex3f(x + width, y - height, z - width); // Back right point + glColor3ub(0, 0, 255); glVertex3f(x - width, y - height, z - width); // Back left point + glEnd(); + + glBegin( GL_LINES ); + glColor4f( 1, 0, 0, 1 ); + glVertex3f( 0, 0, 0 ); + glVertex3f( 5, 0, 0 ); + + glColor4f( 0, 1, 0, 1 ); + glVertex3f( 0, 0, 0 ); + glVertex3f( 0, 5, 0 ); + + glColor4f( 0, 0, 1, 1 ); + glVertex3f( 0, 0, 0 ); + glVertex3f( 0, 0, 5 ); + glEnd(); + + SwapBuffers( m_hDC ); + + m_pTimer->EndTimer(); + + // Camera input + if( m_hWndParent == GetActiveWindow() ) + m_pCamera->ReadInput( m_pTimer->GetTime() ); + else + m_pCamera->ResetInput(); +} + + +void CGLWindow::OnLButtonDown() +{ + if( m_pCamera->m_iMouseMode == MOUSE_RIGHT ) + m_pCamera->m_iMouseMode = MOUSE_BOTH; + else + m_pCamera->m_iMouseMode = MOUSE_LEFT; + ShowCursor( FALSE ); +} + +void CGLWindow::OnLButtonUp() +{ + if( m_pCamera->m_iMouseMode == MOUSE_BOTH ) + m_pCamera->m_iMouseMode = MOUSE_RIGHT; + else + m_pCamera->m_iMouseMode = MOUSE_NONE; + ShowCursor( TRUE ); +} + +void CGLWindow::OnRButtonDown() +{ + if( m_pCamera->m_iMouseMode == MOUSE_LEFT ) + m_pCamera->m_iMouseMode = MOUSE_BOTH; + else + m_pCamera->m_iMouseMode = MOUSE_RIGHT; +} + +void CGLWindow::OnRButtonUp() +{ + if( m_pCamera->m_iMouseMode == MOUSE_BOTH ) + m_pCamera->m_iMouseMode = MOUSE_LEFT; + else + m_pCamera->m_iMouseMode = MOUSE_NONE; + ShowCursor( TRUE ); +} + +void CGLWindow::InitGL( int iWidth, int iHeight ) +{ + psvec3_t origin = { 0.0f, 0.5f, -35.0f }; + psvec3_t angles = { 0.0f, 0.0f, 0.0f }; + + m_hDC = GetDC( m_hWnd ); + + if( !SetPixelFormat() ) + return; + + m_hRC = wglCreateContext( m_hDC ); + wglMakeCurrent( m_hDC, m_hRC ); + + memcpy( m_pCamera->m_Origin, &origin, sizeof( psvec3_t ) ); + memcpy( m_pCamera->m_Angles, &angles, sizeof( psvec3_t ) ); + SetSize( iWidth, iHeight ); +} + +BOOL CGLWindow::SetPixelFormat() +{ + PIXELFORMATDESCRIPTOR pfd = { 0 }; + int iPixelFormat; + + pfd.nSize = sizeof( PIXELFORMATDESCRIPTOR ); + pfd.nVersion = 1; + + pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER; // TODO: Triple Buffer someday ? + pfd.dwLayerMask = PFD_MAIN_PLANE; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 16; // TODO: Set this ? + pfd.cDepthBits = 16; + pfd.cAccumBits = 0; + pfd.cStencilBits = 0; // Stencil ? + + if( (iPixelFormat = ChoosePixelFormat( m_hDC, &pfd )) == FALSE ) + { + CWindow::Error( NULL, "PixelFormatError", "Unable to select proper pixel format", ERR_FATAL ); + return FALSE; + } + + if( !::SetPixelFormat( m_hDC, iPixelFormat, &pfd ) ) + { + CWindow::Error( NULL, "PixelFormatError", "Unable to set pixel format", ERR_FATAL ); + return FALSE; + } + + return TRUE; +} + +void CGLWindow::SetSize( int iWidth, int iHeight ) +{ + if( iHeight == 0 ) + iHeight = 1; + + glViewport( 0, 0, iWidth, iHeight ); + + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + + // TODO: Set FOV ? Min/Max distances ? + gluPerspective( 45.0f, (GLfloat)iWidth/(GLfloat)iHeight, 1.0f, 4096.0f ); + + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); +} diff --git a/utils/WastedFX/src/MainWindow.cpp b/utils/WastedFX/src/MainWindow.cpp new file mode 100644 index 0000000..98c6104 --- /dev/null +++ b/utils/WastedFX/src/MainWindow.cpp @@ -0,0 +1,168 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#include "WastedFX.h" +#include "Window.h" +#include "MainWindow.h" + +#define WINDOW_TITLE PROGRAM_NAME + +/* +========================= + MainWndProc + + Main window procedure +========================= +*/ +LRESULT CALLBACK MainWndProc( HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam ) +{ + switch( iMsg ) + { + case WM_COMMAND: + switch( LOWORD( wParam ) ) + { + case ID_FILE_NEW1: + GetClassFromUserData( hWnd )->OnFileNew(); + return 0; + case ID_FILE_OPEN1: + GetClassFromUserData( hWnd )->OnFileOpen(); + return 0; + case ID_FILE_SAVE1: + GetClassFromUserData( hWnd )->OnFileSave(); + return 0; + case ID_FILE_SAVEAS: + GetClassFromUserData( hWnd )->OnFileSaveas(); + return 0; + case ID_FILE_QUIT: + GetClassFromUserData( hWnd )->OnFileQuit(); + return 0; + case ID_EDIT_CUT1: + GetClassFromUserData( hWnd )->OnEditCut(); + return 0; + case ID_EDIT_COPY1: + GetClassFromUserData( hWnd )->OnEditCopy(); + return 0; + case ID_EDIT_PASTE1: + GetClassFromUserData( hWnd )->OnEditPaste(); + return 0; + case ID_EDIT_OPTIONS: + GetClassFromUserData( hWnd )->OnEditOptions(); + return 0; + case ID_PREVIEW_EMITTERPREVIEW: + GetClassFromUserData( hWnd )->OnPreviewEmitter(); + return 0; + case ID_PREVIEW_MAPPREVIEW: + GetClassFromUserData( hWnd )->OnPreviewMap(); + return 0; + case ID_PREVIEW_MODELPREVIEW: + GetClassFromUserData( hWnd )->OnPreviewModel(); + return 0; + case ID_HELP_ONLINEHELP: + GetClassFromUserData( hWnd )->OnHelpOnlineHelp(); + return 0; + case ID_HELP_ABOUT: + GetClassFromUserData( hWnd )->OnHelpAbout(); + return 0; + } + break; + case WM_CLOSE: + case WM_DESTROY: + // Todo: tie into member func ? + PostQuitMessage( 0 ); + return 0; + } + + return DefWindowProc( hWnd, iMsg, wParam, lParam ); +} + +CMainWindow::CMainWindow( char *pszClassname, HINSTANCE hInstance ) : CWindow( pszClassname, hInstance ) +{ + if( RegisterWindow( CS_HREDRAW|CS_VREDRAW, + MainWndProc, + (HICON)LoadImage( m_hInstance, MAKEINTRESOURCE( IDI_ICON1 ), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE ), + (HICON)LoadImage( m_hInstance, MAKEINTRESOURCE( IDI_ICON1 ), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE ), + LoadCursor( NULL, IDC_ARROW ), + (HBRUSH)GetStockObject( GRAY_BRUSH ), + LoadMenu( m_hInstance, MAKEINTRESOURCE( IDR_MENUMAIN ) ) ) ) + { + InstanceWindow( PROGRAM_NAME, WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_CLIPSIBLINGS, + NULL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT ); + + SetWindowLong( m_hWnd, GWL_USERDATA, (LONG)this ); + + ShowWindow( m_hWnd, SW_NORMAL ); + UpdateWindow( m_hWnd ); + } +} + +CMainWindow::~CMainWindow() +{ +} + +void CMainWindow::Update() +{ +} + +void CMainWindow::OnFileNew() +{ +} + +void CMainWindow::OnFileOpen() +{ +} + +void CMainWindow::OnFileSave() +{ +} + +void CMainWindow::OnFileSaveas() +{ +} + +void CMainWindow::OnFileQuit() +{ + // TODO: Save changes ? + PostQuitMessage( 0 ); +} + +void CMainWindow::OnEditCut() +{ +} + +void CMainWindow::OnEditCopy() +{ +} + +void CMainWindow::OnEditPaste() +{ +} + +void CMainWindow::OnEditOptions() +{ +} + +void CMainWindow::OnPreviewEmitter() +{ +} + +void CMainWindow::OnPreviewMap() +{ +} + +void CMainWindow::OnPreviewModel() +{ +} + +void CMainWindow::OnHelpOnlineHelp() +{ + CWindow::Error( this, "Online Help", "There is no online help available at this time", ERR_INFO ); +} + +void CMainWindow::OnHelpAbout() +{ +} \ No newline at end of file diff --git a/utils/WastedFX/src/Timer.cpp b/utils/WastedFX/src/Timer.cpp new file mode 100644 index 0000000..1ec0edf --- /dev/null +++ b/utils/WastedFX/src/Timer.cpp @@ -0,0 +1,46 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#include "WastedFX.h" +#include "Timer.h" + +CTimer::CTimer() : m_LastTime( 0.0f ) +{ + if( QueryPerformanceFrequency( &m_PerfFrequency ) ) + m_UsePerfCounter = TRUE; + else + m_UsePerfCounter = FALSE; +} + +void CTimer::StartTimer() +{ + if( m_UsePerfCounter ) + { + QueryPerformanceCounter( &m_PerfStartCount ); + } +/* else + { + }*/ +} + +void CTimer::EndTimer() +{ + if( m_UsePerfCounter ) + { + QueryPerformanceCounter( &m_PerfEndCount ); + + m_Time = ((float)m_PerfEndCount.QuadPart - (float)m_PerfStartCount.QuadPart) / m_PerfFrequency.QuadPart; + } + else + { + float CurrentTime = GetTickCount() * 0.001f; + + m_Time = CurrentTime - m_LastTime; + m_LastTime = CurrentTime; + } +} \ No newline at end of file diff --git a/utils/WastedFX/src/Window.cpp b/utils/WastedFX/src/Window.cpp new file mode 100644 index 0000000..bd984c5 --- /dev/null +++ b/utils/WastedFX/src/Window.cpp @@ -0,0 +1,94 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#include "WastedFX.h" +#include "Window.h" + +CWindow::CWindow( char *pszClassname, HINSTANCE hInstance ) : + m_hWnd( NULL ), + m_hWndParent( NULL ), + m_hMenu( NULL ) +{ + m_pszClassname = pszClassname; + m_hInstance = hInstance; +} + +CWindow::~CWindow() +{ + if( m_hMenu != NULL ) + DestroyMenu( m_hMenu ); + + UnregisterClass( m_pszClassname, m_hInstance ); +} + +void CWindow::Update() +{ +} + +BOOL CWindow::RegisterWindow( int style, void *WndProc, + HICON hIcon, HICON hIconSm, + HCURSOR hCursor, + HBRUSH hBrush, + HMENU hMenu ) +{ + WNDCLASSEX wndClass; + + wndClass.cbSize = sizeof( wndClass ); + wndClass.hInstance = m_hInstance; + wndClass.style = style; + wndClass.lpfnWndProc = (WNDPROC)WndProc; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hIcon = hIcon; + wndClass.hIconSm = hIconSm; + wndClass.hCursor = hCursor; + wndClass.hbrBackground = hBrush; + wndClass.lpszMenuName = NULL; + wndClass.lpszClassName = m_pszClassname; + + if( !RegisterClassEx( &wndClass ) ) + return FALSE; + + m_hMenu = hMenu; + + return TRUE; +} + +BOOL CWindow::InstanceWindow( char *pszTitle, int style, HWND hWndParent, + int x, int y, int w, int h ) +{ + m_hWnd = ::CreateWindow( m_pszClassname, pszTitle, style, + x, y, w, h, hWndParent, m_hMenu, m_hInstance, + NULL ); + + m_hWndParent = hWndParent; + + return TRUE; +} + +void CWindow::Error( CWindow *pWindow, char *pszCaption, char *pszMsg, int iErrMode ) +{ + HWND hWnd = NULL; + + if( pWindow != NULL ) + hWnd = pWindow->m_hWnd; + + switch( iErrMode ) + { + case ERR_ALERT: + MessageBox( hWnd, pszMsg, pszCaption, MB_OK|MB_ICONEXCLAMATION ); + break; + case ERR_INFO: + MessageBox( hWnd, pszMsg, pszCaption, MB_OK|MB_ICONINFORMATION ); + break; + case ERR_FATAL: + MessageBox( hWnd, pszMsg, pszCaption, MB_OK|MB_ICONERROR ); + PostQuitMessage( 0 ); + break; + } +} diff --git a/utils/WastedFX/src/util.cpp b/utils/WastedFX/src/util.cpp new file mode 100644 index 0000000..1dcdfdc --- /dev/null +++ b/utils/WastedFX/src/util.cpp @@ -0,0 +1,56 @@ +/*** +* +* WastedFX Tool chain +* +* Author: Joshua Coyne +* Copyright (C) 2003 The Wastes Project, All Rights Reserved. +* +***/ +#include "WastedFX.h" + +#define YAW 1 +#define PITCH 0 +#define ROLL 2 + +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} \ No newline at end of file diff --git a/utils/WastedFX/wfx_icon.ico b/utils/WastedFX/wfx_icon.ico new file mode 100644 index 0000000..e07f1c7 Binary files /dev/null and b/utils/WastedFX/wfx_icon.ico differ diff --git a/utils/sprgen/sprgen.c b/utils/sprgen/sprgen.c index d5db968..48d17a6 100644 --- a/utils/sprgen/sprgen.c +++ b/utils/sprgen/sprgen.c @@ -22,7 +22,7 @@ #include "spritegn.h" -#define MAX_BUFFER_SIZE 0x100000 +#define MAX_BUFFER_SIZE 0x200000 #define MAX_FRAMES 1000 dsprite_t sprite; diff --git a/utils/studiomdl/studiomdl.c b/utils/studiomdl/studiomdl.c index 0f5496b..ec6cf53 100644 --- a/utils/studiomdl/studiomdl.c +++ b/utils/studiomdl/studiomdl.c @@ -1179,9 +1179,6 @@ s_mesh_t *lookup_mesh( s_model_t *pmodel, char *texturename ) return pmodel->pmesh[i]; } - - - s_trianglevert_t *lookup_triangle( s_mesh_t *pmesh, int index ) { if (index >= pmesh->alloctris) { diff --git a/utils/studiotwm/buildtwm.c b/utils/studiotwm/buildtwm.c new file mode 100644 index 0000000..a9795da --- /dev/null +++ b/utils/studiotwm/buildtwm.c @@ -0,0 +1,356 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// build a .twm file from the read .smd file +// +#include +#include +#include +#include +#include +#include "../../common/mathlib.h" +#include "../../common/const.h" +#include "../../common/twm.h" +#include "studiotwm.h" + +/* +============================== +TWM_AddTriToMaterialLump + +============================== +*/ +void TWM_AddTriToMaterialLump(twm_info_t *pTwm,int matindex,int trisindex,smdtriangle_t pTri,char *pszTextureName) +{ + if(strcmp(pTri.texturename,pszTextureName) == 0) + { + pTwm->materialgroup_lump[matindex].num_triangles++; + + // add triangle index to lump + pTwm->materialgroup_lump[matindex].tris_indices = realloc(pTwm->materialgroup_lump[matindex].tris_indices,sizeof(int)*pTwm->materialgroup_lump[matindex].num_triangles); + pTwm->materialgroup_lump[matindex].tris_indices[pTwm->materialgroup_lump[matindex].num_triangles-1] = trisindex; + } +} + +/* +============================== +TWM_AddMaterial + +If this is a unique material +add it to the list +============================== +*/ +void TWM_AddMaterial(twm_info_t *pTwm,char *pszTextureName) +{ + if(pTwm->num_materialgroups) + { + int i; + + // If we already have this material, dont add it + for(i = 0;i < pTwm->num_materialgroups;i++) + if(strcmp(pTwm->materialgroup_lump[i].texturename,pszTextureName) == 0) + return; + } + + pTwm->num_materialgroups++; + pTwm->materialgroup_lump = realloc(pTwm->materialgroup_lump,sizeof(twm_materialgroup_t)*pTwm->num_materialgroups); + + strcpy(pTwm->materialgroup_lump[pTwm->num_materialgroups-1].texturename,pszTextureName); + pTwm->materialgroup_lump[pTwm->num_materialgroups-1].num_triangles = 0; + pTwm->materialgroup_lump[pTwm->num_materialgroups-1].tris_indices = NULL; +} + +/* +============================== +TWM_WriteMaterialgroups + +create material group data +and write to disk +============================== +*/ +int TWM_WriteMaterialgroups(FILE *pFile,twm_info_t *pTwm,smdref_t *pSmd) +{ + char *szExt; + char szTemp[64]; + int i; + + // Pass 1: Load all texture names into mem + for(i = 0;i < pSmd->num_triangles;i++) + TWM_AddMaterial(pTwm,pSmd->triangles[i].texturename); + + // Pass 2: Put proper textures in the right indices + for(i = 0;i < pTwm->num_materialgroups;i++) + { + int j; + + // Go through SMD listing one more time + // and put all matching triangles into the lump + for(j = 0;j < pSmd->num_triangles;j++) + TWM_AddTriToMaterialLump(pTwm,i,j,pSmd->triangles[j],pTwm->materialgroup_lump[i].texturename); + } + + // Write material info to disk + fwrite((char*)&pTwm->num_materialgroups,sizeof(pTwm->num_materialgroups),1,pFile); + + for(i = 0;i < pTwm->num_materialgroups;i++) + { + // We add the texture path here as well as + // rename .bmp to .spr + strcpy(szTemp,g_szTexturePath); + strcat(szTemp,pTwm->materialgroup_lump[i].texturename); + + szExt = strstr(szTemp,".bmp"); + strcpy(szExt,".spr"); + + // Copy back into material texture + strcpy(pTwm->materialgroup_lump[i].texturename,szTemp); + + fwrite(pTwm->materialgroup_lump[i].texturename,sizeof(char)*64,1,pFile); + fwrite((char*)&pTwm->materialgroup_lump[i].num_triangles,sizeof(short),1,pFile); + fwrite((char*)pTwm->materialgroup_lump[i].tris_indices,sizeof(short)*pTwm->materialgroup_lump[i].num_triangles,1,pFile); + } + + return 1; +} + +/* +============================== +TWM_GetIndexForVert + +Go through vert index +looking for a matching vertex +============================== +*/ +int TWM_GetIndexForVert(twm_info_t *pTwm,twm_vert_t vertex) +{ + int i; + + // Go through vertex list looking for + // a matching index + for(i = 0;i < pTwm->num_vertices;i++) + // Compare verts + if(memcmp(pTwm->vertex_lump[i],vertex,sizeof(twm_vert_t)) == 0) + return i; + + return SMD_Error(-1,"ERROR: Invalid Vert Index\n"); +} + +/* +============================== +TWM_WriteTriangles + +write triangle listings +to file +============================== +*/ +int TWM_WriteTriangles(FILE *pFile,twm_info_t *pTwm,smdref_t *pSmd) +{ + int i; + smdtriangle_t *pTriangle; + + for(i = 0;i < pSmd->num_triangles;i++) + { + twm_triangle_t twmTri; + + pTriangle = &pSmd->triangles[i]; + + // increment counter + pTwm->num_tris++; + + // fill UV data + twmTri.u[0] = pTriangle->verts[0].u; + twmTri.v[0] = pTriangle->verts[0].v; + twmTri.u[1] = pTriangle->verts[1].u; + twmTri.v[1] = pTriangle->verts[1].v; + twmTri.u[2] = pTriangle->verts[2].u; + twmTri.v[2] = pTriangle->verts[2].v; + + // get vert indices + twmTri.vert_indices[0] = TWM_GetIndexForVert(pTwm,pTriangle->verts[0].origin); + twmTri.vert_indices[1] = TWM_GetIndexForVert(pTwm,pTriangle->verts[1].origin); + twmTri.vert_indices[2] = TWM_GetIndexForVert(pTwm,pTriangle->verts[2].origin); + + // Store triangle in mem + pTwm->triangle_lump = realloc(pTwm->triangle_lump,sizeof(twm_triangle_t)*pTwm->num_tris); + memcpy((char*)&pTwm->triangle_lump[pTwm->num_tris-1],(char*)&twmTri,sizeof(twm_triangle_t)); + } + + // Write triangles to disk + fwrite((char*)&pTwm->num_tris,sizeof(short),1,pFile); + + // Write element by element + for(i = 0;i < pTwm->num_tris;i++) + { + twm_triangle_t *cur_tri = &pTwm->triangle_lump[i]; + + fwrite((char*)cur_tri->vert_indices,sizeof(short)*3,1,pFile); + fwrite((char*)cur_tri->u,sizeof(float)*3,1,pFile); + fwrite((char*)cur_tri->v,sizeof(float)*3,1,pFile); + } + + return 1; +} + +/* +============================== +TWM_AddVertexToModel + +go through vertex listing, +if this is a unique vert, +add it. +============================== +*/ +void TWM_AddVertexToModel(twm_info_t *pTwm,smdvertex_t vertex) +{ + if(pTwm->num_vertices) + { + int j; + + // Look through current vertex listing. + // If this vertex exists, dont add it + // again. + for(j = 0;j < pTwm->num_vertices;j++) + if(memcmp(pTwm->vertex_lump[j],vertex.origin,sizeof(twm_vert_t)) == 0) + return; + } + + // add it + pTwm->num_vertices++; + pTwm->vertex_lump = realloc(pTwm->vertex_lump,sizeof(twm_vert_t)*pTwm->num_vertices); + memcpy(pTwm->vertex_lump[pTwm->num_vertices-1],vertex.origin,sizeof(twm_vert_t)); +} + +/* +============================== +TWM_WriteVertices + +write all unique vertices +to file +============================== +*/ +int TWM_WriteVertices(FILE *pFile,twm_info_t *pTwm,smdref_t *pSmd) +{ + int i,j; + smdtriangle_t *pTriangle; + + // Go through triangle listing + for(i = 0;i < pSmd->num_triangles;i++) + { + pTriangle = &pSmd->triangles[i]; + + // Read vertex listing + for(j = 0;j < 3;j++) + TWM_AddVertexToModel(pTwm,pTriangle->verts[j]); + } + + // Write remaining verts to file + fwrite((char*)&pTwm->num_vertices,sizeof(short),1,pFile); + fwrite((char*)pTwm->vertex_lump,sizeof(twm_vert_t)*pTwm->num_vertices,1,pFile); + + return 1; +} + +/* +============================== +TWM_WriteHeader + +Initial TWM information +============================== +*/ +int TWM_WriteHeader(FILE *pFile,twm_info_t *pModel) +{ + pModel->header_id = TWM_ID; + pModel->major_version = TWM_MAJOR_VERSION; + pModel->minor_version = TWM_MINOR_VERSION; + + // Write to disk + fwrite((char*)&pModel->header_id,sizeof(int),1,pFile); + fwrite((char*)&pModel->major_version,sizeof(short),1,pFile); + fwrite((char*)&pModel->minor_version,sizeof(short),1,pFile); + + return 1; +} + +/* +============================== +TWM_Initialize + +Reset a twm struct +============================== +*/ +void TWM_Initialize(twm_info_t *pTwm) +{ + pTwm->num_vertices = 0; + pTwm->vertex_lump = NULL; + + pTwm->num_tris = 0; + pTwm->triangle_lump = NULL; + + pTwm->num_materialgroups = 0; + pTwm->materialgroup_lump = NULL; +} + +/* +============================== +TWM_Destroy + +Reset data inside a twm model +============================== +*/ +void TWM_Destroy(twm_info_t *pTwm) +{ + if(pTwm->num_vertices) + free(pTwm->vertex_lump); + + if(pTwm->num_tris) + free(pTwm->triangle_lump); + + if(pTwm->num_materialgroups); + { + int i; + + for(i = 0;i < pTwm->num_materialgroups;i++) + free(pTwm->materialgroup_lump[i].tris_indices); + + free(pTwm->materialgroup_lump); + } +} + +/* +============================== +TWM_BuildFromSMD + +Convert SMD data +into a usable .twm file +============================== +*/ +int TWM_BuildFromSMD(char *filename,smdref_t *smdref) +{ + twm_info_t twmmodel; + FILE *pFile = fopen(filename,"wb"); + + if(pFile == NULL) + return SMD_Error(0,"ERROR: Could not open %s\n",filename); + + TWM_Initialize(&twmmodel); + + TWM_WriteHeader(pFile,&twmmodel); + TWM_WriteVertices(pFile,&twmmodel,smdref); + TWM_WriteTriangles(pFile,&twmmodel,smdref); + TWM_WriteMaterialgroups(pFile,&twmmodel,smdref); + + TWM_Destroy(&twmmodel); + + fclose(pFile); + return 1; +} \ No newline at end of file diff --git a/utils/studiotwm/parsesmd.c b/utils/studiotwm/parsesmd.c new file mode 100644 index 0000000..fdcbcf0 --- /dev/null +++ b/utils/studiotwm/parsesmd.c @@ -0,0 +1,248 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// Read a .smd reference file +// +#include +#include +#include +#include +#include +#include "../../common/mathlib.h" +#include "../../common/const.h" +#include "../../common/twm.h" +#include "studiotwm.h" + +/* +============================== +SMD_Error + +Print an error message +and return an error code +============================== +*/ +int SMD_Error(int errid,char *pszStream,...) +{ + va_list argptr; + char szString[MAX_STRLEN]; + + va_start(argptr,pszStream); + vsprintf(szString,pszStream,argptr); + va_end(argptr); + + fprintf(stderr,szString); + return errid; +} + +/* +============================== +SMD_AddTriangle + +Add triangle to current ref set +============================== +*/ +void SMD_AddTriangle(smdref_t *smdref,smdvertex_t *verts,char *texturename) +{ + int index = smdref->num_triangles; + + smdref->num_triangles++; + + // copy memory + smdref->triangles = (smdtriangle_t*)realloc(smdref->triangles,sizeof(smdtriangle_t)*smdref->num_triangles); + + memcpy(smdref->triangles[index].verts,verts,sizeof(smdvertex_t)*3); + strcpy(smdref->triangles[index].texturename,texturename); +} + +/* +============================== +SMD_ParseTriangles + +Read a triangle data block +Largely read from studiomdl.c Grab_Triangles +============================== +*/ +void SMD_ParseTriangles(FILE *pFile,char *pszLine,smdref_t *smdref) +{ + int i,j; + + while(fgets(pszLine,sizeof(char)*MAX_STRLEN,pFile) != NULL) + { + smdvertex_t p[3]; + char texturename[64]; + int itrash; + float fltrash[3]; + char *c; + + // TODO: Remove when neccesary + fltrash[0] = 1 * 4; + + // check for end + if(strcmp("end\n",pszLine) == 0) + return; + + // strip off trailing smag + strcpy(texturename,pszLine); + for(i = strlen(texturename) - 1;i >= 0 && !isgraph(texturename[i]); i--) + texturename[i + 1] = '\0'; + + // strip off newline + c = strchr(texturename,'\n'); + if(c != NULL) + c[0] = '\0'; + + // Skip blank triangles + if(texturename[0] == '\0') + { + fgets(pszLine,sizeof(char)*MAX_STRLEN,pFile); + fgets(pszLine,sizeof(char)*MAX_STRLEN,pFile); + fgets(pszLine,sizeof(char)*MAX_STRLEN,pFile); + continue; + } + + // Read the triangle data + for(j = 0;j < 3;j++) + { + //TODO: Flip vertex order? + if(fgets(pszLine,sizeof(char)*MAX_STRLEN,pFile) != NULL) + { + // Alot of this crap .TWM doesnt use, so + // just throw it into trash vars + if(!(sscanf(pszLine, "%d %f %f %f %f %f %f %f %f", + &itrash, + &p[j].origin[0],&p[j].origin[1],&p[j].origin[2], + &fltrash[0],&fltrash[1],&fltrash[2], + &p[j].u,&p[j].v) == 9)) + { + SMD_Error(-1,"ERROR: Invalid triangle format\n"); + return; + } + else + { + // if needed rescale the verts + if(g_flScale != 1.0f) + { + p[j].origin[0] *= g_flScale; + p[j].origin[1] *= g_flScale; + p[j].origin[2] *= g_flScale; + } + } + } + } + + // Plug values into smdref + SMD_AddTriangle(smdref,p,texturename); + } +} + +/* +============================== +SMD_ParseSMD + +Read a SMD file and return a new one +returns 0 if parse was not successful +============================== +*/ +int SMD_ParseSMD(char *filename,smdref_t *smdref) +{ + char szLine[MAX_STRLEN]; + char szToken[MAX_STRLEN]; + int iOption; + FILE *pFile = fopen(filename,"r"); + + if(pFile == NULL) + return SMD_Error(0,"ERROR: Invalid file %s\n",filename); + + // Start parsing + while(fgets(szLine,sizeof(szLine),pFile) != NULL) + { + sscanf(szLine,"%s %d",szToken,&iOption); + + if(strcmp("version",szToken) == 0) + { + if(iOption != 1) + { + // Bad version + fprintf(stderr,"ERROR: Invalid version %i\n",iOption); + goto parsesmd_error; + } + } + else if(strcmp("triangles",szToken) == 0) + { + SMD_ParseTriangles(pFile,szLine,smdref); + } + } + + fclose(pFile); + return 1; + +parsesmd_error: + fclose(pFile); + return 0; +} + +/* +============================== +SMD_Initialize + +Reset a smdref +============================== +*/ +void SMD_Initialize(smdref_t *smdref) +{ + // Triangles + smdref->num_triangles = 0; + smdref->triangles = NULL; +} + +/* +============================== +SMD_Destroy + +Destroy a smdref +============================== +*/ +void SMD_Destroy(smdref_t *smdref) +{ + if(smdref->num_triangles) + free(smdref->triangles); +} + +/* +============================== +SMD_ConvertToTWM + +Parse an SMD then create +a TWM from it +============================== +*/ +void SMD_ConvertToTWM(char *filein,char *fileout) +{ + int err_count = 0; + smdref_t smdref; + + printf("Parsing [%s]...",filein); + + SMD_Initialize(&smdref); + + if(SMD_ParseSMD(filein,&smdref)) + { + printf("Building [%s]...",fileout); + + if(TWM_BuildFromSMD(fileout,&smdref)) + printf("Done\n",fileout); + } + + SMD_Destroy(&smdref); +} \ No newline at end of file diff --git a/utils/studiotwm/studiotwm.c b/utils/studiotwm/studiotwm.c new file mode 100644 index 0000000..7857c35 --- /dev/null +++ b/utils/studiotwm/studiotwm.c @@ -0,0 +1,125 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +// +// program entry, scripting parser +// +#include +#include +#include +#include +#include +#include "../../common/mathlib.h" +#include "../../common/const.h" +#include "../../common/twm.h" +#include "studiotwm.h" + +#define VERSION_NUM "1.0" + +int g_errCount = 0; + +char g_szTexturePath[MAX_STRLEN]; +float g_flScale = 1; + +void ParseLine(char *line) +{ + char *token = strtok(line," "); + + if(token != NULL) + { + // parse the opening token + // TODO: triflip ? + if(strcmp("$texturepath",token) == 0) + { + char *param1 = strtok(NULL," "); + + strcpy(g_szTexturePath,param1); + } + else if(strcmp("$convertsmd",token) == 0) + { + char *param1 = strtok(NULL," "); + char *param2 = strtok(NULL," "); + + SMD_ConvertToTWM(param1,param2); + } + else if(strcmp("$scale",token) == 0) + { + char *param1 = strtok(NULL," "); + + sscanf(param1,"%f",&g_flScale); + } + else + { + fprintf(stderr,"ERROR: invalid token %s\n",token); + g_errCount++; + } + } +} + +void RunScript(char *filename) +{ + FILE *pFile = fopen(filename,"r"); + + if(pFile == NULL) + { + fprintf(stderr,"ERROR: Unable to load %s\n",filename); + g_errCount++; + return; + } + else + { + // Begin the parsing operation + char current_line[MAX_STRLEN]; + memset(current_line,0,sizeof(char)*MAX_STRLEN); + + while(fgets(current_line,MAX_STRLEN,pFile) != NULL) + { + // strip comments first + char *comment = strstr(current_line,"//"); + if(comment != NULL) + comment[0] = '\0'; + + // strip newline + comment = strchr(current_line,'\n'); + if(comment != NULL) + comment[0] = '\0'; + + ParseLine(current_line); + } + } + + fclose(pFile); +} + +int main(int argc,char **argv) +{ + // No options put in, print help + if(argc <= 1) + { + printf("usage: studiotwm script1 script2...\n"); + } + else + { + int arg_count; + + printf("studiotwm version %s\n",VERSION_NUM); + + // Start at 1 to bypass the path arg + for(arg_count = 1;arg_count < argc;arg_count++) + RunScript(argv[arg_count]); + + printf("%i errors\n",g_errCount); + } + + return 0; +} \ No newline at end of file diff --git a/utils/studiotwm/studiotwm.dsp b/utils/studiotwm/studiotwm.dsp new file mode 100644 index 0000000..4f1d49a --- /dev/null +++ b/utils/studiotwm/studiotwm.dsp @@ -0,0 +1,115 @@ +# Microsoft Developer Studio Project File - Name="studiotwm" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=studiotwm - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "studiotwm.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "studiotwm.mak" CFG="studiotwm - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "studiotwm - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "studiotwm - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "studiotwm - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../../obj/studiotwm" +# PROP Intermediate_Dir "../../obj/studiotwm" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "../../common/" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"../../tools-exec/studiotwm.exe" +# SUBTRACT LINK32 /incremental:yes + +!ELSEIF "$(CFG)" == "studiotwm - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "../../obj/studiotwm" +# PROP Intermediate_Dir "../../obj/studiotwm" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../../common/" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"../../tools-exec/studiotwm.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "studiotwm - Win32 Release" +# Name "studiotwm - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\buildtwm.c +# End Source File +# Begin Source File + +SOURCE=.\parsesmd.c +# End Source File +# Begin Source File + +SOURCE=.\studiotwm.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\studiotwm.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/studiotwm/studiotwm.dsw b/utils/studiotwm/studiotwm.dsw new file mode 100644 index 0000000..44e6e2f --- /dev/null +++ b/utils/studiotwm/studiotwm.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "studiotwm"=".\studiotwm.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/utils/studiotwm/studiotwm.h b/utils/studiotwm/studiotwm.h new file mode 100644 index 0000000..bd60e37 --- /dev/null +++ b/utils/studiotwm/studiotwm.h @@ -0,0 +1,55 @@ +/*** +* +* Copyright (C) 2002 The Wastes Project, All Rights Reserved. +* +* This product contains software technology from Valve Software, LLC, +* Copyright © 1996-2001, Valve LLC, 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 +* The Wastes Project. All other use, distribution, or modification is prohibited +* without written permission from The Wastes Project. +* +***/ +#ifndef __STUDIOTWM_H_ +#define __STUDIOTWM_H_ + +#define MAX_STRLEN 1024 + +typedef struct smdvertex_s +{ + twm_vert_t origin; + float u,v; +} smdvertex_t; + +typedef struct smdtriangle_s +{ + char texturename[64]; + smdvertex_t verts[3]; +} smdtriangle_t; + +typedef struct smdref_s +{ + // Triangles + int num_triangles; + smdtriangle_t *triangles; +} smdref_t; + +// +// buildtwm.c +// +int TWM_BuildFromSMD(char *filename,smdref_t *smdref); + +// +// parsesmd.c +// +void SMD_ConvertToTWM(char *filein,char *fileout); +int SMD_Error(int errid,char *pszStream,...); + +// +// studiotwm.c +// +extern char g_szTexturePath[MAX_STRLEN]; +extern float g_flScale; + +#endif \ No newline at end of file